Fire-and-forget AcpiOsExecute battery tasks can run after detach using freed softc (UAF)
Summary
acpi_cmbat queues callbacks via AcpiOsExecute carrying raw device_t. AcpiOsExecute purely asynchronous (lwkt_sendmsg) and acpi_cmbat_detach never drains pending tasks so queued acpi_cmbat_get_bif_task/acpi_cmbat_init_battery can execute after softc torn down reading/writing freed memory. attach/resume/notify handler all call AcpiOsExecute(...cb dev). OSL stores pointer verbatim helper thread later invokes cb(dev) with no lifecycle tie. detach only does AcpiRemoveNotifyHandler + acpi_battery_remove returns; does not wait/cancel queued messages. get_bif_task no attachment re-check. init_battery partial guard device_is_attached runs AFTER device_get_softc deref. Trigger: firmware notifies (dock hot-plug) or root ACPI debug path + module unload/dock hot-removal. [PASS-2 CLOSURE: the DF-4823-validated OSL drain inside AcpiRemoveNotifyHandler closes the deferred-task UAF here too - detach removes the handler (acpi_cmbat.c:165) so the drain fires; residual ioctl-vs-detach window is root-only and folded into notes] [VERIFIED pass2: reproduced - deferred get_bif_task after free panics Fatal trap 12 at acpi_cmbat_get_bif+0xaa fault 0xc on NULL softc from Idle behind a 30s blocker; PASS-2 NOTE REFUTED - AcpiOsWaitEventsComplete remains a no-op, the DF-4823 drain does NOT close this; OSL drain fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2376 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.1 KB | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| env.txt | β | 145 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| panic.txt | β | 360 B | view raw | |
| run.log | β | 554 B | view raw | |
| run.sh | β | 121 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-2376 - fire-and-forget battery tasks run after detach (UAF)
Rig (ACHARNESS + ssdt_acsim BAT2)
- op=blockq: AcpiOsExecute a blocker onto the real acpi_task thread (30s)
- eval _SB.NB2: Notify(BAT2, 0x81) -> ACPICA queues the notify dispatch behind the blocker
- free the softc exactly as device_set_driver() does at detach: kfree(dev->softc, M_BUS); dev->softc = NULL
- blocker times out -> dispatch -> handler queues acpi_cmbat_get_bif_task -> task: device_get_softc(dev) == NULL -> sc->bix_present read at 0xc
Result (panic.txt)
Fatal trap 12: page fault, fault virtual address = 0xc Stopped at acpi_cmbat_get_bif+0xaa: movl 0xc(%r13),%r8d current process = Idle (acpi_task helper thread)
Fix (fix.diff = real AcpiOsWaitEventsComplete in OsdSchedule.c)
Pending-message counter + drain; AcpiRemoveNotifyHandler (called by acpi_cmbat_detach) then waits for queued tasks. Compile-validated in the fixed ACHARNESS build; the rig choreography deliberately bypasses the AcpiRemoveNotifyHandler call site, so the rig itself cannot show the fix (the production detach path is what the fix protects).
DF-2376 VERDICT
Status: reproduced (reproduced) Impact: panic Confidence: certain Attempts: 4
Deferred acpi_cmbat task ran AFTER the softc was freed and dereferenced the NULL dev->softc set by the detach-time free - Fatal trap 12 at acpi_cmbat_get_bif+0xaa (movl 0xc(%r13),%r8d, fault 0xc = sc->bix_present via NULL softc), current process Idle (acpi_task helper thread). Choreography (console-narrated): blocker queued on acpi_task; Notify(BAT2, 0x81) queues the notify dispatch behind it; softc freed exactly as device_set_driver() does at detach (kfree M_BUS + softc=NULL); blocker timeout lets the dispatch run, the handler queues acpi_cmbat_get_bif_task, and the task runs against the freed device - AcpiOsWaitEventsComplete is a no-op (acdragonfly.h:219-220) so nothing drains the queue. The pass-2 note claiming a DF-4823-validated drain closes this is wrong: the OSL has no drain at all.
Kernel references
sys/dev/acpica/acpi_cmbat.c:154sys/dev/acpica/acpi_cmbat.c:202sys/dev/acpica/acpi_cmbat.c:443sys/dev/acpica/Osd/OsdSchedule.c:119sys/contrib/dev/acpica/source/include/platform/acdragonfly.h:219
Guest
DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #7: Sat Oct 24 00:08:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/ACHARNESS x86_64
Fix validation
See fix.diff and README.md for the baseline/fixed A/B outputs (run.log / fixed.log).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableConfirmed kernel references
Detail
Exploit chain
notify 0x81 (firmware-controlled) + driver detach without drain -> deferred task on freed softc -> kernel panic; kernel-heap grooming of the freed softc could redirect the sc writes (silent write-after-free captured in variant (a) planning) toward controlled corruption
Evidence (decisive lines)
panic.txt (Fatal trap 12, fault virtual address 0xc, Stopped at acpi_cmbat_get_bif+0xaa, current process Idle); run.log console narration (blocker; NB2; softc freed; blocker out at 30s)
PoC changes
blocker choreography op run_batfreed written for this batch; device_delete_child deadlocked the rig so the detach-time kfree(M_BUS)+softc=NULL is emulated directly
Verified recommended fix
see fix.diff
Verdict
Deferred acpi_cmbat task ran AFTER the softc was freed and dereferenced the NULL dev->softc set by the detach-time free - Fatal trap 12 at acpi_cmbat_get_bif+0xaa (movl 0xc(%r13),%r8d, fault 0xc = sc->bix_present via NULL softc), current process Idle (acpi_task helper thread). Choreography (console-narrated): blocker queued on acpi_task; Notify(BAT2, 0x81) queues the notify dispatch behind it; softc freed exactly as device_set_driver() does at detach (kfree M_BUS + softc=NULL); blocker timeout lets the dispatch run, the handler queues acpi_cmbat_get_bif_task, and the task runs against the freed device - AcpiOsWaitEventsComplete is a no-op (acdragonfly.h:219-220) so nothing drains the queue. The pass-2 note claiming a DF-4823-validated drain closes this is wrong: the OSL has no drain at all.
No comments yet.