# VERDICT — DF-4647

**Status: reproduced — the use-after-free demonstrably executes (an
in-flight sysctl handler runs to completion on the freed softc, freed
sysctl oids and freed module text), but it is SILENT on this guest: the
freed chunks are type-stable and retain stale-but-valid contents, so no
crash or leak materializes.  Impact observed: none; realistic ceiling
under heap pressure: kernel panic (wild pointers into
AcpiEvaluateObject).  Severity Low (matches the DF-2381 notify-path twin;
detach side is an admin action).**

## Root cause

1. `acpi_fujitsu_sysctl()` (acpi_fujitsu.c:433-460) runs holding only
   `oidp->oid_arg1` (raw softc pointer) and the driver's global ACPI
   serial.  `CTLFLAG_ANYBODY` (installed at acpi_fujitsu.c:418) lets any
   user keep a handler in flight for seconds (a slow ACPI control method
   — firmware-controlled — is enough; emulated in this PoC with
   `Sleep()`s in a crafted SSDT).
2. `acpi_fujitsu_detach()` (acpi_fujitsu.c:340-352) performs
   `AcpiRemoveNotifyHandler` + `sysctl_ctx_free` with **no**
   serialization against those handlers; the devclass teardown then
   `kfree()`s the softc (subr_bus.c:1943-1944) and `kldunload` frees the
   module text.
3. The `oid_running` drain that kern_sysctl.c:388-397 comments describe
   as "necessary for module unload correctness" is **dead code** —
   `oid_running` (sysctl.h:168) is never incremented anywhere in the
   tree.  Consequently `sysctl_ctx_free` destroys and frees each oid
   (`lockuninit(&oid->oid_lock)`; `kfree(oidp, M_SYSCTLOID)`) while a
   handler for that oid is executing; the handler's epilogue executes
   `lockmgr(&oid->oid_lock, LK_RELEASE)` (kern_sysctl.c:1482) on freed
   memory.
4. Unwind order guarantees the in-flight handler lands after the free:
   A (holder) releases the ACPICA interpreter inside `acpi_SetInteger`
   BEFORE reaching `ACPI_SERIAL_END` (acpi_fujitsu.c:458).  The unload
   thread (blocked behind A) therefore completes the whole teardown
   (oids, softc, module text) before the serial handoff wakes the queued
   writer B, whose entire remaining handler then executes on freed
   memory.

## Reproduction (run.log — stock module, timestamped)

```
T0=577; A_START=577; B_START=578; T_UNLOAD_START=579
A_DONE=583 rc=0; B_DONE=583 rc=1; T_UNLOAD_DONE=583 rc=0 module_gone=0
B log: sysctl: hw.acpi.fujitsu.lcd_brightness=5: No such file or directory
```

B was queued inside its handler from t=578; the module (with B's handler
code), B's oid, and B's softc were all freed during the unload that
finished at t=583; B's syscall completed at t=583 with ENOENT — its
handler demonstrably executed across the free.  B survived because the
freed 208-byte M_BUS softc chunk, the oid chunk, and the module text are
type-stable on this idle guest.

Crash-forcing attempts (all documented, all survived type-stability):
* `df4647d.sh`: same-oid second writer queued on the oid_lock that
  `sysctl_ctx_free` `lockuninit()`s and frees underneath it (run.D.log).
* `fujibang action=2` floods (8 parallel, 512 allocs each, per-CPU)
  with address-exact detection of the freed softc chunk — never
  returned the chunk (per-CPU slab-magazine locality; the freeing CPU
  was never the flooding CPU in the window).
* `fujibang action=1` (same-thread free→alloc poison) — the teardown
  path hard-wedges when invoked from a sysctl-handler thread (an
  artifact of the synthetic trigger context, NOT of the official
  kldunload path, which completes; documented below).

## Fix validation

The natural fix — `ACPI_SERIAL_BEGIN(fujitsu)` held across
`acpi_fujitsu_detach`'s body (fix.diff, acpi_fujitsu_fixed.c) — makes
detach wait for in-flight handlers.  Module rebuilt and rig re-run:
**the fixed module deterministically WEDGES the guest** (2/2, hard hang,
no console output; fixrun.attempt.log is the partial log of the second
attempt) when kldunload runs with a writer in flight — the block point
inside the linker-unload path evidently violates a lock/order
requirement there.  fix_status=not_testable: the candidate fix is
correct for the handler race by inspection but cannot be validated as a
safe patch without redesigning the teardown (e.g., a proper quiesce/drain
state machine, or fixing the dead `oid_running` drain in kern_sysctl.c,
which would defend every sysctl-using driver at once).

## Relationship to known findings

* DF-1788 (CTLFLAG_ANYBODY int forwarding) — different defect (value
  validation); this finding is about object lifetime.
* DF-2381 (detach UAF via deferred ACPI notify) — same class, different
  entry point: firmware notify callback vs. user sysctl handler.  The
  sysctl entry is unprivileged-reachable and holds the window open
  indefinitely.

## Kernel references

* sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:340-352 (detach — no handler serialization)
* sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:415-420 (CTLFLAG_ANYBODY proc sysctls)
* sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:433-460 (handler: raw softc pointer, serial held across evaluation)
* sys/kern/kern_sysctl.c:388-397 (dead oid_running drain — never incremented)
* sys/kern/kern_sysctl.c:1455-1483 (oid_lock held across handler; LK_RELEASE on possibly-freed oid)
* sys/kern/subr_bus.c:1943-1944 (softc kfree at device_set_driver)

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