Package: calibre
Version: 8.5.0+ds-1+deb13u3
Severity: important

Dear Maintainer,

ebook-convert fails on every EPUB that uses font obfuscation and reports the
file as DRM-protected, although such files carry no DRM. The cause is a
NameError for is_existing_subpath in
calibre/ebooks/conversion/plugins/epub_input.py.

The affected code is the guard added for CVE-2026-25636. Its call site is
present in this package, but neither the import nor the two helper functions
it depends on are, so the guard raises NameError every time it is reached.


Steps to reproduce
------------------
Convert any EPUB whose META-INF/encryption.xml declares font obfuscation, that
is, an EncryptionMethod with one of

  Algorithm="http://www.idpf.org/2008/embedding";   (IDPF obfuscation)
  Algorithm="http://ns.adobe.com/pdf/enc#RC";       (Adobe obfuscation)

Font obfuscation only mangles the embedded font files so they cannot be
extracted and reused directly; it is not a DRM scheme and is common in
commercially published EPUBs. Such a file can be built from any EPUB with an
embedded font by adding a META-INF/encryption.xml that names that font's path
in a CipherReference URI.

  $ ebook-convert book.epub /tmp/out.txt


Actual result
-------------
1% Converting input to HTML...
InputFormatPlugin: EPUB Input running
on book.epub
Traceback (most recent call last):
  File "/usr/lib/calibre/calibre/ebooks/conversion/plugins/epub_input.py", line 
79, in process_encryption
    if (tkey and is_existing_subpath(path, container_base)):
                 ^^^^^^^^^^^^^^^^^^^
NameError: name 'is_existing_subpath' is not defined
Traceback (most recent call last):
  File "/usr/bin/ebook-convert", line 21, in <module>
    sys.exit(main())
             ~~~~^^
  File "/usr/lib/calibre/calibre/ebooks/conversion/cli.py", line 429, in main
    plumber.run()
    ~~~~~~~~~~~^^
  File "/usr/lib/calibre/calibre/ebooks/conversion/plumber.py", line 1089, in 
run
    self.oeb = self.input_plugin(stream, self.opts,
               ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
                                self.input_fmt, self.log,
                                ^^^^^^^^^^^^^^^^^^^^^^^^^
                                accelerators, tdir)
                                ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/calibre/calibre/customize/conversion.py", line 242, in __call__
    ret = self.convert(stream, options, file_ext,
                       log, accelerators)
  File "/usr/lib/calibre/calibre/ebooks/conversion/plugins/epub_input.py", line 
299, in convert
    raise DRMError(os.path.basename(path))
calibre.ebooks.DRMError: book.epub

Two separate things go wrong here. First, process_encryption raises NameError,
which its own bare "except:" catches and prints, after which the method returns
False. Second, the caller treats that False as "the file is encrypted":

  epub_input.py:297-299
      if os.path.exists(encfile):
          if not self.process_encryption(encfile, opf, log):
              raise DRMError(os.path.basename(path))

So the user-visible symptom is a DRM-free file being reported as
DRM-protected, which is misleading beyond the conversion failure itself.


Expected result
---------------
The file converts, with the obfuscated fonts decrypted, as it did before the
security update.


Root cause
----------
Upstream calibre wires the guard up as follows:

  - src/calibre/ebooks/conversion/plugins/epub_input.py imports it locally
    inside process_encryption:
        from calibre.utils.filenames import is_existing_subpath
  - src/calibre/utils/filenames.py defines is_existing_subpath(child, parent)
  - that function calls is_path_inside(parent, child), also defined in
    src/calibre/utils/filenames.py

In 8.5.0+ds-1+deb13u3, only the call site is present:

  $ grep -rn --include='*.py' "is_existing_subpath" /usr/lib/calibre/calibre/
  /usr/lib/calibre/calibre/ebooks/conversion/plugins/epub_input.py:79:
      if (tkey and is_existing_subpath(path, container_base)):

  $ grep -rn --include='*.py' "def is_existing_subpath(\|def is_path_inside(" \
      /usr/lib/calibre/calibre/
  (no output)

The import is absent as well, both at module level and inside
process_encryption, which is why this surfaces as NameError rather than
ImportError.

Three pieces are therefore missing: the import statement, is_existing_subpath,
and is_path_inside.


Security impact
---------------
As far as I can tell, none: the package fails closed. The NameError is raised
before decrypt_font() is reached, and the enclosing bare "except:" turns the
whole call into "return False", so the vulnerable write path described in
CVE-2026-25636 cannot execute at all. The mitigation is thus effective, but at
the price of breaking conversion of every font-obfuscated EPUB.


Suggested fix
-------------
Add the two helper functions to calibre/utils/filenames.py and the local import
to process_encryption, matching upstream.


Other suites
------------
The same CVE fix was backported to bullseye (5.12.0+dfsg-1+deb11u5) and
bookworm (6.13.0+repack-2+deb12u9). I have not checked whether those packages
carry the same omission; it seems worth verifying. Unstable ships calibre
9.12.0+ds+~0.10.6-1, which contains the complete upstream fix (released
upstream in 9.2.0) and is not affected.


-- System Information
Debian Release: 13.6 (trixie)
Architecture: amd64
Observed in a Docker container based on the official python:3.14-slim image,
with calibre installed from trixie main via apt. No calibre plugins are
installed and no configuration was changed; the failure is reproducible on a
freshly built image.

Versions of packages calibre depends on:
ii  calibre   8.5.0+ds-1+deb13u3
ii  python3   3.13.5-1

Reply via email to