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)
PoC verification
Evidence pack
findings/poc/DF-5074 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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):
- an
ACPI_ALL_NOTIFYhandler withcontext = devfor the life of the namespace (acpi_acad.c:193-194), never removed; - a global ioctl hook
ACPIIO_ACAD_GET_STATUSwitharg = dev(acpi_acad.c:172), never deregistered; - for unit 0, an
hw.acpi.aclinesysctl OID added to the acpi bus sysctl context whoseoid_arg1 = &sc->statuspins 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_detachfailsdevice_detach()with ENXIO (sys/kern/subr_bus.c:2139DEVICE_DETACH(dev)dispatches tokobj_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.koon 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.kocannot be unloaded on any machine that has an AC adapter (the acad device's detach failure abortsdevclass_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
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:
- 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.) - 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 NOdevice_detach; -sys/kern/subr_bus.c:2139+sys/kern/subr_kobj.c:69-75β missing method βDEVICE_DETACHreturns ENXIO βdevice_detach()fails; -sys/kern/subr_bus.c:901-902βdevclass_delete_driver()aborts on the first detach failure, so a modularkldunload acpinever reaches the softc free either; - guest confirmsacpiis compiled into the kernel (kldunload acpiβ "can't find file acpi", run.log), and nodevctlutility 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 onkldunload 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.kois permanently blocked by the acad device's ENXIO. Any future one-line "fix" that adds a baredevice_detachreturning 0 β the tempting minimal change β instantly converts this into the DF-5006-class UAF (AMLNotify(PWRS, 0x80)βacpi_acad_notify_handleron freeddevβdevice_get_softcof freed memory βAcpiOsExecuteover freed text after unload; sysctl reader β handler on freed softc viaoid_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).
Recommended fix
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
- sys/dev/acpica/acpi_acad.c:76-82 (no device_detach in methods table)
- sys/dev/acpica/acpi_acad.c:172 (ioctl hook registered, never deregistered)
- sys/dev/acpica/acpi_acad.c:180-183 (sysctl OID pins &sc->status, never removed)
- sys/dev/acpica/acpi_acad.c:193-194 (notify handler context=dev, never removed)
- sys/dev/acpica/acpi_acad.c:140,195 (deferred AcpiOsExecute, no drain)
- sys/kern/subr_bus.c:2129-2153 (device_detach; fails on ENXIO)
- sys/kern/subr_bus.c:853-912 (devclass_delete_driver aborts on detach failure)
- sys/kern/subr_kobj.c:69-75 (kobj_error_method β ENXIO)
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_testableNo 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.
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.
No comments yet.