diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 64293d757331d75..fc4fcebfbe230a7 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -1060,6 +1060,29 @@ def test_uninitialized_BZ2Decompressor_crash(self): self.assertEqual(BZ2Decompressor.__new__(BZ2Decompressor). decompress(bytes()), b'') + @support.nomemtest + def test_decompress_memoryerror_no_dangling_input(self): + # gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the + # decompressor must not keep a dangling pointer into the input buffer. + import _testcapi + data = bz2.compress(b'x' * 4096) + for start in range(0, 40): + d = bz2.BZ2Decompressor() + try: + _testcapi.set_nomemory(start, start + 1) + try: + d.decompress(bytearray(data), max_length=0) + except MemoryError: + pass + finally: + _testcapi.remove_mem_hooks() + try: + out = d.decompress(bytearray(data)) + except (MemoryError, ValueError, EOFError, OSError): + continue + self.assertIsInstance(out, bytes) + self.assertNotIn(b'\x00' * 64, out) + class CompressDecompressTest(BaseTest): def testCompress(self): diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index d4f954b34c12526..7ed6b4fbdf471cf 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -383,6 +383,29 @@ def test_uninitialized_LZMADecompressor_crash(self): self.assertEqual(LZMADecompressor.__new__(LZMADecompressor). decompress(bytes()), b'') + @support.nomemtest + def test_decompress_memoryerror_no_dangling_input(self): + # gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the + # decompressor must not keep a dangling pointer into the input buffer. + import _testcapi + data = lzma.compress(b'x' * 4096) + for start in range(0, 40): + d = lzma.LZMADecompressor() + try: + _testcapi.set_nomemory(start, start + 1) + try: + d.decompress(bytearray(data), max_length=0) + except MemoryError: + pass + finally: + _testcapi.remove_mem_hooks() + try: + out = d.decompress(bytearray(data)) + except (MemoryError, ValueError, EOFError, OSError, LZMAError): + continue + self.assertIsInstance(out, bytes) + self.assertNotIn(b'\x00' * 64, out) + def test_riscv_filter_constant_exists(self): self.assertTrue(lzma.FILTER_RISCV) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index ed9d85408159b28..9e84486e5cce741 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -1216,6 +1216,31 @@ def test_refleaks_in___init__(self): zlibd.__init__() self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + @support.nomemtest + @unittest.skipUnless(hasattr(zlib, '_ZlibDecompressor'), + 'requires zlib._ZlibDecompressor') + def test_decompress_memoryerror_no_dangling_input(self): + # gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the + # decompressor must not keep a dangling pointer into the input buffer. + import _testcapi + data = zlib.compress(b'x' * 4096) + for start in range(0, 40): + d = zlib._ZlibDecompressor() + try: + _testcapi.set_nomemory(start, start + 1) + try: + d.decompress(bytearray(data), max_length=0) + except MemoryError: + pass + finally: + _testcapi.remove_mem_hooks() + try: + out = d.decompress(bytearray(data)) + except (MemoryError, ValueError, EOFError, OSError, zlib.error): + continue + self.assertIsInstance(out, bytes) + self.assertNotIn(b'\x00' * 64, out) + class CustomInt: def __index__(self): diff --git a/Misc/NEWS.d/next/Tests/2026-07-26-07-17-42.gh-issue-148395.rUUXvU.rst b/Misc/NEWS.d/next/Tests/2026-07-26-07-17-42.gh-issue-148395.rUUXvU.rst new file mode 100644 index 000000000000000..e46de753f0c1e06 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-07-26-07-17-42.gh-issue-148395.rUUXvU.rst @@ -0,0 +1,5 @@ +Add regression tests for the CVE-2026-6100 decompressor fix so a +``bz2.BZ2Decompressor``, ``lzma.LZMADecompressor``, or +``zlib._ZlibDecompressor`` that raised ``MemoryError`` during +``decompress()`` cannot silently regain a dangling input pointer on +the next call.