# DF-0490 — Type confusion in `in_lifaddr_ioctl` (AF_INET6 typo in IPv4 handler)

## Verdict: REPRODUCED (logic/type-confusion bug; 0 bytes leaked on default config)

The bug is **real and confirmed**: `sys/netinet/in.c:911` uses `AF_INET6` inside
the IPv4 address-family handler `in_lifaddr_ioctl`, when it must use `AF_INET`.
This skips every IPv4 interface address and matches IPv6 addresses, then casts an
`in6_ifaddr *` to an `in_ifaddr *` at line 923 — a genuine type confusion.

The fix (`AF_INET6` → `AF_INET`) is **validated** by building a single-fix kernel
and confirming SIOCGLIFADDR returns the correct IPv4 address.

## Reachability (the unprivileged path matters)

- `in_lifaddr_ioctl` is reached from `in_control` (`in.c:434-446`).
- `SIOCALIFADDR` and `SIOCDLIFADDR` require `SYSCAP_RESTRICTEDROOT` (`in.c:437-441`) — root only.
- **`SIOCGLIFADDR` has NO privilege check** — it falls straight through
  (`in.c:443-446`) to `in_lifaddr_ioctl`. **Reachable by an unprivileged user.**

## Mechanism (trigger → primitive → effect)

1. **Trigger:** an unprivileged user issues `ioctl(s, SIOCGLIFADDR, &iflr)` on an
   `AF_INET` datagram socket for an interface that has at least one IPv6 address
   (any interface with a link-local `fe80::` qualifies — every DragonFly Ethernet
   interface has one by default).

2. **Primitive — wrong-family filter:** `in.c:911` reads
   `if (ifa->ifa_addr->sa_family != AF_INET6) continue;`. Because the check uses
   `AF_INET6` (=28) instead of `AF_INET` (=2), the loop **skips** the IPv4
   address (`10.0.2.15`, family 2) and **matches** the IPv6 address
   (`fe80::5054:ff:fe12:3456`, family 28). For the plain (non-`IFLR_PREFIX`)
   `SIOCGLIFADDR`, `cmp=0`, so it breaks on that first IPv6 match.

3. **Type confusion:** line 923 `ia = (struct in_ifaddr *)(ifac->ifa)` casts the
   matched `in6_ifaddr *` to `in_ifaddr *`. The two structs lay `ia_addr` at
   **different offsets** (measured on this build):
   - `struct in_ifaddr`: `ia_addr` at **offset 312** (after `ia_net`,
     `ia_netmask`, `ia_subnet`, `ia_subnetmask`, `ia_netbroadcast`, `ia_pad1`,
     `ia_pad2`).
   - `struct in6_ifaddr`: `ia_addr` at **offset 240** (immediately after `ifaddr`).

4. **Effect — wrong-offset read:** line 927
   `bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len)` reads `ia_addr.sin_len`
   from offset 312 — which lands inside `in6_ifaddr.ia_dstaddr` (a `sockaddr_in6`
   spanning offsets 296–324). On the default config that interface is not
   point-to-point, so `ia_dstaddr` is zeroed and `sin_len` reads **0** → `bcopy`
   copies **0 bytes** → the caller's buffer stays all-zero. The returned
   `addr.ss_family` is therefore `-1` (0 interpreted, no valid family) and
   `ss_len` is 0.

   **Leak ceiling:** if the byte at the `sin_len` position were non-zero (a
   point-to-point IPv6 interface whose `ia_dstaddr.sin6_addr` has a non-zero byte
   16), `bcopy` would copy that many bytes of kernel heap (the struct region
   312..) to userspace — an information leak. On the default GENERIC config the
   demonstrated leak is **0 bytes**; the observed effect is a logic error
   (SIOCGLIFADDR returns empty/wrong data instead of the IPv4 address).

## Evidence

**Baseline (unpatched `#0` kernel, `kern.version = 6.5-DEVELOPMENT #0`):**
```
=== SIOCGLIFADDR on vtnet0 (cmp=0 path) ===
SIOCGLIFADDR OK
addr.ss_family = -1 (AF_INET=2 AF_INET6=28)
addr.ss_len = 0
addr raw: all zeros (128 bytes)
[reference] SIOCGIFADDR vtnet0 -> 10.0.2.15 (the REAL IPv4 addr)
```

**Patched (`#1` single-fix kernel, `kern.version = 6.5-DEVELOPMENT #1`):**
```
=== SIOCGLIFADDR on vtnet0 (cmp=0 path) ===
SIOCGLIFADDR OK
addr.ss_family = 2 (AF_INET=2 AF_INET6=28)
addr.ss_len = 16
decoded IPv4 addr: 10.0.2.15
[reference] SIOCGIFADDR vtnet0 -> 10.0.2.15
```
(lo0 likewise returns `127.0.0.1` after the fix; determinism confirmed over 2 runs.)

## Fix

One-line change at `sys/netinet/in.c:911`: `AF_INET6` → `AF_INET`.
See `fix.diff` (git-apply-able; validated by build + boot + re-run).

This matches the finding markdown's `## Recommended fix` proposal (same one-line
change).

## Impact assessment

- **Class:** logic bug + type confusion; reachable unprivileged via `SIOCGLIFADDR`.
- **Demonstrated on default GENERIC:** SIOCGLIFADDR returns wrong/empty data
  (logic error). **0 bytes** of kernel memory leak on the default config
  (`sin_len` reads 0 at the mis-typed offset).
- **Ceiling:** configuration-dependent information leak if the type-confused
  `sin_len` position holds a non-zero value (point-to-point IPv6 `ia_dstaddr`).
- **Not** memory corruption (the `SIOCGLIFADDR` path is read-only; the root-only
  `SIOCDLIFADDR` path could in principle delete/alter the wrong address via the
  same confusion, but that requires root so it is not a privilege escalation).
- **Not** a DoS / panic / privesc.
