# VERDICT — DF-5150 (acpi_lid 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_lid_attach()` installs an ACPICA notify handler with `context = sc`
(sys/dev/acpica/acpi_lid.c:121-122) and enables the PRW wake GPE
(acpi_lid.c:126-127); the driver has **no `device_detach`** (methods table
at acpi_lid.c:66-74). The handler defers `sc` via `AcpiOsExecute`
(acpi_lid.c:194-195). DF-5091 (acpi_button) mirror — DF-5091's own notes
flagged "acpi_lid.c (PNP0C0D) ... filed separately by its own audit",
which is this file.

## What was run (see run.log, trigger.sh)

1. `uname -a` — DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC.
2. `kldstat -v | grep -i lid` — `274 acpi/acpi_lid` **driver registered**
   in the builtin kernel (ACPI compiled in, no acpi.ko).
3. `dmesg | grep -i lid` — **empty**: no acpi_lid *device* instance
   attached on this guest.
4. `acpidump -d | grep -c PNP0C0D` — **0**: QEMU's DSDT declares no lid
   device, so `acpi_lid_probe` never matches and the notify handler is
   never installed here.
5. `sysctl hw.acpi.lid_switch_state` → `NONE` — and note this sysctl is
   owned by acpi.c (sys/dev/acpica/acpi.c:630), **not** by acpi_lid.c;
   this driver creates no sysctl at all.
6. `kldunload acpi` → `can't find file acpi` (rc=1): no unload path
   exists on this system.
7. Guest alive (`uptime`), no panic, no dirt.

## Why not_reproduced (honest classification)

Two halves, as with DF-5091:

- **Structural gap (confirmed, certain):** source shows no detach, no
  `AcpiRemoveNotifyHandler`, no GPE undo, no deferral drain. On real lid
  hardware the handler would be pinned for the kernel's lifetime.
- **UAF (not reachable today):** freeing the softc requires a successful
  `device_detach` of an acpi_lid device. On this guest there is no such
  device (no PNP0C0D in the firmware); in a module build, unloading
  acpi.ko routes through `bus_generic_detach` (sys/dev/acpica/acpi.c:182)
  → child DETACH missing → ENXIO → unload aborts **before** any softc is
  freed. Structurally non-UAF today, exactly like DF-5074/DF-5091.

Exploit chain: none on this configuration. Lid notifies require the
firmware to emit them (physical lid action); unprivileged users cannot
synthesize a notify, cannot kldunload, and there is no devd detach
operation.

## Exploitability ceiling (if a detach path is ever added)

Lid notify → `acpi_lid_notify_handler` → `AcpiOsExecute(OSL_NOTIFY_HANDLER,
acpi_lid_notify_status_changed, sc)` queued → detach frees softc → the
deferred callback dereferences `sc->lid_handle` (acpi_lid.c:161) and
`sc->lid_dev` (acpi_lid.c:165) on freed memory → UAF. Additionally the
PRW GPE stays enabled pointing at this device. Cheap fix, belongs
in-tree.

## Pass-2 depth recorded alongside (killed candidates)

- **sysctl state read**: no sysctl in this file (hw.acpi.lid_switch_state
  is acpi.c:630; the *value* consumers see is `acpi_sc->acpi_lid_switch_sx`
  read under ACPI_SERIAL at acpi_lid.c:154-177 — int read, no leak surface).
- **_LID eval failure defaults**: on `ACPI_FAILURE` the code `goto out`
  before consuming `lid_status` (acpi_lid.c:161-163) — no stale/uninit
  value is exported, and the newbus softc is zeroed anyway.
- **suspend/resume states**: both are no-ops (acpi_lid.c:132-142); state
  is re-derived from `_LID` on every notify, so no stale-state bug.
- **wake GPE enable-without-handler** (acpi_lid.c:126-127): the standard
  DF/FreeBSD PRW wake pattern (same in acpi_button/acpi_acad); GPE
  dispatch is owned by ACPICA/AML, not a raw handler pointer.
- **notify default case**: `device_printf` only (acpi_lid.c:197-199);
  firmware-controlled flooding is out of threat model.

## Fix

`fix.diff`: `acpi_lid_detach()` mirrors attach — removes the notify
handler, disables the wake GPE; DEVMETHOD registered; notes the
AcpiOsExecute drain requirement. `git apply --check` clean; not
build-validated (nothing to A/B on this guest — see verdict.json
fix_status: not_testable).

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