Skip to content

Commit 5bcd671

Browse files
committed
gh-148395: Add regression tests for decompressor UAF after MemoryError
The CVE-2026-6100 fix (next_in = NULL on error) already landed. Add nomemtest coverage for bz2, lzma and zlib._ZlibDecompressor so a regression cannot silently return.
1 parent f5f5059 commit 5bcd671

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

Lib/test/test_bz2.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,29 @@ def test_uninitialized_BZ2Decompressor_crash(self):
10601060
self.assertEqual(BZ2Decompressor.__new__(BZ2Decompressor).
10611061
decompress(bytes()), b'')
10621062

1063+
@support.nomemtest
1064+
def test_decompress_memoryerror_no_dangling_input(self):
1065+
# gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the
1066+
# decompressor must not keep a dangling pointer into the input buffer.
1067+
import _testcapi
1068+
data = bz2.compress(b'x' * 4096)
1069+
for start in range(0, 40):
1070+
d = bz2.BZ2Decompressor()
1071+
try:
1072+
_testcapi.set_nomemory(start, start + 1)
1073+
try:
1074+
d.decompress(bytearray(data), max_length=0)
1075+
except MemoryError:
1076+
pass
1077+
finally:
1078+
_testcapi.remove_mem_hooks()
1079+
try:
1080+
out = d.decompress(bytearray(data))
1081+
except (MemoryError, ValueError, EOFError, OSError):
1082+
continue
1083+
self.assertIsInstance(out, bytes)
1084+
self.assertNotIn(b'\x00' * 64, out)
1085+
10631086

10641087
class CompressDecompressTest(BaseTest):
10651088
def testCompress(self):

Lib/test/test_lzma.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,29 @@ def test_uninitialized_LZMADecompressor_crash(self):
383383
self.assertEqual(LZMADecompressor.__new__(LZMADecompressor).
384384
decompress(bytes()), b'')
385385

386+
@support.nomemtest
387+
def test_decompress_memoryerror_no_dangling_input(self):
388+
# gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the
389+
# decompressor must not keep a dangling pointer into the input buffer.
390+
import _testcapi
391+
data = lzma.compress(b'x' * 4096)
392+
for start in range(0, 40):
393+
d = lzma.LZMADecompressor()
394+
try:
395+
_testcapi.set_nomemory(start, start + 1)
396+
try:
397+
d.decompress(bytearray(data), max_length=0)
398+
except MemoryError:
399+
pass
400+
finally:
401+
_testcapi.remove_mem_hooks()
402+
try:
403+
out = d.decompress(bytearray(data))
404+
except (MemoryError, ValueError, EOFError, OSError, LZMAError):
405+
continue
406+
self.assertIsInstance(out, bytes)
407+
self.assertNotIn(b'\x00' * 64, out)
408+
386409
def test_riscv_filter_constant_exists(self):
387410
self.assertTrue(lzma.FILTER_RISCV)
388411

Lib/test/test_zlib.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,31 @@ def test_refleaks_in___init__(self):
12161216
zlibd.__init__()
12171217
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
12181218

1219+
@support.nomemtest
1220+
@unittest.skipUnless(hasattr(zlib, '_ZlibDecompressor'),
1221+
'requires zlib._ZlibDecompressor')
1222+
def test_decompress_memoryerror_no_dangling_input(self):
1223+
# gh-148395 / CVE-2026-6100: after MemoryError in decompress(), the
1224+
# decompressor must not keep a dangling pointer into the input buffer.
1225+
import _testcapi
1226+
data = zlib.compress(b'x' * 4096)
1227+
for start in range(0, 40):
1228+
d = zlib._ZlibDecompressor()
1229+
try:
1230+
_testcapi.set_nomemory(start, start + 1)
1231+
try:
1232+
d.decompress(bytearray(data), max_length=0)
1233+
except MemoryError:
1234+
pass
1235+
finally:
1236+
_testcapi.remove_mem_hooks()
1237+
try:
1238+
out = d.decompress(bytearray(data))
1239+
except (MemoryError, ValueError, EOFError, OSError, zlib.error):
1240+
continue
1241+
self.assertIsInstance(out, bytes)
1242+
self.assertNotIn(b'\x00' * 64, out)
1243+
12191244

12201245
class CustomInt:
12211246
def __index__(self):

0 commit comments

Comments
 (0)