acpi_dock: deferred AcpiOsExecute(acpi_dock_attach_later) callbacks jump into freed module text after kldunload β OSL has no drain (AcpiOsWaitEventsComplete unimplemented/stubbed)
Summary
attach_later is queued per _EJD-dependent on every dock insert; DF's AcpiOsExecute is fire-and-forget with no module refcount and AcpiOsWaitEventsComplete is #define-stubbed out (XXX TBI). kldunload frees module text while callbacks/notify handler are pending -> jump into unmapped KVA. Distinct sink from DF-1905/2380 (RemoveNotifyHandler alone does not close it). REPRODUCED deterministically with a crafted dock SSDT: Fatal trap 12 at RIP == freed acpi_dock.ko+0x57e; fix (OSL drain counter + unstub + detach RemoveNotifyHandler/drain) validated on full kernel rebuild - same trigger survives, unload blocks ~1s draining. Also live-verified the DF-1905/2380 mechanism for the first time.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4823 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ssdt_dock.dsl | trigger-src | crafted SSDT: dock station w/ 3s-sleep _STA, 96 _EJD dependents, DFNT notify trigger | 24.5 KB | β download |
| ssdt_dock.aml | trigger-blob | compiled AML (iasl 20251212, guest) | 4.4 KB | β download |
| dfnt.c | trigger-src | kld module evaluating \\_SB_.DFNT at MOD_LOAD (fires the dock notify) | 1.6 KB | view raw |
| run_dockrace.sh | run-script | deterministic trigger sequence | 1.5 KB | view raw |
| dockrace.c | trigger-src | brute-force kldload(2)/kldunload(2) racer (survived 3000 cycles; superseded) | 917 B | view raw |
| run.log | run-log | full serial-console capture of the decisive baseline run incl. kldstat module addresses | 12.4 KB | view raw |
| panic.txt | panic-proof | Fatal trap 12, RIP=freed acpi_dock.ko+0x57e, DDB wedge | 1.3 KB | view raw |
| run.fixed.log | run-log | same trigger on fix.diff-patched kernel: SURVIVED, no fatal | 642 B | view raw |
| build.log | build-log | dfnt.ko module build output | 1.2 KB | view raw |
| fix_build.log | fix-log | kernel build completion lines (nativekernel, 0 errors) | 184 B | view raw |
| fix_install.log | fix-log | installkernel output | 83.9 KB | view raw |
| fix.diff | suggested-fix | OSL drain + platform-header unstub + acpi_dock_detach RemoveNotifyHandler+drain | 2.9 KB | view raw |
| env.txt | env | guest uname, versions | 661 B | view raw |
| mechanism_check_df1906/ | negative-result | live refutation of DF-1906's sysctl-vs-unload UAF mechanism (pcpu sysctllock drain blocks kldunload 4s) | 4.0 KB | β download |
Fix verification
fixedfix.diff (OsdSchedule.c drain + acdragonfly.h unstub + acpi_dock_detach RemoveNotifyHandler+drain) applied in guest /usr/src, make -j6 nativekernel KERNCONF=X86_64_GENERIC (0 errors) + installkernel + reboot; exact same trigger re-run: NO panic, guest alive, kldunload blocks ~1s while draining the parked handler + 96 attach_later callbacks, then completes; follow-up kldload/kldunload cycles clean. Baseline (stock kernel) same trigger: Fatal trap 12 into freed module text + DDB wedge.
fix.diff; fix_build.log (kernel build completed 0 errors); fix_install.log; run.fixed.log (SURVIVED, no fatal); timing rerun in VERDICT.md (kldunload blocked_for=1s then completed, NO-FATAL)
Confirmed kernel references
- sys/dev/acpica/acpi_dock/acpi_dock.c:237
- sys/dev/acpica/acpi_dock/acpi_dock.c:184-197
- sys/dev/acpica/acpi_dock/acpi_dock.c:524-533
- sys/dev/acpica/Osd/OsdSchedule.c:119-141
- sys/contrib/dev/acpica/source/include/platform/acdragonfly.h:218-220
- sys/contrib/dev/acpica/source/components/events/evmisc.c:292-296
Detail
Exploit chain
root: kldload acpi_dock (docks via crafted SSDT, queues 96 attach_later) -> sysctl hw.acpi.dock.status=0 -> kldload dfnt.ko (fires Notify; kthread parks 3s inside acpi_dock_notify_handler via _STA Sleep) -> kldunload acpi_dock completes during the stall (detach takes no drain) -> module text freed -> kthread resumes/jumps into freed KVA -> Fatal trap 12 (instruction fetch, page not present) -> unrecoverable DDB wedge. No uid=0 chain: trigger requires root, primitive is a jump to unmapped module text (offset not attacker-chosen); class is correctness/robustness, same bar as DF-1905.
Evidence (decisive lines)
run.log (full serial console: kldstat module base 0xffffffff82600000 -> Fatal trap 12 RIP 0xffffffff8260057e); panic.txt (trap dump + db> wedge); run.fixed.log (patched kernel: SURVIVED, no fatal); fix_build.log/fix_install.log (nativekernel 0 errors, install completed); mechanism_check_df1906/run.log (adjacent DF-1906 refutation: kldunload blocked 4s by pcpu sysctllock drain)
PoC changes
Crafted the whole harness (no seed existed): SSDT with dock station (PNP0C15 + _DCK + 3s-sleep _STA) + 96 _EJD dependents (fixed 5-char ACPI NameSegs -> 4-char DFxx) + DFNT Notify method; dfnt.ko trigger module (minimal AcpiEvaluateObject extern -- full ACPICA include maze fails in standalone kmod builds); brute-force racers (300 shell + 3000 tight C kldload(2)/kldunload(2) cycles) survived, superseded by the deterministic _STA-Sleep stall trigger.
Verified recommended fix
Implement AcpiOsWaitEventsComplete() in OsdSchedule.c (pending-callback counter + drain), remove the acdragonfly.h XXX-TBI stub, and make acpi_dock_detach() call AcpiRemoveNotifyHandler + AcpiOsWaitEventsComplete before sysctl_ctx_free (fix.diff, validated).
Verdict
REPRODUCED on the DF 6.5-DEVELOPMENT guest with a crafted dock SSDT: acpi_dock_insert_child (acpi_dock.c:237) queues AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_dock_attach_later, dev) with zero lifetime protection -- DF's OSL has no AcpiOsWaitEventsComplete (platform header stubs it out) and no module refcount -- so kldunload acpi_dock frees module text while deferred callbacks (and the mid-execution notify handler, DF-1905/2380 family) still reference it. Deterministic trigger (3s-sleeping _STA parks the acpi_task kthread inside the handler; kldunload lands in the stall) yields Fatal trap 12 with RIP == 0xffffffff8260057e, +0x57e into the freed acpi_dock.ko mapping (kldstat: 0xffffffff82600000), DDB 'Stopped at -0x7d9ffa82'. Root-gated (kldload/kldunload + ACPI event) => Low severity, memcorrupt family. Fix (OSL drain + AcpiRemoveNotifyHandler + drain-in-detach) validated by full kernel rebuild: same trigger SURVIVES, kldunload blocks ~1s while draining then completes, follow-up cycles clean.
No comments yet.