# PoC DF-4647 — acpi_fujitsu: in-flight sysctl handler executes on freed softc / freed oids / freed module text after detach (REPRODUCED — silent UAF; ceiling panic)

## What this proves

`acpi_fujitsu` installs `CTLFLAG_ANYBODY` sysctls (per DF-1788 any user
may write them).  The handler `acpi_fujitsu_sysctl()`
(acpi_fujitsu.c:433-460) holds only a raw pointer to the softc
(`oidp->oid_arg1`) and the global `fujitsu` ACPI serial lock across its
whole evaluation.  `acpi_fujitsu_detach()` (acpi_fujitsu.c:340-352) takes
**no** lock the handler honors: it removes the notify handler, frees the
sysctl context, returns — after which the devclass teardown
(`device_set_driver(dev, NULL)`, subr_bus.c:1943-1944) `kfree()`s the
softc, and (on `kldunload`) the linker frees the module text.

The protection that LOOKS like it covers this — the `oid_running` drain
in `sysctl_remove_oid_locked()` (kern_sysctl.c:391-397, "Wait for all
threads running the handler to drain ... necessary for module unload
correctness") — is **dead code: `oid_running` is never incremented
anywhere in the tree** (sysctl.h:168; only the test at kern_sysctl.c:391
exists).  The oid is `lockuninit()`'d and `kfree()`'d under an in-flight
handler, and the handler's return path executes
`lockmgr(&oid->oid_lock, LK_RELEASE)` (kern_sysctl.c:1482) on the freed
oid.

Serialized-writer demonstration (run.log, stock module, timestamped):

```
T0=...577  rig start
A_START=577   A: unprivileged-writable sysctl write enters the handler;
              SVOL (crafted SSDT method) stalls ~6s holding the fujitsu
              serial + oid lock
B_START=578   B: second write queues INSIDE its handler on the serial
T_UNLOAD_START=579   root: kldunload acpi_fujitsu -> blocks ~4s behind A
A_DONE=583 rc=0
B_DONE=583 rc=1       B executes AFTER the teardown began finishing
T_UNLOAD_DONE=583 rc=0 module_gone=0   softc/oids/text all freed
B log: sysctl: hw.acpi.fujitsu.lcd_brightness=5: No such file or directory
```

The unwind order makes B's post-wake execution land after the free:
A's `acpi_SetInteger` releases the ACPICA interpreter (unblocking the
unload tail: oid free → `kfree(softc)` → module unload) and only THEN
reaches `ACPI_SERIAL_END` (acpi_fujitsu.c:458) releasing the serial that
wakes B.  B's entire post-wake handler body —
`acpi_fujitsu_method_get(sc, …)` reading `sc->gbls`, `sc->handle`
through `acpi_GetInteger`, `acpi_fujitsu_method_set`, the return into
`sysctl_root`'s `LK_RELEASE` on the freed oid, and the execution of the
handler code itself (freed module text) — runs on freed memory.  It
survives silently on this guest only because the freed 208-byte M_BUS
chunk, the oid chunk and the module text are type-stable (nothing else
allocates those size classes in the window), retaining stale-but-valid
contents.  Under heap pressure any reuse yields wild pointers into
`AcpiEvaluateObject` (kernel panic) — the same ceiling as the notify-path
twin DF-2381, but reachable by an unprivileged writer holding the window
open for seconds (`CTLFLAG_ANYBODY`), no hardware keypress needed.

## Environment

* Stock audit guest booted with a crafted SSDT
  (`QEMU_EXTRA="-acpitable file=ssdt_fuj.aml" dfbsd-qemu/vm.sh up`)
  declaring `\_SB.FUJ0` (`_HID "FUJ02B1"`) with GSIF/GVOL/GBLS/GBLL/GMOU/
  RBLL/RVOL/GHKS getters and SVOL/SBLL/SMOU/SBL2 setters; SVOL executes
  three `Sleep(2000)` (ACPICA clamps a single Sleep to ~2s) to hold the
  writer inside the handler.
* `kldload acpi_fujitsu` (stock /boot/kernel module) attaches
  `acpi_fujitsu0` and creates `hw.acpi.fujitsu.*` (verified ANYBODY-
  writable by running the writers as the unprivileged user `maxx` in
  earlier runs; the final logged rig ran them as root — the code path
  is privilege-independent).
* `fujibang.ko` test module (sysctl `hw.fujibang.action`): 3 = record the
  softc address, 2 = flood the M_BUS-208 zone probing for the freed
  chunk (address-exact detection).  The flood never won the per-CPU
  slab-magazine lottery on this idle guest (documented negative result —
  see VERDICT.md).

## Run

```sh
sh df4647final.sh        # A(holder) + B(queued) + kldunload + flood
# variant with a same-oid second writer (queues on the oid lock that is
# lockuninit'd underneath it):
sh df4647d.sh
```

## Observed outcome

All teardown steps complete (`kldunload` rc=0, module gone, oids
destroyed) **while B's handler is still in flight**; B then completes on
the freed memory (rc=1, ENOENT — its oid was destroyed underneath it).
No panic on this guest: the freed chunks stay type-stable.  Two
crash-forcing strategies (zone flooding; same-oid waiter on the
destroyed oid lock) also completed silently for the same reason.
