# 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 by `dev/netif/mxge`
- `net/zlib.c  optional netgraph7_deflate`     — used by `netgraph7/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:

1. **`inflate_codes` COPY 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;
   ```
   When `dist - (q - s->window) > (s->end - s->window)` (i.e. `dist` exceeds
   the window size plus the current write offset), the subtraction
   `s->end - (dist - (q - s->window))` underflows past `s->window`. The
   subsequent copy loop (`OUTBYTE(*f++)`, lines 4834-4838) then reads memory
   **before** the window allocation.

2. **`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 `#include`s
`"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 enabling `NETGRAPH7_DEFLATE`. Effective severity on this
  config: Low (latent hardening gap). The `mxge` path is safe.
- **On a kernel with `NETGRAPH7_DEFLATE` enabled:** 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_codes` DISTEXT→COPY (`sys/net/zlib.c:4820`): after the distance is
  fully decoded, `if (c->sub.copy.dist > (uInt)(s->end - s->window))` → set
  `BADCODE`, `Z_DATA_ERROR`, `LEAVE`.
- `inflate_fast` (`sys/net/zlib.c:5063`): after `d` is 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).
