# LZMADecompressor.decompress Use After Free

Malformed LZMA stream leaves a dangling next_in pointer in Python's _lzma decompressor; a second decompress() call memcpy's attacker data into freed memory, enabling code execution.

- Payout: $1,500
- Severity: critical
- Program: Internet Bug Bounty
- Credited: johnleitch
- Disclosed: 2016-12-05
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/172562
- Library page: https://bugbountylibrary.com/r/h1%3A172562
- Tags: domain:oss impact:rce kind:bounty platform:hackerone sev:critical tech:python technique:incomplete-fix technique:source-review vuln:dos vuln:memory-corruption

**Target:** _lzma module LZMADecompressor.decompress (CPython 3.5.2)
**Root cause:** When decompress_buf fails on a malformed stream, decompress() returns without clearing lzs->next_in, which still points into the caller's Py_buffer; the buffer is released on return, leaving a dangling pointer reused by the next decompress() call.
**Steps:**
1. Create `d = _lzma.LZMADecompressor()`
2. Call `d.decompress(malformed_stream)` so decompress_buf fails, leaving lzs->next_in dangling after PyBuffer_Release frees the input
3. Heap-spray to reclaim the freed allocation with controlled bytes
4. Call `d.decompress(attacker_data)` again; memcpy writes into the dangling lzs->next_in, corrupting memory
5. Overwrite a function pointer / return address to redirect execution (PoC uses return-to-libc to system address 0x76064070)
**How found:** Code review of _lzmamodule.c: noticed the early `return NULL` on decompress_buf failure skips clearing lzs->next_in, while the wrapper releases the Py_buffer, creating a dangling pointer reused on subsequent calls.
**Exploitation:** Heap spray ~0x700 MB buffers of `\x76\x70\x40\x06` (system address) to reclaim the freed input buffer, then trigger a second decompress with a crafted payload so the memcpy writes attacker-controlled bytes over the reclaimed region, achieving a return-to-libc code execution.
**Escalation:** Remote exploitation possible where untrusted LZMA streams are received over a network.
**Insight:** In stateful streaming decompressor/decoder APIs, audit error paths that return early: any cached pointer into caller-owned input (next_in/avail_in) must be invalidated on failure, since the wrapper frees the buffer but the object retains the pointer for the next call.
