β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3269

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3269 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

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_testable
↓ fix.diffper-fix-DF-3269

Confirmed 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.