mlxd lifetime TOCTOU: MLX_DETACH_DRIVE checks MLXD_OPEN then sleeps in mlx_flush before device_delete_child; mlxd_detach has no recheck and no in-flight drain β softc freed under racing open fds / in-flight bios (DF-5025/DF-5046 family, last LD sibling)
Summary
Check-then-sleep-then-act: the flush polls up to 15M MMIO iterations with MLX_STATE_SHUTDOWN not raised - a concurrent open succeeds mid-window and the subsequent delete frees the softc under the fd and in-flight bios; mlx_shutdown deletes ALL children with no open-check on reboot. Root-only trigger (no unprivileged event-driven delete) - Low like DF-5025/5046. Raise-SHUTDOWN-for-the-window + detach-recheck fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5057 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 5.4 KB | β raw | |
| env.txt | β | 415 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 5.3 KB | view raw |
DF-5057 β mlxd lifetime TOCTOU: MLX_DETACH_DRIVE deletes the mlxd child
across a sleeping flush with no open-gate raised, and mlxd_detach neither rechecks MLXD_OPEN nor drains in-flight bios β softc UAF
file: sys/dev/raid/mlx/mlx_disk.c (+ trigger window in sys/dev/raid/mlx/mlx.c)
bucket: memcorrupt (DF-5013 / DF-5025 / DF-5046 family β the last LD sibling)
severity: Low (trigger requires root: /dev/mlxN control node is 0600
root:operator, mlx.c:466-468; no unprivileged runtime delete path exists in
mlx, unlike mfi's AEN path)
Reproduce
Not runnable on this guest: the driver is compiled into the running kernel
(device mlx, X86_64_GENERIC:134; kldload mlx β "already loaded or in
kernel") but the guest has no Mylex PCI function (env.txt: vendor 0x1069
absent, 0x1011:1065 absent) so no controller ever attaches and /dev/mlxd*
does not exist. This is the same hardware-gated situation as the family
twins DF-5046/DF-5048 (packs contain the live-guest probe output).
Expected behavior on real hardware
mlxcontrol detach <unit>(or any root caller ofioctl(mlxctl, MLX_DETACH_DRIVE, unit)).- Concurrently, open
/dev/mlxdNin a loop (any user with the device permission, e.g. operator group). - The open that lands inside the flush window succeeds β
device_delete_childfrees the mlxd_softc while the fd (and any bio it then submits) references it βmlxd_close/mlxd_strategy/mlxd_ioctlwrite into freed kernel memory; a late controller completion runsmlxd_intrβdevstat_end_transaction_buf(&sc->mlxd_stats)into freed memory.
Fix
fix.diff (git-apply-able against this tree):
* MLX_DETACH_DRIVE raises MLX_STATE_SHUTDOWN for the whole flush+delete
window so the existing mlxd_open gate (mlx_disk.c:98) rejects racing
opens with ENXIO (the flag is cleared again at detach_out).
* mlxd_detach rechecks MLXD_OPEN and returns EBUSY (belt-and-suspenders
for the check-then-act gap at mlx.c:782β794).
Full remediation additionally needs an in-flight drain (wait for
mlx_waitbufs == 0 / outstanding mlxd commands before disk_destroy).
DF-5057 VERDICT β mlxd detach/delete lifetime TOCTOU + no in-flight drain
status: untested (hardware-gated) | impact: none demonstrated (memcorrupt class) | confidence: certain (code trace)
Reproduced?
No. Requires a physical Mylex DAC960 family adapter (PCI vendor 0x1069, or
0x1011:1065 for the type-5 interface, mlx_pci.c:80-83). Guest evidence
(env.txt, live run): no such PCI function exists, /dev/mlx*//dev/mlxd*
absent, yet the driver is compiled into the running X86_64_GENERIC kernel
(device mlx at X86_64_GENERIC:134; kldload mlx β already loaded). Same
hardware-gated situation the family accepted for DF-5046 (ida) and DF-5048.
Why the finding is real (trace)
The delete paths and their gates:
- MLX_DETACH_DRIVE (mlx.c:774-804): checks
mlxd->mlxd_flags & MLXD_OPENat mlx.c:782, then callsmlx_flush(sc)at mlx.c:788 beforedevice_delete_childat mlx.c:794.mlx_flush(mlx.c:1516-1549) runsmlx_poll_command(mlx.c:1677-1703) which busy-spins polling the controller for up to 15,000,000 iterations (mlx.c:1690-1694) β a window of milliseconds to seconds, during which the controller is NOT marked shut down:MLX_STATE_SHUTDOWNis only set bymlx_shutdown(mlx.c:599), never by MLX_DETACH_DRIVE. - mlxd_open (mlx_disk.c:86-115) has exactly one gate β
sc->mlxd_controller->mlx_state & MLX_STATE_SHUTDOWNat mlx_disk.c:98 β which is clear during the entire MLX_DETACH_DRIVE window, so a concurrent open succeeds and setsMLXD_OPEN(mlx_disk.c:113) after the check at mlx.c:782 has already passed. No lock is held by either side (crit_enter()in mlx.c:780? β none; MLX_DETACH_DRIVE runs with no crit/lock at all). - mlxd_detach (mlx_disk.c:290-301) then runs
devstat_remove_entry+disk_destroywith no MLXD_OPEN recheck and no drain of bios already queued on the controller (sc->mlx_bioq, set by mlxd_strategy β mlx_submit_bio at mlx_disk.c:180 / mlx.c:688) or of commands already in the controller'smlx_workqueue. disk_destroy(subr_disk.c:545-549 β DISK_DISK_DESTROY at subr_disk.c:873-897) only interlocks against disk-enumeration refs (dp->d_refs); the cdev teardown (destroy_dev) is asynchronous and the open descriptor keeps the cdev alive withsi_ops = mlxd_opsandsi_drv1 = sc(set at mlx_disk.c:259-260) dangling into freed memory once newbus frees the mlxd_softc after detach β the exact mechanism established for this family in DF-5046 (same disk_create/ devfs teardown: subr_disk.c:875-881, devfs_core.c:759-763, devfs_core.c:1440-1459, kern_device.c:355-389).- Post-free touch points:
mlxd_closewritessc->mlxd_flags &= ~MLXD_OPEN(mlx_disk.c:127);mlxd_strategyreadssc->mlxd_drive/mlxd_controllerand callsdevstat_start_transaction(&sc->mlxd_stats)+ submits with freed pointers (mlx_disk.c:161,173,179-180);mlxd_ioctldereferencessc->mlxd_controller(mlx_disk.c:143); and a late controller completion for an in-flight bio runsmlx_completeio(mlx.c:1811-1841) βmlxd_intrβdevstat_end_transaction_buf(&sc->mlxd_stats, bp)(mlx_disk.c:204) β a write into freed kernel memory. - Second leg:
mlx_shutdown(mlx.c:611-617) deletes all children unconditionally β the MLXD_OPEN pre-check exists only inmlx_detach(mlx.c:558-566), not inmlx_shutdownitself.mlx_pci.c:54wiresmlx_shutdownasdevice_shutdown, so the system reboot/shutdown path deletes every mlxd child under open descriptors and in-flight bios with no gate at all.
Not a duplicate
- DF-2074 (this file) = stale
bio_driver_infoin strategy β different root cause (no device lookup), no detach involvement. - DF-1303/DF-1304/DF-3949βDF-3952 (mlx.c) = firmware-bound trust, DCDB fixup, NEXT_CHILD NULL panic, wait-command loop, wedge-path slot leak, dmamap panic β none covers the child lifetime/TOCTOU.
- DF-5013/DF-5025/DF-5046/DF-5049 = same family defect in mfi/ips/ida/mfi- syspd; this is the mlx instance (the last LD sibling), with the nuance that mlx has an open-check but it is check-then-sleep-then-act.
Severity rationale
Low, matching DF-5025 (ips): initiation requires root (MLX_DETACH_DRIVE on the 0600 root:operator control node, or reboot). There is no mfi-style unprivileged event-driven delete in mlx (the periodic enquiry only prints state changes, mlx.c:1126-1154). The reboot leg (leg 2) fires on any machine with a mounted mlxd root/swap, but requires physical DAC960 hardware.
Fix assessment
fix.diff: (1) MLX_DETACH_DRIVE raises MLX_STATE_SHUTDOWN for the whole flush+delete window and clears it at detach_out β closes the open-race window using mlxd_open's existing gate; (2) mlxd_detach rechecks MLXD_OPEN and returns EBUSY. The remaining in-flight-bio leg needs a drain (wait on mlx_waitbufs / outstanding commands) before disk_destroy β noted in the diff header as the complete fix. Cannot be build/boot-validated (no hardware); diff is git-apply-clean against sys/.
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 closes the open-race leg (SHUTDOWN flag raised across the sleeping flush so mlxd_open fails with ENXIO inside the window; flag cleared at detach_out since the controller keeps running) and adds an EBUSY recheck in mlxd_detach. The in-flight-bio leg additionally needs a drain (wait on mlx_waitbufs / outstanding commands) before disk_destroy β flagged in the diff comment as the complete fix. Cannot be build/boot validated: no Mylex hardware on the guest. Diff is git-apply-clean against sys/.
fix.diff (git apply --check: APPLIES_CLEAN); hunks at mlx.c MLX_DETACH_DRIVE (SHUTDOWN set/clear) and mlx_disk.c mlxd_detach (thread2.h include + EBUSY recheck)
Confirmed kernel references
- sys/dev/raid/mlx/mlx_disk.c:86-115
- sys/dev/raid/mlx/mlx_disk.c:290-301
- sys/dev/raid/mlx/mlx_disk.c:192-206
- sys/dev/raid/mlx/mlx.c:774-804
- sys/dev/raid/mlx/mlx.c:1516-1549
- sys/dev/raid/mlx/mlx.c:1677-1703
- sys/dev/raid/mlx/mlx.c:589-622
- sys/dev/raid/mlx/mlx.c:545-576
- sys/dev/raid/mlx/mlx_pci.c:54
- sys/dev/raid/mlx/mlx.c:466-468
- sys/kern/subr_disk.c:873-897
Detail
Exploit chain
root ioctl(MLX_DETACH_DRIVE) on /dev/mlxN + racing open('/dev/mlxdN') landing inside the mlx_flush window -> device_delete_child -> mlxd_detach frees softc under the open fd -> mlxd_close/mlxd_strategy write freed memory (mlxd_flags clear, devstat_start_transaction, submit) and late completions write via devstat_end_transaction_buf. Not an escalation primitive: the initiating side is root-only; on hardware this is a root-triggerable kernel UAF write with heap-groom potential.
Evidence (decisive lines)
env.txt (live guest: no Mylex PCI function, no /dev/mlx*, driver in kernel); VERDICT.md full path:line trace of both legs; fix.diff (git apply --check: APPLIES_CLEAN)
PoC changes
No runnable PoC exists (hardware-gated). Pack contains hardware-absence evidence and the line-accurate code trace; fix.diff generated via edited copies + diff -u and validated with git apply --check.
Verified recommended fix
Raise MLX_STATE_SHUTDOWN for the whole MLX_DETACH_DRIVE flush+delete window (closes the open race via mlxd_open's existing gate) and recheck MLXD_OPEN in mlxd_detach returning EBUSY; full fix also drains in-flight bios before disk_destroy.
Verdict
Genuine DF-5025/DF-5013/DF-5046-family detach lifetime TOCTOU, mlx instance: MLX_DETACH_DRIVE (mlx.c:774-804) checks mlxd_flags & MLXD_OPEN at mlx.c:782, then SLEEPS in mlx_flush (mlx.c:788 -> mlx_poll_command spinning up to 15M MMIO polls, mlx.c:1690-1694) before device_delete_child (mlx.c:794) β and MLX_STATE_SHUTDOWN (mlxd_open's only gate, mlx_disk.c:98) is never raised on this path, so a concurrent mlxd_open succeeds inside the window and sets MLXD_OPEN (mlx_disk.c:113) after the check passed. mlxd_detach (mlx_disk.c:290-301) has no recheck and no drain: devstat_remove_entry + disk_destroy free path runs while the racing fd (si_drv1 = sc set at mlx_disk.c:259-260) and any bios already queued on the controller are alive; disk_destroy only interlocks disk-enumeration refs (subr_disk.c:875-881), cdev teardown keeps the open cdev with si_drv1 dangling (DF-5046 mechanism), and a late completion runs mlx_completeio (mlx.c:1811-1841) -> mlxd_intr -> devstat_end_transaction_buf(&sc->mlxd_stats) (mlx_disk.c:204) writing freed memory; mlxd_close (mlx_disk.c:127) and mlxd_strategy (mlx_disk.c:161,173,179-180) likewise touch the freed softc. Second leg: mlx_shutdown (mlx.c:611-617) deletes ALL children with no open-check of its own (pre-check only in mlx_detach, mlx.c:558-566) and is wired to device_shutdown (mlx_pci.c:54), so reboot deletes children under open descriptors and in-flight bios. Untested solely because the guest has no Mylex DAC960 PCI function (env.txt: no 0x1069/0x1011:1065 device, no /dev/mlx*) although the driver is compiled into the running X86_64_GENERIC. Severity Low matching DF-5025: trigger requires root (MLX_DETACH_DRIVE on the 0600 root:operator control node mlx.c:466-468, or reboot); mlx has no mfi-style unprivileged event-driven delete (periodic enquiry only prints, mlx.c:1126-1154).
No comments yet.