# DF-3318 — unvalidated `ifra_dstaddr.sin6_len` in `in6_update_ifa()` → OOB read (unprivileged leak) + kernel stack OOB write

File: `sys/netinet6/in6.c` (pass-2 audit, GLM 5.3)

## Root cause (one line)

`in6_update_ifa()` validates `ifra_prefixmask.sin6_len` (`in6.c:901`) but
never validates `ifra_dstaddr.sin6_len`; the whole `sockaddr_in6` —
including a user-chosen `sin6_len` up to 255 — is copied into
`ia->ia_dstaddr` (`in6.c:1052-1067`), and `in6_lifaddr_ioctl()` later uses
`ia->ia_dstaddr.sin6_len` as a `bcopy()` length:

* `in6.c:1570-1572` — `SIOCGLIFADDR` (**no privilege check**, `in6.c:490-499`):
  `bcopy(&ia->ia_dstaddr, &iflr->dstaddr, ia->ia_dstaddr.sin6_len)` into the
  280-byte kernel ioctl buffer at offset 152 → OOB heap **read** past
  `struct in6_ifaddr`; bytes 28..127 of `iflr->dstaddr` (kernel heap contents,
  incl. `ia_next` and `ia6_ndpr` pointers) are copied back to the unprivileged
  caller by `sys/kern/sys_generic.c:730`.
* `in6.c:1598-1602` — `SIOCDLIFADDR` (privileged):
  `bcopy(&ia->ia_dstaddr, &ifra.ifra_dstaddr, ia->ia_dstaddr.sin6_len)` into a
  **128-byte on-stack** `struct in6_aliasreq` at offset 44 → kernel **stack
  buffer overflow** of up to 171 bytes with kernel-heap-derived data.

## Reproduce on the QEMU guest

```
./build.sh                  # cc -O -o setup setup.c; cc -O -o leak leak.c; cc -O -o delete delete.c
# 1) privileged one-time setup: gif0 + 2001:db8::1/128 --> 2001:db8::2 with
#    ifra_dstaddr.sin6_len = 200, infinite lifetimes, plus 2001:db8::5/64
#    (so crafted_ia->ia_next != NULL):
./setup gif0 200
# 2) unprivileged leak (as nobody) -- expect kernel pointers at dstaddr[64]
#    and dstaddr[120]:
su -m nobody -c "./leak gif0"
# 3) privileged kernel stack smash (panics the guest; RIP=0):
./delete gif0
```

Success criteria (all observed on stock INVARIANTS kernel #0):

* `leak`: `dstaddr.sin6_len == 200` echoed back from kernel state; bytes
  36..51 = the /128 prefix mask; bytes 96..103 = `0xffffffff` lifetimes;
  **bytes 64..71 = `0xfffff8004f3097c0` (ia_next, kernel heap pointer)**;
  bytes 120..127 = `0xfffff8004f3098b0` (adjacent slab content, past the end
  of the `in6_ifaddr` allocation). Identical across 3 runs as `nobody`.
* `delete`: immediate `Fatal trap 12`, `instruction pointer = 0x8:0x0`,
  `frame pointer = 0x0` — return path of `in6_lifaddr_ioctl()` smashed.

## Fix

`fix.diff` — reject `ifra_dstaddr.sin6_len > sizeof(struct sockaddr_in6)`
in `in6_update_ifa()`, mirroring the existing prefixmask check one line
above. Validated in-guest (kernel rebuild + rerun): setup now returns
`EINVAL` and no address/leak/panic is possible.

## Contents

* `setup.c` / `leak.c` / `delete.c` — sources
* `build.sh` / `run.sh` — exact commands
* `build.log`, `run.log` (leak), `run.2.log`, `run.3.log`, `delete.log` — full untrimmed output
* `panic.txt` — serial-console panic from the privileged stack smash
* `leak_sample.txt` — the three leak runs side by side
* `env.txt` — guest uname / compiler
* `fix.diff` — the validated fix (never applied to the read-only `sys/` tree)
* `VERDICT.md`, `manifest.json`, `verdict.json` — verdicts
