DF-5455 / verdict.json
{ "finding_id": "DF-5455", "status": "reproduced", "reproduced": true, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED with production code on the guest (KLD-harness-with-production-code class; no MCS7703 device model exists): umoscom_cfg_read() (umoscom.c:539-557) declares `uint8_t val;` uninitialized, issues a 1-byte vendor control-IN through ucom_cfg_do_request (usb_serial.h:220-221, actlen=NULL, 1000 ms) and returns val with the usb_error_t discarded. Traced core semantics (usb_request.c:642-650 timeout/stall/cancel break BEFORE the copy-out at :680-681; :666-683 zero-length data stage copies nothing) leave the buffer untouched in every failure mode. The harness compiles a byte-identical production copy (1 guard hunk rerouting only the request macro to a cross-TU shim with those exact failure semantics) and calls the real umoscom_cfg_get_status(): stack poisoned with known patterns, the stale bytes bits 4-7 surfaced as SER_CTS/SER_DCD/SER_RI/SER_DSR exactly per umoscom.c:511-521 -> sc->sc_msr (usb_serial.c:1527) -> ucom_modem (:1321-1332) -> ucom_totio (:1259-1270) -> TIOCMGET copyout (:1209-1211). 12/12 pattern-match per load across 6 patterns x 2 failure modes, 3 loads, 0 mismatches (0xff->0xf0/TIOCM 0741, 0xa5->0xa0/0501, 0x5a->0x50/0241); disassembly shows the genuine post-call `movzbl -0x9(%rbx-frame),%eax` load. Counterfactual with the one-line fix returns 0x00 under identical poisoning. Impact ceiling: 4 bits of stale ucom-task-thread kernel stack per status refresh, device-failure precondition (evil or wedged MCS7703) -- Low, kernleak bucket, but with a live userspace consumer unlike the uvscom DF-5448 sibling.", "exploit_chain": "evil MCS7703 (0x9710:0x7703) is inserted; user opens /dev/ttyUx; device stays silent on the MSR/LSR vendor status reads (each times out after 1000 ms, buffer untouched); umoscom_cfg_get_status maps the stale stack bytes bits 4-7 into SER_* bits; ucom_cfg_status_change stores them into sc->sc_msr; unprivileged user calls TIOCMGET and reads TIOCM_CTS/CD/RI/DSR derived from uninitialized kernel stack; repeat to harvest stale-stack bits (harness proves the mapping deterministically with poisoned patterns).", "evidence": "run.log: 3 loads x RESULT: 12/12 pattern-match ... UNINITIALIZED STACK DISCLOSED THROUGH MODEM-STATUS BITS, 36 MATCH / 0 MISMATCH lines with per-pattern TIOCM values; fix counterfactual lines FIXED (val=0): msr=0x00 (clean); disassembly in VERDICT.md (umoscom_cfg_read.constprop.2: lea -0x9(%rbp),%rsi; callq; movzbl -0x9(%rbp),%eax feeding the four orb SER translations); build.werror_evidence.log (production-shaped cross-TU build emits no diagnostic -- why it ships); run.1.fold.log (same-TU optimizer-fold artifact, documented); fix_compile.log RC=0.", "kernel_refs": [ "sys/bus/u4b/serial/umoscom.c:539-557", "sys/bus/u4b/serial/umoscom.c:495-522", "sys/bus/u4b/serial/usb_serial.h:220-221", "sys/bus/u4b/usb_request.c:642-650", "sys/bus/u4b/usb_request.c:666-683", "sys/bus/u4b/serial/usb_serial.c:1515-1518", "sys/bus/u4b/serial/usb_serial.c:1527", "sys/bus/u4b/serial/usb_serial.c:1321-1332", "sys/bus/u4b/serial/usb_serial.c:1246-1273", "sys/bus/u4b/serial/usb_serial.c:1209-1211" ], "poc_changes": "Seed concept executed as the family KLD-harness class: (1) production umoscom.c compiled in verbatim via a 1-hunk guarded copy (prod_guard.diff -- only the ucom_cfg_do_request macro is rerouted after its usb_serial.h definition; call sites byte-identical); (2) the failure-semantics shim lives in a SEPARATE TU (mo5455_shim.c) mirroring productions opaque cross-module usbd_do_request_proc -- when it was same-TU inlinable, GCC -O2 folded the provably-uninitialized load to constant 0 (run.1.fold.log artifact); (3) deterministic stack poisoning (recursive 256B pads x7) replaces the device/grooming precondition; (4) both failure modes exercised: timeout and successful zero-length data stage.", "attempts": 4, "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": 90, "guest_dirty": 1, "build_cmd": "sh build.sh (cd /usr/src/sys/bus/u4b/serial/mo5455 && make; artifacts: umoscomunleak.ko)", "run_cmd": "sh run.sh (kldload /root/df5455/umoscomunleak.ko x3; kldunload umoscomunleak; dmesg | grep MO5455 | tee run.log)", "code_hash": "34bcfaca9b658eddedde999953c1eeee42e35213ff8ddf8d88f5fe3ae5a4a960", "notes": "Guest reset (with-src) after the run; /usr/src copies of the harness dirs vanish with the snapshot revert. Core-level alternative fix (family-tracked): make ucom_cfg_do_request pass a real actlen so the core zeroizes the buffer tail. MD5 of production umoscom.c on guest = local tree = 7ea63e2473f526648d911e95e27d6e63.", "recommended_fix": "Initialize the read buffer in umoscom_cfg_read() (one line, full diff in fix.diff); optionally fix the core macro to pass actlen.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 1, "fix_verdict": "Compile dimension: patched production umoscom.ko builds -Werror-clean RC=0 WITH -Wuninitialized armed (fix_compile.log -- the same-TU diagnostic that fires on unfixed code is gone). Runtime dimension: the fixed twin (verbatim cfg_read/cfg_get_status bodies with `uint8_t val = 0;`) runs under the identical shim + poisoning and reports msr=0x00 in all 36 cases while the unfixed production path pattern-matches 36/36 -- the one-line init provably closes the disclosure at the exact code point.", "fix_evidence": "fix.diff (sha256 adc851f3357e395d8f1991719211c1866f7d86126ededaac567d8b9800050aeb); fix_compile.log (RC=0); run.log FIXED lines (36x msr=0x00 (clean))." } |