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

acpi_lid has no device_detach: ACPI_DEVICE_NOTIFY handler installed with context = softc, AcpiOsExecute deferrals of that softc, and the enabled PRW wake GPE are never removed β€” DF-5091 (acpi_button) mirror; guaranteed UAF if a detach path is ever added

Summary

The exact DF-5091-instance DF-5091's notes anticipated for PNP0C0D: handler context=sc, deferrals, wake GPE, no detach - structurally non-UAF today (builtin acpi cannot unload). No PNP0C0D in the QEMU DSDT so the handler is never installed on this guest. Remove-handler + disable-GPE detach fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5150 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 4.4 KB ↓ raw
trigger.sh β€” 1.5 KB view raw
build.sh β€” 133 B view raw
run.sh β€” 160 B view raw
build.log β€” 88 B view raw
run.log β€” 677 B view raw
env.txt β€” 716 B view raw
fix.diff β€” 1.7 KB view raw
manifest.json β€” 987 B view raw
verdict.json β€” 4.1 KB view raw

DF-5150 β€” acpi_lid has no device_detach: ACPI notify handler (context = softc) and PRW wake GPE are never removed (DF-5091 mirror)

File: sys/dev/acpica/acpi_lid.c (pass 2) Severity: Low (structural; no reachable teardown path today) Β· Confidence: certain Status: not_reproduced (runtime) β€” see verdict.json / VERDICT.md

What

acpi_lid_attach() installs an ACPICA notify handler with context = sc (AcpiInstallNotifyHandler(sc->lid_handle, ACPI_DEVICE_NOTIFY, acpi_lid_notify_handler, sc), sys/dev/acpica/acpi_lid.c:121-122), enables the PRW wake GPE (acpi_lid.c:126-127), and the driver has no device_detach (methods table at acpi_lid.c:66-74). The handler defers sc into AcpiOsExecute() (acpi_lid.c:194-195).

Why it matters

Exactly the DF-5091 (acpi_button) / DF-5074 (acpi_acad) / DF-5006 (acpi_cpu) family: the handler holds a raw softc pointer forever, pinning the driver; if a detach path is ever added (or ACPICA teardown ever runs), lid notify β†’ acpi_lid_notify_handler β†’ queued acpi_lid_notify_status_changed(sc) fires after the softc is freed β†’ UAF with firmware-influenced timing. Today it is structurally non-UAF: unloading the builtin acpi code is impossible and a module build aborts at the missing child detach.

Build / Run / Expected

./build.sh          # nothing to build (structural finding; sh -n syntax check)
./run.sh            # runs trigger.sh as root on the QEMU guest

Expected (see run.log): acpi/acpi_lid driver registered in the builtin kernel (kldstat -v id 274); no acpi_lid device instance (dmesg empty); zero PNP0C0D entries in the QEMU DSDT (acpidump) β€” the firmware declares no lid, so the handler is never even installed on this guest; kldunload acpi β†’ "can't find file"; guest stays up.

Fix

fix.diff β€” add acpi_lid_detach() mirroring attach (remove the notify handler, disable the wake GPE) and register DEVMETHOD(device_detach, …). git apply --check clean against sys/ (not applied).

Artifacts

trigger.sh build.sh run.sh build.log run.log env.txt fix.diff manifest.json verdict.json VERDICT.md

VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.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_lid device, no PNP0C0D in the QEMU firmware, 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-5150/fix.diff (proposed, unapplied; adds acpi_lid_detach() + DEVMETHOD entry; notes the AcpiOsExecute drain requirement)
↓ fix.diffper-fix-DF-5150

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (decisive: 'kldstat -v | grep -i lid' shows acpi/acpi_lid registered; 'dmesg | grep -i lid' empty; 'acpidump -d | grep -c PNP0C0D' = 0; sysctl hw.acpi.lid_switch_state belongs to acpi.c:630 not this driver; 'kldunload acpi' fails 'can't find file acpi'; guest stays up). VERDICT.md for the full path:line trace plus the pass-2 killed candidates (sysctl ownership, _LID failure path, suspend/resume no-ops, wake-GPE pattern, notify default case); 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, absent DSDT node, blocked unload), no seed code to repair.

Verified recommended fix

Add acpi_lid_detach() mirroring attach (AcpiRemoveNotifyHandler for ACPI_DEVICE_NOTIFY; acpi_wake_set_enable(dev,0); AcpiDisableGpe for the PRW GPE) and register DEVMETHOD(device_detach, ...) -- see fix.diff.

Verdict

Structural teardown gap confirmed in source and probed at runtime, but the UAF half is not reachable on any current configuration, so the security impact today is none. acpi_lid_attach() installs AcpiInstallNotifyHandler(..., ACPI_DEVICE_NOTIFY, acpi_lid_notify_handler, sc) with context = softc (sys/dev/acpica/acpi_lid.c:121-122), enables the PRW wake GPE (acpi_lid.c:126-127), defers sc via AcpiOsExecute (acpi_lid.c:194-195), and the driver has no device_detach (acpi_lid.c:66-74) -- the exact DF-5091 (acpi_button) mirror flagged in that finding's notes ('acpi_lid.c (PNP0C0D) ... filed separately'). On the guest: the acpi_lid driver is registered in the builtin kernel (kldstat -v id 274), no acpi_lid device instance is attached (dmesg empty), and QEMU's DSDT contains zero PNP0C0D entries (acpidump), so the handler is never even installed here; kldunload acpi fails ('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 handler/GPE if the hardware exists, and a guaranteed UAF (notify -> AcpiOsExecute(sc) -> freed softc) if a detach path is ever added. Low severity, fix is cheap and belongs in-tree.