# DF-2000 — VERDICT

## Verdict: CONFIRMED (source-trace), HW-gated — inconclusive at runtime

The TOCTOU/UAF race is real and confirmed by a complete source trace. It
cannot be exercised on this audit guest because the `ic(4)` interface requires
PCF-style parallel-port i2c hardware (`I2C` over parallel port), which is not
present. Per the standard HW-gated pattern, runtime reproduction is
`inconclusive` / `reproduced=0` / `impact=none`, with the race window proven by
code inspection.

## Mechanism (confirmed path:line)

1. **`SIOCSIFMTU` swap-and-free, no driver lock** —
   `sys/dev/netif/ic/if_ic.c:205-223`: saves `iptr=sc->ic_ifbuf`,
   `optr=sc->ic_obuf`, repoints both at fresh `kmalloc(...,M_WAITOK)`, then
   `kfree(iptr)` (line 217) and `kfree(optr)` (line 220). No serializer is
   taken *inside* `icioctl` for these buffer pointers.

2. **Central `ifioctl` wrapper is ineffective here** —
   `sys/net/if.c:2276-2278` wraps `ifp->if_ioctl` (i.e. `icioctl`) in
   `ifnet_serialize_all(ifp)`, which acquires `ifp->if_serializer`. BUT
   `icattach` passes `NULL` to `if_attach(ifp, NULL)` (`if_ic.c:146`), so the
   **default embedded serializer** is installed (`if.c:537-543`). That
   serializer is only useful if every buffer-touching path takes it — and
   neither `icoutput` nor `icintr` does (see below). So the central wrapper
   provides **no mutual exclusion** for this driver.

3. **`icoutput` (TX) uses `crit_enter()` only** — `if_ic.c:335-408`:
   `crit_enter()` (line 349) is per-CPU (blocks preempt/interrupts on the
   *local* CPU only). It dereferences `sc->ic_obuf` at lines 358
   (`bcopy(&hdr, sc->ic_obuf, ICHDRLEN)`), 360 (`cp = sc->ic_obuf + ICHDRLEN`),
   370 (`bcopy(..., cp, mm->m_len)`), and again in `iicbus_block_write(parent,
   sc->ic_addr, sc->ic_obuf, ...)` (line 390 — *outside* the crit section).
   A concurrent `SIOCSIFMTU` on another CPU frees `sc->ic_obuf` between the
   pointer capture and the dereference → **UAF write into freed M_DEVBUF**.

4. **`icintr` (RX) uses `crit_enter()` only** — `if_ic.c:253-330`:
   captures `sc->ic_cp = sc->ic_ifbuf` (line 267) and writes
   `*sc->ic_cp++ = *ptr` (line 309), again under `crit_enter()` (line 261)
   only. Same SMP race against `SIOCSIFMTU`'s `kfree(iptr)` (line 217).

**Net:** `crit_enter()` does not block `SIOCSIFMTU` running on another CPU, so
on SMP the race window is open whenever MTU changes overlap with TX/RX traffic.
The freed `M_DEVBUF` chunk returns to the slab allocator and may be reused
before the stale write lands → kernel heap corruption (panic, or with grooming,
an arbitrary write primitive). The write size/contents are attacker-controlled
(`mm->m_len` up to `if_mtu`, attacker-supplied payload on TX).

**Privilege note:** `SIOCSIFMTU` requires `SYSCAP_RESTRICTEDROOT` (root) per
`if.c:2265`. The finding's CVSS reflects `PR:H`. The TX side (sending traffic)
is unprivileged, but the MTU-change side needs root — so the full race needs a
root-driven MTU-change loop concurrent with user traffic.

## Exploit chain

Not developed — the primitive is gated behind PCF parallel-port i2c hardware
(not present on the guest; `ic0` does not exist). This is a valid hard blocker
(the vulnerable code path is not exercisable on this guest AND no in-guest
harness can create an `ic(4)` interface without the hardware). The race window
is characterized at the source level: concurrent `SIOCSIFMTU` (root) +
`icoutput`/`icintr` (traffic) on SMP yields a stale-pointer UAF write of
attacker-controlled size into a freed `M_DEVBUF` slab chunk.

## PoC changes

- Added `df2000_confirm.c` — a static structural check that documents and
  confirms the four race-enabling properties at their cited line numbers.
- Added `build.sh` / `run.sh`.
- Authored `fix.diff` — adds a `struct lwkt_token ic_tok` to `ic_softc`, inits
  it in `icattach`, and acquires it in `icioctl(SIOCSIFMTU)` (around the
  swap-and-free), `icintr` (around the body), and `icoutput` (around the obuf
  access + `iicbus_block_write`). This **supersedes** the finding markdown's
  proposal: that proposal had a token-leak on the icoutput normal path (the
  release was only at the `error:` label, not after the normal return); my fix
  adds the missing release after `iicbus_block_write` and also covers the
  obuf read inside `iicbus_block_write` by holding the token across it.

## Fix

`fix.diff` is a minimal, correct lwkt_token-based serialization:
- `struct lwkt_token ic_tok` added to `ic_softc` (`if_ic.c:85`).
- `lwkt_token_init(&sc->ic_tok, "ic_tok")` in `icattach` (`if_ic.c:134`).
- `lwkt_gettoken`/`lwkt_reltoken` around the SIOCSIFMTU swap-and-free.
- `lwkt_gettoken`/`lwkt_reltoken` around the `icintr` body (after/before
  `crit_enter`/`crit_exit`).
- `lwkt_gettoken` after `crit_enter` in `icoutput`, released after
  `iicbus_block_write` (normal path) and at the `error:` label.

`lwkt_token` is the DragonFly idiom for driver data that may be touched across
sleeping points (`kmalloc(M_WAITOK)`, `iicbus_block_write`). `git apply --check`
passes. Validated by a clean kernel build in Phase 8.
