vinum volume-minor aliasing: volume #256 wraps into VOL[0] β every access to /dev/vinum/vol/v256 is served by volume v0 (deterministic cross-volume data corruption + wrong-object sizing)
Summary
VINUM_VOL_WIDTH is 8 bits but nothing bounds the volume count: v256 gets minor 0x100 which Volno() decodes as volno 0 - open/read/write/ioctl/DIOCGPART all execute against VOL[0]. REPRODUCED on guest: diskinfo v256 reports v0's 4MB; data written through v256 reads back through v0. Patched vinum.ko refuses the node (ENOENT, v0 intact). Root vinum-config privilege class.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5062 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5062_volalias.sh | β | 2.9 KB | view raw | |
| alias2.sh | β | 596 B | view raw | |
| run.sh | β | 141 B | view raw | |
| run.log | β | 35.9 KB | view raw | |
| run.2.log | β | 1.4 KB | view raw | |
| build.log | β | 171 B | view raw | |
| fix.diff | β | 2.4 KB | view raw | |
| fix_build.log | β | 4.0 KB | view raw | |
| fix_run.log | β | 35.9 KB | view raw | |
| fix_run2.log | β | 968 B | view raw | |
| env.txt | β | 782 B | view raw | |
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw |
DF-5062 β vinum volume minor aliasing (volno >= 256 wraps into VOL[0])
What
made_vol() (sys/dev/raid/vinum/vinumconfig.c:763) creates each
/dev/vinum/vol/<name> node with VINUMDEV(vol->volno, 0, 0, VINUM_VOLUME_TYPE)
and never checks that the volume number fits in the VINUM_VOL_WIDTH = 8
low bits of the minor (sys/dev/raid/vinum/vinumvar.h:83-86,113-116).
Nothing else bounds the volume count: get_empty_volume()
(vinumconfig.c:875-899) grows VOL[] indefinitely. Creating the 257th
volume gives volume #256 the minor 0x100, which Volno()
(sys/dev/raid/vinum/vinumutil.c:241) decodes as volno 0 β every
open/read/write/ioctl/DIOCGPART on /dev/vinum/vol/v256 is served by
VOL[0], a different volume. Deterministic cross-volume data corruption
and wrong-object sizing from ordinary root vinum configuration.
Kernel minor() returns the raw si_uminor (sys/kern/kern_conf.c:70-75),
so the wrap reaches vinum even though makeudev() (kern_conf.c:154-160)
flags minor 0x100 as NOUDEV for the stat path β that is why
ls -l /dev/vinum/vol/v256 displays the bogus rdev 255, 0xffff00ff
while open()/read()/write() still execute against VOL[0].
How to reproduce (root on the DragonFly guest)
# push label.bin (from findings/poc/DF-4592) + df5062_volalias.sh kldload vinum sh /tmp/df5062_volalias.sh # creates vn0 drive, v0 (4 MB sd), v1..v256 sh /tmp/alias2.sh # explicit data-aliasing check (od output)
Expected on a vulnerable kernel:
/dev/vinum/vol/v256exists with the bogus rdev255, 0xffff00ff(v0/v255 show32, 0x00000000/32, 0x000000ff).diskinfo /dev/vinum/vol/v256reports v0's size (0x400000 = 4 MB), though v256 has zero plexes and size 0.- Writing
BBBB-v256-ALIASthrough/dev/vinum/vol/v256and reading back through/dev/vinum/vol/v0returns BBBB β writes through v256 land in v0's storage.
All three reproduced (run.log, run.2.log).
Fix validation
fix.diff (against sys/, applied in the guest's /usr/src, vinum.ko
rebuilt standalone, installed, rebooted):
- kernel logs
vinum: volume v256: volume number 256 does not fit in the 8-bit device minor; no device node created; /dev/vinum/vol/v256no longer exists (write β ENOENT);- positive control intact: v0 still reads/writes its own data (fix_run.log, fix_run2.log).
Notes
- Root-gated (vinum configuration requires the root-only superdevice, vinum.c:457-468) β same threat model as the rest of the vinum family.
- The same wrap turns volume #512's node into minor 0x200 β VOL[0] again, #768 β 0x300, etc.; every multiple of 256 aliases onto VOL[0].
- fix.diff also carries the DF-5063 hardening hunk for vinumutil.c (bounds-checked composite-minor decode); both were validated together.
DF-5062 β VERDICT
Status: REPRODUCED (deterministic, twice, plus fix-validated). Impact: deterministic cross-volume data corruption + wrong-object sizing. Root-gated (vinum config path), hence Medium, not High.
Root cause (line-accurate)
sys/dev/raid/vinum/vinumvar.h:83-86βVINUM_VOL_SHIFT=0,VINUM_VOL_WIDTH=8: a volume device minor holds the volume number in 8 bits.sys/dev/raid/vinum/vinumvar.h:113-116βVINUMDEV(v,p,s,t)=v<<0 | p<<16 | s<<20 | t<<28; nothing masksv.sys/dev/raid/vinum/vinumconfig.c:759-774βmade_vol()doesmake_dev(&vinum_ops, VINUMDEV(vol->volno,0,0,VINUM_VOLUME_TYPE), ...)with no range check onvol->volno;get_empty_volume()(vinumconfig.c:875-899) expandsVOL[]without limit.sys/dev/raid/vinum/vinumutil.c:238-242βVolno(dev)=(minor(dev) & MASK(8)) >> 0β minor 0x100 decodes as volno 0.sys/kern/kern_conf.c:70-75β kernelminor(cdev_t)returns the rawsi_uminor(256), so vinum really is entered with 0x100;kern_conf.c:154-160makeudev()only rejects it for the stat path (ls -lshows255, 0xffff00ffβ the NOUDEV pattern), which is cosmetic.
Reproduction evidence
run.log (stock module, fresh vm.sh reset with-src boot):
* 257 volumes created (vinum l header: 257 volumes:).
* ls -l /dev/vinum/vol/v256 β crw- 255, 0xffff00ff (v0: 32, 0x00000000).
* diskinfo /dev/vinum/vol/v256 β size=0x000000400000 4.00 MB β exactly
v0's size, while v256 has Plexes: 0 Size: 0 B.
* run.2.log (explicit od dump): write AAAA-v0-mark via v0 β read via v0
= AAAA; write BBBB-v256-ALIAS via v256 β read via v0 = BBBB.
Writes through v256 land in v0's storage.
Impact ceiling
Any vinum deployment with β₯257 configured volumes (volume numbers are
handed out sequentially and never reused until removal): all I/O to the
aliased node silently reads/writes a different volume β filesystem
corruption, swap-on-wrong-volume (DIOCGPART serves v0's geometry), and the
bogus st_rdev shown by stat. Attack path is the ordinary root vinum
configuration interface (vinum create -f), the same privilege class as
every other vinum finding in this audit (superdev open is
SYSCAP_RESTRICTEDROOT, vinum.c:457-468).
Fix validation (done in this run)
fix.diff applied to the guest's /usr/src, vinum.ko rebuilt standalone
(make rc=0, fix_build.log), installed to /boot/kernel, rebooted, PoC
re-run (fix_run.log, fix_run2.log):
* kernel logs vinum: volume v256: volume number 256 does not fit in the
8-bit device minor; no device node created;
* /dev/vinum/vol/v256 absent; write attempt β ENOENT (aliasing gone);
* positive control: v0 data I/O intact (PATCHED-OK/AAAA readbacks).
Not pursued
The minor-wrap also corrupts plex/sd sub-minor fields for volno β₯ 65536 (volno bits spill into bits 16+), but 65k+ volumes is not a realistic configuration; the 8-bit wrap is the practical case.
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
fixedvinum.ko rebuilt from patched /usr/src in the guest (make rc=0), installed, rebooted, exact PoC re-run: kernel logs 'vinum: volume v256: volume number 256 does not fit in the 8-bit device minor; no device node created', /dev/vinum/vol/v256 is absent (write -> ENOENT, diskinfo -> No such file), and the positive control /dev/vinum/vol/v0 still reads/writes its own data. Aliasing gone; fix_build.log/fix_run.log/fix_run2.log.
findings/poc/DF-5062/fix_run.log (patched log line + missing node + v0 4.00 MB control); fix_run2.log (AAAA survives, BBBB write via v256 -> ENOENT); fix_build.log (clean rebuild); fix.diff in the same pack
Confirmed kernel references
Detail
Exploit chain
root: vinum create -f (257 volumes) -> /dev/vinum/vol/v256 (minor 0x100) aliases VOL[0] -> all I/O through the v256 node silently reads/writes volume v0's storage: filesystem/swap corruption of the wrong volume (data-integrity attack, not kernel-memory corruption)
Evidence (decisive lines)
run.log: '257 volumes:', ls -l v256 rdev 255,0xffff00ff, diskinfo v256 'size=0x400000 4.00 MB'; run.2.log: od dump showing BBBB-v256-ALIAS written via v256 read back via v0; fix_run.log/fix_run2.log: patched module logs 'volume number 256 does not fit in the 8-bit device minor', v256 ENOENT, v0 positive control intact
PoC changes
wrote df5062_volalias.sh from scratch (no seed): vn0 vnode disk + vinum label (label.bin from the DF-4592 pack) since the guest has no mdconfig(8); first attempt used mdconfig (absent) so the drive failed and v0 stayed down (all EIO) -- switched to vnconfig(8), added 'vinum start v0' and conv=sync dd markers with od readback
Verified recommended fix
made_vol(): refuse to make_dev a volume whose number does not fit in VINUM_VOL_WIDTH bits (log + return); see fix.diff
Verdict
made_vol() (sys/dev/raid/vinum/vinumconfig.c:763) creates /dev/vinum/vol/
No comments yet.