# VERDICT — DF-4284

**Status: reproduced (model harness on the audit guest) / UAF read in midi_unload**
**Impact: dos (root-gated kldunload path; panic / groom-controlled pointer use)**
**Confidence: certain**

## Root cause (source-certain)

`midi_unload()` [sys/dev/sound/midi/midi.c:1469-1477]:

```c
TAILQ_FOREACH(m, &midi_devs, link) {
        lockmgr(&m->lock, LK_EXCLUSIVE);
        if (m->busy)
                retval = EBUSY;
        else
                retval = midi_destroy(m, 1);
        if (retval)
                goto exit1;
}
```

`midi_destroy()` [midi.c:1418-1438] does `TAILQ_REMOVE(&midi_devs, m, link)`
[1429] and `kfree(m, M_MIDI)` [1436]. `TAILQ_FOREACH` expands to
`for ((var) = TAILQ_FIRST(head); (var); (var) = TAILQ_NEXT(var, field))` —
the increment `m->link.tqe_next` is read **after** the body has freed `m`.
With ≥2 registered devices the pointer read from freed memory becomes the
loop's next `m`, and the next body iteration runs `lockmgr(&m->lock,
LK_EXCLUSIVE)` [1470] and touches `m->busy` [1471] through it. With slab
reuse between the free and the increment, the value is attacker-groomable
(M_MIDI-sized allocation churn), so the "device" operated on is an
attacker-chosen address.

Adjacent latent defect (not fixed by the minimal diff, documented only):
the `exit1` path [1488-1489] does `lockmgr(&m->lock, LK_RELEASE)` after
`midi_destroy()` already released and freed m->lock — currently dead code
because midi_destroy() unconditionally returns 0, but a tripwire if that
ever changes.

## Reachability

MOD_UNLOAD of the midi module only (`midi_modevent` [midi.c:1515-1516] ←
`kldunload sound`), i.e. root, and it only does work when at least one
MIDI device is registered — in-tree only on snd_emu10kx hardware
(emu10kx-midi.c:201 → mpu401.c:185 → midi_init). Not triggerable live on
the audit guest (no EMU audio, verified: `pciconf` shows no 1102 device,
no /dev/midi* nodes after `kldload sound`).

## Verification performed (this run)

Model harness `midi_unload_model` on the guest (DragonFly 6.5-DEVELOPMENT,
gcc 8.3), TAILQ macros verbatim from sys/sys/queue.h:

- mode `reuse`: after each destroy the freed slot is re-groomed by a
  same-size chunk with forged `link.tqe_next = 0x414141414140`. The loop
  advance reads the forged pointer and the next iteration faults at
  **0x414141414150** (= forged + offsetof(unit)) → SIGSEGV, exit 99.
  run.log: "[groom] freed slot reoccupied; forged link.tqe_next =
  0x414141414140" then "[reuse] signal 11 at 0x414141414150". This is the
  controlled-UAF-read demonstration: in-kernel the equivalent read feeds
  `lockmgr(&m->lock)` on an attacker-influenced address.
- mode `stale`: plain free(); this libc preserves tqe_next so the loop
  happens to walk the stale chain — an uncontrolled UAF read (undefined
  value, real allocator reuse makes it arbitrary).
- mode `fixed` (fix.diff semantics — stash TAILQ_NEXT before destroy):
  all three devices visited and destroyed cleanly, exit 0.

## Exploit-chain assessment

Requires root (kldunload) already, so no privilege-boundary crossing; worst
case is kernel panic / memory-unsafety during a privileged operation with
groom-controlled pointers (root → kernel-exec adjacent, but root can load
modules anyway). Severity Low; filed for correctness/lifetime hygiene.

## Fix validation

Model-level: `fixed` mode runs the same scenario with the patched loop
shape and completes cleanly (run.log "[fixed] clean: all devices destroyed,
no UAF"). Kernel-level patched build not exercisable on this guest (needs
registered midi devices, i.e. emu10kx hw).

## Bottom line

Real use-after-free read (with groom-controlled pointer consumption) in
the midi module unload path — certain at source level, demonstrated by a
verbatim-semantics harness on the guest; root- and hardware-gated, hence
Low severity.

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