# 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
`lockuninit`s an unused lock. The teardown order in newbus
`device_detach` (sys/kern/subr_bus.c:2130-2154) is:

```c
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.
