apple_smc: detach frees sensor-key state while its per-device sysctl oids stay registered until device_delete β post-detach UAF read / NULL-ioport panic window (DF-4647 family)
Summary
apple_smc_detach frees the sc_*_sensors[] strings first thing but removes none of the oids whose handlers deref them; the device sysctl ctx is only freed at device_delete AFTER detach returns. In the window (any kldunload) a world-readable sensor read consumes freed heap as the SMC key and on the ISA backend NULL-writes the released ioport -> panic; the oid_running drain in kern_sysctl is dead code (the reproduced DF-4647 mechanism). Mechanism-level A/B on stock guest kernel via KLD replica: freed->recycled 0xdeadbeef disclosed through the still-registered oid; fix order returns unknown-oid. Apple hw-gated for the driver path. ctx-free-at-top-of-detach fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4773 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4773.c | β | 10.2 KB | view raw | |
| Makefile | β | 52 B | β download | |
| build.sh | β | 162 B | view raw | |
| run.sh | β | 343 B | view raw | |
| hammer.sh | β | 355 B | view raw | |
| build.log | β | 1005 B | view raw | |
| run.log | β | 319 B | view raw | |
| run.fixed.log | β | 412 B | view raw | |
| race.attempts.log | β | 864 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| verdict.json | β | 6.0 KB | view raw | |
| env.txt | β | 463 B | view raw |
Fix verification
not_testableCannot validate on a kernel running the actual smc.c patch (the driver cannot attach on this guest β no Apple SMC hardware). Validated at mechanism level with the contract replica A/B instead: buggy order leaks recycled heap through the still-registered oid (run.log: 0xdeadbeef); fix.diff order (ctx freed at top of detach, from detach context) returns 'unknown oid' and closes the window (run.fixed.log). fix.diff applies clean against sys/ (git apply --check).
run.log vs run.fixed.log (A/B); fix.diff; race.attempts.log
Confirmed kernel references
Detail
Exploit chain
unprivileged user loops reads of dev.acpi_APP0001.N.temperature.* (world-readable; light.control is even CTLFLAG_ANYBODY-writable) while root runs kldunload apple_smc -> driver detach frees the sensor-key heap chunks (smc.c:507-512) while every oid remains registered until device_delete -> read lands in the window: freed-heap pointer dereferenced as the 4-byte SMC key (heap info leak into the SMC protocol / handler) and, on the ISA backend, NULL-ioport write -> kernel panic; alternatively an in-flight handler races ctx_free's oid kfree with no drain (DF-4647 dead code)
Evidence (decisive lines)
run.log (buggy order: 0x0a5a5a5a -> 0x0 (freed) -> 0xdeadbeef recycled heap through the still-registered oid; dmesg: 'oid removal happens NOW β after backing free'); run.fixed.log (fix.diff order: 'unknown oid' after detach β window closed); race.attempts.log (2 unload-race attempts, not won); df4773.c (teardown-contract replica incl. the verified handler-side sysctl_ctx_free self-deadlock on the per-cpu sysctllock, kern_sysctl.c:1572-1574); fix.diff (git-apply clean vs sys/)
PoC changes
Built the teardown-contract replica from scratch: dynamic SYSCTL_ADD_PROC oid in a sysctl_ctx (static SYSCTL_PROC oids cannot be removed on DFly β 'can't remove non-dynamic nodes!'); the fix-order branch must free the ctx from a kthread because a sysctl handler calling sysctl_ctx_free/sysctl_remove_oid self-deadlocks (userland_sysctl holds the per-cpu sysctllock SHARED across handler invocation, kern_sysctl.c:1572-1574, and removal needs it EXCLUSIVE β wedge verified deterministically twice); kmalloc sizeof(int) chunks mirror smc.c:725-727's KEYLEN+1 allocs; values are logged as ints surfaced through sysctl.
Verified recommended fix
free the device sysctl ctx at the TOP of apple_smc_detach (before freeing sensor strings / releasing resources) β sysctl_ctx_free(device_get_sysctl_ctx(dev)); the later newbus device_sysctl_fini is a no-op on the drained ctx
Verdict
Driver-specific execution is impossible on the audit guest (the Apple SMC cannot attach under QEMU: the SMC handshake needs real hardware answering the ISA/MMIO protocol, and apple_smc_init fails before any oid is registered), so the finding could not be exercised through smc_sysctl.c itself. The teardown CONTRACT was replicated on the stock guest kernel with generic primitives and the driver's own dynamic-oid API: registering a sensor oid in a device-style sysctl ctx whose handler dereferences backing chunks (== smc_sysctl.c:179), then running the apple_smc_detach order (free the sensor chunks at 'detach', oid ctx freed only at 'unload' == device_delete, smc.c:507-512 vs subr_bus.c:2129-2149/device_sysctl_fini) yields a DETERMINISTIC post-detach UAF read: the still-registered oid returns 0xdeadbeef β recycled kernel heap β to an unprivileged reader (run.log). Applying the fix.diff ordering (ctx freed at the top of detach, from detach context) eliminates the window: the read returns 'unknown oid' (run.fixed.log) β mechanism-level A/B. The in-flight-handler race prong (dead oid_running drain, kern_sysctl.c:391-398 β nothing increments oid_running, no wakeup exists) is the already-reproduced DF-4647 mechanism on this same kernel; two hammer+kldunload attempts here did not win the race (212/212 reads clean then READ-FAIL, race.attempts.log). Expected driver-path outcomes, source-traced: UAF read of freed sensor-key heap as the SMC key, then SMC_LOCK on the lockuninit'd sc_lock (smc.c:544) and bus_write_1(sc->sc_ioport==NULL) (smc.h:110) -> NULL-page write -> panic in the detach..delete window of any kldunload apple_smc.
No comments yet.