State-machine ioctls run without lock_config and race config frees: setstate()/set_sd_state()/update_plex_state() walk SD[]/plex->sdnos that unlocked VINUM_DETACH/ATTACH and locked VINUM_CREATE free/reallocate (UAF-class; guest killed 2/3 runs)
Summary
Only VINUM_CREATE takes lock_config; SETSTATE/FORCE/RESETSTATS/ATTACH/DETACH mutate and walk the config arrays unlocked. setstate's loop re-reads plex->subdisks and sdnos every iteration while concurrent DETACH frees/NULLs the same array and ATTACH/give_sd_to_plex EXPANDs it; set_sd_state holds &SD[sdno] across sleeping save_config while expand_table frees the old array. REPRODUCED: 4 SETSTATE threads vs 2 attach/detach killed the guest 2/3 runs; the survivor showed torn statemaps. Root-gated /dev/vinum/control - but ordinary vinum(8) automation racing itself triggers it. Fix (lock_config on the five ioctls) validated: 8.9M vs 1.6M ops survive.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4478 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| vinum_unlocked_race.c | poc | original 2-thread version | 4.4 KB | view raw |
| vinum_unlocked_race_v1b.c | poc | killer variant (4+2 threads) | 2.1 KB | view raw |
| serial_v1b_run3_death.log | panic | 199,723-line console capture ending mid-kprintf | 3.5 MB | β download |
| run.log | run-log | 3 baseline runs | 910 B | view raw |
| fix.diff | suggested-fix | lock_config around state ioctls | 1.6 KB | view raw |
| fix_run.log | run-log | patched: SURVIVED 8.9M vs 1.59M | 1.2 KB | view raw |
| env.txt | env | guest/kernel info | 314 B | view raw |
Fix verification
fixedfix.diff wraps the five mutation ioctls in lock_config/unlock_config (throw-safe: no throw_rude_remark on these paths). Patched kernel survived 180s with 8.9M SETSTATE vs 1.59M attach/detach (20x the baseline load that killed it in half the runs).
fix_run.log ('SURVIVED ss=8902936 at=1588979 dt=1589187'); fix_build.log
Confirmed kernel references
Detail
Exploit chain
root (0600 /dev/vinum/control) creates plex+sd via VINUM_CREATE, then 4 threads hammer VINUM_SETSTATE(plex0, up/down, force) while 2 threads hammer VINUM_ATTACH/VINUM_DETACH(sd0) -> detach's Free(plex->sdnos)+NULL races the walker's plex->sdnos[sdno] load (NULL/low-address deref or freed-array read -> wild SD[garbage]) -> kernel death. Root-gated ioctl path; the same unlocked walkers also run from vinum(8) administrative automation.
Evidence (decisive lines)
serial_v1b_run3_death.log (199,723-line console capture ending mid-kprintf at guest death); run.log (3 baseline runs: kill/survive-with-tearing/kill); fix_run.log (patched: 8,902,936 SETSTATE vs 1,589,179 attach/detach, SURVIVED)
PoC changes
initial 2-thread version survived 45s (412k vs 184k ops); scaled to 4 SETSTATE walkers alternating object_up/object_down (adds invalidate_subdisks' second sdnos load window) + 2 attach/detach threads; VINUM_ATTACH/DETACH ioctl numbers must use _IOC(IOC_INOUT,'F',87/88,260) - struct _ioctl_reply is 260 bytes, not 256
Verified recommended fix
Take lock_config()/unlock_config() around SETSTATE, SETSTATE_FORCE, RESETSTATS, ATTACH, DETACH (and RESETCONFIG) in vinumioctl dispatch (see fix.diff)
Verdict
The vinum state-mutation ioctls (SETSTATE/SETSTATE_FORCE/RESETSTATS/ATTACH/DETACH) run without lock_config while detachobject/attachobject free and reallocate plex->sdnos and VINUM_CREATE's expand_table frees the SD/PLEX/VOL/DRIVE arrays; setstate()/set_sd_state()/update_plex_state() walk those raw pointers unlocked (and across sleeping save_config). Racing SETSTATE(plex0) vs ATTACH/DETACH(sd0) killed the guest in 2 of 3 runs (~90-150s); the surviving run showed the state machine tearing live on the console ('vinum: p0 is faulty' churn). Guest death signature: console output stops mid-kprintf, ssh dead, status down.
No comments yet.