acpi_cpu has no detach teardown: ACPI notify handler (context=sc), both sysctl contexts and the sensordev outlive the softc β use-after-free on driver/module detach; attach error paths leak the same resources and orphan the first child
Summary
The notify handler is installed with the softc as context and never removed; bare bus_generic_detach frees the softc while the handler, sysctl OIDs (children hang handlers on glob ctx) and sensordev keep referencing it - the next 0x80/0x81 notify dispatches over freed memory (UAF call through cpu_cst_notify). Gated: root + module-built acpi (compiled-in on the guest - unload refused, evidence captured). Sibling drivers DO remove handlers. Remove-first detach fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5006 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.8 KB | β raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| fix.diff | β | 2.6 KB | view raw | |
| env_probe.txt | β | 1.1 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-5006 β acpi_cpu: no detach teardown β stale ACPI notify handler, sysctl contexts and sensordev outlive the softc (UAF), attach error paths leak them
sys/dev/acpica/acpi_cpu.c installs three things into global namespaces and
never removes any of them:
AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, acpi_cpu_notify, sc)at acpi_cpu.c:252 βcontext = sc(the softc);- two sysctl contexts with persistent OIDs:
glob_sysctl_ctx(hw.acpi.cpu, :171-178, holds the children'scx_lowestetc. handlers added viasc->cst_parent->glob_sysctl_ctx, acpi_cpu_cstate.c:759-774) andpcpu_sysctl_ctx(hw.acpi.cpuN, :180-188); sensordev_install(&sc->cpu_sensdev)at :233.
Yet the driver's detach method is plain bus_generic_detach
(acpi_cpu.c:67) β it detaches the cpu_cst/cpu_pst children and
nothing else. After a driver detach (module unload of an acpi.ko built as a
module), the softc is freed by newbus (sys/kern/subr_bus.c:1941-1944) while:
- the ACPI notify handler still points at it β the next
Notify(cpu, 0x80/ 0x81)from AML (P-state/C-state changes fire routinely, e.g. on resume or thermal events) dispatchesacpi_cpu_notify(:324-342) which readssc->cpu_cst_notify/sc->cpu_pst_notifyandsc->cpu_devfrom freed memory β use-after-free (call through a freed pointer β RIP control lands on whatever the slab now holds; realistically a panic); - the sysctl OIDs registered against
&sc->glob_sysctl_ctx/&sc->pcpu_sysctl_ctxkeep resolvinghw.acpi.cpu.cx_lowestetc. into handlers whose context pointers reference freed softc memory β UAF read/ write on sysctl access; - the sensordev stays in the global
sensordev_listwith a dangling softc-backed name.
The notify-function-pointer protocol has the same hole without unload only
in theory: children set cpu_cst_notify at attach
(acpi_cpu_cstate.c:751, acpi_cpu_pstate.c:810) before
AcpiInstallNotifyHandler runs at acpi_cpu.c:252 (bus_generic_attach at
:250 precedes it), so install-order is safe β but nothing ever clears the
pointers, so the window opens the moment a child detaches before the parent.
Attach error paths leak the same resources: if the first BUS_ADD_CHILD
fails (:236-237) both sysctl contexts and the sensordev stay installed;
if the second fails (:243-244) the first child is orphaned on top. (The
sysctl_ctx_free(&sc->glob_sysctl_ctx) at :186 on a softc where
glob_sysctl_ctx was never initialized for cpu_id != 0 is harmless only
because newbus zero-allocates softc β sys/kern/subr_bus.c:1952-1954 β and
sysctl_ctx_free on a zeroed TAILQ head is a no-op.)
Sibling drivers in the same directory do remove their handlers
(acpi_thermal.c:384, acpi_video, acpi_cmbat, β¦), which makes the omission
a genuine divergence, not a convention.
Gating (why this is Low and untested on the guest)
- Requires a privileged operator: the only route to
acpi_cpudetach on DragonFly is unloading an acpi.ko built as a module (DRIVER_MODULE(cpu, acpi, β¦)driver detach duringkldunload); there is no unprivileged or remote trigger, and no runtime device-delete path for ACPI cpu devices. - On the audit guest acpi is compiled into the kernel (
kldstatshows no acpi.ko;kldunload acpiβ "can't find file acpi", seeenv_probe.txt), so the path cannot be exercised there at all.
No PoC is provided (contract: skip for Low, root+build-config gated). The
fix.diff adds a real acpi_cpu_detach (remove handler β detach children β
free both contexts β deinstall sensordev) and fixes the attach error paths.
Environment evidence (guest)
See env_probe.txt: live cpu_cst0..5 children, hw.acpi.cpu.* C1-only
sysctls (matches the corepower audit), acpi compiled-in β unload refused.
DF-5006 VERDICT β UNTESTED (gated: privileged unload + module-built acpi); code defect certain
What is certain (code): acpi_cpu.c installs an ACPI notify handler with
sc as context (acpi_cpu.c:252), two sysctl contexts (:171, :180) that the
children also hang handlers on (acpi_cpu_cstate.c:759-774), and a sensordev
(:233), and provides no teardown: device_detach is bare
bus_generic_detach (acpi_cpu.c:67). Newbus frees the softc on driver
detach (sys/kern/subr_bus.c:1941-1944), after which (a) any
Notify(.,0x80/0x81) from AML dispatches acpi_cpu_notify (:324-342) over
a freed softc β read of sc->cpu_cst_notify/sc->cpu_pst_notify and call
through it = use-after-free; (b) hw.acpi.cpu.cx_lowest-style sysctls
resolve handlers whose ctx pointers reference freed softc memory; (c) the
sensordev stays globally registered. Attach error paths (:236-237,
:243-244) leak the same resources without any detach at all.
Why not exercised on the guest: the only route to this driver's detach
is unloading an acpi.ko built as a module β a privileged, non-default
configuration. On the audit guest acpi is compiled into the kernel
(env_probe.txt: kldunload acpi β "can't find file acpi"), so the
path is unreachable there; no unprivileged or remote trigger exists. Per
contract (Low severity, privileged-only), no PoC was attempted β status
untested, not a refutation.
Reachability detail for maintainers: the notify-pointer protocol is
attach-order-safe today (bus_generic_attach at :250 precedes
AcpiInstallNotifyHandler at :252, and children set the pointers in their
attach β acpi_cpu_cstate.c:751, acpi_cpu_pstate.c:810) β the UAF needs the
driver/module teardown that this driver simply doesn't implement. FreeBSD's
acpi_cpu grew a proper detach (AcpiRemoveNotifyHandler + sysctl_ctx_free +
sensordev_deinstall) years ago; DragonFly's 2008 import never got it.
Fix: fix.diff β acpi_cpu_detach (remove handler first, then detach
children, then free both contexts, then deinstall sensordev) + attach
error-path cleanup (delete orphaned cpu_cst child, free contexts,
deinstall sensordev). Not validated on the guest (path unreachable there);
kernel-build of this diff is straightforward (no API changes) but was not
performed since the guest cannot reach the code path either way.
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_testablefix.diff authored (real detach + error-path cleanup); not validated: the detach path is unreachable on this guest (acpi compiled into the kernel) and building a module-acpi custom kernel was out of scope for a Low finding.
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
fix.diff (proposed teardown); env_probe.txt (guest: cpu_cst0..5 live, acpi compiled-in, kldunload refused β path unreachable on guest)
PoC changes
no seed; no runnable PoC (path gated on module-built acpi + kldunload)
Verified recommended fix
add acpi_cpu_detach: AcpiRemoveNotifyHandler before bus_generic_detach, then sysctl_ctx_free(glob+pcpu) and sensordev_deinstall; fix attach error paths to free the same (fix.diff)
Verdict
UNTESTED by design (contract: Low severity + privileged-only trigger; guest path unreachable). Code defect is certain: acpi_cpu.c installs an ACPI_DEVICE_NOTIFY handler with the softc as context (:252), two sysctl contexts (:171/:180) that children hang handlers on (acpi_cpu_cstate.c:759-774), and a sensordev (:233), but device_detach is bare bus_generic_detach (:67) β nothing is ever removed. On driver/module detach newbus frees the softc (subr_bus.c:1941-1944) while the handler, sysctl OIDs and sensordev keep referencing it: the next AML Notify(cpu,0x80/0x81) runs acpi_cpu_notify (:324-342) over freed memory (UAF call through cpu_cst_notify/cpu_pst_notify) and sysctl access hits handlers with dangling ctx. Attach error paths (:236-237, :243-244) leak the same resources without any unload. Gating: only reachable by unloading an acpi.ko built as a module (root-only, non-default config); guest has acpi compiled-in (kldunload acpi -> 'can't find file', env_probe.txt) so the path cannot be exercised there. fix.diff adds a real acpi_cpu_detach + error-path cleanup (modeled on the sibling drivers that DO remove handlers, e.g. acpi_thermal.c:384).
No comments yet.