Uninitialized deflate_state.status read on the deflateInit2_ allocation-failure path: deflateEnd skips all cleanup, leaking every stream allocation
Summary
deflateInit2_ allocates the deflate_state with the consumer zalloc (kernel kmalloc - uninitialized; ng_deflate's z_alloc has no M_ZERO) and defers setting s->status until deflateReset. If any of the four follow-on allocations (window/prev/head/overlay) fails under kmem exhaustion, the error path calls deflateEnd, which reads the uninitialized s->status and - because garbage almost never equals INIT_STATE/BUSY_STATE/FINISH_STATE - returns Z_STREAM_ERROR BEFORE any TRY_FREE, leaking the already-succeeded allocations plus the state itself (~256KB per failed init at windowBits=15/memLevel=8). Even when the garbage matches a valid state, the return value is decided by uninitialized memory. Repeated node creation under induced memory pressure converts transient allocation failure into permanent kmem loss (~5x amplification). Only ng_deflate (M_NULLOK) can trigger; mxge's z_alloc cannot fail. Low/code-proven (no PoC; error-path defect). Consumer map: zlib.c compiles only via netgraph7_deflate or mxge (module code, not GENERIC); DF-0265/0266 live via ng_deflate's raw-deflate small-window configs (peer-reachable after CCP); mxge inflate is root-controlled firmware attach. All four known findings UNFIXED in-tree. Fix: s->status = INIT_STATE immediately after the state alloc. [VERIFIED pass2: reproduced via userland build of sys-net-zlib.c itself - failed window alloc makes deflateEnd return Z_STREAM_ERROR from uninitialized status with 4 allocations leaked; clean path leaks nothing]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3269 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| zharness.c | β | 2.9 KB | view raw | |
| zlib_userland.c | β | 173.0 KB | view raw | |
| zlib.h | β | 42.4 KB | view raw | |
| README.md | β | 589 B | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| build.sh | β | 157 B | view raw | |
| run.sh | β | 97 B | view raw | |
| manifest.json | β | 634 B | view raw | |
| verdict.json | β | 2.2 KB | view raw | |
| fix.diff | β | 763 B | view raw | |
| env.txt | β | 272 B | view raw |
DF-3269 -- verification pack
Finding: deflateInit2_ failure path: uninitialized status read makes deflateEnd leak everything
Status: reproduced / impact leak / confidence certain
What was run
guest: cc -O -o zharness zharness.c zlib_userland.c -I.
How to reproduce
guest: ./zharness
Expected
deflateEnd -> Z_STREAM_ERROR (status garbage) and 4 live allocations after the failed init; clean case leaks nothing
See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: zharness.c, zlib_userland.c, zlib.h, run.log.
DF-3269 verification
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Bottom line
Reproduced against the kernel source file itself: sys/net/zlib.c compiled without KERNEL (a configuration its own ifdef ladder supports), driven by an instrumented allocator that 0xAA-fills every allocation (provably uninitialized, matching ng_deflates kmalloc without M_ZERO) and fails the Nth allocation. With the window allocation failing, deflateInit2 returns Z_MEM_ERROR after 5 allocations, and the error-path deflateEnd returns Z_STREAM_ERROR - because s->status is 0xAAAAAAAA garbage that matches no valid state - with FOUR allocations still live (leaked); a clean init+End leaves zero live. The return value of deflateEnd is decided by uninitialized memory exactly as claimed, and the leak is ~4 allocations per failed init (up to ~256KB at windowBits 15/memLevel 8 for the later allocations). Kernel trigger requires kmem exhaustion inside ng_deflate; the userland harness exercises the identical defect code path in the identical source.
Kernel references
- sys/net/zlib.c:785-800
- sys/net/zlib.c:1108-1127
Exploit chain
(none: availability/correctness finding; no privilege chain)
Fix
s->status = INIT_STATE immediately after the state allocation; the same harness against the patched file returns Z_OK on the failure path with zero live allocations
See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).
Reproduction
build: guest: cc -O -o zharness zharness.c zlib_userland.c -I.
run: guest: ./zharness
expected: deflateEnd -> Z_STREAM_ERROR (status garbage) and 4 live allocations after the failed init; clean case leaks nothing
Notes on the PoC (what changed vs the finding sketch)
zlib.c compiles userland natively except MODULE_VERSION(zlib,1) which is now ifdeffed _KERNEL in the packed copy; MAX() is defined for the non-kernel build
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
run.log (case 1: Z_STREAM_ERROR from garbage status + live=4 LEAKED; case 2 sanity: live=0), zharness.c, zlib_userland.c (verbatim sys/net/zlib.c with MODULE_VERSION ifdefd)
PoC changes
zlib.c compiles userland natively except MODULE_VERSION(zlib,1) which is now ifdeffed _KERNEL in the packed copy; MAX() is defined for the non-kernel build
Verified recommended fix
s->status = INIT_STATE immediately after the state allocation; the same harness against the patched file returns Z_OK on the failure path with zero live allocations
Verdict
Reproduced against the kernel source file itself: sys/net/zlib.c compiled without KERNEL (a configuration its own ifdef ladder supports), driven by an instrumented allocator that 0xAA-fills every allocation (provably uninitialized, matching ng_deflates kmalloc without M_ZERO) and fails the Nth allocation. With the window allocation failing, deflateInit2 returns Z_MEM_ERROR after 5 allocations, and the error-path deflateEnd returns Z_STREAM_ERROR - because s->status is 0xAAAAAAAA garbage that matches no valid state - with FOUR allocations still live (leaked); a clean init+End leaves zero live. The return value of deflateEnd is decided by uninitialized memory exactly as claimed, and the leak is ~4 allocations per failed init (up to ~256KB at windowBits 15/memLevel 8 for the later allocations). Kernel trigger requires kmem exhaustion inside ng_deflate; the userland harness exercises the identical defect code path in the identical source.
No comments yet.