Version in base suite: 8.5.0+ds-1+deb13u2 Base version: calibre_8.5.0+ds-1+deb13u2 Target version: calibre_8.5.0+ds-1+deb13u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/calibre/calibre_8.5.0+ds-1+deb13u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/calibre/calibre_8.5.0+ds-1+deb13u3.dsc changelog | 14 patches/series | 5 patches/upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch | 163 ++++++++++ patches/upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch | 58 +++ patches/upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch | 31 + patches/upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch | 36 ++ patches/upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch | 27 + 7 files changed, 334 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp_fu1sgal/calibre_8.5.0+ds-1+deb13u2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp_fu1sgal/calibre_8.5.0+ds-1+deb13u3.dsc: no acceptable signature found diff -Nru calibre-8.5.0+ds/debian/changelog calibre-8.5.0+ds/debian/changelog --- calibre-8.5.0+ds/debian/changelog 2026-03-01 07:11:43.000000000 +0000 +++ calibre-8.5.0+ds/debian/changelog 2026-05-24 23:19:53.000000000 +0000 @@ -1,3 +1,17 @@ +calibre (8.5.0+ds-1+deb13u3) trixie; urgency=medium + + * Fix security vulnerabilities and code quality issues (Closes: #1135543) + * CVE-2026-30853: RB Input: Ensure files are extracted within container + dir + * CVE-2026-33205 (1/2): E-book viewer: prevent reading background images + from outside the config dir + * CVE-2026-33205 (2/2): E-book viewer: Disallow background images from + the internet. This was an unused feature anyway + * CVE-2026-33206: TXT Input: Ensure resource files are read only from + book contents + + -- YOKOTA Hiroshi Mon, 25 May 2026 08:19:53 +0900 + calibre (8.5.0+ds-1+deb13u2) trixie; urgency=medium * CVE-2026-25635: CHM Input: Ignore internal files that have paths that diff -Nru calibre-8.5.0+ds/debian/patches/series calibre-8.5.0+ds/debian/patches/series --- calibre-8.5.0+ds/debian/patches/series 2026-03-01 07:11:43.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/series 2026-05-24 23:19:53.000000000 +0000 @@ -87,3 +87,8 @@ upstream/0087-CVE-2026-26065-PDB-Input-Ensure-extracted-images-are.patch upstream/0088-CVE-2026-27810-Content-server-Sanitize-content-dispo.patch upstream/0089-CVE-2026-27824-Content-server-When-banning-IPs-for-r.patch +upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch +upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch +upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch +upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch +upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch diff -Nru calibre-8.5.0+ds/debian/patches/upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch calibre-8.5.0+ds/debian/patches/upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch --- calibre-8.5.0+ds/debian/patches/upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch 1970-01-01 00:00:00.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/upstream/0090-Fix-security-vulnerabilities-and-code-quality-issues.patch 2026-05-24 23:19:53.000000000 +0000 @@ -0,0 +1,163 @@ +From: ECB +Date: Tue, 21 Apr 2026 12:48:42 +0200 +Subject: Fix security vulnerabilities and code quality issues (Closes: + #1135543) + +Forwarded: not-needed +Bug: https://github.com/kovidgoyal/calibre/pull/3101 +Origin: backport, https://github.com/kovidgoyal/calibre/commit/b0c4ba19686232d5bff99d58ce6019546ef4d166 + +High severity: +- Fix typo normapth -> normpath in srv/content.py (broken endpoint) +- Replace eval() with ast.literal_eval() in catalogs/epub_mobi.py +- Log exceptions in FunctionDispatcher.dispatch instead of swallowing + +Medium severity: +- Add path traversal protection to DirContainer read/write/exists +- Fix XPath injection in comments_editor.py merge_contiguous_links +- Use parameterized SQL queries in database2.py library_id setter +- Add safety comment to pickle_loads in utils/serialize.py +--- + src/calibre/ebooks/oeb/base.py | 12 +++++++++--- + src/calibre/gui2/__init__.py | 2 ++ + src/calibre/gui2/comments_editor.py | 2 +- + src/calibre/library/catalogs/epub_mobi.py | 10 ++++++---- + src/calibre/library/database2.py | 6 ++---- + src/calibre/srv/content.py | 2 +- + src/calibre/utils/serialize.py | 2 +- + 7 files changed, 22 insertions(+), 14 deletions(-) + +diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py +index 1192c87..44abb94 100644 +--- a/src/calibre/ebooks/oeb/base.py ++++ b/src/calibre/ebooks/oeb/base.py +@@ -597,12 +597,16 @@ class DirContainer: + def read(self, path): + if path is None: + path = self.opfname +- path = os.path.join(self.rootdir, self._unquote(path)) ++ path = os.path.abspath(os.path.join(self.rootdir, self._unquote(path))) ++ if not path.startswith(os.path.abspath(self.rootdir)): ++ raise ValueError(f'Path {path!r} is not inside {self.rootdir!r}') + with open(path, 'rb') as f: + return f.read() + + def write(self, path, data): +- path = os.path.join(self.rootdir, self._unquote(path)) ++ path = os.path.abspath(os.path.join(self.rootdir, self._unquote(path))) ++ if not path.startswith(os.path.abspath(self.rootdir)): ++ raise ValueError(f'Path {path!r} is not inside {self.rootdir!r}') + dir = os.path.dirname(path) + if not os.path.isdir(dir): + os.makedirs(dir) +@@ -613,9 +617,11 @@ class DirContainer: + if not path: + return False + try: +- path = os.path.join(self.rootdir, self._unquote(path)) ++ path = os.path.abspath(os.path.join(self.rootdir, self._unquote(path))) + except ValueError: # Happens if path contains quoted special chars + return False ++ if not path.startswith(os.path.abspath(self.rootdir)): ++ return False + try: + return os.path.isfile(path) + except UnicodeEncodeError: +diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py +index a5893d2..aca7df6 100644 +--- a/src/calibre/gui2/__init__.py ++++ b/src/calibre/gui2/__init__.py +@@ -845,6 +845,8 @@ class FunctionDispatcher(QObject): + try: + res = self.func(*args, **kwargs) + except: ++ import traceback ++ traceback.print_exc() + res = None + q.put(res) + +diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py +index 8fb2919..6017c57 100644 +--- a/src/calibre/gui2/comments_editor.py ++++ b/src/calibre/gui2/comments_editor.py +@@ -198,7 +198,7 @@ def use_implicit_styling_for_a(a, style_map): + def merge_contiguous_links(root): + all_hrefs = set(root.xpath('//a/@href')) + for href in all_hrefs: +- tags = root.xpath(f'//a[@href="{href}"]') ++ tags = root.xpath('//a[@href=$h]', h=href) + processed = set() + + def insert_tag(parent, child): +diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py +index 3e56266..e6408f6 100644 +--- a/src/calibre/library/catalogs/epub_mobi.py ++++ b/src/calibre/library/catalogs/epub_mobi.py +@@ -351,10 +351,11 @@ class EPUB_MOBI(CatalogPlugin): + log.error(f"coercing thumb_width from '{opts.thumb_width}' to '{self.THUMB_SMALLEST}'") + opts.thumb_width = '1.0' + +- # eval prefix_rules if passed from command line ++ # parse prefix_rules if passed from command line + if type(opts.prefix_rules) is not tuple: + try: +- opts.prefix_rules = eval(opts.prefix_rules) ++ import ast ++ opts.prefix_rules = ast.literal_eval(opts.prefix_rules) + except: + log.error(f'malformed --prefix-rules: {opts.prefix_rules}') + raise +@@ -362,10 +363,11 @@ class EPUB_MOBI(CatalogPlugin): + if len(rule) != 4: + log.error(f'incorrect number of args for --prefix-rules: {rule!r}') + +- # eval exclusion_rules if passed from command line ++ # parse exclusion_rules if passed from command line + if type(opts.exclusion_rules) is not tuple: + try: +- opts.exclusion_rules = eval(opts.exclusion_rules) ++ import ast ++ opts.exclusion_rules = ast.literal_eval(opts.exclusion_rules) + except: + log.error(f'malformed --exclusion-rules: {opts.exclusion_rules}') + raise +diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py +index ff0d30e..646ad15 100644 +--- a/src/calibre/library/database2.py ++++ b/src/calibre/library/database2.py +@@ -108,10 +108,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): + @library_id.setter + def library_id(self, val): + self._library_id_ = str(val) +- self.conn.executescript(f''' +- DELETE FROM library_id; +- INSERT INTO library_id (uuid) VALUES ("{self._library_id_}"); +- ''') ++ self.conn.execute('DELETE FROM library_id') ++ self.conn.execute('INSERT INTO library_id (uuid) VALUES (?)', (self._library_id_,)) + self.conn.commit() + + def connect(self): +diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py +index 0094580..2b1a0e5 100644 +--- a/src/calibre/srv/content.py ++++ b/src/calibre/srv/content.py +@@ -306,7 +306,7 @@ def icon(ctx, rd, which): + + @endpoint('/reader-background/{encoded_fname}', android_workaround=True) + def reader_background(ctx, rd, encoded_fname): +- base = os.path.abspath(os.path.normapth(os.path.join(config_dir, 'viewer', 'background-images'))) ++ base = os.path.abspath(os.path.normpath(os.path.join(config_dir, 'viewer', 'background-images'))) + fname = bytes.fromhex(encoded_fname) + q = os.path.abspath(os.path.normpath(os.path.join(base, fname))) + if not q.startswith(base): +diff --git a/src/calibre/utils/serialize.py b/src/calibre/utils/serialize.py +index 81e187f..75f3370 100644 +--- a/src/calibre/utils/serialize.py ++++ b/src/calibre/utils/serialize.py +@@ -119,4 +119,4 @@ def pickle_dumps(data): + + def pickle_loads(dump): + import pickle +- return pickle.loads(dump, encoding='utf-8') ++ return pickle.loads(dump, encoding='utf-8') # nosec: only used for calibre's own serialized data diff -Nru calibre-8.5.0+ds/debian/patches/upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch calibre-8.5.0+ds/debian/patches/upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch --- calibre-8.5.0+ds/debian/patches/upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch 1970-01-01 00:00:00.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/upstream/0091-CVE-2026-30853-RB-Input-Ensure-files-are-extracted-w.patch 2026-05-24 23:19:53.000000000 +0000 @@ -0,0 +1,58 @@ +From: Kovid Goyal +Date: Fri, 6 Mar 2026 07:39:44 +0530 +Subject: CVE-2026-30853: RB Input: Ensure files are extracted within + container dir + +Forwarded: not-needed +Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-7mp7-rfrg-542x +Origin: https://github.com/kovidgoyal/calibre/commit/0f8dc639337d9ace67201e15ca12d5906d05f4c8 + +Signed-off-by: YOKOTA Hiroshi +--- + src/calibre/ebooks/rb/reader.py | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/src/calibre/ebooks/rb/reader.py b/src/calibre/ebooks/rb/reader.py +index c1f77dd..b13066a 100644 +--- a/src/calibre/ebooks/rb/reader.py ++++ b/src/calibre/ebooks/rb/reader.py +@@ -67,6 +67,15 @@ class Reader: + + return toc + ++ def get_safe_path(self, output_dir, name): ++ base = os.path.abspath(output_dir) ++ 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(self, toc_item, output_dir): + if toc_item.flags in (1, 2): + return +@@ -87,8 +96,9 @@ class Reader: + else: + output += self.stream.read(toc_item.size).decode('cp1252' if self.encoding is None else self.encoding, 'replace') + +- with open(os.path.join(output_dir, toc_item.name.decode('utf-8')), 'wb') as html: +- html.write(output.replace('', '<TITLE> ').encode('utf-8')) ++ if path := self.get_safe_path(output_dir, toc_item.name.decode('utf-8')): ++ with open(path, 'wb') as html: ++ html.write(output.replace('<TITLE>', '<TITLE> ').encode('utf-8')) + + def get_image(self, toc_item, output_dir): + if toc_item.flags != 0: +@@ -97,8 +107,9 @@ class Reader: + self.stream.seek(toc_item.offset) + data = self.stream.read(toc_item.size) + +- with open(os.path.join(output_dir, toc_item.name.decode('utf-8')), 'wb') as img: +- img.write(data) ++ if path := self.get_safe_path(output_dir, toc_item.name.decode('utf-8')): ++ with open(path, 'wb') as img: ++ img.write(data) + + def extract_content(self, output_dir): + self.log.debug('Extracting content from file...') diff -Nru calibre-8.5.0+ds/debian/patches/upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch calibre-8.5.0+ds/debian/patches/upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch --- calibre-8.5.0+ds/debian/patches/upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch 1970-01-01 00:00:00.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/upstream/0092-CVE-2026-33205-1-2-E-book-viewer-prevent-reading-bac.patch 2026-05-24 23:19:53.000000000 +0000 @@ -0,0 +1,31 @@ +From: Kovid Goyal <kovid@kovidgoyal.net> +Date: Mon, 16 Mar 2026 08:50:19 +0530 +Subject: CVE-2026-33205 (1/2): E-book viewer: prevent reading background + images from outside the config dir + +Forwarded: not-needed +Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-4926-v9px-wv7v +Origin: https://github.com/kovidgoyal/calibre/commit/6eb7b5458f183c8a037e9d7dac428122a77204e4 + +Signed-off-by: YOKOTA Hiroshi <yokota.hgml@gmail.com> +--- + src/calibre/gui2/viewer/web_view.py | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py +index d2116bb..e09688c 100644 +--- a/src/calibre/gui2/viewer/web_view.py ++++ b/src/calibre/gui2/viewer/web_view.py +@@ -106,8 +106,11 @@ def background_image(encoded_fname=''): + except FileNotFoundError: + return 'image/jpeg', b'' + fname = bytes.fromhex(encoded_fname).decode() +- img_path = os.path.join(viewer_config_dir, 'background-images', fname) ++ base = os.path.abspath(os.path.join(viewer_config_dir, 'background-images')) + os.sep ++ img_path = os.path.abspath(os.path.join(base, fname)) + mt = guess_type(fname)[0] or 'image/jpeg' ++ if not img_path.startswith(base): ++ return mt, b'' + try: + with open(make_long_path_useable(img_path), 'rb') as f: + return mt, f.read() diff -Nru calibre-8.5.0+ds/debian/patches/upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch calibre-8.5.0+ds/debian/patches/upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch --- calibre-8.5.0+ds/debian/patches/upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch 1970-01-01 00:00:00.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/upstream/0093-CVE-2026-33205-2-2-E-book-viewer-Disallow-background.patch 2026-05-24 23:19:53.000000000 +0000 @@ -0,0 +1,36 @@ +From: Kovid Goyal <kovid@kovidgoyal.net> +Date: Mon, 16 Mar 2026 08:58:25 +0530 +Subject: CVE-2026-33205 (2/2): E-book viewer: Disallow background images from + the internet. This was an unused feature anyway + +Forwarded: not-needed +Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-4926-v9px-wv7v +Origin: https://github.com/kovidgoyal/calibre/commit/b1ef6a8142b8dadeb7e72c250c65d42b36ee7118 + +Signed-off-by: YOKOTA Hiroshi <yokota.hgml@gmail.com> +--- + src/calibre/gui2/viewer/web_view.py | 11 ----------- + 1 file changed, 11 deletions(-) + +diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py +index e09688c..9c8e237 100644 +--- a/src/calibre/gui2/viewer/web_view.py ++++ b/src/calibre/gui2/viewer/web_view.py +@@ -115,17 +115,6 @@ def background_image(encoded_fname=''): + with open(make_long_path_useable(img_path), 'rb') as f: + return mt, f.read() + except FileNotFoundError: +- if fname.startswith(('https://', 'http://')): +- from calibre import browser +- br = browser() +- try: +- with br.open(fname) as src: +- data = src.read() +- except Exception: +- return mt, b'' +- with open(make_long_path_useable(img_path), 'wb') as dest: +- dest.write(data) +- return mt, data + return mt, b'' + + diff -Nru calibre-8.5.0+ds/debian/patches/upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch calibre-8.5.0+ds/debian/patches/upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch --- calibre-8.5.0+ds/debian/patches/upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch 1970-01-01 00:00:00.000000000 +0000 +++ calibre-8.5.0+ds/debian/patches/upstream/0094-CVE-2026-33206-TXT-Input-Ensure-resource-files-are-r.patch 2026-05-24 23:19:53.000000000 +0000 @@ -0,0 +1,27 @@ +From: Kovid Goyal <kovid@kovidgoyal.net> +Date: Mon, 16 Mar 2026 08:37:16 +0530 +Subject: CVE-2026-33206: TXT Input: Ensure resource files are read only from + book contents + +Forwarded: not-needed +Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-h3p4-m74f-43g6 +Origin: https://github.com/kovidgoyal/calibre/commit/c43f347837dbc00d9a7b5ff15a228b6f6081e290 + +Signed-off-by: YOKOTA Hiroshi <yokota.hgml@gmail.com> +--- + src/calibre/ebooks/conversion/plugins/txt_input.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/calibre/ebooks/conversion/plugins/txt_input.py b/src/calibre/ebooks/conversion/plugins/txt_input.py +index d6d43a6..6bc8289 100644 +--- a/src/calibre/ebooks/conversion/plugins/txt_input.py ++++ b/src/calibre/ebooks/conversion/plugins/txt_input.py +@@ -112,7 +112,7 @@ class TXTInput(InputFormatPlugin): + src = img.get('src') + prefix = src.split(':', 1)[0].lower() + if src and prefix not in ('file', 'http', 'https', 'ftp') and not os.path.isabs(src): +- src = os.path.join(base_dir, src) ++ src = os.path.abspath(os.path.join(base_dir, src)) + if os.path.normcase(src).startswith(base_dir) and os.path.isfile(src) and os.access(src, os.R_OK): + with open(src, 'rb') as f: + data = f.read()