DF-2383 / verdict.json
{ "finding_id": "DF-2383", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "UAF write through the real acpi_hp_notify confirmed deterministically (3/3) with the verbatim acpi_hp.c compiled into a custom INVARIANTS kernel and the WMI world interposed: a notify stalled mid-handler while the real detach+newbus kfree(softc,M_BUS) ran underneath, the freed 1208-byte chunk was reclaimed by a kmalloc victim (victim==old_sc every run), and the released handler wrote sc->was_wlan_on_air (softc+56, acpi_hp.c:403) into the victim; the copied-but-late-dispatch variant panicked the kernel at acpi_hp_notify+0x27. Root cause confirmed line-accurate: AcpiOsWaitEventsComplete is an empty macro (acdragonfly.h:219-220), acpi_wmi_notify_handler dispatches after releasing the WMI lock (acpi_wmi.c:649-651), acpi_hp_notify takes no hp lock, and acpi_hp_detach removes the handler on the WRONG device (kobj_error_method ENXIO, so the handler is never unregistered at all). Reach is root/kldunload racing hardware WMI events on HP laptops: a genuine kernel UAF write plus panic, but no unprivileged trigger path, so not a privesc. fix.diff (correct remove target + in-flight dispatch drain in acpi_wmi) A/B validated: 0 victim writes and no panic with identical inputs.", "exploit_chain": "root on HP WMI hardware: kldunload acpi_hp while WMI hotkey events arrive -> acpi_wmi_notify_handler copies handler+ctx under the WMI lock, releases it, then calls acpi_hp_notify -> concurrently acpi_hp_detach returns (its REMOVE call is a kobj ENXIO no-op on the wrong device) and newbus kfrees the 1208-byte softc and the device struct -> the still-running notify continues on the freed softc: reads sc->wmi_dev/sc->verbose/flags and writes sc->was_*_on_air (byte 0/1 toggle at +56/+60/+64) -> demonstrated deterministically as a write into a replacement M_BUS object, and as a page-fault panic (acpi_hp_notify+0x27) when the dispatch lands after the free. Grooming the replacement chunk is trivial for the root attacker that is already required, so the write primitive is real but adds nothing beyond root: no unprivileged privesc chain exists.", "evidence": "run.log (baseline: CONTROL 0 writes vs UAF WRITE at softc+56 3/3, RESULT: UAF WRITE CONFIRMED; fixed: drain=1, 0 writes 3/3; delayed: baseline panic vs fixed clean); panic.txt (Fatal trap 12 fault 0x8 at acpi_hp_notify+0x27 from hp_sim_inject_thread); serial.baseline_runs.txt (full untrimmed serial of the decisive baseline scenarios); run.fixed.hpsimlog.txt (full kernel-side log of the fixed runs); hp_sim.c (interposition harness); fix.diff (validated); build.log (nativekernel build+install, rc=0); env.txt (kernel/config/sha256 gate/offsets)", "kernel_refs": [ "sys/dev/acpica/acpi_hp/acpi_hp.c:810-826", "sys/dev/acpica/acpi_hp/acpi_hp.c:815", "sys/dev/acpica/acpi_hp/acpi_hp.c:822", "sys/dev/acpica/acpi_hp/acpi_hp.c:351-431", "sys/dev/acpica/acpi_hp/acpi_hp.c:579-605", "sys/dev/acpica/acpi_hp/acpi_hp.c:591-592", "sys/dev/acpica/acpi_wmi/acpi_wmi.c:627-653", "sys/dev/acpica/acpi_wmi/acpi_wmi.c:649-651", "sys/dev/acpica/acpi_wmi/acpi_wmi.c:441-460", "sys/contrib/dev/acpica/source/include/platform/acdragonfly.h:219-220", "sys/kern/subr_bus.c:1941-1944", "sys/kern/subr_kobj.c:69-73" ], "poc_changes": "No seed PoC existed; built the verbatim-interposition kernel harness from scratch: fake acpi_wmi bus driver parented to the REAL acpi0 (so acpi_device_get_parent_softc resolves to the real acpi_softc and hw.acpi.hp.* lands in the real tree), all 7 acpi_wmi_if KBI methods mirroring the real lock protocol, worker-kthread command plane (a sysctl handler may not create sysctls: kernel_sysctl holds SYSCTL_SLOCK across handlers), stall hook inside the interposed ACPI_WMI_GET_EVENT_DATA, deterministic M_BUS victim reclaim of the freed softc, and HP_SIM_FIX_DRAIN mirroring the acpi_wmi.c drain hunk for A/B. Harness-only discoveries: nexus parenting dies in acpi_child_pnpinfo (needs real ACPI ivars via BUS_ADD_CHILD on acpi0) and the has_cmi=0 detach path panics in sbuf_delete (independent bug, noted in VERDICT.md).", "attempts": 6, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #11: Mon Oct 19 07:18:02 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/HPHARNESS x86_64", "runtime_sec": 16200, "guest_dirty": 0, "build_cmd": "sh build.sh # baseline (pushes sha256-gated verbatim acpi_hp.c + hp_sim.c, builds and installs HPHARNESS via make -j6 nativekernel, reboots); sh build.sh fixed # applies fix.diff + HP_SIM_FIX_DRAIN=1, rebuilds", "run_cmd": "sh run.sh # hw.hpsim.cmd 5 (control), 4 x3 (uaf), 6 (delayed); read hw.hpsim.log / serial console", "code_hash": "f241473f5ec4e5efb82a736f39c1f7216a955f2622b010f7f7ef17657f2620aa", "notes": "Independent bug observed live, not filed here: acpi_hp_attach never initializes hpcmi_bufptr when has_cmi=0, so acpi_hp_detach:595-598 sbuf_deletes a zeroed sbuf (panic under INVARIANTS on clean kldunload). Fix: initialize hpcmi_bufptr=-1 at attach. Also note buildkernel refuses without buildworld on this guest snapshot; nativekernel is the established target. The QEMU guest has no HP WMI hardware; the harness supplies the WMI/BIOS side, which is the trust boundary under test.", "recommended_fix": "acpi_hp_detach: call ACPI_WMI_REMOVE_EVENT_HANDLER(sc->wmi_dev,...) (not dev); acpi_wmi: refcount in-flight dispatches under the WMI lock in acpi_wmi_notify_handler and drain them (lksleep) in acpi_wmi_remove_event_handler_method" } |