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

acpi_acad has no device_detach: ACPI notify handler (context=dev), global ACPIIO_ACAD_GET_STATUS ioctl hook and hw.acpi.acline sysctl OID are never removed (DF-5006-family; resource pinning + blocked kldunload, structurally non-UAF today)

Summary

Three persistent registrations never removed - the mirror image of DF-5006: no detach means DEVICE_DETACH returns ENXIO and devclass_delete_driver aborts, so the softc can never be freed (pinning, not UAF; kldunload permanently blocked with acad present). Any future bare detach returning 0 converts it to the DF-5006 UAF class. Reverse-the-registrations detach fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5074 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 4.0 KB ↓ raw
run.log β€” 1.3 KB view raw
env.txt β€” 813 B view raw
fix.diff β€” 2.0 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.4 KB view raw

DF-5074 β€” acpi_acad has no device_detach: ACPI notify handler, ioctl hook and hw.acpi.acline sysctl OID are never removed (resource pinning; DF-5006-family latent UAF)

What

acpi_acad.c installs three persistent kernel-side registrations at attach and provides no device_detach method at all (methods table: sys/dev/acpica/acpi_acad.c:76-82 β€” probe/attach only):

  1. an ACPI_ALL_NOTIFY handler with context = dev for the life of the namespace (acpi_acad.c:193-194), never removed;
  2. a global ioctl hook ACPIIO_ACAD_GET_STATUS with arg = dev (acpi_acad.c:172), never deregistered;
  3. for unit 0, an hw.acpi.acline sysctl OID added to the acpi bus sysctl context whose oid_arg1 = &sc->status pins the acad softc (acpi_acad.c:178-184), never removed;

plus deferred AcpiOsExecute(OSL_NOTIFY_HANDLER, ...) callbacks (acpi_acad.c:140, :195) that hold dev with no drain on any teardown (the DF-2381 family problem: AcpiOsWaitEventsComplete is not implemented in DragonFly's OSL).

Why it is NOT a live UAF (unlike DF-5006) β€” verified mechanics

  • A driver without device_detach fails device_detach() with ENXIO (sys/kern/subr_bus.c:2139 DEVICE_DETACH(dev) dispatches to kobj_error_method, sys/kern/subr_kobj.c:69-75).
  • devclass_delete_driver() β€” the only newbus path that frees driver-owned devices on module unload β€” aborts on the first detach failure (sys/kern/subr_bus.c:901-902).
  • Therefore the acad softc can never be freed: every teardown path (devctl delete, kldunload acpi.ko on a modular build) fails before newbus frees anything. The dangling references never dangle in practice.

Contrast with DF-5006 (acpi_cpu): there device_detach was implemented as bus_generic_detach, which returns 0, so newbus freed the softc while the notify handler / sysctl OIDs / sensordev still referenced it β†’ real UAF. In acpi_acad the defect is the mirror image:

  • modular acpi.ko cannot be unloaded on any machine that has an AC adapter (the acad device's detach failure aborts devclass_delete_driver);
  • the notify handler, ioctl hook and sysctl OID are pinned forever (resource leak across hypothetical ACPI namespace teardowns);
  • the driver is one trivial "add a bare detach returning 0" patch away from becoming the DF-5006-class UAF β€” the teardown bug is latent, not absent.

Threat model

Root-only or boot-time-firmware configurations (kldunload by root, devctl by root). No unprivileged trigger exists. Severity: Low / hardening.

Fix

fix.diff adds a real acpi_acad_detach: removes the notify handler (ACPICA drains notify work inside AcpiRemoveNotifyHandler β€” evxface.c:1276), then deregisters the ioctl hook and removes the sysctl OID (stored in the softc).

Files

  • README.md, VERDICT.md, run.log (guest gate documentation), env.txt, fix.diff, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5074 β€” VERDICT

Status: untested (by design) / impact: none / confidence: certain (code structure verified in source and against newbus/kobj mechanics).

Why untested

The defect cannot be exercised on the audit guest β€” or on any stock DragonFly system:

  1. Stock guest: QEMU's DSDT contains no ACPI0003 device, so acpi_acad never attaches (sysctl hw.acpi.acline β†’ unknown oid; see run.log). (That acad is bootable on this guest at all was proven for DF-5073 via an injected SSDT β€” but the teardown gap needs a detach, not an attach.)
  2. No teardown path exists. The defect's harm requires the acad softc to be freed while the notify handler / ioctl hook / sysctl OID still reference it. Verified in source: - acpi_acad.c:76-82 β€” methods table has NO device_detach; - sys/kern/subr_bus.c:2139 + sys/kern/subr_kobj.c:69-75 β€” missing method β‡’ DEVICE_DETACH returns ENXIO β‡’ device_detach() fails; - sys/kern/subr_bus.c:901-902 β€” devclass_delete_driver() aborts on the first detach failure, so a modular kldunload acpi never reaches the softc free either; - guest confirms acpi is compiled into the kernel (kldunload acpi β†’ "can't find file acpi", run.log), and no devctl utility exists on the guest anyway. Even with DF-5073's SSDT-injected AC adapter, device_detach(acpi_acad0) would simply return ENXIO and free nothing.

What the defect actually is (pass-2 depth)

Same family as DF-5006 (acpi_cpu) but the mirror image:

  • DF-5006: detach succeeds (bare bus_generic_detach) β‡’ softc freed β‡’ handler/OIDs/sensordev dangle β‡’ UAF on kldunload acpi (modular builds).
  • DF-5074 (this): detach cannot succeed β‡’ softc never freed β‡’ nothing dangles; instead the three global registrations (acpi_acad.c:172,180-183,193-194) are pinned forever and, on any machine with a real AC adapter, kldunload acpi.ko is permanently blocked by the acad device's ENXIO. Any future one-line "fix" that adds a bare device_detach returning 0 β€” the tempting minimal change β€” instantly converts this into the DF-5006-class UAF (AML Notify(PWRS, 0x80) β†’ acpi_acad_notify_handler on freed dev β†’ device_get_softc of freed memory β†’ AcpiOsExecute over freed text after unload; sysctl reader β†’ handler on freed softc via oid_arg1 = &sc->status).

Also noted (info-level, folded here): AcpiInstallNotifyHandler's return value is ignored at acpi_acad.c:193, and deferred AcpiOsExecute(OSL_NOTIFY_HANDLER, ...) work (acpi_acad.c:140,195) has no drain β€” moot while no teardown exists, relevant once fix.diff is applied (AcpiRemoveNotifyHandler performs ACPICA-internal draining).

fix.diff β€” add acpi_acad_detach that removes the ACPI_ALL_NOTIFY handler, deregisters ACPIIO_ACAD_GET_STATUS, and removes the unit-0 sysctl OID (now tracked in the softc). Not applied to sys/; not built (untested β€” no triggerable path exists to validate against; the diff follows the exact registration order reversed).

Kernel references

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

No triggerable baseline exists (untested verdict) so a fix build has nothing to validate against; fix.diff follows the exact attach registrations reversed and is not applied to sys/.

n/a β€” no runtime baseline possible; structural analysis in VERDICT.md.
↓ fix.diffper-fix-DF-5074

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (full newbus/kobj mechanics with path:line proving the softc can never be freed); run.log (guest gate: no ACPI0003 on stock QEMU DSDT so acad never attaches; acpi compiled into kernel so kldunload acpi -> 'can't find file'; no devctl utility); fix.diff (real acpi_acad_detach: AcpiRemoveNotifyHandler + acpi_deregister_ioctl + sysctl_remove_oid)

PoC changes

No seed PoC (new pass-2 finding). No trigger program written because the cited path is dead at runtime on this guest and on stock systems β€” documented instead (run.log).

Verified recommended fix

Add acpi_acad_detach that removes the ACPI_ALL_NOTIFY handler, deregisters ACPIIO_ACAD_GET_STATUS, and removes the unit-0 acline sysctl OID tracked in the softc (fix.diff).

Verdict

Code defect certain, structurally untriggerable on any stock configuration. acpi_acad installs an ACPI_ALL_NOTIFY handler with context=dev (acpi_acad.c:193-194), a global ACPIIO_ACAD_GET_STATUS ioctl hook (acpi_acad.c:172), and the hw.acpi.acline sysctl OID pinning &sc->status (acpi_acad.c:180-183), but its methods table (acpi_acad.c:76-82) has NO device_detach. Because a missing method makes DEVICE_DETACH return ENXIO (subr_kobj.c:69-75) and devclass_delete_driver aborts on the first detach failure (subr_bus.c:901-902), the acad softc can never be freed β€” so, unlike DF-5006 (where bus_generic_detach succeeded and freed the softc), no UAF materializes. Real-world effect: the three registrations are pinned forever and kldunload of a modular acpi.ko is permanently blocked on any machine with an AC adapter; any future bare detach-that-returns-0 converts it into the DF-5006-class UAF. Unprivileged reachability: none.