# DF-3491 — v1 ng_pppoe PRIMED branch: unvalidated `ph->length` → kernel
heap OOB read disclosed in the emitted PADO

`twin of ng7 DF-3487; third sink distinct from DF-0457 (ethernet-hook path)`

File: `sys/netgraph/pppoe/ng_pppoe.c` — **the LIVE v1 netgraph stack**
(stock-loadable: `kldload ng_pppoe`).

## Root cause

`ng_pppoe_rcvdata()` session-hook branch, `PPPOE_PRIMED` state
(:1297-1359):

* `m_pullup(sizeof(*wh))` only (:1304-1309) — 20 bytes contiguous;
* `length = ntohs(wh->ph.length)` (:1312) — **never validated against
  `m->m_pkthdr.len`**;
* `get_tag(ph, PTT_SRV_NAME)` (:1340), `get_tag(ph, PTT_HOST_UNIQ)`
  (:1353), `scan_tags(sp, ph)` (:1356) all bound their walk by
  `&ph->tag[0] + ntohs(ph->length)` under the comment *"assume we already
  sanity checked ph->length"* (ng_pppoe.c:280-282);
* `insert_tag(sp, tag)` → `make_packet()` (:376) bcopy's the
  attacker-shaped Service-Name tag (up to ~1470 bytes) into the PADO and
  `sendpacket()` emits it on the ethernet hook.

The session branch below (:1221-1229) *does* validate
(`m_pkthdr.len < length` → drop); the ethernet discovery branch is
DF-0457; this PRIMED branch is a third, distinct sink.

## Reproduce (guest, root)

```sh
kldload ng_socket && kldload ng_pppoe
cc -O -o ppdprimed ppdprimed.c && ./ppdprimed
```

Pure-userland harness (no helper module): control+data ng_socket; graph
`srvN:eN<->pppoe:ethernet`, `srvN:sN<->pppoe:svcN`; `NGM_PPPOE_OFFER`
puts svcN in PRIMED; a 32-byte PADI (ph.length=1024, SRV tag claims 900
data bytes, 8 real) is sent down the session hook; the PADO comes back
on the ethernet-side hook.

## Expected output (stock module)

```
[+] round N: PADO received: 945 bytes (expected 945 ...)
    [DF-3491] Service-Name carries 892 bytes read OOB past the 32-byte mbuf
      ptr-like sequences found: 14
    [DF-3493] AC-COOKIE = raw kernel sess_con pointer 0xfffff8008d9e65e0
```

Busy-heap boots leak live pointers in the OOB region — `run.log`
(first invocation) contains `0xfffff8008dcf2800` (direct-map heap),
`0xffffffff806bd330` / `0xffffffff806bc610` (kernel text/data),
`0xfffff8004f0a25d0`, mbuf metadata (`00 08 00 00`, `00 03 c0 00`).
`run.2.log` is the 3/3-rounds stability run.

## Threat model

The PRIMED branch parses a PADI echoed back by the PPPoE server daemon
(the standard PADI→PADO offer flow); a daemon relaying peer-supplied
PADI payload from an unauthenticated LAN peer delivers attacker-chosen
`ph->length`. Direct trigger also by the netgraph-controlling root user.
Impact: kernel heap disclosure (pointers + adjacent objects) and
potential panic when the tag walk crosses an unmapped page (up to 0xFFFF).

## Fix

`fix.diff` mirrors the session-branch check (`m->m_pkthdr.len -
sizeof(*wh) < length → EMSGSIZE`). Validated: after module rebuild the
identical input is rejected 3/3 (`sendto: Message too long`), no PADO,
no crash (`fixrun.log`).
