⬢ DragonFlyBSD Kernel Audit
DF-3941 / verdict.json
← back to finding ↓ download raw
{
  "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)"
}