# DF-5025 — ipsd open/detach TOCTOU → ipsdisk_softc UAF (ips_disk.c)

**File:** `sys/dev/raid/ips/ips_disk.c` (pass 2)
**Lines:** 92 (unlocked open flag), 103 (unlocked close, missing NULL check),
134-137 (strategy touches dsc unlocked), 211-212 (unlocked EBUSY gate)
**Class:** lifetime/TOCTOU → use-after-free write (CWE-367 + CWE-824-family);
the ips instance of the DF-5013 (mfi_disk) pattern.

## Why not reproduced at runtime

Requires (a) an IBM ServeRAID IPS adapter — absent from the guest (env.txt:
no 0x1014/0x9005 PCI devices, no /dev/ips*, no ipsd disks) — and (b) a
root-initiated detach (kldunload/devctl) racing an unprivileged `open()`.
Root initiation caps the security impact (a privileged trigger), which is
why this is filed Low rather than High like DF-5013 (whose mfi AEN path
needed no privilege).

## Source-level chain (certain, no locks anywhere on the path)

1. `ipsd_open` (:84-95): `dsc->state |= IPS_DEV_OPEN` — no lock.
2. `ipsd_detach` (:204-216): `if (dsc->state & IPS_DEV_OPEN) return (EBUSY)`
   — no lock. Window: open fetched a non-NULL `si_drv1` but has not set the
   flag yet → detach proceeds → `devstat_remove_entry` + `disk_destroy`
   (:213-214) → `destroy_dev` → cdev unlinked; newbus then frees the
   `ipsdisk_softc_t` when the child device is deleted
   (ips.c:304-319 `ips_diskdev_free` via `ips_adapter_free`).
3. The racer's fd is still open: devfs holds a cdev reference until last
   close (`release_dev`, kern_conf.c:1455 / devfs_core.c:1440-1459), so
   `ipsd_close` IS still called on the destroyed device with
   `si_drv1` pointing at the freed softc → `dsc->state &= ~IPS_DEV_OPEN`
   (:103) = **UAF write**; likewise any strategy in flight
   (`devstat_start_transaction(&dsc->stats)`, :137).
4. Independent defect on the same line: `ipsd_close` (:101-103) dereferences
   `si_drv1` with **no NULL check**, while `ipsd_open` (:90-91) has one —
   the close path panics outright on NULL.

The strategy-vs-detach variant (bio in flight while parent detaches) is
partially masked by `ips_adapter_free`'s `used_commands != 0` EBUSY
(ips.c:533-537), but bios that are queued-but-not-started are invisible to
that gate: they are abandoned forever (see DF-5026) and their `bio_driver_info`
softc reference goes stale.

## Fix

`fix.diff` (modeled on the verified DF-5013 mfi fix): serialize the
IPS_DEV_OPEN state changes with `sc->queue_lock`, mark the disk disabled in
detach and drain in-flight bios (`active` counter, `lksleep` until quiescent)
before `disk_destroy`, fail-fast in `ipsd_strategy` once disabled, and add
the missing NULL check in `ipsd_close`. Compile-validated as part of the
patched ips.ko build (`-Werror` clean) — see DF-5026's pack for the build
log; behavioral validation is hardware-gated (not_testable).

## Artifacts

`env.txt` (hardware-absence probe), `fix.diff`, this README, VERDICT.md,
manifest.json, verdict.json.
