# DF-0354 PoC — nd6_sysctl_prlist stack over-read (info leak)

**Status:** REPRODUCED as an unprivileged kernel-stack info leak.
**Severity:** Medium
**CVSS:** `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L`

## Bug
`sys/netinet6/nd6.c:nd6_sysctl_prlist` copies `advance =
sizeof(*p) + sizeof(*sin6) * advrtrs` bytes from a 1024-byte stack
buffer `buf[]` to userspace via `SYSCTL_OUT`. `advrtrs` is the **total**
count of routers attached to a prefix (the loop counts even the ones
that didn't fit), so when one prefix has more than ~33 routers, the
copy reads past `buf` into kernel stack residue.

## Threat model (split preconditions)
- **State setup** (the "remote attacker" half): a remote on-link
  attacker floods forged RAs from many sources all announcing the same
  prefix. On the isolated QEMU audit guest we replicate this by writing
  forged RA Ethernet frames to `/dev/tap0` via `ra_inject_tap` (root
  setup, simulating the remote flood). Each unique source IPv6 creates
  one `nd_defrouter`; each RA attaches that router to the shared
  prefix's `ndpr_advrtrs` list.
- **Exploit step** (the unprivileged half): any user reads
  `net.inet6.icmp6.nd6_prlist` via sysctl. The returned buffer is
  `>1024` bytes; bytes past offset 1024 are kernel stack residue.

## Files
| File              | Purpose                                                |
|-------------------|--------------------------------------------------------|
| `ra_inject_tap.c` | forged-RA injector via `/dev/tap0` (root, simulates flood) |
| `ra_read.c`       | unprivileged sysctl reader / dumper                    |
| `ra_inject.c`     | BPF variant (alternative)                              |
| `ra_test.c`       | minimal one-packet injector (debug)                    |
| `bpf_sniff.c`     | BPF sniffer (debug)                                    |
| `fix.diff`        | one-line-clamp fix                                     |
| `VERDICT.md`      | full narrative                                         |

## Reproduce
```sh
./build.sh                # cc the tools (root)
./run.sh                  # setup + inject + unpriv read; prints leak
```
Expected on the unpatched kernel: `TOTAL_BYTES_RETURNED=1968`
(or similar `>1024`), plus a hex dump of the leaked kernel stack
bytes (with varying `0xfffff8…` heap pointers across runs).

## Verified fix
Apply `fix.diff`, build a single-fix kernel
(`make -j6 nativekernel KERNCONF=X86_64_GENERIC`), boot it, re-run
`run.sh`. Expected: `TOTAL_BYTES_RETURNED<=1024` (only entries that
fit), zero bytes leaked past the buffer.

See `VERDICT.md` for the full mechanism trace and evidence.
