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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5103 Β· 5 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

DF-5103 VERDICT

Status: untested (Low severity, root-coordinated race β€” verification skipped per the audit contract). Mechanism source-certain.

The window, traced

  1. 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) whose arg1 pointers 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). All CTLFLAG_RD β€” world-readable.

  2. On removal, device_detach (subr_bus.c:2130) calls the driver's apple_fw_detach (apple_fw.c:149-156 β€” a bare lockuninit, 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

  1. Readers: userland_sysctl wraps sysctl_root in SYSCTL_SLOCK() (kern_sysctl.c:1572-1576); handlers run under that shared lock. sysctl_unregister_oid requires SYSCTL_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.

  2. Consequence: string oids read freed heap until a NUL (leak of reused slab contents to an unprivileged caller); OPAQUE oids copy val_len bytes 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.

  • 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_testable
baseline no→ patch + rebuild →patched clean

No 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.
↓ fix.diffper-fix-DF-5103

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).