hammer2 zlib port drops zcalloc's zero-initialization: deflate window/state kmalloc'd unzeroed, leaving stale-heap reads whose output-independence rests on subtle invariants
| Field | Value |
|---|---|
| ID | DF-2656 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-908 Use of Uninitialized Resource |
| File | sys/vfs/hammer2/zlib/hammer2_zlib_deflate.c |
| Lines | 239, 254-256, 262 (inflate state :196) |
| Area | vfs |
| Confidence | certain |
| Discovered | 2026-08-29 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | hammer2 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
Stock zlib 1.2.8 allocates deflate's window/prev/head/overlay via
zcalloc (which returns zeroed memory). The port replaced these with
kmalloc(..., M_INTWAIT) β none zeroed. Every algorithm-read field was
verified still initialized before use EXCEPT the deflate sliding window
beyond the data written so far: longest_match scans up to
strstart+MAX_MATCH ignoring lookahead (deflate.c:641-757; the code
itself documents the uninitialized accesses at :669-676) and
fill_window's hash re-init reads window[str..str+MIN_MATCH-1]
(:883-901, bounded in-bounds of written data). No leak into output is
possible today: match length is capped to lookahead at return
(:743-758) and match_start+length < strstart+lookahead bounds every
emitted (length,distance) pair to written bytes, so decompressed output
always equals the input. Residual risk: compression CHOICES (hence
comp_size and on-disk compressed bytes) depend on stale heap contents β
a nondeterminism surface with no observable channel found (physical
compressed bytes, dedup XXH64 hashes, and failed-decompress reads are
not exposed to unprivileged users).
Threat model & preconditions
Defense-in-depth only. The concern is fragility: any future edit that lets a match/literal reference window data beyond strstart+lookahead instantly becomes a cross-user kernel-heap disclosure into user-readable file contents, because the structural zeroing upstream zlib relies on is absent. Distinct from DF-0266 (inflate-side window).
Recommended fix
Restore stock zcalloc semantics β add | M_ZERO to the five kmalloc
sites (deflate.c:239,254-256,262; optionally the inflate state at
inflate.c:196, though every inflate field read is explicitly reset
today).
References
- DF-0266 (inflate-side window leak), DF-2655 (error-path behavior surfaced in the same batch audit)
Timeline
- 2026-08-29 Discovered during the pass-2 zlib batch audit (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2656 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 2.0 KB | β raw | |
| README.md | β | 250 B | β raw | |
| build.sh | β | 85 B | view raw | |
| run.sh | β | 49 B | view raw | |
| env.txt | β | 314 B | view raw | |
| manifest.json | β | 692 B | view raw | |
| verdict.json | β | 2.2 KB | view raw | |
| fix.diff | β | 1.7 KB | view raw |
DF-2656 evidence pack
- finding: sys/vfs/hammer2/zlib/hammer2_zlib_deflate.c (239-262)
- verdict: reproduced (certain) - see VERDICT.md
- source-only confirmation (no runtime PoC)
- reproduce: build.sh / run.sh
- fix.diff: present, git-apply-able
DF-2656 - Verification Verdict
Status: reproduced Impact: none (hardening) Confidence: certain
Mechanism
The port allocates the deflate state, window, prev, head and pending overlay with plain kmalloc(M_INTWAIT) - upstream zlib zcalloc() uses calloc semantics (ZEROED memory). Output independence therefore rests on runtime invariants rather than allocation guarantees: the port DOES keep the upstream fill_window high_water zeroing (lines 915-938 zmemzero window beyond high_water) and zmemzero(head) at init (line 194), which currently prevent stale-heap bytes from ever being read into the output window/match chain.
Verdict
source-confirmed (no runtime PoC; deterministic code trace of the cited path:line in the read-only sys/ tree). Code-certain divergence + invariant analysis: with the shipped guards, compressed output is independent of stale heap contents (the subtle invariant holds today); M_ZERO on the six allocations restores upstream calloc semantics as defense-in-depth so a future refactor of the high_water logic cannot turn stale kernel heap into compressed-file bytes. Compile-validated with the batch build.
Threat / reachability
Defense-in-depth on the hammer2 compression path: any future loss of the high_water zeroing invariant would leak freed kernel memory into compressed file contents.
Privilege escalation assessment
n/a (hardening)
Fix
Add M_ZERO to the six kmallocs (matches upstream zcalloc/calloc allocation semantics).
Kernel references
sys/vfs/hammer2/zlib/hammer2_zlib_deflate.c:239sys/vfs/hammer2/zlib/hammer2_zlib_deflate.c:254sys/vfs/hammer2/zlib/hammer2_zlib_deflate.c:915
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_testablefix.diff compile-validated in the batch combined kernel build (all 37 diffs of this batch applied to in-guest /usr/src; make -j6 nativekernel KERNCONF=X86_64_GENERIC, see batch build log referenced in VERDICT.md); no behavior A/B performed for this Info-tier item; behavior A/B intentionally skipped for Info tier
batch combined kernel build log (see findings/poc/_batch41 note in VERDICT.md)
Confirmed kernel references
Detail
Exploit chain
no escalation path (Info-tier hardening; see VERDICT.md)
Evidence (decisive lines)
VERDICT.md mechanism/verdict sections; fix.diff
PoC changes
none (source-only confirmation for this Info-tier item)
Verified recommended fix
Add M_ZERO to the six kmallocs (matches upstream zcalloc/calloc allocation semantics).
Verdict
source-confirmed (no runtime PoC; deterministic code trace of the cited path:line in the read-only sys/ tree). Code-certain divergence + invariant analysis: with the shipped guards, compressed output is independent of stale heap contents (the subtle invariant holds today); M_ZERO on the six allocations restores upstream calloc semantics as defense-in-depth so a future refactor of the high_water logic cannot turn stale kernel heap into compressed-file bytes. Compile-validated with the batch build.
No comments yet.