# DF-3272 — Remote peer heap OOB write via IPCP VJ "Max-Slot-Id" (sl_compress_init unclamped)

## What
The peer-supplied IPCP compression option's Max-Slot-Id byte (`p[4]`, 0..255)
is passed **unclamped** to `sl_compress_init(sp->pp_comp, p[4])` at
if_spppsubr.c:2976 (CONF-REQ pass 2) and :3167 (CONF-NAK handler).
`sl_compress_init()` then executes

```c
for (i = max_state; i > 0; --i) {
        tstate[i].cs_id = i;            /* struct cstate tstate[MAX_STATES=16] */
        tstate[i].cs_next = &tstate[i - 1];
}
```

`sizeof(struct cstate)` is 144 (8 ptr + 2+1+1 + 128 hdr, padded); entries
`tstate[16..31]` land in `rstate[]` (inside the allocation) and
**`tstate[32..255]` write up to ~32KB past the end of the 4656-byte
`kmalloc(sizeof(struct slcompress))` heap object** — 9 attacker-positioned
bytes every 144: a valid kernel-heap pointer (`&tstate[i-1]`) at +0 and the
index byte `i` at +10.

## Reachability
Unauthenticated PPP peer. Default config: `CONF_ENABLE_VJ` is set at attach
(if_spppsubr.c:958-959), no auth is configured, so a 3-frame LCP handshake
(peer ACKs the kernel's CONF-REQ, sends its own magic-only CONF-REQ) reaches
PHASE_NETWORK; then ONE IPCP CONF-REQ frame with option
`02 06 00 2d ff 01` (VJ, Max-Slot-Id=255, Comp-Slot-Id=1) detonates it.

## Reproduced (baseline, stock INVARIANTS kernel)
Heap corruption proven by before/after kernel-memory dumps of the `pp_comp`
neighbourhood (read back via a harness ioctl — introspection only):

* BEFORE: all zeros (fresh `sl_compress_init(comp, -1)`).
* AFTER: at `comp+3932` (= `&tstate[27]`): pointer `0xfffff801185bb4d0`
  — exactly `comp+48+144*26` = `&tstate[26]` — followed by byte `0x1b` (27).
* AFTER, **16 bytes past the end of the allocation**, at `comp+4672`
  (= `&tstate[32]`): pointer `0xfffff801185bb7a0` = `comp+48+144*31`, and
  byte `0x20` (32) — i.e. the **neighbouring 4656-byte heap chunk was
  overwritten** with values predicted exactly by the source analysis.

Delayed panics observed in an earlier session (jump to NULL ip=0x0, and a
"vm_fault: fault on stack guard" inside sppp_cp_input's ICF clone) are
consistent with later use of the corrupted objects, but were not
attributably captured in a clean run (the write is sparse: 9 of every 144
bytes), so the verdict rests on the proven corruption itself.

## Exploitability (honest ceiling)
* Primitive: repeatable (every IPCP re-negotiation), attacker-chosen extent
  (maxslot 17..255 => reach `comp+2352 .. comp+36768`), sparse stride 144,
  values = valid kernel heap pointers + small integers. Pointer values are
  *valid kernel addresses*, so pointer-validation hardening does not help.
* Cross-object corruption into the adjacent chunk of the same size class
  (4097..5120 byte kmallocs) is proven (the tstate[32] write).
* uid0/remote-RCE chain NOT completed this run: needs a victim kernel object
  in that size class with an exploitable field at a matching offset
  (`+0`/`+8` pointer or `+10` byte) that the peer can cause to be allocated
  adjacent — a realistic but non-trivial grooming effort on DFly's
  size-segregated slab allocator. Documented as the escalation path.

## Files
- `poc_3272.c`  — original PoC (handshake + weapon + churn + mem dumps)
- `drive3.c`    — the deterministic driver actually used for the recorded run
  (spray, before-dump, LCP handshake w/ ident re-sync, weapon, after-dump,
  unspray, churn) — compile & run exactly as in run.sh
- `sppp_harness.c` — PoC harness module (see DF-3273 README)
- `run.log`     — decisive baseline run (corruption visible in AFTER dumps)
- `run.fixed.log` — same driver on the fix.diff-patched sppp.ko: AFTER == BEFORE (zeros)
- `fix.diff`    — clamp Max-Slot-Id to MAX_STATES-1 (NAK out-of-range), same
  diff also carries the DF-3273 len<4 guard
- `env.txt`     — guest uname etc.

## Build / run (guest, root)
```sh
cd /root/poc && make && cc -O2 -o drive3 drive3.c
kldload /root/poc/sppp_harness.ko
./drive3
```

## Expected
Baseline: `[+] LCP OPENED` then AFTER-dumps show stride-144 pointer+index
writes incl. past the end of the allocation (corruption), machine may or may
not panic immediately (sparse write).
Patched: AFTER-dumps identical to BEFORE (all zero), link negotiates on.
