From cc7d163afee8001cecd096c24a6957f34ebcd3b8 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 23 Aug 2026 10:18:03 +0100 Subject: [PATCH 1/2] gh-156173: Fix `zlib.Decompress.flush()` silently returning corrupted output instead of raising `zlib.error` (GH-156176) (cherry picked from commit 54426877bd7ea4e532f5d531a2958d5aa1c29f7c) Co-authored-by: Stan Ulbrych --- Lib/test/test_zlib.py | 14 ++++++++++++++ .../2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst | 2 ++ Modules/zlibmodule.c | 11 +++++++++++ 3 files changed, 27 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 32add7bc1e92f7..ed2eb0e05eb532 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -611,6 +611,20 @@ def test_decompress_eof_incomplete_stream(self): dco.flush() self.assertFalse(dco.eof) + def test_decompress_flush_corrupt_stream(self): + x = b'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # 'foo' + corrupt = x[:-1] + b'\x00' + dco = zlib.decompressobj() + self.assertEqual(dco.decompress(corrupt, 1), b'f') + self.assertRaises(zlib.error, dco.flush) + + def test_decompress_flush_twice(self): + x = b'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # 'foo' + dco = zlib.decompressobj() + self.assertEqual(dco.decompress(x), b'foo') + self.assertEqual(dco.flush(), b'') + self.assertEqual(dco.flush(), b'') + def test_decompress_unused_data(self): # Repeated calls to decompress() after EOF should accumulate data in # dco.unused_data, instead of just storing the arg to the last call. diff --git a/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst b/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst new file mode 100644 index 00000000000000..846931a1cab511 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst @@ -0,0 +1,2 @@ +Calling :meth:`zlib.Decompress.flush` on invalid compressed data now +raises :exc:`zlib.error` instead of being silently ignored. diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 820bace9420358..69cf2bffde5ffd 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1277,6 +1277,13 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, ENTER_ZLIB(self); + /* A previous flush() already reached the end of the stream and freed the + decompression state, so there is nothing left to process. */ + if (!self->is_initialised) { + PyMutex_Unlock(&self->mutex); + return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } + if (PyObject_GetBuffer(self->unconsumed_tail, &data, PyBUF_SIMPLE) == -1) { LEAVE_ZLIB(self); return NULL; @@ -1334,6 +1341,10 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, goto abort; } } + else if (err != Z_OK && err != Z_BUF_ERROR) { + zlib_error(state, self->zst, err, "while decompressing data"); + goto abort; + } return_value = OutputBuffer_WindowFinish(&buffer, &window, self->zst.avail_out); if (return_value != NULL) { From 6c6f93d932108735c052da6ae39ddeebe8a09831 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 23 Aug 2026 10:27:20 +0100 Subject: [PATCH 2/2] !fixup backport --- Modules/zlibmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 69cf2bffde5ffd..a3fdc5ac369431 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1280,7 +1280,7 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, /* A previous flush() already reached the end of the stream and freed the decompression state, so there is nothing left to process. */ if (!self->is_initialised) { - PyMutex_Unlock(&self->mutex); + LEAVE_ZLIB(self); return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); }