# DF-3318 VERDICT

**Status: reproduced** (unprivileged info leak 3/3 runs + privileged kernel
stack smash → panic captured). **Impact: kernel heap pointer disclosure to
unprivileged users (after one-time privileged misconfiguration) + kernel
stack buffer overflow (privileged trigger).**

## The bug

`in6_update_ifa()` (`sys/netinet6/in6.c`) validates the *prefixmask*
sockaddr length at `in6.c:901-902`:

```c
if (ifra->ifra_prefixmask.sin6_len > sizeof(struct sockaddr_in6))
        return (EINVAL);
```

but performs **no equivalent check for `ifra_dstaddr`** — only the address
*family* is checked (`in6.c:893-896`, `AF_INET6`/`AF_UNSPEC` on p2p). For a
point-to-point or loopback interface with prefix length 128, the user-supplied
`sockaddr_in6` is copied verbatim into the live ifaddr at `in6.c:1052-1067`:

```c
if (dst6.sin6_family == AF_INET6 && ...) {
        ...
        ia->ia_dstaddr = dst6;      /* sin6_len = any u_char, e.g. 200 */
}
```

`ia_dstaddr.sin6_len` then becomes a `bcopy()` length in
`in6_lifaddr_ioctl()`:

1. **`SIOCGLIFADDR` — `in6.c:1570-1572` — unprivileged** (the privilege check
   at `in6.c:490-499` covers only SIOCALIFADDR/SIOCDLIFADDR):

   ```c
   if (ifp->if_flags & IFF_POINTOPOINT) {
           bcopy(&ia->ia_dstaddr, &iflr->dstaddr,
               ia->ia_dstaddr.sin6_len);      /* 200 */
   ```

   The generic ioctl layer allocated exactly 280 bytes for
   `struct if_laddrreq` (`sys/kern/sys_generic.c:668-680`,
   `SIOCGLIFADDR = _IOWR('i', 28, struct if_laddrreq)`,
   280 > STK_PARAMETERS=128 → `kmalloc(280, M_IOCTLOPS)`).
   `iflr->dstaddr` sits at offset 152, so a length of 200 writes 72 bytes
   past the allocation (masked in practice by the allocator's 512-byte bin)
   and — decisively — **reads 172 bytes past `ia_dstaddr`, i.e. through the
   rest of `struct in6_ifaddr` and past the end of the heap object**. The
   first 128 bytes of `iflr->dstaddr` are then copied back to userspace
   (`sys_generic.c:730`).

2. **`SIOCDLIFADDR` — `in6.c:1598-1602` — privileged**:

   ```c
   struct in6_aliasreq ifra;   /* 128-byte on-stack local, dstaddr at +44 */
   ...
   bcopy(&ia->ia_dstaddr, &ifra.ifra_dstaddr, ia->ia_dstaddr.sin6_len);
   ```

   200 bytes written into a 128-byte stack object → kernel stack buffer
   overflow with kernel-heap-derived (partially groomable) data.

## Reproduction log (stock INVARIANTS kernel #0, guest `dfbsd 6.5-DEVELOPMENT`)

* Setup as root (`setup.c`): `SIOCAIFADDR_IN6` on a fresh `gif0`
  (POINTOPOINT) — `2001:db8::1/128` dst `2001:db8::2`, `sin6_len=200`,
  infinite lifetimes; a second address `2001:db8::5/64` is added afterwards
  so the crafted `ia` is not the tail of the global `in6_ifaddr` list.
* Leak as `nobody` (`leak.c`, `SIOCGLIFADDR` with `IFLR_PREFIX`/128):
  `dstaddr.sin6_len=200` echoed back from kernel state, and bytes 28..127 of
  `dstaddr` contain kernel-side heap data:

  - offset 36..51: the /128 prefix mask (16 × 0xff)
  - offset 64..71: `0xfffff8004f3097c0` — **`ia->ia_next`, a kernel heap
    pointer to the `2001:db8::5` ifaddr**
  - offset 96..103: `0xffffffff_ffffffff` — the ia6t_vltime/ia6t_pltime
    infinite lifetimes set during setup
  - offset 120..127: `0xfffff8004f3098b0` — content past the end of the
    `in6_ifaddr` allocation (cross-object slab read)

  Identical output across three consecutive runs (`run.log`, `run.2.log`,
  `run.3.log`).
* Stack smash as root (`delete.c`, `SIOCDLIFADDR`): immediate
  `Fatal trap 12: page fault while in kernel mode`,
  `instruction pointer = 0x8:0x0`, `frame pointer = 0x0` — the smashed
  return path of `in6_lifaddr_ioctl()` (see `panic.txt`). Guest wedged in
  DDB; ssh gone.

## Why the impact ceiling is what it is

* The **unprivileged** manifestation is an info leak of kernel heap
  addresses (KASLR-defeating; pointer + slab-layout disclosure), not a
  write: creating the malformed `sin6_len` state requires one privileged
  `SIOCAIFADDR_IN6` (root must misconfigure once — e.g. a buggy config
  tool). No unprivileged-only path to the OOB write exists, so no uid=0
  chain is honestly claimable; this is not reported as privesc.
* The **privileged** manifestation is a deterministic kernel stack
  control-flow corruption (RIP=0 observed; the smashed bytes come from
  beyond `ia_dstaddr` in the `M_IFADDR` slab, i.e. groomable by the same
  privileged user) — local DoS at minimum for root, and it elevates the
  missing check from "cosmetic validation" to memory-corruption class.

## Fix validation

`fix.diff` adds the symmetric length check in `in6_update_ifa()`:

```c
if (ifra->ifra_dstaddr.sin6_len > sizeof(struct sockaddr_in6))
        return (EINVAL);
```

Applied inside the guest's `/usr/src`, `make -j4 nativekernel`, rebooted
into the patched kernel (`fix.log`):

* baseline (stock kernel): setup OK → leak shows `sin6_len=200` + kernel
  pointers; delete panics the guest (`panic.txt`).
* patched: `./setup gif0 200` → `SIOCAIFADDR_IN6 failed: Invalid argument`
  (`fix.log`); no address is created; `./leak` → `EADDRNOTAVAIL` (no
  matching address); `./delete` → `EADDRNOTAVAIL`, guest stays up.
  Bad behavior gone; correct error returned.

## Lines cited

* `sys/netinet6/in6.c:893-896` — family-only dstaddr validation
* `sys/netinet6/in6.c:901-902` — prefixmask check (no dstaddr analogue)
* `sys/netinet6/in6.c:930`, `sys/netinet6/in6.c:1052-1067` — bogus sin6_len persisted into `ia_dstaddr`
* `sys/netinet6/in6.c:1570-1572` — unprivileged OOB read (`SIOCGLIFADDR`)
* `sys/netinet6/in6.c:1598-1602` — privileged stack smash (`SIOCDLIFADDR`)
* `sys/netinet6/in6.c:490-499` — SIOCGLIFADR/SIOCGLIFADDR privilege split
* `sys/kern/sys_generic.c:668-680,730` — 280-byte kmalloc + copyout

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
