From: Kovid Goyal <kovid@kovidgoyal.net>
Date: Thu, 12 Feb 2026 10:31:53 +0530
Subject: CVE-2026-26065: PDB Input: Ensure extracted images are within the
 container

Forwarded: not-needed
Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-vmfh-7mr7-pp2w
Origin: https://github.com/kovidgoyal/calibre/commit/b6da1c3878c06eb1356cb0ec1106cb66e0e9bfb8

Signed-off-by: YOKOTA Hiroshi <yokota.hgml@gmail.com>
---
 src/calibre/ebooks/pdb/ereader/reader132.py | 27 +++++++++++++++++++--------
 src/calibre/ebooks/pdb/ereader/reader202.py | 25 ++++++++++++++++++-------
 2 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/src/calibre/ebooks/pdb/ereader/reader132.py b/src/calibre/ebooks/pdb/ereader/reader132.py
index 16c35b8..5b1348c 100644
--- a/src/calibre/ebooks/pdb/ereader/reader132.py
+++ b/src/calibre/ebooks/pdb/ereader/reader132.py
@@ -90,6 +90,15 @@ class Reader132(FormatReader):
         img = data[62:]
         return name, img
 
+    def image_dest(self, name, cwd):
+        base = os.path.abspath(cwd)
+        if not base.endswith(os.sep):
+            base += os.sep
+        ans = os.path.abspath(os.path.join(base, name))
+        if os.path.commonprefix([ans, base]) != base:
+            ans = ''
+        return ans
+
     def get_text_page(self, number):
         '''
         Only palmdoc and zlib compressed are supported. The text is
@@ -157,13 +166,14 @@ class Reader132(FormatReader):
         if not os.path.exists(os.path.join(output_dir, 'images/')):
             os.makedirs(os.path.join(output_dir, 'images/'))
         images = []
-        with CurrentDir(os.path.join(output_dir, 'images/')):
+        with CurrentDir(os.path.join(output_dir, 'images/')) as cwd:
             for i in range(0, self.header_record.num_image_pages):
                 name, img = self.get_image(self.header_record.image_data_offset + i)
-                images.append(name)
-                with open(name, 'wb') as imgf:
-                    self.log.debug('Writing image %s to images/' % name)
-                    imgf.write(img)
+                if dest := self.image_dest(name, cwd):
+                    images.append(name)
+                    with open(dest, 'wb') as imgf:
+                        self.log.debug(f'Writing image {name} to images/')
+                        imgf.write(img)
 
         opf_path = self.create_opf(output_dir, images, toc)
 
@@ -210,8 +220,9 @@ class Reader132(FormatReader):
         if not os.path.exists(output_dir):
             os.makedirs(output_dir)
 
-        with CurrentDir(output_dir):
+        with CurrentDir(output_dir) as cwd:
             for i in range(0, self.header_record.num_image_pages):
                 name, img = self.get_image(self.header_record.image_data_offset + i)
-                with open(name, 'wb') as imgf:
-                    imgf.write(img)
+                if dest := self.image_dest(name, cwd):
+                    with open(dest, 'wb') as imgf:
+                        imgf.write(img)
diff --git a/src/calibre/ebooks/pdb/ereader/reader202.py b/src/calibre/ebooks/pdb/ereader/reader202.py
index 152b3d1..9b4ccf4 100644
--- a/src/calibre/ebooks/pdb/ereader/reader202.py
+++ b/src/calibre/ebooks/pdb/ereader/reader202.py
@@ -109,13 +109,13 @@ class Reader202(FormatReader):
         if not os.path.exists(os.path.join(output_dir, 'images/')):
             os.makedirs(os.path.join(output_dir, 'images/'))
         images = []
-        with CurrentDir(os.path.join(output_dir, 'images/')):
+        with CurrentDir(os.path.join(output_dir, 'images/')) as cwd:
             for i in range(self.header_record.non_text_offset, len(self.sections)):
                 name, img = self.get_image(i)
-                if name:
-                    name = as_unicode(name)
+                name = as_unicode(name or b'')
+                if name and (dest := self.image_dest(name, cwd)):
                     images.append(name)
-                    with open(name, 'wb') as imgf:
+                    with open(dest, 'wb') as imgf:
                         self.log.debug('Writing image %s to images/' % name)
                         imgf.write(img)
 
@@ -151,6 +151,15 @@ class Reader202(FormatReader):
 
         return pml
 
+    def image_dest(self, name, cwd):
+        base = os.path.abspath(cwd)
+        if not base.endswith(os.sep):
+            base += os.sep
+        ans = os.path.abspath(os.path.join(base, name))
+        if os.path.commonprefix([ans, base]) != base:
+            ans = ''
+        return ans
+
     def dump_images(self, output_dir):
         '''
         This is primarily used for debugging and 3rd party tools to
@@ -159,8 +168,10 @@ class Reader202(FormatReader):
         if not os.path.exists(output_dir):
             os.makedirs(output_dir)
 
-        with CurrentDir(output_dir):
+        with CurrentDir(output_dir) as cwd:
             for i in range(0, self.header_record.num_image_pages):
                 name, img = self.get_image(self.header_record.image_data_offset + i)
-                with open(name, 'wb') as imgf:
-                    imgf.write(img)
+                name = as_unicode(name or b'')
+                if name and (dest := self.image_dest(name, cwd)):
+                    with open(dest, 'wb') as imgf:
+                        imgf.write(img)
