cxm(4) REMOTE_GETKEY: cxm_ir_key() tsleeps 50 ms inside an unserialized ioctl while cxm_detach frees the softc and destroys sc->iicbus β use-after-free (IR-path twin of DF-4753/DF-3941 family)
Summary
The IR path parks 50ms in tsleep with the softc only stack-referenced while detach tears down the iic children and frees the softc - the waker then dispatches through a dangling device_t (compounded by DF-4983's permanent no-op invalidation). Third instance of the driver-wide no-locking family, covered by neither sibling. Unpriv trigger on the 0444 cdev; root kldunload frees. Hardware-gated. Lock-drain fix (subsumed by the pending DF-4753 fix).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5212 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 4.8 KB | β raw | |
| cxm_ir_getkey.c | β | 1.7 KB | view raw | |
| build.sh | β | 208 B | view raw | |
| run.sh | β | 172 B | view raw | |
| build.log | β | 18.0 KB | view raw | |
| fix_build.log | β | 17.9 KB | view raw | |
| run.log | β | 613 B | view raw | |
| env.txt | β | 199 B | view raw | |
| fix.diff | β | 3.9 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-5212 β cxm(4) REMOTE_GETKEY ioctl vs detach use-after-free (cxm_ir.c path)
What
cxm_ir_key() (sys/dev/video/cxm/cxm_ir.c:100-117) β reached from the
unprivileged REMOTE_GETKEY ioctl (cxm.c:2894-2898, _IOR('x', 71,
struct{u_char[3]}), ioctl_bt848.h:266-269) on the world-readable
0444 /dev/cxmN cdev (cxm.c:1884-1885, no priv_check anywhere in
cxm_ioctl, cxm.c:2306) β can sleep 50 ms inside the kernel
(tsleep(..., hz/20) at cxm_ir.c:114) with the softc referenced only
from the sleeping thread's stack, then perform a second i2c transaction
on sc->iicbus (cxm_ir.c:116). cxm_detach() (cxm.c:1944-2004)
deletes the iicbus children (cxm.c:1993-1996) and frees the softc
without draining in-flight ioctls β dev_ops_remove_minor
(cxm.c:1957) only dead-ectors new calls β so a concurrent
kldunload cxm during the 50 ms window leaves the waker touching freed
memory (use-after-free read of sc->iicbus + method dispatch on a
dangling device_t). Third instance of the driver-wide no-locking
family already filed as DF-3941 (read path) and DF-4753 (msp34xxx audio
path); this file/path was not covered by either.
Build
On the QEMU DragonFly guest (see env.txt):
# trigger cc -O2 -Wall -o /root/df5212/cxm_ir_getkey /root/df5212/cxm_ir_getkey.c # baseline + patched module builds (both rc=0, -Werror): cp -r /usr/src/sys/dev/video/cxm /root/df5212/base && (cd /root/df5212/base && make) # build.log # apply fix.diff to cxm.c/cxm.h in /usr/src/sys/dev/video/cxm, then: cp -r /usr/src/sys/dev/video/cxm /root/df5212/fix && (cd /root/df5212/fix && make) # fix_build.log
Run
On a host with a Hauppauge-style Conexant iTVC15/16 card (none exists on this guest):
/root/df5212/cxm_ir_getkey & # victim: parks >= 50 ms per ioctl while :; do kldunload cxm; kldload cxm; done # root: the freeing half
Expected (on hardware): panic within a few unload iterations β the
sleeper resumes into the freed softc, reads sc->iicbus, and dispatches
through the destroyed iicbus device's vtable.
Observed (this guest): run.log β trigger exits 3
hardware absent: open(/dev/cxm0): No such file or directory;
kldload cxm succeeds (rc=0) but no /dev/cxm* appears and no
vendor-0x4444 PCI device exists, so the victim ioctl cannot be
instantiated. Runtime reproduction is hardware-gated (same gate as
DF-4753, established independently there). The structural defect is
proven by source trace in VERDICT.md.
Fix
fix.diff β serialize the cdev ioctl entry against cxm_detach with a
softc lock (drained in detach). Subsumed by the pending DF-4753 fix
(which wraps the whole cxm_ioctl switch); compile-validated here as
the full cxm.ko module with -Werror (fix_build.log, rc=0).
DF-5212 β VERDICT
Status: inconclusive (hardware-gated). Impact if triggered: panic / UAF (memcorrupt family). Confidence in the structural defect: certain.
The path, line by line
-
Unprivileged entry.
/dev/cxmNis created 0444 root:wheel (sys/dev/video/cxm/cxm.c:1884-1885), so any local user can open it read-only.cxm_ioctl(cxm.c:2306) contains nopriv_check/ capability check anywhere and takes no lock (the driver has none βstruct cxm_softc,cxm.h:216-264, has no lock member). -
The sleeping leg.
case REMOTE_GETKEY:(cxm.c:2894-2898) castsap->a_datatostruct bktr_remote *and callscxm_ir_key(sc, (char *)remote, sizeof(*remote)). Buffer math is sound: the ioctl is_IOR('x', 71, struct bktr_remote)with a 3-byte payload (sys/dev/video/bktr/ioctl_bt848.h:266-269), DragonFly'ssys_ioctlallocates/zeros/copyouts exactlyIOCPARM_LEN(cmd)bytes (sys/kern/sys_generic.c:668,696,730), andiicbb_readfills exactlylenbytes (sys/bus/iicbus/iicbb.c:409-424) β no OOB, no uninitialized disclosure on this path.cxm_ir_key(cxm_ir.c:100-117) runs the i2c read; on failure ittsleep(&sc->iicbus, 0, "IR", hz / 20)β 50 ms (cxm_ir.c:114) β then re-reads throughsc->iicbus(cxm_ir.c:116). Every iteration of the trigger loop parks the thread β₯ 50 ms in-kernel holding nothing but a stack reference tosc. -
The freeing leg (privileged).
kldunload cxmβcxm_detach(cxm.c:1944-2004):dev_ops_remove_minor(cxm.c:1957) only dead-ectors new ioctl dispatches (kern_conf.c semantics, per the DF-4753 verification); in-flight sleepers are not drained. Detach then frees the DMA maps (cxm.c:1962-1976), detaches/deletes the iic children (cxm.c:1993-1996) β destroying the verysc->iicbusdevice the sleeper will use atcxm_ir.c:116β and releases the softc's resources. -
The collision. The waker dereferences freed softc memory (
sc->iicbusatcxm_ir.c:104,116), callsiicbus_starton a danglingdevice_t(NULL/garbage vtable dispatch β panic; compounding hazard:sc->iicbusinvalidation incxm_iic_detachis already a no-op, DF-4983, so the pointer dangles even without winning the 50 ms race precisely).
Same structural class as DF-4753 (msp34xxx path, verified on this same guest) and DF-4419 (bktr twin); this is the cxm IR path, not covered by either finding.
Why not reproduced here
The QEMU guest has no Conexant iTVC15/16 PCI capture card:
kldload cxm returns 0 but /dev/cxm* never appears and
pciconf -l shows no vendor-0x4444 device (run.log). The victim ioctl
therefore cannot be instantiated; the race cannot be exercised. This is
the identical gate DF-4753 hit with the audio path.
Exploit ceiling (on hardware)
Unprivileged victim thread (any user) + privileged unload loop (root). The freeing half is privileged, hence severity Low. With a controlled heap reclaim of the freed softc before wake, code-exec is theoretically reachable; not developed (hardware-gated).
Fix validation
fix.diff (softc lock + ioctl entry serialization + detach drain β
minimal standalone subset of the pending DF-4753 fix, which wraps the
whole cxm_ioctl switch and thereby covers this REMOTE_GETKEY path as
well) compiles clean as the full cxm.ko module with -Werror
(fix_build.log, rc=0) against guest source 6.5-DEVELOPMENT. Behavioral
before/after requires cxm hardware; baseline module also builds rc=0
(build.log). /usr/src/sys/dev/video/cxm restored pristine after the
build (sha256 re-verified, run.log).
Negative results (classes hunted and killed in this file)
- Decoder/pulse/key-table OOB: cxm_ir.c has no decoder, tables or maps β it returns 3 raw i2c bytes; parsing lives in userland. Killed.
- cdev read/buf sizes: REMOTE_GETKEY 3-byte OUT payload matches
kernel copyout size exactly (
sys_generic.c:668,730);iicbb_readwrites exactlylen(iicbb.c:409-424);received β€ lenand is never used as an index. Killed. - Uninitialized-memory leak via partial read: the IOC_OUT buffer is
pre-zeroed by
sys_ioctl(sys_generic.c:696) and iicbb fills all 3 bytes on the success path. Killed. cxm_ir_init(cxm_ir.c:85-96): attach-time 1-byte read intounsigned char key[1], bounded. Killed.- Sleep-DoS: each REMOTE_GETKEY parks β₯ 50 ms (
cxm_ir.c:114); unbounded looping is ordinary per-thread sleep cost, no global resource held. Not filed.
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 + cxm_ioctl wrapper around cxm_ioctl_locked + detach drain + lockuninit) compiles clean as the full cxm.ko module (-Werror, rc=0) on guest 6.5-DEVELOPMENT source (fix_build.log); baseline module also rc=0 (build.log). Behavioral validation requires cxm hardware absent from this guest.
fix_build.log (patched module rc=0); build.log (baseline rc=0); fix.diff; run.log tail (pristine-src sha256 re-verified after restore)
Confirmed kernel references
Detail
Exploit chain
unprivileged: open /dev/cxmN O_RDONLY (0444) -> ioctl(REMOTE_GETKEY) in a loop, each iteration parking >= 50 ms at cxm_ir.c:114; privileged: 'while :; do kldunload cxm; kldload cxm; done' frees the softc mid-sleep; on wake: UAF read of sc->iicbus + iicbus_start method dispatch on a destroyed device_t -> panic. Code-exec ceiling exists via controlled reclaim of the freed softc before wake but was not developed: hardware-gated and the freeing half is privileged (severity Low).
Evidence (decisive lines)
VERDICT.md (line-by-line trace incl. the buffer-math negative results); run.log (trigger exit 3, kldload cxm rc=0, no /dev/cxm*, no vendor-0x4444 device, pristine-src sha256 re-verify); build.log vs fix_build.log (baseline and lock-patched cxm.ko both rc=0, -Werror); fix.diff (softc lock + detach drain; subsumed by pending DF-4753 fix); cxm_ir_getkey.c (victim half; REMOTE_GETKEY constant via sys/ioccom.h _IOR, matches ioctl_bt848.h:269)
PoC changes
Authored fresh (no seed): REMOTE_GETKEY defined via _IOR('x',71,struct bktr_remote) from sys/ioccom.h so userland/kernel encodings cannot drift (kernel: ioctl_bt848.h:269); trigger exits 3 with an explicit hardware-gated message when /dev/cxm0 is absent.
Verified recommended fix
Serialize cdev ioctls against cxm_detach (softc lock drained in detach) - see fix.diff; fully subsumed by the pending DF-4753 fix.
Verdict
Structural defect certain by source trace; runtime reproduction hardware-gated. cxm_ir_key() (sys/dev/video/cxm/cxm_ir.c:100-117) is reached from the unprivileged REMOTE_GETKEY ioctl (cxm.c:2894-2898) on the 0444 /dev/cxmN cdev (cxm.c:1884-1885; zero priv_check/locks in cxm_ioctl, cxm.c:2306). On a failed i2c read it tsleeps 50 ms (cxm_ir.c:114) with the softc referenced only from the sleeping thread's stack, then transacts again on sc->iicbus (cxm_ir.c:116). cxm_detach (cxm.c:1944-2004) deletes the iic children (cxm.c:1993-1996) and frees the softc without draining in-flight ioctls - dev_ops_remove_minor (cxm.c:1957) only dead-vectors NEW calls - so kldunload cxm during the 50 ms window makes the waker read freed memory and dispatch through a dangling device_t (UAF -> panic; compounded by DF-4983's no-op iicbus invalidation). Third instance of the driver-wide no-locking family (DF-3941 read path, DF-4753 msp34xxx audio path) - the IR path was covered by neither. On this guest: trigger compiles and runs but exits 3 'hardware absent' - kldload cxm rc=0 yet no /dev/cxm* and no vendor-0x4444 PCI device (run.log), same gate DF-4753 hit. Buffer-math hunt on the same path is negative: _IOR 3-byte payload == kernel copyout size (sys_generic.c:668,696,730) and iicbb_read fills exactly len (iicbb.c:409-424).
No comments yet.