# DF-3319 VERDICT

**Status: reproduced** — privileged (SIOCDLIFADDR requires
SYSCAP_RESTRICTEDROOT, in.c:437-441) deterministic kernel stack buffer
overflow with control-flow corruption; **fix validated** on a rebuilt kernel.

## The bug

Three bcopy calls in `in_lifaddr_ioctl()` (`sys/netinet/in.c`) use `sin_len`
fields of the selected ifaddr as copy lengths into the 64-byte on-stack
`struct in_aliasreq ifra` (`:941-959`), with no validation. The ifaddr is
selected by the DF-0490 confusion (`:911` `!= AF_INET6` in the IPv4 handler),
so `ia` (`:923`) is really a `struct in6_ifaddr`:

* `ia_sockmask.sin_len` = in6_ifaddr offset 344 = `ia_prefixmask.sin6_addr[12]`
  → **0xff for every v6 prefix ≥ /104**, lo0's stock `::1/128` included
  (`in6_var.h:116` layout: ia_addr@240, ia_net@268, ia_dstaddr@296,
  ia_prefixmask@324).
* the resulting `bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr, 255)` writes
  255 bytes at stack offset +32 of a 64-byte object — 223 bytes past its end,
  smashing the return path of `in_lifaddr_ioctl()`, which runs on netisr0
  (in.c:230-248 dispatches SIOCDLIFADDR).

DF-0490's filed claim covers only the unprivileged *read* at `:927`. The
stack-smash at `:949-956` (and its DF-3318-twin persistence route: root
poisons `ia_dstaddr.sin_len` via SIOCSIFDSTADDR `:602` / `ia_sockmask.sin_len`
via SIOCAIFADDR `:668`, reachable directly once `:911` is corrected) is the
residual this pass-2 finding captures.

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

* `./getlif lo0` (precheck.log): `SIOCGLIFADDR` returns `family=0 len=0
  addr=0.0.0.0` — the type confusion, live on a stock interface.
* `./smash lo0` as root: SIOCDLIFADDR with match address 0.248.255.255
  (derived in smash.c from the `:915-916` candidate expression, which reads
  through `&ifa->ifa_addr`, i.e. the high 4 bytes of the pointer — always
  0xfffff800 on this guest). Result: **Fatal trap 12**,
  `supervisor write data` at `0xe4`, `instruction pointer = 0xfffff8004f308f46`
  (a kernel-heap address — the smashed return address contained a pointer from
  the bcopy source region), `current process = Idle` (netisr0 thread), CPU
  stopped executing heap garbage (`addb %al,(%rax)`), guest wedged in DDB,
  ssh dead (`vm.sh status` ⇒ down). See `panic.txt`.
* Two attempts total: the first used INADDR_ANY as the match (walk arithmetic
  then never matched → EADDRNOTAVAIL); after deriving the pointer-byte match,
  the panic is deterministic (it fires on the single ioctl; reproduced once
  per guest boot because the guest must be reset afterwards).

## Why the impact ceiling is what it is

The trigger ioctl is privileged, so this is not an unpriv→root escalation by
itself; it is kernel stack memory corruption with content sourced from the
in6_ifaddr tail plus adjacent slab (groomable by the same privileged user),
deterministic control-flow destruction (RIP observed executing a heap
pointer), and a hard DoS (wedged DDB). The *unprivileged* side of the same
confusion remains DF-0490's `:927` leak. Fixing DF-0490's filter alone would
*activate* the DF-3318-twin route (poisoned v4 `sin_len` → these same
stack bcopies), which is why the fix below validates lengths as well.

## Fix validation

`fix.diff` (git-apply-able against `sys/netinet/in.c`) contains:

1. `:911` filter `AF_INET6` → `AF_INET` (the DF-0490 typo — needed so the v4
   handler stops feeding in6_ifaddr objects into these bcopies and so v4
   SIOC[GD]LIFADDR work at all);
2. `:915-916` candidate `(struct sockaddr_in *)&ifa->ifa_addr` → deref the
   sockaddr (second half of the same botched copy; without it the walk matches
   every ifaddr by pointer bytes and deletes wrong addresses);
3. consume-side clamps (`alen/dlen/mlen` ≤ `sizeof(struct sockaddr_in)`) at
   `:927/:930/:949/:952/:955` — defense in depth for any garbage/poisoned
   `sin_len` in a live ifaddr;
4. set-side validation: SIOCAIFADDR (`addr/mask/dstaddr/broadaddr`), SIOCSIFDSTADDR,
   SIOCSIFBRDADDR reject `sin_len > sizeof(struct sockaddr_in)` before
   persisting; `in_ifinit()` rejects oversized `sin->sin_len` (kills the
   DF-3320 persistence at `:998`).

Built in-guest (`make -j4 nativekernel KERNCONF=X86_64_GENERIC`, 0 warnings),
installed, rebooted into kernel #3 (`Thu Sep 10 19:51:37 UTC 2026`):

* baseline (stock #0): smash ⇒ Fatal trap 12 / DDB wedge (`panic.txt`).
* patched (#3): smash ⇒ **EADDRNOTAVAIL**, lo0 keeps 127.0.0.1 (no bogus
  delete), guest stays up; `./getlif lo0` ⇒ `127.0.0.1 len=16 /8` (v4
  SIOCGLIFADDR now *functional* — regression proof); normal alias
  add/remove works (`fix_run.log`).
* intermediate kernel #2 (clamps present, candidate hunk missing due to a
  patch-application slip, documented): no panic, but the pointer-byte match
  deleted 127.0.0.1 — demonstrating the clamps alone are insufficient for
  correct behavior and motivating hunks 1-2.

## Lines cited

* `sys/netinet/in.c:911` — AF_INET6 filter in IPv4 handler (DF-0490)
* `sys/netinet/in.c:915-916` — candidate read through `&ifa->ifa_addr`
* `sys/netinet/in.c:923` — in6_ifaddr cast to in_ifaddr
* `sys/netinet/in.c:941-959` — on-stack `in_aliasreq ifra` + unvalidated
  bcopy lengths (`:949-950`, `:952-953`, `:955-956`)
* `sys/netinet/in.c:230-248` — SIOCDLIFADDR dispatch to netisr0
* `sys/netinet/in.c:437-441` — privilege gate (privileged trigger)
* `sys/netinet/in.c:602, :668` — privileged sin_len persistence (twin route)
* `sys/netinet6/in6_var.h:116-132` — in6_ifaddr layout (offset arithmetic)

## 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.
