# DF-5253 VERDICT — uaudio mixer sysctl handler lifetime race (REPRODUCED, panic)

## Status

**reproduced** — stock-kernel panic under the exact code path cited, on the
DFBSD QEMU guest with an emulated USB audio device; fix validated (panic gone).

- impact: **panic** (kernel NULL-deref in `lockmgr` on the sysctl write path;
  the class is a lock-lifetime TOCTOU — with allocator luck the same window is
  a freed-lock `lockmgr`, i.e. UAF-write potential — but the NULL store in
  `uaudio_mixer_uninit_sub()` makes the deterministic outcome a NULL deref).
- severity assigned: **Low** — the racing sysctl *write* requires privilege
  (`SYSCAP_NOSYSCTL_WR` gate at sys/kern/kern_sysctl.c:1446-1450), so the
  practical trigger is "root process writes a uaudio mixer sysctl while the
  device is unplugged". Unprivileged users cannot reach the wide window (their
  writes fail EPERM before the handler runs; reads return before the second
  lockmgr). Still a real kernel defect: a legitimate privileged sysctl write
  racing an unplug panics the kernel.

## Root cause (path:line)

`sys/bus/u4b/audio/uaudio.c:2736` `uaudio_mixer_sysctl_handler()`:

```c
2748:   if (sc->sc_mixer_lock == NULL)
2749:       return (ENXIO);
2753:   lockmgr(sc->sc_mixer_lock, LK_EXCLUSIVE);   /* use #1 */
...     /* walk sc->sc_mixer_root */
2764:   lockmgr(sc->sc_mixer_lock, LK_RELEASE);
2766:   error = sysctl_handle_int(oidp, &temp, 0, req);  /* crosses user memory: unbounded sleep */
2767:   if (error != 0 || req->newptr == NULL)
2768:       return (error);
2772:   lockmgr(sc->sc_mixer_lock, LK_EXCLUSIVE);   /* use #2 — re-reads the field! */
2777:   pmc->wData[chan] = temp;                     /* stale pmc write */
```

Teardown chain, all while the mixer sysctl OIDs are still registered (they
are children of the pcm device's sysctl ctx, freed only afterwards in
`device_sysctl_fini()`, sys/kern/subr_bus.c:234-240):

- `uaudio_detach()` (uaudio.c:1192) → `bus_generic_detach()` (:1214)
- → `ua_detach` → `uaudio_detach_sub()` (uaudio.c:1166) → `pcm_unregister()`
- → `mixer_uninit()` (sys/dev/sound/pcm/mixer.c:767):
  - `MIXER_UNINIT(m)` (:809) → `ua_mixer_uninit` → `uaudio_mixer_uninit_sub()`
    (uaudio.c:5378) → `sc->sc_mixer_lock = NULL` (:5385)
  - `snd_mtxfree(m->lock)` (:811) → `lockuninit` + `kfree` of the 32-byte
    M_DEVBUF `struct lock` (sys/dev/sound/pcm/sound.c:92-108)
  - `kobj_delete((kobj_t)m)` (:812) frees the snd_mixer
- only then: `device_sysctl_fini()` → `sysctl_ctx_free()` removes the OIDs.

So for the whole duration of `mixer_uninit()` + the tail of the pcm child
detach, `dev.pcm.X.mixer.*.val` OIDs resolve and dispatch into the handler,
whose second `lockmgr` re-reads a field that has just been NULLed/freed.
Handler dispatch is serialized against OID *removal* by the global sysctllock
(shared in handlers, exclusive in ctx teardown) — but nothing serializes the
handler against `sc_mixer_lock`'s lifetime.

## How it was proven (chronological, honest)

1. Booted the guest with QEMU's emulated USB audio device on a PIIX3 UHCI
   controller: real `uaudio0` + `pcm0` attach, real mixer sysctls
   (`dev.pcm.0.mixer.vol_0_0.val` etc.).
2. Unprivileged hammer (hammer.c, 8 × in-process `sysctlbyname` read+write,
   ~1.4M ops/s) + 100+ monitor `device_del`/`device_add` cycles: **no crash**.
3. Instrumented uaudio.c on the guest (probe only: print when a handler
   reaches the 2nd lockmgr with the field NULL; print each uninit free):
   20 cycles with the *unprivileged* hammer → 20 frees, **0 traversals**.
4. Root-cause of (2)/(3): sysctl writes are gated by
   `caps_priv_check(SYSCAP_NOSYSCTL_WR)` at kern_sysctl.c:1446 — unprivileged
   `sysctlbyname(..., newp, ...)` returns EPERM *before* the handler runs, so
   `req->newptr` is never non-NULL for them and the 2nd lockmgr is unreachable.
5. Same instrumented module + hammer run **as root** (write path live):
   **8 cycles → `DF5253: RACE HIT: handler reached 2nd lockmgr after teardown
   (pmc=0xfffff80117b6aa00 chan=1)`** — traversal proven on the real driver.
6. Stock module restored, rebooted, root hammer + 10 cycles → **kernel panic**
   (panic.txt): `Fatal trap 12`, fault VA 0x8,
   `Stopped at lockmgr_release+0x11: movq 0x8(%rdi),%rsi` — i.e.
   `lockmgr(NULL)`, exactly the cited use.
7. Fix validation: applied `fix.diff` (uaudio-owned sysctl ctx, freed in
   `uaudio_detach()` *before* `bus_generic_detach()`, which drains in-flight
   handlers via the sysctllock; plus a defensive NULL re-check in the
   handler), rebuilt uaudio.ko, installed, rebooted, root hammer + **25
   cycles → zero panics**, guest healthy, mixer functional after replug.

Side observation (different subsystem, root-triggered, out of scope for this
file): `kldunload uaudio` once panicked in `sndstat_sysuninit` →
`sbuf_delete` ("sbuf_delete called with uninitialized or corrupt sbuf") via
`linker_file_unload` — a sound.ko teardown bug worth its own ticket.

## Exploit chain

Not applicable beyond panic: the reachable traversal requires a privileged
sysctl writer, so this is not an unprivileged privesc primitive. With the
NULL store removed (hypothetically), the same window would be a freed-lock
`lockmgr` (32-byte M_DEVBUF UAF-write class), which is why the fix removes
the window rather than relying on the NULL store.

## kernel_refs

- sys/bus/u4b/audio/uaudio.c:2736-2786 (handler, both lockmgr uses)
- sys/bus/u4b/audio/uaudio.c:5378-5388 (uaudio_mixer_uninit_sub NULL store)
- sys/bus/u4b/audio/uaudio.c:1192-1227 (uaudio_detach ordering)
- sys/dev/sound/pcm/mixer.c:767-813 (mixer_uninit: MIXER_UNINIT → snd_mtxfree → kobj_delete)
- sys/kern/subr_bus.c:234-240 (device_sysctl_fini after driver detach)
- sys/kern/kern_sysctl.c:1446-1450 (write privilege gate — reachability limit)

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