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

1. `uname -a` — DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC.
2. `kldstat` — kernel + ehci.ko + xhci.ko only; **no acpi.ko module**
   (ACPI compiled in via `device acpi`, X86_64_GENERIC:59).
3. `kldstat -v | grep button` — `acpi/acpi_button` driver **registered**
   in the builtin kernel.
4. `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.
5. `kldunload acpi` → `can't find file acpi` (rc=1); `kldunload -a` →
   illegal option. **No unload path exists on this system.**
6. 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_detach` of an acpi_button device. On this guest there is no such
  device; in a module build, unloading acpi.ko routes through
  `bus_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.
