DF-5301 / verdict.json
{ "finding_id": "DF-5301", "status": "not_reproduced", "reproduced": false, "impact": "none", "confidence": "speculative", "verdict": "Code defect certain, exploitable interleaving not closed. uhub_child_location_string()/uhub_child_pnpinfo_string() (sys/bus/u4b/usb_hub.c:1660-1757) guard a sc->sc_udev->hub dereference with a non-atomic device_is_attached(parent) check; device_is_attached stays TRUE for the whole of uhub_detach() and ~100-300us of device_detach() tail (sys/kern/subr_bus.c:2129-2153), while uhub_detach kfrees the hub (usb_hub.c:1590) and NULLs it (:1592) inside that window -> NULL-deref panic at usb_hub.c:1637 or UAF read; usb_bus_port_get_device (:2190-2201) additionally reads bus->devices[] unlocked. Reachable only via world-readable sysctls (dev.uhid.N.%location/%pnpinfo, hw.bus.devices walk). Campaign on the QEMU guest: 953 full uhub_detach windows (QEMU usb-hub device_del cycling) against an unprivileged 60-thread reader storm sustaining ~155-190k successful vulnerable dispatches/s - zero panics. Traced cause: child sysctl OIDs are removed ~1-2ms BEFORE hub=NULL so no fresh dispatches exist at the vulnerable instant; the ~10-50ns check->deref interval contains no blocking/preemption point (DFly does not preempt kernel-resident equal-priority threads); the global walk re-derives device pointers after each copyout sleep and skips unlinked children. Residual window is single-store/ns-gap interrupt alignment (~1e-7/s) - not demonstrable. The oid_running drain that would serialize this (kern_sysctl.c:387-397) is dead code (counter never incremented). Filed as hardening with NULL-guard fix; practical impact none on this kernel as built.", "exploit_chain": "", "evidence": "findings/poc/DF-5301/VERDICT.md (full mechanics); detach_count.txt (953 windows); hammer-tail.log (~155-190k dispatches/s sustained, zero faults); replug3.log/replug4.log (400+300 cycles); boot-snippet.txt (uhub1/uhid attach-detach churn); hammer.c/replug.py (exact harness); fix.diff (NULL guard, applies clean)", "kernel_refs": [ "sys/bus/u4b/usb_hub.c:1660", "sys/bus/u4b/usb_hub.c:1675", "sys/bus/u4b/usb_hub.c:1637", "sys/bus/u4b/usb_hub.c:1590", "sys/bus/u4b/usb_hub.c:1592", "sys/bus/u4b/usb_hub.c:1702", "sys/bus/u4b/usb_hub.c:1718", "sys/bus/u4b/usb_hub.c:2190", "sys/kern/subr_bus.c:2129", "sys/kern/subr_bus.c:1902", "sys/kern/subr_bus.c:3899" ], "poc_changes": "Rewrote the seeded concept into hammer.c v3 (tight loop only on the 4 sysctls that dispatch uhub1s methods, after measuring that dev.uhub.1.* dispatches the root-hub method and is useless as a target) + host-side monitor replug driver (replug.py); switched QEMU xhci->piix4-usb-uhci because DFly xhci hotplug enumeration of qemu-xhci devices fails before probing uhub.", "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": 8400, "guest_dirty": 1, "build_cmd": "cc -O2 -pthread -o hammer hammer.c (on guest, as user maxx)", "run_cmd": "guest: for i in 1..6; do ./hammer 10 & done; host: python3 findings/poc/DF-5301/replug.py 300 0.7 0.6 (QEMU monitor device_del/device_add usb-hub cycle)", "code_hash": "c8db6b62401f35ffdd2afe6956fffa29467a2671612d190d60b72ba15390343d", "notes": "QEMU must be started with QEMU_EXTRA=-device piix4-usb-uhci,id=uhci0; hub via monitor: device_add usb-hub,id=h1,bus=uhci0.0,port=1; children: device_add usb-tablet,id=ta,bus=uhci0.0,port=1.1 (and 1.2). Guest left with the uhci controller + hub hotplugged (harmless). Related dead-code observation: sysctl_remove_oid_lockeds oid_running drain (sys/kern/kern_sysctl.c:387-397) never increments its counter, so it provides no reader-drain protection anywhere.", "recommended_fix": "NULL-guard hub after device_is_attached() in both uhub_child_location_string() and uhub_child_pnpinfo_string() (see fix.diff)", "fix_status": "not_testable", "fix_baseline_reproduced": false, "fix_patched_reproduced": false, "fix_verdict": "Fix not built: baseline never reproduced, no observable to diff; the NULL-guard is trivially-correct hardening (git apply --check passes against sys/bus/u4b/usb_hub.c).", "fix_evidence": "findings/poc/DF-5301/fix.diff (git-apply clean, +12 lines in 2 hunks)" } |