ipsd open/detach TOCTOU: detach can free the ipsdisk softc under an open descriptor β ipsd_close/strategy then write freed memory (DF-5013 pattern in ips)
Summary
Unlocked IPS_DEV_OPEN set/read between open and the detach EBUSY gate: a lost race frees the softc under the fd (devfs retains the cdev) - close executes a UAF write of the state bits, strategy writes freed devstat memory. Only root can start the race (kldunload/PCI detach) - Low; hardware-gated. DF-5013-style disabled-flag+drain fix compile-validated. NOTE routed: ips.c:796-801 copperhead sem-bit timeout silently drops a command (slot leak + never-completed bio) - file-scope prevented filing here.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5025 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| env.txt | β | 400 B | view raw | |
| fix.diff | β | 2.5 KB | view raw | |
| fix_validation.log | β | 1.8 KB | view raw | |
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| verdict.json | β | 3.6 KB | view raw |
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)
ipsd_open(:84-95):dsc->state |= IPS_DEV_OPENβ no lock.ipsd_detach(:204-216):if (dsc->state & IPS_DEV_OPEN) return (EBUSY)β no lock. Window: open fetched a non-NULLsi_drv1but has not set the flag yet β detach proceeds βdevstat_remove_entry+disk_destroy(:213-214) βdestroy_devβ cdev unlinked; newbus then frees theipsdisk_softc_twhen the child device is deleted (ips.c:304-319ips_diskdev_freeviaips_adapter_free).- 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), soipsd_closeIS still called on the destroyed device withsi_drv1pointing at the freed softc βdsc->state &= ~IPS_DEV_OPEN(:103) = UAF write; likewise any strategy in flight (devstat_start_transaction(&dsc->stats), :137). - Independent defect on the same line:
ipsd_close(:101-103) dereferencessi_drv1with no NULL check, whileipsd_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.
DF-5025 Verification Verdict
Status: not_reproduced (hardware-gated entry + privileged trigger; source-chain certain, runtime exercise impossible on this guest)
What is certain (code)
ipsd_opensetsIPS_DEV_OPENwith no lock (ips_disk.c:92);ipsd_detachtests it with no lock (ips_disk.c:211). Nothing on the openβdetach path serializes them β a textbook TOCTOU.- On losing the race, detach completes
disk_destroy(:214) while an fd is open; devfs retains the cdev until last close, soipsd_closelater runsdsc->state &= ~IPS_DEV_OPEN(:103) on the newbus-freedipsdisk_softc_tβ use-after-free write. Reference chain: subr_disk.c:876β527-548 (disk_destroy), kern_conf.c:345-354 (destroy_dev), devfs_core.c:1440-1459 (release_dev), ips.c:304-319 (diskdev free). ipsd_closeitself lacks thesi_drv1NULL check its open twin has (compare :90-91 vs :101-103).- In-flight-bio variant:
ips_adapter_free'sused_commandsgate (ips.c:533-537) does not cover queued-but-unstarted bios; those are abandoned (DF-5026) referencing a softc about to be freed.
Why it cannot be exercised here
- Guest has no IBM ServeRAID adapter (env.txt probe: no vendor 0x1014/0x9005
PCI devices, no /dev/ips*, no ipsd disks) β the ips driver code is
compiled into the guest kernel (
kldstat -vshows idsips/ipsd,pci/ips) but attach never runs without hardware. - The only detach initiators are privileged (kldunload / PCI bus detach), so even with hardware the trigger requires root β this caps severity at Low (contrast DF-5013, whose mfi AEN offline path was unprivileged).
Fix validation
fix.diff mirrors the verified DF-5013 mfi fix (DISABLED flag + in-flight
drain + lock-serialized state + close NULL check). It compile-validates as
part of the merged patched ips.ko build (-Werror clean, see DF-5026 pack /
fix_validation.log). Behavioral validation hardware-gated β fix_status
not_testable.
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_testablefix.diff compile-validated as part of the merged patched ips.ko module build on the guest (-Werror clean, fix_validation.log in DF-5026's pack; sources restored). Behavioral validation is hardware-gated (no IPS adapter on the guest), so the fix cannot be exercised end-to-end.
fix.diff; patched ips.ko build log (fix_validation.log, referenced from DF-5026 pack); VERDICT.md fix section
Confirmed kernel references
Detail
Evidence (decisive lines)
env.txt (hardware-absence probe: no IPS PCI devices, no /dev/ips*, no ipsd disks, ips code compiled into kernel); VERDICT.md source trace with the lock-free open/detach/close/strategy citations and the disk_destroy/release_dev lifetime chain
PoC changes
no runtime PoC possible; hardware-absence probe captured as env.txt evidence
Verified recommended fix
Serialize the IPS_DEV_OPEN state changes with sc->queue_lock, add a disabled flag + in-flight drain (active counter, lksleep) in ipsd_detach before disk_destroy, fail-fast in ipsd_strategy once disabled, and add the missing si_drv1 NULL check in ipsd_close (fix.diff mirrors the verified DF-5013 mfi fix).
Verdict
Source-verified TOCTOU/UAF, runtime reproduction impossible on this guest: ipsd_open sets IPS_DEV_OPEN with no lock (ips_disk.c:92) while ipsd_detach tests it with no lock (ips_disk.c:211); losing the race lets detach complete disk_destroy (:214) under a still-open fd (devfs retains the cdev until last close, kern_conf.c:345-354/devfs_core.c:1440-1459), so the later ipsd_close executes dsc->state &= ~IPS_DEV_OPEN (:103) as a use-after-free WRITE on the newbus-freed ipsdisk_softc, and any in-flight strategy touches freed devstat memory (:137). ipsd_close additionally lacks the si_drv1 NULL check its open twin has (:101-103 vs :90-91). Guest hardware probe (env.txt): no IBM/Adaptec ServeRAID PCI devices (vendor 0x1014/0x9005), no /dev/ips*, no ipsd disks -- the ips driver is compiled in (kldstat -v ids ips/ipsd, pci/ips) but attach never runs. Even with hardware the only detach initiators are privileged (kldunload/devctl), capping severity at Low (the DF-5013 mfi pattern-instance here; DF-5013 was High only because its AEN path needed no privilege). fix.diff (DF-5013-style: lock-serialized state, disabled flag, in-flight drain before destroy, close NULL check) compile-validated in the patched ips.ko build (-Werror clean).
No comments yet.