β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4753

cxm_msp_* audio-decoder path runs with zero serialization: unprivileged ioctls sleep ~0.5s in cxm_msp_wait_for_lock while cxm_detach (kldunload) frees the softc β€” use-after-free (DF-4419 cxm twin; cxm_open is_opened TOCTOU folded)

Summary

No lock anywhere in cxm; /dev/cxmN is 0444 so any user can open(O_RDONLY)+ioctl (BT848_SAUDIO) and tsleep ~0.5s inside cxm_msp_wait_for_lock holding only the softc; kldunload cxm dead-vectors NEW calls but the in-flight sleeper resumes into freed memory (sc->iicbus read, device_printf via dangling sc). Reliable panic ceiling heap-reclaim exec. Privileged freeing side (like DF-4419). Folded sibling: cxm_open unlocked is_opened check-then-set. Lock+drain fix compile-validated as full cxm.ko -Werror. DF-1600 bktr twin absent (no carrier loop in cxm).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4753 Β· 14 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 4.9 KB ↓ raw
cxm_uaf_race.c β€” 2.0 KB view raw
build.sh β€” 125 B view raw
run.sh β€” 228 B view raw
build.log β€” 11.5 KB view raw
fix_build.log β€” 6.1 KB view raw
run.log β€” 656 B view raw
env.txt β€” 958 B view raw
fix.diff β€” 3.3 KB view raw
fix_cxm.c.patched β€” 65.5 KB ↓ download
fix_cxm.h.patched β€” 19.9 KB ↓ download
manifest.json β€” 1.1 KB view raw
verdict.json β€” 4.3 KB view raw

DF-4753 β€” cxm(4) msp34xxx audio-decoder calls run unserialized against cxm_detach β†’ UAF

File: sys/dev/video/cxm/cxm_msp34xxx.c (pass 2; root cause shared with cxm.c)

What

Every exported cxm_msp_* function dereferences sc / sc->iicbus / sc->dev with no lock anywhere in the driver (struct cxm_softc (cxm.h:216-264) has no lock member; there is no priv_check in cxm.c):

  • cxm_msp_init cxm_msp_dpl_reset(sc->iicbus, ...) (cxm_msp34xxx.c:295),
  • all of cxm_msp_dpl_read/write dispatch through sc->iicbus (cxm_msp34xxx.c:230/266 via callers at :298,:302,:338,:365,...),
  • cxm_msp_wait_for_lock tsleeps 10 Γ— hz/20 (~500 ms window) (cxm_msp34xxx.c:601-622) and then logs through sc->dev (cxm_msp34xxx.c:624).

The ioctl surface reaching these is unprivileged: /dev/cxmN is created mode 0444 (cxm.c:1885-1886) so any user can open(O_RDONLY), and DF devfs does not gate ioctl() on the open mode:

unprivileged ioctl(BT848_SAUDIO, AUDIO_TUNER)     cxm.c:2358
  -> cxm_pause_encoder / cxm_msp_select_source    cxm.c:2401-2404
  -> cxm_encoder_wait_for_lock                    cxm.c:~1510
  -> cxm_msp_autodetect_standard                  cxm.c:1574
  -> cxm_msp_wait_for_lock (sleeps ~0.5 s)        cxm_msp34xxx.c:588-626

While such an ioctl sleeps, privileged kldunload cxm (module ships as /boot/kernel/cxm.ko) runs cxm_detach (cxm.c:1944-2004), which dev_ops_remove_minor()s the cdev, detaches the I2C bus children (cxm.c:1993-1996) and, at module unload completion, the devclass frees the softc. The sleeping ioctl then wakes into freed memory: reads sc->iicbus (garbage/NULL β†’ method dispatch on a dangling device_t), and device_printf(sc->dev, ...) (cxm_msp34xxx.c:624) dereferences a dangling sc->dev. Secondary defect in the same no-locking family: cxm_open's is_opened check-then-set is unlocked (cxm.c:2158-2161), so racing opens defeat the single-open serialization and interleave I2C transactions.

This is the cxm twin of bktr's DF-4419; the cxm.c pass-1/pass-2 audits (DF-1295, DF-3941) never covered the detach-vs-ioctl lifetime hole.

Impact

Low β€” triggering the freeing side requires privilege (kldunload / PCI hot-remove), exactly like DF-4419 (bktr, Low). Once triggered: reliable use-after-free deref (NULL/garbage device_t vtable dispatch, dangling sc->dev) β†’ kernel panic; heap-reclaim exploitation is theoretically possible but not developed (hardware-gated).

Reproduce (hardware-gated)

cxm_uaf_race.c + run.sh: victim child hammers BT848_SAUDIO; root parent loops kldunload cxm; kldload cxm. Requires a Conexant iTVC15/16 card. On the audit guest: kldload cxm succeeds but no /dev/cxm* appears (no card) β†’ trigger exits 3.

Fix

fix.diff (cxm.h + cxm.c): add struct lock cxm_lock, serialize open/close/ioctl, and drain it in cxm_detach before tearing the softc apart. Compile-validated as the full cxm.ko module with -Werror (fix_build.log).

VERDICT.md
↓ download raw

DF-4753 VERDICT

Status: INCONCLUSIVE (runtime) β€” structural defect certain by source trace; runtime reproduction hardware-gated (no Conexant CX2388x PCI card in the QEMU guest); fix compile-validated (full cxm.ko, -Werror).

Impact: Low β€” UAF (panic ceiling) with a privileged trigger half, the cxm twin of DF-4419 (bktr, Low). Bucket: memcorrupt.

Why it is real (source trace)

  1. No serialization anywhere. struct cxm_softc (cxm.h:216-264) has no lock; rg lockmgr|lockinit over cxm.c returns nothing. cxm_ioctl (cxm.c:2306) fetches sc and immediately dispatches.
  2. Long sleep window with sc stack-referenced only. cxm_msp_wait_for_lock (cxm_msp34xxx.c:588-626) loops tsleep(&sc->iicbus, 0, "audio", hz/20) Γ—10 = ~0.5 s per call; reached from the unprivileged ioctl path BT848_SAUDIO β†’ cxm_encoder_wait_for_lock (cxm.c:2410) β†’ cxm_msp_autodetect_standard (cxm.c:1574).
  3. Unprivileged surface. /dev/cxmN is make_dev(&cxm_ops, unit, 0, 0, 0444, "cxm%d", unit) (cxm.c:1885-1886); DF devfs does not check open mode for ioctls; no priv_check exists in cxm.c (rg: 0 hits). Pass-1 cxm_ir audit reached the same conclusion for REMOTE_GETKEY.
  4. Freed mid-flight. cxm_detach (cxm.c:1944-2004) = dev_ops_remove_minor (cxm.c:1957) β†’ DMA teardown β†’ bus_generic_detach + device_delete_child of the I2C children (cxm.c:1993-1996) β†’ resource release. destroy_dev/dev_ops_remove_minor only revector NEW calls to dead ops (kern_conf.c:334-343, kern_device.c:83); an ioctl already inside the old body keeps running. Module unload then frees the softc (devclass free after detach). The sleeper wakes and executes cxm_msp_is_locked β†’ cxm_msp_dpl_read(sc->iicbus, …) (cxm_msp34xxx.c:560) and device_printf(sc->dev, …) (cxm_msp34xxx.c:624) on freed memory β†’ NULL/garbage device_t method dispatch β†’ panic.
  5. Sibling defect in the same family (folded): cxm_open's if (sc->is_opened) return EBUSY; sc->is_opened = 1; (cxm.c:2158-2161) is an unlocked TOCTOU β€” racing opens defeat single-open and interleave iicbb transactions on the shared bus.

Guest verification (what could and could not be done)

  • kldload cxm succeeds (module present: /boot/kernel/cxm.ko), kldstat -v shows the cxm driver registered β€” but no /dev/cxm0 appears (no Conexant iTVC15/16 PCI device in QEMU) β†’ the victim ioctl cannot be instantiated; kldunload cxm clean. See run.log.
  • PoC cxm_uaf_race.c written with verified constants (BT848_SAUDIO _IOW('x',46,int), AUDIO_TUNER 0x00 from ioctl_bt848.h:83,130); exits 3 with a clear hardware-gated message.
  • Fix compile-validated: baseline cxm.ko and fix.diff-patched cxm.ko both built on the guest against pristine /usr/src with -Werror (build.log, fix_build.log, rc=0 both).
  • Guest /usr/src restored and hash-verified afterwards (cxm.c 22808bde…, cxm.h 3c863567…).

Negative results for the other hunt items (this file)

  • DF-1600 carrier-detect twin: ABSENT. cxm_msp34xxx.c has no carrier_detect loop (rg 'carrier' over sys/dev/video/cxm: 0 hits); the driver uses MSP34x5 AUTOMATIC Standard Select (init writes DEM 0x0020, cxm_msp34xxx.c:74-75) and polls the 2-byte STATUS/standard registers in cxm_msp_is_locked (:575-583) β€” no max1=-1 indexing pattern exists.
  • msp_name overflow: SAFE (bktr DF-1850 does not carry over): cxm uses bounded ksnprintf(sc->msp_name /* [10] */, ...) (cxm_msp34xxx.c:306, cxm.h:249); format always emits β‰₯ 8 chars so msp_name[3]/[4] probes (:315-316, :332) stay in-bounds; worst-case 11-char output truncates.
  • read/write helpers: received/sent set by the iic layer; -1 return vs sizeof() (size_t) comparison catches errors; no short-read use of uninitialized stack buffers (buffers only consumed when the return equals the expected size).
  • No volume table walk exists in cxm (mute/unmute are fixed register writes); no division anywhere in the file.

Fix

fix.diff (cxm.h + cxm.c): struct lock cxm_lock in the softc, lockinit in attach, lock held across open/close and the whole ioctl body (thin cxm_ioctl wrapper around the renamed cxm_ioctl_locked), and a drain (LK_EXCLUSIVE acquire/release) in cxm_detach right after dev_ops_remove_minor, with lockuninit as the last step. Known residual (not introduced here): cxm_read still runs unlocked β€” that path is owned by DF-3941's read-ring rework.

Baseline vs patched

Not runtime-testable on this guest (no cxm hardware). fix_status =inconclusive: build verified (cxm.ko, -Werror), behavior hardware-gated.

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.

Fix verification

inconclusive
baseline no→ patch + rebuild →patched clean

fix.diff (cxm.h lock member + lockinit + open/close/ioctl serialization via cxm_ioctl wrapper + detach drain + lockuninit) compiles clean as the full cxm.ko module (-Werror, rc=0) on guest 6.5-DEVELOPMENT source. Behavioral validation requires cxm hardware absent from this guest; baseline bad behavior therefore also unobservable here.

fix_build.log (patched module, rc=0); build.log (baseline module, rc=0); fix.diff
↓ fix.diffper-fix-DF-4753

Confirmed kernel references

Detail

Exploit chain

unprivileged: open /dev/cxmN O_RDONLY (0444) -> ioctl(BT848_SAUDIO, AUDIO_TUNER) parks in cxm_msp_wait_for_lock tsleep (~0.5 s window, repeatable); privileged: kldunload cxm frees the softc mid-sleep; on wake: UAF read of sc->iicbus + method dispatch on dangling device_t + device_printf(sc->dev) deref -> panic. Heap-reclaim code-exec ceiling exists in theory (controlled reallocation of the freed softc before wake) but was not developed: hardware-gated.

Evidence (decisive lines)

VERDICT.md (line-by-line trace + negative results for the DF-1600 twin and msp_name overflow); run.log (kldload cxm OK, /dev/cxm* absent, kldunload OK); build.log vs fix_build.log (baseline and patched cxm.ko both rc=0, -Werror); fix.diff; cxm_uaf_race.c (verified ioctl constants from ioctl_bt848.h:83,130)

PoC changes

Authored fresh (no seed): BT848_SAUDIO _IOW('x',46,int) / AUDIO_TUNER 0x00 confirmed from sys/dev/video/bktr/ioctl_bt848.h; program exits 3 with explicit hardware-gated message when /dev/cxmN is absent.

Verified recommended fix

Add a softc lock serializing cdev entry points against cxm_detach (drain before teardown); see fix.diff.

Verdict

Structural defect certain by source trace: the cxm(4) driver has no lock anywhere (struct cxm_softc cxm.h:216-264; zero lockmgr/priv_check in cxm.c). Every exported cxm_msp_* function (cxm_msp34xxx.c) dereferences sc/sc->iicbus/sc->dev, and the unprivileged ioctl path BT848_SAUDIO (0444 cdev, cxm.c:1885; no open-mode gate, no priv_check) reaches cxm_msp_wait_for_lock which tsleeps ~0.5 s (cxm_msp34xxx.c:601-622) with sc referenced only from the sleeping thread's stack. Privileged kldunload cxm then runs cxm_detach (cxm.c:1944-2004): dev_ops_remove_minor only dead-vectors NEW calls (kern_conf.c:334-343); the in-flight sleeper resumes into freed softc - cxm_msp_is_locked reads sc->iicbus (cxm_msp34xxx.c:560) and device_printf(sc->dev) (cxm_msp34xxx.c:624) dereference freed memory -> NULL/garbage device_t vtable dispatch -> panic (memcorrupt family; cxm twin of bktr DF-4419, Low because the freeing half is privileged). Folded sibling of the same no-locking family: cxm_open's unlocked is_opened TOCTOU (cxm.c:2158-2161). Runtime reproduction hardware-gated: kldload cxm succeeds on the guest but no Conexant iTVC15/16 PCI card exists, so /dev/cxm0 never appears (run.log) and the victim ioctl cannot be instantiated; fix.diff compile-validated as the full cxm.ko module with -Werror (fix_build.log rc=0).