β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2376

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2376 Β· 8 files
FileTypeDescriptionSize
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)

  1. op=blockq: AcpiOsExecute a blocker onto the real acpi_task thread (30s)
  2. eval _SB.NB2: Notify(BAT2, 0x81) -> ACPICA queues the notify dispatch behind the blocker
  3. free the softc exactly as device_set_driver() does at detach: kfree(dev->softc, M_BUS); dev->softc = NULL
  4. 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).

VERDICT.md
↓ download raw

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

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_testable
↓ fix.diffper-fix-DF-2376

Confirmed 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.