Skip to content

zlib._ZlibDecompressor(zdict=...) raises SystemError on a zlib-wrapped stream #160001

Open

Description

@pganssle

Bug description

zlib._ZlibDecompressor accepts a zdict argument, but for a zlib-wrapped stream (positive wbits) compressed with that dictionary, decompress fails with a SystemError instead of decompressing. zlib.decompressobj handles the same input:

>>> import zlib
>>> zd = b"hello world " * 10
>>> c = zlib.compressobj(zdict=zd)
>>> s = c.compress(b"hello world hello") + c.flush()
>>> zlib.decompressobj(zdict=zd).decompress(s)
b'hello world hello'
>>> zlib._ZlibDecompressor(zdict=zd).decompress(s)
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
    zlib._ZlibDecompressor(zdict=zd).decompress(s)
SystemError: <method 'decompress' of 'zlib._ZlibDecompressor' objects> returned NULL without setting an exception

Raw deflate works, so this is specific to the wrapped format:

>>> c = zlib.compressobj(wbits=-15, zdict=zd)
>>> s = c.compress(b"hello world hello") + c.flush()
>>> zlib._ZlibDecompressor(-15, zdict=zd).decompress(s)
b'hello world hello'

Two things combine here. The constructor only applies the dictionary up front for raw deflate (wbits < 0): Modules/zlibmodule.c lines 1775-1780. For a wrapped stream zlib instead signals Z_NEED_DICT after reading the header, and decompress_buf handles that by jumping to error without setting an exception: Modules/zlibmodule.c lines 1535-1541. zlib.Decompress handles the same return by calling inflateSetDictionary and continuing: Modules/zlibmodule.c lines 932-938, which is presumably what _ZlibDecompressor should do as well (and raise zlib.error when no zdict was given, as Decompress does).

_ZlibDecompressor is private and gzip only uses it with wbits=-15, so the impact is low; but a SystemError from a C method is always a bug, and the argument is accepted for this case, so we may as well fix it.

Found by the Hypothesis tests in #159960, recorded there as ZlibDecompressorZdictTests.test_zlib_wrapper under expectedFailure.

CPython versions tested on

CPython main (4e3d5ec), zlib 1.3.2

Operating systems tested on

Linux

Activity

  1. pganssle commented on Oct 21, 2026

    @pganssle
    MemberAuthor

    Fuzzing the tests in #159960 under Atheris turned up a broader trigger for this: no zdict is needed. Any zlib-wrapped input whose header has the FDICT flag set makes _ZlibDecompressor.decompress return NULL without an exception, since that is exactly the Z_NEED_DICT branch linked above. Eight bytes are enough:

    >>> import zlib
    >>> stream = b"\x78\xbb" + b"\x00\x00\x00\x01\x03\x00"   # CMF/FLG with FDICT set
    >>> zlib.decompressobj().decompress(stream)
    Traceback (most recent call last):
      File "<python-input-2>", line 1, in <module>
        zlib.decompressobj().decompress(stream)
    zlib.error: Error 2 while decompressing data
    >>> zlib._ZlibDecompressor().decompress(stream)
    Traceback (most recent call last):
      File "<python-input-3>", line 1, in <module>
        zlib._ZlibDecompressor().decompress(stream)
    SystemError: <method 'decompress' of 'zlib._ZlibDecompressor' objects> returned NULL without setting an exception

    In a debug build that is a fatal C assertion in ceval.c rather than a SystemError, which is what killed the test worker on every CI job when #159960 first ran, so random invalid input reaches it easily. gzip only constructs _ZlibDecompressor with wbits=-15, where there is no header to carry the flag, so the stdlib's own use is not affected; it is only reachable by calling the private class with a positive or automatic wbits. So the "impact is low" assessment above still holds, but the fix should raise zlib.error for the no-zdict case rather than only wiring up inflateSetDictionary.

    #159960 now skips such headers in the zlib wrapper's test_invalid_input and pins the behaviour in test_fdict_header (release builds only, for the reason above).

  2. santhoshkumarselvaraj784-hue commented on Oct 22, 2026

    @santhoshkumarselvaraj784-hue

    Hi, I’m a beginner Python developer and would like to make my first open-source contribution. I’m interested in working on this issue.

    Could you please let me know if I can work on it? I’ll follow the project’s contribution guidelines and submit a pull request once the changes are ready.
    @aisk

  3. aisk commented on Oct 22, 2026

    @aisk
    Member

    8. Hi, I’m a beginner Python developer and would like to make my first open-source contribution. I’m interested in working on this issue.
    Could you please let me know if I can work on it? I’ll follow the project’s contribution guidelines and submit a pull request once the changes are ready.
    @aisk

    Hi, we generally don't assign issues to people. If the issue is valid, the proposed fix makes sense, and nobody else is already working on it or has said they plan to, feel free to go ahead and open a PR. Please have a brief read of the devguide before starting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions