acpi_button has no device_detach: ACPI notify handler and fixed-event handler (context = softc) plus PRW wake GPE are never removed β DF-5006/DF-5074 family teardown gap, guaranteed UAF if a detach path is ever added
Summary
Handlers keep context=sc forever; today pinned resources + blocked kldunload (structurally non-UAF, verified live on guest: kldunload acpi ENOENT, module builds abort at bus_generic_detach). Any future detach path turns a button press into a softc UAF. Mirror-attach detach fix authored (with the AcpiOsExecute-drain caveat noted).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5091 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| trigger.sh | β | 1.2 KB | view raw | |
| build.sh | β | 133 B | view raw | |
| run.sh | β | 160 B | view raw | |
| run.log | β | 979 B | view raw | |
| env.txt | β | 660 B | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| manifest.json | β | 948 B | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-5091 β acpi_button: no device_detach β ACPI notify/fixed-event handlers (context = softc) are never removed
What the finding is (source-level, certain)
sys/dev/acpica/acpi_button.c registers two kinds of ACPICA callbacks whose
context is the per-device softc and never removes either:
AcpiInstallFixedEventHandler(event, acpi_button_fixed_handler, sc)(acpi_button.c:150-151, fixed PNP0C0C/ACPI_FPB & PNP0C0E/ACPI_FSB path)AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, acpi_button_notify_handler, sc)(acpi_button.c:158-159, AML path)
The methods table (acpi_button.c:74-83) has no device_detach entry β
probe/attach/suspend/shutdown/resume only. Both handlers defer work via
AcpiOsExecute(OSL_NOTIFY_HANDLER, ..., sc) (acpi_button.c:255, 258), and
attach also enables a wake GPE that is never disabled
(acpi_button.c:169-170). This is the DF-5006 (acpi_cpu) / DF-5074
(acpi_acad) family, mirrored.
Consequences:
1. If a detach path is ever added (bus re-enumeration, driver split into its
own module, force-unload), the still-registered ACPICA handler fires into
freed softc memory β UAF. The AcpiOsExecute indirection widens the
race window past AcpiRemoveNotifyHandler's own quiesce.
2. Today it is structurally non-UAF: acpi_button.c is compiled into
acpi.ko (sys/dev/acpica/Makefile:66) or into the base kernel
(device acpi in X86_64_GENERIC), and the acpi bus driver's own detach
is bus_generic_detach (sys/dev/acpica/acpi.c:182) β detaching acpi0
walks the children, hits the missing child detach method, gets ENXIO,
and the unload aborts before any softc is freed. Resource pinning +
permanently blocked kldunload acpi.
Reproduce
Nothing to build. Run on the host repo root:
findings/poc/DF-5091/run.sh # pushes trigger.sh to the guest, runs it as root
Expected (matches run.log):
- kldstat -v | grep button β acpi/acpi_button driver registered
- dmesg | grep acpi_button β empty (no acpi_button device on this
guest; QEMU's buttons are fixed events handled by acpi.c:1974-1993, whose
"acpi0: Power Button (fixed)" line is the only button-ish dmesg output)
- kldunload acpi β can't find file acpi: No such file or directory
(ACPI is built into the kernel; there is no module to unload)
- guest stays up (uptime at the end)
Verdict
not_reproduced / impact none: the UAF half of the gap has no reachable
trigger on this system (no acpi_button device instance, no unload path at
all); the pinning/blocked-teardown half is confirmed by the run. The
structural gap itself is certain from source. Severity: Low (matches the
DF-5006/DF-5074 family precedent).
fix.diff adds the missing acpi_button_detach (git-apply --check clean;
NOT applied β sys/ is a read-only audit target). Not build-validated:
fix validation is only mandatory for reproduced memory corruption.
VERDICT β DF-5091 (acpi_button no-detach teardown gap)
status: not_reproduced | reproduced: false | impact: none | confidence: certain (structural gap) guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64 (X86_64_GENERIC), up, clean
What was claimed
acpi_button_attach() installs ACPICA callbacks with context = sc
(AcpiInstallFixedEventHandler at sys/dev/acpica/acpi_button.c:150-151,
AcpiInstallNotifyHandler at :158-159) and the driver has no
device_detach (methods table at :74-83) β so the handlers, the
AcpiOsExecute deferrals they queue (:255, :258) and the wake GPE enabled
at :169-170 are never torn down. DF-5006 (acpi_cpu) / DF-5074 (acpi_acad)
family mirror.
What was run (see run.log, trigger.sh)
uname -aβ DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC.kldstatβ kernel + ehci.ko + xhci.ko only; no acpi.ko module (ACPI compiled in viadevice acpi, X86_64_GENERIC:59).kldstat -v | grep buttonβacpi/acpi_buttondriver registered in the builtin kernel.dmesg | grep acpi_buttonβ empty: no acpi_button device attached on this guest. The single button-ish dmesg line,acpi0: Power Button (fixed), comes from acpi.c:1974-1993 (acpi_enable_fixed_events), where acpi.c installs its own fixed-event handlers (context = acpi_softc) because QEMU's FADT declares the fixed buttons.kldunload acpiβcan't find file acpi(rc=1);kldunload -aβ illegal option. No unload path exists on this system.- Guest alive (
uptime), no panic, no dirt.
Why not_reproduced (and why that is the honest classification)
The finding has two halves:
- Structural gap (confirmed, certain): source shows no detach, no handler removal, no GPE undo. Runtime confirms the pinning consequence: the driver cannot be removed from the running system.
- UAF (not reachable today): freeing the softc requires a successful
device_detachof an acpi_button device. On this guest there is no such device; in a module build, unloading acpi.ko routes throughbus_generic_detach(sys/dev/acpica/acpi.c:182) β child DETACH method missing β ENXIO β unload aborts before any softc is freed. Hence "structurally non-UAF today" β exactly the DF-5074 conclusion, now also demonstrated live (blocked/absent unload on the running guest).
Exploit chain: none possible on this configuration. An attacker gains
nothing: no user-triggerable detach path exists (DragonFly has no
devd/devctl "detach" operation, no kldunload -f), and unprivileged users
cannot kldunload at all (priv required).
Exploitability ceiling (if a detach path is ever added)
Power/sleep button notify β acpi_button_notify_handler β
AcpiOsExecute(..., sc) fires after detach frees the softc β UAF with
attacker-influenced timing (press button while unload in flight). That is
the reason the fix belongs in-tree now, cheaply.
Fix
fix.diff (generated against the read-only sys/ tree; git apply --check
clean; NOT applied): adds acpi_button_detach() mirroring attach β removes
the fixed-event handler or the notify handler. Residual hardening note in
the diff: AcpiRemoveNotifyHandler does not drain AcpiOsExecute work
already queued; a generation counter in the softc would close that.
Fix validation (patched-kernel rebuild) not performed: mandatory only when a memory-corruption finding reproduces; here nothing reproduces (baseline clean, no fault to A/B). fix_status: not_testable.
Attempts
1 decisive run (run.log); guest reused from the earlier i915/acpi
inspection, no reset needed (no state was modified β all probes read-only,
kldunload attempts failed benignly).
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_testablefix.diff authored and git-apply --check clean against sys/ but not build-validated: the finding produced no runtime fault on the baseline guest (structurally non-UAF today; no acpi_button device, no unload path), so there is no bad behavior to A/B. Fix validation is mandatory only when a memory-corruption finding reproduces.
findings/poc/DF-5091/fix.diff (proposed, unapplied; adds acpi_button_detach + DEVMETHOD entry; notes the AcpiOsExecute drain requirement)
Confirmed kernel references
- sys/dev/acpica/acpi_button.c:74
- sys/dev/acpica/acpi_button.c:148
- sys/dev/acpica/acpi_button.c:150
- sys/dev/acpica/acpi_button.c:158
- sys/dev/acpica/acpi_button.c:169
- sys/dev/acpica/acpi_button.c:255
- sys/dev/acpica/acpi_button.c:266
- sys/dev/acpica/acpi.c:182
- sys/dev/acpica/acpi.c:1974
- sys/dev/acpica/Makefile:66
Detail
Evidence (decisive lines)
run.log (decisive: 'kldstat -v' shows acpi/acpi_button driver registered; 'dmesg|grep acpi_button' empty; 'kldunload acpi' fails 'can't find file acpi'; guest stays up). VERDICT.md for the full path:line trace; fix.diff for the proposed detach (git apply --check clean, not applied).
PoC changes
None β structural finding; trigger is a plain /bin/sh runtime demonstration (driver registration, device absence, blocked/absent unload path), no seed code to repair.
Verified recommended fix
Add acpi_button_detach() mirroring attach (remove fixed-event or notify handler; disable the PRW wake GPE) and register DEVMETHOD(device_detach, ...) β see fix.diff.
Verdict
Structural teardown gap confirmed in source and by runtime demonstration, but the UAF half is not reachable on any current configuration, so the security impact today is none. acpi_button_attach() installs AcpiInstallFixedEventHandler/AcpiInstallNotifyHandler with context=sc (acpi_button.c:150-151,158-159) and the driver has no device_detach (acpi_button.c:74-83), mirroring DF-5006 (acpi_cpu) and DF-5074 (acpi_acad). On the guest: the acpi_button driver is registered in the builtin kernel (kldstat -v), no acpi_button device instance is attached (dmesg empty; QEMU fixed buttons are handled by acpi.c:1974-1993), no acpi.ko exists to unload (kldunload acpi -> 'can't find file'), and in a module build unloading acpi.ko aborts at bus_generic_detach (acpi.c:182) -> missing child detach -> ENXIO before any softc is freed. Net effect today: permanently pinned handlers/resources and a guaranteed UAF if a detach path is ever added (including via the AcpiOsExecute deferrals at acpi_button.c:255,258). Low severity, fix is cheap and belongs in-tree.
No comments yet.