# DF-1231 — VERDICT

**Finding:** `aac_getnext_aif()` walks the per-controller `sc->fibctx` list
and `tsleep()`s without holding `sc->aac_aifq_lock`, while concurrent
`aac_close_aif()` (which DOES hold the lock, unlinks and `kfree()`s the
context) creates a use-after-free.

**Status:** NOT REPRODUCED (latent — no Adaptec FSA RAID controller on this guest).
**Confidence (bug is real):** certain (traced line-by-line in `sys/`).
**Impact ceiling:** use-after-free; kernel memory corruption (slab poisoning
or attacker-reclaimed slab), panic/DoS or control-flow hijack on
INVARIANTS-OFF kernels. Reachable by any local user in the `operator` group
once an `aac(4)` controller is present.

## Mechanism (confirmed line-by-line in `sys/`)

1. `aac_getnext_aif` at `sys/dev/raid/aac/aac.c:3519`:
   ```c
   if ((error = copyin(arg, &agf, sizeof(agf))) == 0) {
       for (ctx = sc->fibctx; ctx; ctx = ctx->next) {       /* :3528 */
           if (agf.AdapterFibContext == ctx->unique)
               break;
       }
       if (!ctx) return (EFAULT);
       error = aac_return_aif(sc, ctx, agf.AifFib);
       if (error == EAGAIN && agf.Wait) {
           ...
           while (error == EAGAIN) {
               error = tsleep(sc->aac_aifq, PCATCH, "aacaif", 0);  /* :3540 */
               if (error == 0)
                   error = aac_return_aif(sc, ctx, agf.AifFib);    /* :3543 */
           }
   ```
   **The lookup loop at :3528 and the tsleep at :3540 execute WITHOUT
   `aac_aifq_lock` held.**

2. `aac_close_aif` at `sys/dev/raid/aac/aac.c:3489-3512`:
   ```c
   lockmgr(&sc->aac_aifq_lock, LK_EXCLUSIVE);                /* :3495 */
   for (ctx = sc->fibctx; ctx; ctx = ctx->next) { ... unlink ... }
   lockmgr(&sc->aac_aifq_lock, LK_RELEASE);                  /* :3508 */
   if (ctx) kfree(ctx, M_AACBUF);                            /* :3510 */
   ```

3. **Race A (traversal):** a concurrent `close` runs between two iterations
   of the `getnext` lookup loop. `close` takes the lock, unlinks `ctx`,
   releases the lock, and `kfree()`s `ctx`. The next loop iteration in
   `getnext` then reads `ctx->next` of freed memory → UAF read.

4. **Race B (sleep):** `getnext` enters `tsleep` at :3540 without the lock.
   `close` runs end-to-end (lock, unlink, release, `kfree(ctx)`). When
   `getnext` wakes, it calls `aac_return_aif(sc, ctx, agf.AifFib)` at :3543,
   which dereferences `ctx->ctx_idx` (`aac.c:3562`) and writes
   `ctx->ctx_wrap` / `ctx->ctx_idx` (`aac.c:3574-3575`) — UAF read+write
   on freed memory.

5. `aac_softc->aac_aifq_lock` is a recursive `lockmgr` lock
   (`aac.c:264` `lockinit(..., LK_CANRECURSE)`), and `aac_return_aif`
   itself takes it (`aac.c:3561`), so holding it across the lookup AND
   across an `lksleep` is safe.

6. `lksleep(ident, &lock, ...)` is the standard DragonFly primitive that
   atomically releases the lock during sleep and reacquires before
   returning; it is already used elsewhere in this driver (`aac.c:672,
   1014, 1352, 3003, 3094`). With the lock held, `aac_close_aif` cannot
   unlink or free `ctx` while `getnext` is sleeping — the race is closed.

## Why it is NOT REPRODUCED on this guest

- `pciconf -lv` shows only QEMU i440BX/PIIX3/virtio devices. No Adaptec
  AAC RAID controller is present.
- `kldstat -v` confirms `pci/aac`, `aac/aacd`, `aac/aacp`, `pci/aacch` are
  statically linked into `X86_64_GENERIC` (the driver code IS in the
  running kernel), but `aac_attach` never runs without matching HW, so no
  `/dev/aacN` device node is created.
- `ls /dev/aac*` returns no device nodes. PoC `aac_getnext_aif_race.c`
  confirms this at runtime.

## Threat model & privilege boundary

`/dev/aacN` is created with default devfs perms (0640 root:operator). Any
local user in the `operator` group (commonly granted to administrative
staff for tape/CD-ROM access) can open the device and issue
`FSACTL_OPEN_GET_ADAPTER_FIB` / `FSACTL_GET_NEXT_ADAPTER_FIB` /
`FSACTL_CLOSE_FIB_ADAPTER` ioctls. Racing two of these is therefore a
**real local-user DoS / kernel-corruption vector** on any host with an
`aac(4)` RAID adapter — not requiring root. On the audit guest there is no
such adapter, so the bug is dormant.

## Fix (authored in `fix.diff`, applied + compile-validated)

Take `sc->aac_aifq_lock` across the entire `aac_getnext_aif` body: the
list traversal, the `aac_return_aif` call (recursive take is fine — the
lock is `LK_CANRECURSE`), and the sleep. The `tsleep` is replaced by
`lksleep(sc->aac_aifq, &sc->aac_aifq_lock, PCATCH, "aacaif", 0)` so the
lock is dropped during sleep but `aac_close_aif` cannot proceed (it
needs the same lock to unlink+free), eliminating both races.

This mirrors how the driver already uses `lksleep(..., &sc->aac_io_lock,
...)` at lines 672, 1014, 1352.

## Validation

- `fix.diff` applies cleanly with `patch -p1 --forward` (verified).
- All 5 audit fixes applied together; `make -j6 nativekernel
  KERNCONF=X86_64_GENERIC` returned **rc=0** with **no errors / warnings**
  under `-Werror`. `aac.c` was compiled cleanly into both the kernel and
  the `aac.ko` module.
- Fix is **not_testable** at runtime on this guest (no AAC controller).
