Module teardown is unsynchronized: vinum_inactive() TOCTOU vs vinumopen(), the state > volume_down gap skips open-but-down volumes, and VF_OPEN/VF_DAEMONOPEN are refcount-free booleans (UAF-class teardown window)
Summary
MOD_UNLOAD decides it may free everything from two unsynchronized heuristics then free_vinum() frees/bzeroes vinum_conf unlocked while unlocked opens race it; busy-check skips open-but-down volumes; boolean open flags cleared on first close corrupt the answer both directions. Privileged race, same class guest-confirmed by sibling DF-4478. Fix: lock_config across unload decision+teardown and opens; per-object open refcounts.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4759 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.9 KB | β raw | |
| verdict.json | file | 2.0 KB | view raw |
DF-4759 β vinum module teardown has no real synchronization: MOD_UNLOAD TOCTOU vs vinumopen, plus the state > volume_down gap skips open-but-down volumes (UAF-class teardown race; privileged parties)
- Files:
sys/dev/raid/vinum/vinum.c:171-189(vinum_inactive),vinum.c:295-358(vinum_modeventMOD_UNLOAD),vinum.c:198-259(free_vinum), racingvinum.c:372-476(vinumopen) and the strategy path. - Severity: Low (all racing parties are privileged: kldunload needs root; volume/plex/sd device nodes are 0640 root:operator β operator β raw-disk access β root).
- Status: untested (analysis only β nondeterministic privileged race; the same unlocked-teardown class was guest-confirmed by the sibling finding DF-4478 on vinumstate/vinumioctl).
What
MOD_UNLOAD decides it is safe to free everything with two
non-atomic heuristics, then frees DRIVE/SD/PLEX/VOL and bzeros
vinum_conf with no lock held for the duration:
-
TOCTOU:
vinum_inactive(1)returns "inactive", and only afterwards can another processopen()a vinum node;vinumopen(e.g. VOLUME/RAWSD paths, vinum.c:386-455) then writesvol->flags |= VF_OPEN/sd->flags |= VF_OPENinto arrays thatfree_vinum()may concurrentlyFree()(vinum.c:213-256) β a write-after-free window between the check and the frees, and a use-after-free for any strategy/close issued on the fd afterwards. (The open side also takes nolock_config.) -
State gap:
vinum_inactiveonly counts a volume as open whenVOL[i].state > volume_down && (VOL[i].flags & VF_OPEN)(vinum.c:181-182). A volume that was opened whileupand has since gonevolume_down(drive error,vinum set state down) keeps its open fd andVF_OPENflag but is skipped by the check βkldunloadproceeds to free the arrays under the live fd. -
Flag β refcount:
VF_OPEN/VF_DAEMONOPENare booleans set on every open and cleared on the first close (vinum.c:398/453 vs 502/521/534/543-545) β two open superdev/controld fds + one close leaves the modules' "is anything open?" answer wrong in both directions (see also DF-4760's use of the same flag).
Why Low / not guest-verified
Both sides of every interleaving are privileged (root for kldunload and the superdev; operator for volume/rawplex/rawsd nodes β operator has raw disk R/W on this system class). The window is small and the family-class outcome (tearing/UAF) is already guest-demonstrated by DF-4478. Filing as Low with the concrete gaps cited; a deterministic PoC would need a purpose-built driver harness.
Recommended fix
Hold lock_config() across vinum_inactive()'s decision and the whole
of free_vinum()/dev teardown in MOD_UNLOAD; make vinumopen take
lock_config() around its state test + VF_OPEN set; change the
open-check to (VOL[i].flags & VF_OPEN) regardless of state (or keep
per-object open refcounts instead of boolean flags).
Fix verification
not_testablenot tested (finding not guest-reproduced; fix is structural and cited in README)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4759/README.md (code-cited gaps; no guest artifacts)
PoC changes
none (no PoC attempted; race requires privileged parties)
Verified recommended fix
Serialize MOD_UNLOAD teardown with lock_config(), fix the state>volume_down open-check gap, and replace boolean VF_OPEN/VF_DAEMONOPEN open-flags with per-object open refcounts
Verdict
Analysis-only. vinum's MOD_UNLOAD decides it may free the entire configuration via two unsynchronized heuristics (vinum_inactive TOCTOU vs vinumopen; the state > volume_down gap that skips open-but-down volumes; boolean VF_OPEN/VF_DAEMONOPEN flags instead of refcounts), then frees DRIVE/SD/PLEX/VOL and bzeros vinum_conf with no lock held for the duration -- a UAF-class teardown window. Not guest-verified: both racing parties are privileged (kldunload = root; volume nodes 0640 root:operator) and the interleaving is nondeterministic; the same unlocked-teardown class was guest-confirmed by sibling DF-4478.
No comments yet.