β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4759

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4759 Β· 2 files
FileTypeDescriptionSize
README.md file 2.9 KB ↓ raw
verdict.json file 2.0 KB view raw
README.md file
↓ download 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_modevent MOD_UNLOAD), vinum.c:198-259 (free_vinum), racing vinum.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:

  1. TOCTOU: vinum_inactive(1) returns "inactive", and only afterwards can another process open() a vinum node; vinumopen (e.g. VOLUME/RAWSD paths, vinum.c:386-455) then writes vol->flags |= VF_OPEN / sd->flags |= VF_OPEN into arrays that free_vinum() may concurrently Free() (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 no lock_config.)

  2. State gap: vinum_inactive only counts a volume as open when VOL[i].state > volume_down && (VOL[i].flags & VF_OPEN) (vinum.c:181-182). A volume that was opened while up and has since gone volume_down (drive error, vinum set state down) keeps its open fd and VF_OPEN flag but is skipped by the check β†’ kldunload proceeds to free the arrays under the live fd.

  3. Flag β‰  refcount: VF_OPEN/VF_DAEMONOPEN are 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.

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_testable
baseline no→ patch + rebuild →patched clean

not tested (finding not guest-reproduced; fix is structural and cited in README)

per-fix-DF-4759

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.