Missing distance bounds validation in inflate: heap OOB read when windowBits<15
| Field | Value |
|---|---|
| ID | DF-0265 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:C |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/net/zlib.c |
| Lines | 4824-5085 |
| Area | net |
| Confidence | certain |
| Discovered | 2026-06-30 |
| Reported | pending |
| Known CVE | CVE-2018-25032 (related, NOT equivalent) |
| CVE match | variant |
Summary
The in-kernel zlib is version 1.0.4 from December 1997 (FILEVERSION
971210) β 28 years old, predating ALL modern zlib security hardening.
The inflate code computes back-reference source pointers from decoded
distance codes without checking that the distance does not exceed the
allocated window size. DEFLATE allows distances up to 32768 bytes, but
inflateInit2_ permits windowBits as low as 8 (256-byte window). A
malicious compressed stream using large distance codes against a small
window causes a heap buffer over-read of up to ~32KB.
Root cause
Distance code range: cpdist[30] (line 4149) allows distances up
to 32768 bytes.
Window size: inflateInit2_ (line 3180) allows windowBits 8..15.
Window allocation is 1 << wbits bytes (line 3350).
Missing check: The decoded distance is used directly in pointer
arithmetic at line 4825 (inflate_codes COPY case):
s->end - (c->sub.copy.dist - (q - s->window))
and line 5076 (inflate_fast):
r = s->end - e
No check exists that dist <= (1 << s->wbits). Modern zlib validates
dist <= state->dmax β this check is entirely absent in 1.0.4.
Threat model & preconditions
- Attacker position: Authenticated PPP peer (via netgraph7_deflate
compression). The PPP peer negotiates
windowBits < 15during CCP, then sends a compressed frame containing a large distance code. - Impact: Kernel heap info leak (up to ~32KB of adjacent heap contents per malicious packet) and/or kernel panic (OOB read crossing into unmapped memory).
- Required config: PPP with deflate compression enabled.
netgraph7_deflateuses raw DEFLATE mode (-windowBits,nowrap=1), bypassing zlib header validation entirely.
Recommended fix
Short-term: Add distance validation before the COPY case:
if (c->sub.copy.dist > (1 << s->wbits)) {
z->msg = "invalid distance too far back";
c->mode = BADCODE;
r = Z_DATA_ERROR;
LEAVE;
}
Long-term: Replace this 28-year-old zlib with the modern
sys/contrib/zlib/ already present in the tree, which has 28 years
of security fixes including this one.
References
- CVE-2018-25032 / FreeBSD-SA-22:08.zlib β related but NOT equivalent. That
CVE is an out-of-bounds write in zlib's deflate (compression) routine in
contrib/zlib. DF-0265 is an out-of-bounds read in zlib's inflate (decompression) distance handling in the separate, ancientsys/net/zlib.c1.0.4 copy (netboot PPP Deflate compression), not the contrib/ tree. Same broad bug class (zlib OOB memory access), different operation, different code copy, different impact (read vs write) β hencevariant, notequivalent. - This zlib version: 1.0.4, FILEVERSION 971210
- Modern zlib fix:
if (dist > state->dmax)check ininflate.c
Timeline
- 2026-06-30 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0265 Β· 19 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | guard-page allocator + inflate driver; links the verbatim audited zlib.c and catches the window underflow as SIGSEGV | 11.1 KB | view raw |
| zlib.c | audit-source-copy | byte-identical copy of sys/net/zlib.c (the vulnerable code under test) | 172.9 KB | view raw |
| zlib.h | audit-source-copy | byte-identical copy of sys/net/zlib.h | 42.4 KB | view raw |
| kshim.h | build-shim | userspace stubs for kernel-only MODULE_VERSION/MAX tokens; -include'd, does NOT touch inflate code | 842 B | view raw |
| gen_stream.py | stream-generator | host-side script that produced the crafted raw-DEFLATE stream embedded in harness.c | 1.6 KB | view raw |
| build.sh | build-script | exact cc command | 480 B | view raw |
| run.sh | run-script | exact run command (arg = windowBits) | 362 B | view raw |
| build.log | build-log | final successful build, full output | 13 B | view raw |
| run.log | run-log | decisive vulnerable run (wbits=-8): SIGSEGV -255 bytes before window | 709 B | view raw |
| run.control.log | run-log | control run (wbits=-15): clean Z_STREAM_END, total_out=916 | 573 B | view raw |
| run_stress.log | run-log | 3 repeat runs of the vulnerable config (deterministic) | 1.9 KB | view raw |
| env.txt | environment | uname, cc version, kldstat, module availability (ng_deflate NOT built) | 748 B | view raw |
| fix.diff | suggested-fix | git-apply-able unified diff: dist>window check in inflate_codes AND inflate_fast; VERIFIED (patch -p1 clean, harness OOB closed, kernel modules build -Werror) | 1.6 KB | view raw |
| fix_run.log | fix-run-log | patched-harness run: wbits=-8 -> Z_DATA_ERROR 'invalid distance too far back' (no OOB); wbits=-15 still Z_STREAM_END total_out=916; deterministic x3 | 2.1 KB | view raw |
| fix_build.log | fix-build-log | kernel-tree: patch -p1 applies both hunks (4818,5073); zlib.ko + ng_deflate.ko build clean with -D_KERNEL -Werror (MAKE_RC=0); new error string in zlib.o | 13.4 KB | view raw |
| VERDICT.md | verdict | full narrative analysis: root cause, reproduction, reachability, fix verification (Section 6 = Phase 8 results) | 10.5 KB | β raw |
| README.md | readme | human reproduce instructions | 3.0 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0265 β PoC: heap OOB read in zlib 1.0.4 inflate when windowBits < 15
Verbatim copy of the audited sys/net/zlib.c (and sys/net/zlib.h), plus a
userspace harness that links them unmodified and drives inflate with a crafted
raw-DEFLATE stream. A guard page placed before every zlib allocation surfaces
the sliding-window underflow as a SIGSEGV β conclusive proof of the missing
distance-bounds check.
Build
./build.sh # cc -O2 -Wall -include kshim.h -o harness harness.c zlib.c
kshim.h only stubs out the kernel-only MODULE_VERSION / MAX tokens that
zlib.c references at file scope; it does not touch the inflate code
under test (the file is byte-identical to sys/net/zlib.c).
Run
./run.sh # vulnerable config: windowBits=-8 (256-byte window) ./run.sh -15 # control: windowBits=-15 (32KiB window), no OOB
Expected (bug present), windowBits=-8
[harness] *** SIGSEGV/SIGBUS during inflate at 0x800480f01 *** [harness] nearest alloc #2 [user=0x800481000 size=256 (0x100)]: fault is -255 bytes (BEFORE-start (underflow into guard page)) [harness] RESULT: OOB READ CONFIRMED -- inflate dereferenced memory outside an allocation ...
The fault address is 255 bytes before the 256-byte sliding window β i.e.
inflate read s->end - (dist - (q - s->window)) with dist far larger than
the window and underflowed past s->window into the guard page.
Expected, windowBits=-15 (control)
[harness] inflate returned 1, msg=(null), total_out=916
The 32 KiB window swallows the stream's distance (658) cleanly β same code,
same stream, no OOB. This isolates the cause to the missing dist <= window
check at small windowBits.
With the fix applied (see fix.diff)
windowBits=-8 : inflate returned -3, msg=invalid distance too far back # rejected, no OOB windowBits=-15: inflate returned 1, total_out=916 # still decodes cleanly
Files
| file | purpose |
|---|---|
harness.c |
guard-page allocator + inflate driver; identifies the faulting allocation |
zlib.c,zlib.h |
verbatim copies of the audited sys/net/zlib.c / sys/net/zlib.h |
kshim.h |
userspace stubs for the kernel-only MODULE_VERSION / MAX tokens (-include) |
build.sh,run.sh |
exact reproduce commands |
gen_stream.py |
how the crafted raw-DEFLATE stream embedded in harness.c was produced |
build.log |
final successful build output |
run.log |
decisive vulnerable run (wbits=-8), full output |
run.control.log |
control run (wbits=-15), full output |
run_stress.log |
3 repeat runs (deterministic) |
env.txt |
guest uname / cc / kldstat / module availability |
fix.diff |
git-apply-able unified diff adding the distance-bounds check |
VERDICT.md |
full narrative analysis |
manifest.json |
machine-readable artifact catalog |
See VERDICT.md for the full root-cause analysis, reachability on the audited
kernel, and the threat model.
DF-0265 β Verdict: REPRODUCED (latent on the audited kernel config)
Verdict: REPRODUCED β the missing distance-bounds check in
sys/net/zlib.c (zlib 1.0.4, FILEVERSION 971210) is real and exploitable,
confirmed deterministically by linking the exact, unmodified audited source
into a userspace harness and catching a sliding-window underflow on a guard
page. The vulnerable kernel consumer (netgraph7_deflate) is not compiled
into the audited X86_64_GENERIC kernel and has no loadable ng_deflate.ko
on the guest, so the defect is latent on the shipped master DEV kernel;
it becomes live the moment an operator builds/loads NETGRAPH7_DEFLATE.
1. The bug, line by line
sys/net/zlib.c is zlib 1.0.4 (header at line 18: ==FILEVERSION 971210==).
It is compiled into the kernel via sys/conf/files:
net/zlib.c optional mxge pciβ used bydev/netif/mxgenet/zlib.c optional netgraph7_deflateβ used bynetgraph7/deflate
inflateInit2_ (sys/net/zlib.c:3143) clamps windowBits to 8..15
(line 3175) and allocates a window of 1 << wbits bytes in
inflate_blocks_new (sys/net/zlib.c:3714-3726: s->window = ZALLOC(z,1,w),
s->end = s->window + w). With windowBits=8 the window is 256 bytes.
DEFLATE distance codes, however, can resolve to 1..32768 bytes
(cpdist[30] at sys/net/zlib.c:4149, max entry 24577 + extra bits β 32768).
Nothing in the decoder rejects a distance larger than the window. Two code
paths consume the decoded distance directly in pointer arithmetic with no
bounds check:
-
inflate_codesCOPY case (sys/net/zlib.c:4822-4831):c f = (uInt)(q - s->window) < c->sub.copy.dist ? s->end - (c->sub.copy.dist - (q - s->window)) : /* <-- underflow */ q - c->sub.copy.dist;Whendist - (q - s->window) > (s->end - s->window)(i.e.distexceeds the window size plus the current write offset), the subtractions->end - (dist - (q - s->window))underflows pasts->window. The subsequent copy loop (OUTBYTE(*f++), lines 4834-4838) then reads memory before the window allocation. -
inflate_fast(sys/net/zlib.c:5073-5088): same pattern,e = d - (uInt)(q - s->window); r = s->end - e;at lines 5075-5076, followed by*q++ = *r++copies that read from the underflowed pointer.
Modern zlib validates this. The newer copy in the same tree
(sys/vfs/hammer2/zlib/) has the check in both paths:
- hammer2_zlib_inflate.c:932: if (copy > state->whave) { "invalid distance too far back"; BAD; }
- hammer2_zlib_inffast.c:177: if (dist > dmax) { "invalid distance too far back"; ... }
The 1.0.4 code in sys/net/zlib.c has neither β confirmed by reading the
full inflate_codes and inflate_fast functions. The finding is accurate.
2. Reproduction
The harness (harness.c) links the verbatim sys/net/zlib.c in its
userspace compile mode (which the file explicitly supports: it #includes
"zlib.h" when _KERNEL is undefined and contains the identical
distance-handling code that ships in the kernel). It supplies a custom
zcalloc/zcfree that mmap's every allocation with a PROT_NONE guard page
immediately before the user pointer, then drives inflate with a crafted
raw-DEFLATE stream (see gen_stream.py) that contains a distance of ~658 β
well past a 256-byte window.
Decisive run, windowBits=-8 (256-byte window), run.log:
[harness] 3 allocations made by inflateInit2:
#2 user=0x800481000 size=256 (0x100) <-- the sliding window
[harness] calling inflate on 551-byte stream, window=256 bytes...
[harness] *** SIGSEGV/SIGBUS during inflate at 0x800480f01 ***
[harness] nearest alloc #2 [user=0x800481000 size=256 (0x100)]:
fault is -255 bytes (BEFORE-start (underflow into guard page))
[harness] RESULT: OOB READ CONFIRMED
inflate read 255 bytes before the window start, into the guard page. The
same stream with windowBits=-15 (32 KiB window) decompresses cleanly
(run.control.log: inflate returned 1, total_out=916) β same code, same
input, no OOB. This isolates the cause to the missing distance check at small
windowBits.
Reproduced 3/3 times (run_stress.log); fully deterministic.
3. Reachability on the audited master DEV kernel
| consumer | windowBits | reachable on X86_64_GENERIC? |
|---|---|---|
dev/netif/mxge/if_mxge.c:675 (firmware inflate) |
inflateInit β default 15 (32 KiB) |
safe by construction (distance β€ 32768 β€ window); plus the firmware blob is root-loaded from disk, not attacker-controlled. |
netgraph7/deflate/ng_deflate.c:270 (PPP deflate) |
inflateInit2(-windowBits), 8..15 accepted (line 236) |
NOT compiled in. No ng_deflate.ko on the guest, NETGRAPH7/NETGRAPH7_DEFLATE absent from sys/config/X86_64_GENERIC. kldstat shows only kernel/ehci.ko/xhci.ko. |
So on the shipped master DEV kernel the vulnerable path is dead code:
sys/net/zlib.c is compiled only for mxge (which uses the safe default
window). The defect becomes reachable only if an operator explicitly enables
netgraph7_deflate (custom kernel build or a hand-built module, both
root-gated).
If netgraph7_deflate is enabled, the threat model in the finding holds: a
malicious authenticated PPP peer negotiates windowBits=8 during CCP, the
local PPP daemon configures the node via NGM_DEFLATE_CONFIG, and the peer
then ships a compressed frame whose DEFLATE stream carries a large distance
code. inflate underflows the 256-byte window and either leaks adjacent kernel
heap into the decompressed output (returned to the peer) or panics if the
underflow crosses into an unmapped page. That is a genuine remote
authenticated heap OOB read / DoS β the High severity the finding claims β
but only on a kernel built with that option.
4. Impact classification
- Code defect: real, confirmed, deterministic OOB read of up to ~32 KiB of kernel heap before the inflate window (and panic when the underflow leaves the slab/page).
- On the audited kernel (
X86_64_GENERIC, master DEV): latent β not reachable without enablingNETGRAPH7_DEFLATE. Effective severity on this config: Low (latent hardening gap). Themxgepath is safe. - On a kernel with
NETGRAPH7_DEFLATEenabled: High β remote authenticated heap info-leak / DoS via crafted PPP deflate frames, exactly as the finding states.
5. The fix (fix.diff, verified)
Add a dist > window check in both consume sites, mirroring modern zlib's
"invalid distance too far back" rejection:
inflate_codesDISTEXTβCOPY (sys/net/zlib.c:4820): after the distance is fully decoded,if (c->sub.copy.dist > (uInt)(s->end - s->window))β setBADCODE,Z_DATA_ERROR,LEAVE.inflate_fast(sys/net/zlib.c:5063): afterdis computed,if (d > (uInt)(s->end - s->window))βUNGRAB; UPDATE; return Z_DATA_ERROR.
The check uses s->end - s->window (the actual allocated window size) so it
needs no new state. Verified against the harness: with the patch applied,
windowBits=-8 returns Z_DATA_ERROR "invalid distance too far back" (no
OOB), while windowBits=-15 still decompresses the legitimate stream
(total_out=916). The long-term recommendation (replace the 28-year-old
sys/net/zlib.c with the modern sys/vfs/hammer2/zlib/ copy already in the
tree, or upstream sys/contrib/zlib/) stands; this minimal fix closes the
specific OOB read.
This fix matches the finding markdown's ## Recommended fix (the
short-term dist > (1 << s->wbits) guard) in intent; the implementation here
uses the equivalent (s->end - s->window) and covers both inflate_codes
and inflate_fast (the markdown snippet only showed inflate_codes).
6. Phase 8 β fix validation (harness + kernel-tree, this run)
Because the vulnerable consumer (netgraph7_deflate) is latent on
X86_64_GENERIC (not compiled in, no ng_deflate.ko), the bug is exercised
through the userspace harness that links the verbatim audited net/zlib.c.
Phase 8 validates the fix two independent ways.
6a. Harness-level (primary) β clean before/after on the SAME crafted stream
| config | baseline (zlib.c unpatched) |
patched (zlib.c + fix.diff) |
|---|---|---|
windowBits=-8 (256 B win) |
SIGSEGV, fault β255 B before window | Z_DATA_ERROR (-3) "invalid distance too far back", total_out=659, no OOB |
windowBits=-15 (32K win) |
Z_STREAM_END (1), total_out=916 |
Z_STREAM_END (1), total_out=916 (no regression) |
Determinism: 3/3 patched -8 runs return the identical Z_DATA_ERROR with no
fault (saved in fix_run.log). The previously-faulting stream is now rejected
at the distance-bounds check and never reaches the underflowing pointer
arithmetic; the legitimate 32 KiB-window decode is byte-for-byte unchanged.
β fix_status: harness OOB closed.
6b. Kernel-tree (secondary) β patch applies + modules build with -Werror
On the with-src guest (kern.version = 6.5-DEVELOPMENT #0, unpatched):
- cd /usr/src && patch -p1 < fix.diff β both hunks apply cleanly (hunk #1 @
4818, hunk #2 @ 5073); patch -p1 --dry-run rc=0.
- make in /usr/src/sys/net/zlib (the zlib.ko module, which compiles
net/zlib.c with full kernel CFLAGS -D_KERNEL -DKLD_MODULE -Werror) β
MAKE_RC=0, produces zlib.ko (44128 B) + zlib.o (44016 B). The new
string "invalid distance too far back" is present in the compiled object
(strings zlib.o), confirming the guard compiled into both inflate_codes
and inflate_fast (symbols T inflate_codes / T inflate_fast present).
- make in /usr/src/sys/netgraph7/deflate (the only vulnerable caller,
ng_deflate.c) β NG_MAKE_RC=0, produces ng_deflate.ko (13720 B)
against the patched tree β the fix does not break the consumer's build.
Per the finding's latent status, no full kernel was rebuilt/booted (the
path is dead code in GENERIC, so a boot test would not exercise it). The
module builds + the harness before/after together prove the fix closes the bug
at the code level. Full output in fix_build.log.
Fix classification: FIXED (harness OOB eliminated + kernel modules compile
clean with -Werror). recommended_fix = matches the finding markdown's
short-term proposal; the long-term recommendation (replace the 28-year-old
sys/net/zlib.c with the modern sys/contrib/zlib/ already in the tree)
stands and would subsume this minimal patch.
Fix verification
fixedVALIDATED at the code level (latent in-kernel). Baseline harness: windowBits=-8 SIGSEGVs at -255 bytes before the 256-byte window (OOB READ CONFIRMED, deterministic 3/3) while windowBits=-15 decodes cleanly. Patched harness (zlib.c + fix.diff, same crafted stream): windowBits=-8 now returns Z_DATA_ERROR (-3) 'invalid distance too far back' with NO fault (deterministic 3/3) and windowBits=-15 is byte-for-byte unchanged (Z_STREAM_END total_out=916) => fix closes the OOB with no regression. Kernel-tree: fix.diff applies to /usr/src/sys/net/zlib.c via 'patch -p1' (both hunks @4818/@5073, dry-run rc=0); the patched net/zlib.c compiles into zlib.ko with full kernel CFLAGS -D_KERNEL -DKLD_MODULE -Werror (MAKE_RC=0) and the new error string is in the compiled object; ng_deflate.ko (the only vulnerable caller) also builds clean (NG_MAKE_RC=0). bug closed; effective on a NETGRAPH7_DEFLATE kernel the moment the module is built with this fix.
baseline wbits=-8: 'SIGSEGV during inflate at 0x800480f01' / 'fault is -255 bytes (BEFORE-start (underflow into guard page))' / 'OOB READ CONFIRMED' (3/3). baseline wbits=-15: 'inflate returned 1, total_out=916'. PATCHED wbits=-8: 'inflate returned -3, msg=invalid distance too far back, total_out=659' RUN_EXIT=0 NO fault (3/3). PATCHED wbits=-15: 'inflate returned 1, total_out=916' (no regression). kernel-tree: patch -p1 hunk#1@4818 hunk#2@5073 rc=0; zlib.ko build cc -D_KERNEL -DKLD_MODULE -Werror MAKE_RC=0; ng_deflate.ko NG_MAKE_RC=0; strings zlib.o => 'invalid distance too far back'.
Confirmed kernel references
- sys/net/zlib.c:18 (FILEVERSION 971210 == zlib 1.0.4)
- sys/net/zlib.c:3714 (inflate_blocks_new: window=ZALLOC(z,1,w); s->end=s->window+w)
- sys/net/zlib.c:4149 (cpdist[] allows distances up to 32768)
- sys/net/zlib.c:4822 (inflate_codes COPY: missing dist<=window check)
- sys/net/zlib.c:4827 (FIX: dist>(s->end-s->window) -> Z_DATA_ERROR 'invalid distance too far back')
- sys/net/zlib.c:5073 (inflate_fast: missing dist<=window check)
- sys/net/zlib.c:5081 (FIX: mirror guard in inflate_fast)
- sys/vfs/hammer2/zlib/hammer2_zlib_inflate.c:932 (modern zlib HAS whave check)
- sys/vfs/hammer2/zlib/hammer2_zlib_inffast.c:177 (modern zlib HAS dmax check)
- sys/conf/files:1189,1751 (net/zlib.c compiled for mxge / netgraph7_deflate)
- sys/netgraph7/deflate/ng_deflate.c:236,270 (only vulnerable caller; inflateInit2(-windowBits) 8..15; NOT built on X86_64_GENERIC)
- sys/dev/netif/mxge/if_mxge.c:675 (other caller uses inflateInit==wbits15, safe)
Detail
Exploit chain
inflate OOB read via crafted raw-DEFLATE stream (windowBits<15, oversized distance) -> underflow past s->window into adjacent heap (up to ~32KB info-leak per malicious frame, or panic crossing into an unmapped page). Reachable only from an authenticated PPP peer when netgraph7_deflate is compiled/loaded (the only vulnerable caller, ng_deflate.c:270 inflateInit2(-windowBits)). LATENT in the shipped GENERIC kernel; no in-kernel primitive derivable on this config. No memory-corruption write primitive (read-only OOB), so no escalation chain to develop β impact ceiling is remote authenticated heap info-leak / DoS once the module is enabled.
Evidence (decisive lines)
BASELINE (unpatched, wbits=-8): '[harness] *** SIGSEGV/SIGBUS during inflate at 0x800480f01 ***' / 'nearest alloc #2 [user=0x800481000 size=256 (0x100)]: fault is -255 bytes (BEFORE-start (underflow into guard page))' / 'RESULT: OOB READ CONFIRMED' (deterministic 3/3). CONTROL (wbits=-15): 'inflate returned 1, msg=(null), total_out=916'. PATCHED HARNESS (wbits=-8): 'inflate returned -3, msg=invalid distance too far back, total_out=659' RUN_EXIT=0, NO fault (deterministic 3/3); wbits=-15 still 'inflate returned 1, total_out=916' (no regression). KERNEL-TREE: 'cd /usr/src && patch -p1' hunk#1@4818 hunk#2@5073 rc=0; 'make' in sys/net/zlib -> zlib.ko (44128B) + zlib.o with cc -D_KERNEL -DKLD_MODULE -Werror MAKE_RC=0; strings zlib.o contains 'invalid distance too far back'; ng_deflate.ko NG_MAKE_RC=0.
PoC changes
No changes to the .c/.sh/.py PoC sources (code_hash unchanged). fix.diff validated AS-IS (applies cleanly to both the harness zlib.c via patch -p3 and the kernel tree /usr/src via patch -p1; closes the OOB at harness level; zlib.ko + ng_deflate.ko compile clean). Updated VERDICT.md with a new Section 6 documenting the Phase 8 harness before/after + kernel-module build results; updated manifest.json (guest_uname, tested_at, fix_status=fixed, added fix_run.log + fix_build.log artifact entries). Added fix_run.log (full patched-harness output, deterministic x3) and fix_build.log (full patch -p1 + zlib.ko/ng_deflate.ko build output).
Verified recommended fix
In sys/net/zlib.c add a distance-bounds guard in BOTH consume sites: after the distance is fully decoded in inflate_codes (line 4820, before 'c->mode=COPY') insert 'if (c->sub.copy.dist > (uInt)(s->end - s->window)) { z->msg="invalid distance too far back"; c->mode=BADCODE; r=Z_DATA_ERROR; LEAVE }', and the mirror in inflate_fast (line 5063, before the copy) 'if (d > (uInt)(s->end - s->window)) { z->msg="invalid distance too far back"; UNGRAB; UPDATE; return Z_DATA_ERROR; }'. This mirrors modern zlib's 'dist > dmax' check (already present in sys/vfs/hammer2/zlib/) and uses s->end-s->window (the actual allocated window) so no new state is needed. matches finding markdown's short-term proposal (the markdown snippet showed only inflate_codes; this covers both paths). Long-term: replace the 28-year-old sys/net/zlib.c with the modern sys/contrib/zlib/ already in the tree. Full git-apply-able diff in findings/poc/DF-0265/fix.diff.
Verdict
REPRODUCED at the code level and fix VALIDATED. The harness links the byte-identical audited sys/net/zlib.c (md5 7a3d369127c0773f099c2ccf0b9972f1) into userspace and feeds a crafted raw-DEFLATE stream (windowBits=-8, distance ~658 >> 256-byte window). On the unpatched zlib.c, inflate SIGSEGVs at a fault address 255 bytes BEFORE the 256-byte sliding window (underflow into a guard page) β OOB READ CONFIRMED, deterministic 3/3 (run.log/run_stress.log). The -15 control (32KiB window) decompresses cleanly (Z_STREAM_END, total_out=916), isolating the cause to the missing dist<=window check at small windowBits. The defect is real in inflate_codes (sys/net/zlib.c:4822-4831) and inflate_fast (sys/net/zlib.c:5073-5088); the modern zlib copy in sys/vfs/hammer2/zlib/ HAS both checks, the 1.0.4 code has neither. LATENT on X86_64_GENERIC: sys/conf/files only compiles net/zlib.c for mxge (safe, wbits=15 from firmware inflateInit) or netgraph7_deflate (NOT built β no ng_deflate.ko, NETGRAPH7 absent from config); it goes live only if an operator builds/loads NETGRAPH7_DEFLATE. fix.diff adds the dist>(s->end-s->window) guard in BOTH inflate paths; validated harness-side (OOB gone) and kernel-tree (zlib.ko + ng_deflate.ko build clean with -Werror).
No comments yet.