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