DF-3941 / verdict.json
{ "finding_id": "DF-3941", "status": "inconclusive", "reproduced": 0, "impact": "leak", "confidence": "likely", "verdict": "Hardware-gated: the audit QEMU guest has no Conexant CX23415/16 PCI device (no 14f1:8800/8802, no cxm KLD, no /dev/cxm*), so the trigger stops at open(ENOENT) \u2014 runtime reproduction inconclusive on this machine. The defect itself is source-proven: cxm_read() (sys/dev/video/cxm/cxm.c:2211-2299) consumes the DMA ring with no serialization; enc_pool.offset is a single shared size_t (cxm.h:74) saved non-atomically with the read pointer (cxm.c:2290-2296). Racing readers on one fork-shared fd (open() is world-readable, cxm.c:1885-1886, and only a second OPEN is refused, cxm.c:2158) \u2014 or a METEORCAPTUR restart racing an in-flight read (reset at cxm.c:1119-1121) \u2014 pair a stale offset X with a different buffer j, making `bufs[current].size - offset` (cxm.c:2270) underflow (size_t); the only clamp is uio_resid (cxm.c:2273), so uiomove (cxm.c:2276) copies up to uio_resid bytes past the 61440-byte DMA buffer => kernel-heap disclosure via read(2). The simpler desync makes read() return never-DMAd, never-zeroed bus_dmamem_alloc buffers (cxm.c:1806). Fix compile-validated (-Werror) on the guest kernel tree.", "exploit_chain": "unprivileged open(/dev/cxm0) (mode 0444) -> fork N readers on the shared fd -> start capture (read auto-start or METEORCAPTUR) -> race partial-read offset saves vs full-consumption/read-advance (or race METEORCAPTUR STOP/CONTINOUS against an in-flight read) -> size/buffer mismatch -> size_t underflow at cxm.c:2270 -> uiomove OOB read past 61440-byte DMA buffer into kernel heap -> disclosure to userspace (repeatable). No write primitive identified.", "evidence": "VERDICT.md (line-by-line interleaving); run.log (open ENOENT on guest + hardware-absence checks); fix_build.log (patched cxm.ko builds clean); fix.diff (lock + defensive clamp)", "kernel_refs": [ "sys/dev/video/cxm/cxm.c:2211", "sys/dev/video/cxm/cxm.c:2270", "sys/dev/video/cxm/cxm.c:2276", "sys/dev/video/cxm/cxm.c:2290", "sys/dev/video/cxm/cxm.c:1119", "sys/dev/video/cxm/cxm.c:2158", "sys/dev/video/cxm/cxm.c:1885", "sys/dev/video/cxm/cxm.c:1806" ], "poc_changes": "Seed sketch rewritten: full trigger program with fork-shared-fd racing readers, partial-read offset generation, MPEG-framing anomaly detector, and honest no-hardware path", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 420, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/cxm_read_race cxm_read_race.c", "run_cmd": "timeout 20 /tmp/cxm_read_race", "code_hash": "1285b1fbc0fdd085967d1099715dabd5a5fa3c6719176fb0f76d3c825433886a", "notes": "DF-1295 (METEORSSIGNAL proc UAF) known and excluded. Also reviewed and killed: SG-list/RISC construction bounded (cxm.c:1477-1513, rounding <= CXM_SG_SEGMENT since 0xf000 % 256 == 0; buffers_pending < buffers_free keeps enc_sg in bounds); no d_mmap exists; all ioctl sizes match the _IOW/_IOR encodings; REMOTE_GETKEY -> iicbus_read bounded by sizeof(bktr_remote)=3; cxm_close early-return EBUSY-wedge and cxm_detach-with-open-fd UAF are root/hardware-gated (not filed); YUV fixup math is exactly buffer-bounded and unreachable anyway (sc->mpeg never leaves 1, cxm.c:2162).", "recommended_fix": "Serialize ring consumption (driver lock around cxm_read consumption + start_encoder/close resets) and clamp offset to bufs[i].size before the subtraction (fix.diff)", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (driver lock + defensive offset clamp) applied to guest /usr/src and the cxm KLD rebuilt with -Werror rc=0 (fix_build.log). Runtime baseline/patched comparison impossible: no CX23416 hardware on the guest.", "fix_evidence": "fix_build.log (patched cxm.c compiled + cxm.ko relinked, CXM_FIX_MAKE_RC=0); fix.diff is git-apply-able (git apply --check passed on the audit tree)" } |