apple_fw detach leaves softc-backed world-readable sysctls live while newbus frees the softc: use-after-free read window during driver unload
Summary
~hundreds of CTLFLAG_RD sysctls point directly into the ~330KB softc; newbus frees the softc (subr_bus.c:1944) BEFORE sysctl_ctx_free (:2151) - an unpriv sysctl read racing root unload executes handlers over freed heap (info leak of the reused chunk). Microsecond window + root cooperation; same family as DF-4773 (apple_smc). Own-the-ctx + fini-before-set_driver fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5103 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| fix.diff | β | 2.0 KB | view raw | |
| manifest.json | β | 782 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-5103 β apple_fw: softc freed before its device-sysctl oids are unregistered (detach-time UAF-read window on world-readable sysctls)
What
apple_fw_attach (sys/dev/apple/fw/apple_fw.c:144) registers hundreds of
world-readable sysctl oids that point directly into the softc
(dev.apple_fw.N.osdw, .ssdt_count, .ssdt.* strings, .dsm.*
names/paths/property strings/buffers β apple_fw_sysctl.c:32-126). The softc
is ~330KB (sc_dsm_devs[64] of ~5.4KB each, apple_fw.h:53-78), so the oids
expose a large slab region.
apple_fw_detach (apple_fw.c:149-156) quiesces nothing β it only
lockuninits an unused lock. The teardown order in newbus
device_detach (sys/kern/subr_bus.c:2130-2154) is:
device_set_driver(dev, NULL); /* frees dev->softc (subr_bus.c:1941-1947) */
device_sysctl_fini(dev); /* sysctl_ctx_free() (subr_bus.c:2151) */
Between those two statements the oids are still live in the global MIB while
their backing memory is freed heap. Sysctl readers hold the shared sysctl
lock across oid resolution and handler execution (kern_sysctl.c:1572-1576),
and sysctl_ctx_free needs the exclusive lock β so mutual exclusion only
starts at unregistration, after the softc is gone. An unprivileged reader
that wins the tiny race during a root-initiated unload (kldunload
apple_fw / device removal) executes handlers against freed memory:
sysctl_handle_string strlen+copies from freed/reused heap (info leak of
whatever reuses the 330KB chunk), sysctl_handle_opaque copies
prop->val_len bytes, sysctl_handle_int/quad read freed ints.
Severity rationale
Low: the window is microscopic (adjacent statements in device_detach),
requires root to initiate the unload, and the unprivileged side must land a
sysctl read inside it. Mechanism is source-certain; exploitation is a race.
(Same family as DF-4773 in apple_smc, which had a wider window.)
Verification
Not run (Low severity β per the audit contract, PoC verification is reserved
for Critical/High, memcorrupt/privesc buckets, or trivially-runnable local
PoCs; this needs a root-coordinated race). The code path is fully traced in
VERDICT.md. verdict.json records status untested.
Fix
Driver-level (fix.diff): give apple_fw its own sysctl ctx, and free it in
apple_fw_detach before returning, so the oids are gone before newbus
frees the softc.
DF-5103 VERDICT
Status: untested (Low severity, root-coordinated race β verification skipped per the audit contract). Mechanism source-certain.
The window, traced
-
apple_fw_attachβapple_fw_sysctl_init(dev)(apple_fw.c:144) adds oids into the device-managed ctx (device_get_sysctl_ctx, apple_fw_sysctl.c:28) whosearg1pointers target softc fields:&sc->sc_osdw(:32-34),&sc->sc_ssdt_count(:37-39),sc->sc_ssdts[i].oem_table_id(:47-50),ddev->name/path/sval/bval(:69-121). AllCTLFLAG_RDβ world-readable. -
On removal,
device_detach(subr_bus.c:2130) calls the driver'sapple_fw_detach(apple_fw.c:149-156 β a barelockuninit, no sysctl teardown), then:
subr_bus.c:2149 device_set_driver(dev, NULL);
ββ subr_bus.c:1941-1947: kfree(dev->softc) <-- softc FREED
subr_bus.c:2151 device_sysctl_fini(dev);
ββ subr_bus.c:238: sysctl_ctx_free(&dev->sysctl_ctx) <-- oids removed
-
Readers:
userland_sysctlwrapssysctl_rootinSYSCTL_SLOCK()(kern_sysctl.c:1572-1576); handlers run under that shared lock.sysctl_unregister_oidrequiresSYSCTL_XLOCK(kern_sysctl.c:70-76). Nothing takes the sysctl lock between (2)'s two statements, so a reader can acquire the SLOCK, resolve a still-registered oid, and run its handler against the already-freed softc. -
Consequence: string oids read freed heap until a NUL (leak of reused slab contents to an unprivileged caller); OPAQUE oids copy
val_lenbytes captured at registration (apple_fw_sysctl.c:116-120) out of freed memory; INT/QUAD oids read freed scalars. The freed chunk is ~330KB (64 Γ sizeof(struct apple_fw_dsm_dev) β 64 Γ 5.4KB + 32Γ9B SSDT ids), a very reusable allocation.
Why not reproduced
Requires root to initiate the unload while an unprivileged process lands a sysctl read in a microsecond-scale window; classified Low, and the contract exempts Low findings from mandatory guest verification. The ordering defect itself is unambiguous from source; no dynamic behavior could contradict it.
Not re-reported / related
- DF-4773 (apple_smc): same family, longer window (oids outlive detach until device_delete), rated Medium there.
- The generic half of this bug (device_set_driver frees softc before device_sysctl_fini) affects every driver that registers softc-backed oids in the device ctx; this finding is filed against apple_fw because its ~330KB softc surface makes it the concrete instance audited here.
Fix
fix.diff: driver-owned sysctl ctx freed in apple_fw_detach before the
softc is released by newbus. (The belt-and-braces kernel-wide fix β
reordering device_sysctl_fini before device_set_driver in
device_detach β is noted in the finding markdown but out of this file's
scope.)
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 baseline or patched run performed (finding untested β Low severity race). fix.diff is a source-level driver fix authored from the traced teardown sequence: driver-owned ctx freed in detach guarantees the oids are unregistered before the softc is kfree'd by device_set_driver.
VERDICT.md source trace; fix.diff.
Confirmed kernel references
Detail
Exploit chain
theorized: (1) unprivileged process spins on sysctl dev.apple_fw.0.dsm.* reads; (2) root kldunloads apple_fw; (3) a read landing between subr_bus.c:1944 (kfree softc) and :2151 (sysctl_ctx_free) strlen/copies freed heap to userspace β leak of reused slab contents. Not demonstrated.
Evidence (decisive lines)
VERDICT.md traces the window statement-by-statement: apple_fw.c:144,149-156; apple_fw_sysctl.c:28-126; subr_bus.c:2130-2154, 1941-1947, 234-240; kern_sysctl.c:70-76, 1572-1576. No runtime artifacts (untested).
PoC changes
No PoC written: exploitation needs a root-initiated unload racing an unprivileged reader inside a microsecond window; classified Low and exempt from mandatory verification.
Verified recommended fix
Free a driver-owned sysctl ctx inside apple_fw_detach before newbus frees the softc (fix.diff); kernel-wide hardening: call device_sysctl_fini before device_set_driver in device_detach.
Verdict
apple_fw registers hundreds of world-readable sysctl oids pointing directly into its ~330KB softc (apple_fw_sysctl.c:32-126 via apple_fw.c:144) but apple_fw_detach (apple_fw.c:149-156) tears down nothing. Newbus device_detach frees the softc via device_set_driver (subr_bus.c:1941-1947) BEFORE device_sysctl_fini unregisters the oids (subr_bus.c:2151), and readers run handlers under only a shared sysctl lock (kern_sysctl.c:1572-1576) that the freeing path does not take in between β so a racing unprivileged sysctl read during a root-initiated unload executes string/opaque/int handlers against freed, possibly reused heap. Mechanism is source-certain; the race window is microscopic and root-cooperated, hence Low severity and no guest reproduction attempted (contract exempts Low findings).
No comments yet.