# DF-1109 — VERDICT

## Verdict
**SOURCE-CONFIRMED (real bug), NOT REPRODUCED AT RUNTIME on this guest.**
The TOCTOU double-fetch of `d->nmsgs` is traced line-by-line in compiled module
source. It does not fire here because the I2C driver never attaches (no i2c
controller, no `/dev/iic*`). Dormant code path, **not** a false positive
(`iic.ko` ships in `/boot/kernel`). Additional reachability caveat: even with
HW, the device node is `0600 root:wheel` so the bug is **root→kernel** (no
unprivileged boundary crossing).

## Mechanism (source trace)
1. `iicioctl` (`sys/bus/iicbus/iic.c:291`) casts `data = ap->a_data` — the
   **raw user ioctl-data pointer**; the kernel does **not** copyin ioctl structs
   for drivers (`device.h:114` `a_data` is `caddr_t` user pointer).
2. `case I2CRDWR:` (`iic.c:~347-369`) dereferences `d->nmsgs` from user memory
   at **six** separate points:
   - `:348` `buf = kmalloc(sizeof(*d->msgs) * d->nmsgs, ...)` reads N1
   - `:349` `usrbufs = kmalloc(sizeof(void*) * d->nmsgs, ...)` reads N1
   - `:350` `copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs)` reads N2
   - `:354` `for (i = 0; i < d->nmsgs; i++)` reads N3
   - `:361` `iicbus_transfer(parent, buf, d->nmsgs)` reads N4
   - `:363` `for (i = 0; i < d->nmsgs; i++)` reads N5
3. The intervening `kmalloc(M_WAITOK)` calls **sleep**, widening the race
   window. A racing thread that bumps `nmsgs` after the allocation (small N1)
   but before the copyin/loops (large N2..N5) overflows `buf` (16 B/excess
   entry — `struct iic_msg` = slave/len/buf) and `usrbufs` (8 B/excess entry).
4. The overflowed `m->buf`/`m->len`/`m->flags` are then read from adjacent heap
   and used as a `kmalloc` size and `copyin`/`copyout` arguments → controlled
   heap corruption / info leak.
5. Secondary DoS: `nmsgs = UINT32_MAX` requests ~24 GB `M_WAITOK` allocations
   (`16 * UINT32_MAX` and `8 * UINT32_MAX`) → OOM.

## Reachability / privilege caveat
`/dev/iicN` is created mode `0600` `UID_ROOT GID_WHEEL` (`iic.c:129`). Opening
it requires root. The bug is therefore a **root→kernel** TOCTOU (root corrupting
its own kernel) — no privilege boundary is crossed. This is a valid hard
blocker for any `uid=0` claim: there is no unprivileged path to the write.

## Why not reproduced here
No i2c controller HW; `iic.ko` not loaded; `/dev/iic*` absent. Trigger cannot
`open()` a device.

## Fix
`fix.diff` snapshots `d->nmsgs` once into a local `uint32_t nmsgs`, caps it at
65536 (kills the OOM DoS and bounds the allocation), and uses the local at every
subsequent site (both kmallocs, copyin, both loops, transfer). **Supersedes**
the finding's proposal (which described the snapshot but did not bound nmsgs).

## Fix validation (compile)
**Applies** (`git apply --check` clean) and **compiles**: `iic.ko` rebuilt from
patched source under `-Werror`, `iic.c` compiled clean, rc=0. Runtime
before/after is **not_testable** (no HW; and even with HW the path is
root-only, so no unprivileged escalation to validate).

## Exploit chain
None — the primitive is root-reachable only (no privilege boundary crossed), and
unreachable at runtime on this guest regardless.
