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)
PoC verification
Evidence pack
findings/poc/DF-4753 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_initcxm_msp_dpl_reset(sc->iicbus, ...)(cxm_msp34xxx.c:295),- all of
cxm_msp_dpl_read/writedispatch throughsc->iicbus(cxm_msp34xxx.c:230/266 via callers at :298,:302,:338,:365,...), cxm_msp_wait_for_locktsleeps 10 Γ hz/20 (~500 ms window) (cxm_msp34xxx.c:601-622) and then logs throughsc->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).
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)
- No serialization anywhere.
struct cxm_softc(cxm.h:216-264) has no lock;rg lockmgr|lockinitover cxm.c returns nothing.cxm_ioctl(cxm.c:2306) fetchesscand immediately dispatches. - Long sleep window with sc stack-referenced only.
cxm_msp_wait_for_lock(cxm_msp34xxx.c:588-626) loopstsleep(&sc->iicbus, 0, "audio", hz/20)Γ10 = ~0.5 s per call; reached from the unprivileged ioctl pathBT848_SAUDIO β cxm_encoder_wait_for_lock (cxm.c:2410) β cxm_msp_autodetect_standard (cxm.c:1574). - Unprivileged surface.
/dev/cxmNismake_dev(&cxm_ops, unit, 0, 0, 0444, "cxm%d", unit)(cxm.c:1885-1886); DF devfs does not check open mode for ioctls; nopriv_checkexists in cxm.c (rg: 0 hits). Pass-1 cxm_ir audit reached the same conclusion for REMOTE_GETKEY. - Freed mid-flight.
cxm_detach(cxm.c:1944-2004) =dev_ops_remove_minor(cxm.c:1957) β DMA teardown βbus_generic_detach+device_delete_childof the I2C children (cxm.c:1993-1996) β resource release.destroy_dev/dev_ops_remove_minoronly 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 executescxm_msp_is_locked β cxm_msp_dpl_read(sc->iicbus, β¦)(cxm_msp34xxx.c:560) anddevice_printf(sc->dev, β¦)(cxm_msp34xxx.c:624) on freed memory β NULL/garbage device_t method dispatch β panic. - Sibling defect in the same family (folded):
cxm_open'sif (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 cxmsucceeds (module present: /boot/kernel/cxm.ko),kldstat -vshows the cxm driver registered β but no /dev/cxm0 appears (no Conexant iTVC15/16 PCI device in QEMU) β the victim ioctl cannot be instantiated;kldunload cxmclean. See run.log.- PoC
cxm_uaf_race.cwritten with verified constants (BT848_SAUDIO _IOW('x',46,int),AUDIO_TUNER 0x00from 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 somsp_name[3]/[4]probes (:315-316, :332) stay in-bounds; worst-case 11-char output truncates. - read/write helpers:
received/sentset by the iic layer;-1return vssizeof()(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
inconclusivefix.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
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).
No comments yet.