Skip to content

gh-148395: Add regression tests for decompressor UAF after MemoryError (CVE-2026-6100)#154713

Closed
Vamsi-klu wants to merge 3 commits into
python:mainfrom
Vamsi-klu:fix-gh148395-decompressor-uaf-tests
Closed

gh-148395: Add regression tests for decompressor UAF after MemoryError (CVE-2026-6100)#154713
Vamsi-klu wants to merge 3 commits into
python:mainfrom
Vamsi-klu:fix-gh148395-decompressor-uaf-tests

Conversation

@Vamsi-klu

Copy link
Copy Markdown

Summary

Adds the missing regression tests for CVE-2026-6100 (gh-148395). The security fix itself (next_in = NULL at the error: labels in bz2 / lzma / zlib decompressors) is already merged on main and most release branches. No production code changes in this PR.

Closes the test gap for gh-148395 (does not replace the still-open 3.12 backport #148503, which is an RM action).


What the issue is

CVE-2026-6100: after a MemoryError inside {BZ2,LZMA,_Zlib}Decompressor.decompress(), the static helper left next_in pointing into the caller's released Py_buffer. The next decompress() call could memcpy through a freed pointer (attacker-controlled length and content) — CVSS 9.1.

The fix (8fc66ae and backports) nulls next_in on the error path. No branch shipped a regression test. A future refactor can reintroduce the UAF with a green CI.


Why I solved it that way

  1. Tests only. Competing with [3.12] gh-148395: Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor (GH-148396) #148503 or re-patching the C modules would be wrong — the fix is done; coverage is not.
  2. support.nomemtest, not bare _testcapi.set_nomemory, so Py_TRACE_REFS builds skip cleanly (they fatal inside _PyRefchain_Trace on allocation failure).
  3. Sweep start in range(0, 40) rather than a hard-coded allocation index — indices are unstable across pymalloc/ASAN/debug/bit-width.
  4. bytearray(data) + max_length=0bytes can stay alive and hide the UAF; max_length=0 forces the !input_buffer_in_use tail-copy path that historically hit the failing PyMem_Malloc.
  5. remove_mem_hooks() in finally — the hook is process-global across RAW/MEM/OBJ; leaking it destroys the rest of the suite.
  6. Single-threaded only — not placed under test_free_threading (global allocator hooks + workers = flaky).
  7. zlib gated on hasattr(zlib, '_ZlibDecompressor') for older-branch cherry-picks.

I did not fold in the optional sticky-error / success-path hardening from the issue discussion — that is a behavior change for a follow-up.


How I did it

File Test
Lib/test/test_bz2.py BZ2DecompressorTest.test_decompress_memoryerror_no_dangling_input
Lib/test/test_lzma.py CompressorDecompressorTestCase.test_decompress_memoryerror_no_dangling_input
Lib/test/test_zlib.py ZlibDecompressorTest.test_decompress_memoryerror_no_dangling_input

Shape (bz2; others mirror):

@support.nomemtest
def test_decompress_memoryerror_no_dangling_input(self):
    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)  # cheap corruption tripwire

No NEWS entry (test-only; Security NEWS for the CVE already released).


Impact


Testing plan

  • ./python -m test test_bz2 test_lzma test_zlib on a debug build — all three new tests PASS with the fix present.
  • Pre-commit / ruff on the test files.
  • Ideal watch-it-fail (optional, for reviewer confidence): locally revert the three next_in = NULL lines, rebuild with ASAN (--with-address-sanitizer --without-pymalloc), confirm heap-use-after-free on the memcpy path, restore fix. I did not block this PR on an ASAN cycle; happy to attach traces if a reviewer wants them.
  • CI: standard Linux/macOS/Windows test jobs.

Everything else


Requested reviewers (subject-matter experts)

Could the following SMEs take a look when convenient (I cannot formally request reviews from this fork account):

  • StanFromIreland — CVE fix author / issue assignee
  • gpshead or security-interested core devs for test shape

Thank you!

…ryError

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.
@bedevere-app bedevere-app Bot added the tests Tests in the Lib/test dir label Jul 26, 2026
@python-cla-bot

python-cla-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app

bedevere-app Bot commented Jul 26, 2026

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

… tests

Satisfy bedevere/news for the test-only PR (Security NEWS for the CVE
fix already shipped).
@Vamsi-klu

Copy link
Copy Markdown
Author

Added a Misc/NEWS.d/next/Tests/ blurb for these regression tests so bedevere/news is satisfied. (A Security NEWS entry already shipped with the CVE fix itself; this Tests entry only covers the new coverage.)

For pure test-only changes, skip news would also be appropriate if a maintainer prefers that instead.

zlib._ZlibDecompressor is not a documented public class, so :class:
roles in the blurb fail Docs CI (--fail-if-new-news-nit). Use plain
double-backticks instead.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc8494c611

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Lib/test/test_bz2.py
Comment on lines +1081 to +1082
except (MemoryError, ValueError, EOFError, OSError):
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make the bz2 test detect the missing pointer reset

When the injected failure reaches the tail-buffer allocation (start == 2 on a --with-pydebug build), removing both bzs->next_in = NULL assignments from _bz2module.c makes the follow-up call raise OSError: Invalid data stream; this handler accepts that result, so the test still passes with the CVE fix reverted. Use a follow-up operation and explicit expected outcome that distinguish a cleared pointer from the dangling-pointer state rather than treating this decompression error as success.

Useful? React with 👍 / 👎.

Comment thread Lib/test/test_zlib.py
Comment on lines +1239 to +1240
except (MemoryError, ValueError, EOFError, OSError, zlib.error):
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make the zlib test detect the missing pointer reset

When the injected failure reaches the tail-buffer allocation (start == 2 on a --with-pydebug build), removing both self->zst.next_in = NULL assignments from zlibmodule.c makes the follow-up call raise zlib.error for an incorrect data check; this handler accepts that result, so the test still passes with the CVE fix reverted. Exercise a post-error operation with an explicit safe result that differs when the stale pointer remains instead of allowing this error to satisfy the regression test.

Useful? React with 👍 / 👎.

@picnixz picnixz closed this Jul 26, 2026
@picnixz

picnixz commented Jul 26, 2026

Copy link
Copy Markdown
Member

We donnot add tests for that as it may depend on many other factors and is CPython specific.

@picnixz

picnixz commented Jul 26, 2026

Copy link
Copy Markdown
Member

Also avoid just letting an agent review everytjing and write everything. This is against our AI policy explained in the devguide.

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

Labels

awaiting review tests Tests in the Lib/test dir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants