vinumio: heap buffer overflow - unbounded config-line copy in vinum_scandisk reads attacker-controlled disk data into 2KB buffer
Summary
vinum_scandisk at 763 Malloc(MAXCONFIGLINE*2)=2048 bytes config_line. Line 775 read_drive reads MAXCONFIG*2=131072 bytes disk data into config_text NO validation. Copy loop at 799-800: for(eptr=config_line;(*cptr!=newline)&&(*cptr!=NUL);)*eptr++=*cptr++; advances eptr without ever comparing to config_line+2048. Single config line >2047 bytes no newline/NUL overflows by up to ~129KB attacker-controlled bytes. Outer loop 796 for(cptr=config_text;*cptr!=NUL;) itself unbounded so disk with no NUL walks into adjacent heap. Trigger: boot with vinum.drives=<crafted device> OR VINUM_CREATE read <devname> -> parse_config -> vinum_scandisk. read_drive_label magic check does NOT sanitize config area. Fix: bound inner+outer loops.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1732 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness that reproduces the bug logic | 4.0 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 98 B | view raw |
| run.sh | run-script | ./harness | 59 B | view raw |
| build.log | build-log | full build output | 13 B | view raw |
| run.log | run-log | full decisive run output | 630 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, Phase 6, fix | 2.2 KB | β raw |
| fix.diff | suggested-fix | git-apply-able one-logical-change fix | 978 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1732 β vinumio.c heap overflow in vinum_scandisk config-line copy
Verdict
REPRODUCED (logic/harness) β bug confirmed by source trace. Trigger
is root-only (vinum is a geom class accessed via vinum(8) /
VINUM_CREATE ioctl), so the realistic threat model is an admin who
mounts / makes mountable a crafted vinum drive image (the
vfs.usermount=1 + attacker-owned-image pattern is a realistic
precondition). Not a default-QEMU unprivileged reach.
Mechanism (path:line)
sys/dev/raid/vinum/vinumio.c:761βconfig_text = Malloc(MAXCONFIG*2);// 131072 bytessys/dev/raid/vinum/vinumio.c:763βconfig_line = Malloc(MAXCONFIGLINE*2);// 2048 bytessys/dev/raid/vinum/vinumio.c:775βread_drive(drive, config_text, MAXCONFIG*2, VINUM_CONFIG_OFFSET);reads attacker-controlled disk data with no per-line validation.sys/dev/raid/vinum/vinumio.c:796βfor (cptr = config_text; *cptr != '\0';)β outer loop unbounded.sys/dev/raid/vinum/vinumio.c:799-800βfor (eptr = config_line; (*cptr != '\n') && (*cptr != '\0');) *eptr++ = *cptr++;β inner loop has no upper bound oneptr.
A single config line > 2047 bytes (no \n, no \0 within 2 KB)
overflows config_line by up to ~129 KB of attacker-controlled bytes.
A config area with no NUL at all walks into adjacent heap.
Phase 6 escalation
Root-only trigger. Full heap corruption with attacker-shaped content. uid0 escalation is moot (the trigger is already root). The realistic concern is root β kernel-priv via crafted vinum metadata; once you have the write primitive, the slab-grooming chain is straightforward on this guest (no SMAP/SMEP/KASLR). Not developed because the trigger is root-only and the harness is the defensible reproduction.
PoC
harness.c simulates the inner copy loop with a config_text of all
'A' (no newline, no NUL). The vulnerable loop would copy 131072 bytes
into the 2048-byte config_line β a 129024-byte overflow.
Fix
fix.diff bounds both loops: the outer cptr loop is bounded by
config_text + MAXCONFIG*2, the inner eptr loop is bounded by
config_line + MAXCONFIGLINE*2 - 1 (reserving a byte for the NUL
terminator). Validated by a clean vinum.ko rebuild with the patch
applied.
Fix verification
fixedVALIDATED at module-build level: applied fix.diff to vinum source, 'make' rc=0, vinum.ko links cleanly with the bounded loops.
baseline: harness shows inner loop would copy 131072 bytes into 2048-byte config_line -> 129024-byte overflow patched: vinum.ko builds clean; vinumio.c:796 outer loop now bounded by config_text+MAXCONFIG*2, inner loop bounded by config_line+MAXCONFIGLINE*2-1.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- v
- i
- n
- u
- m
- /
- v
- i
- n
- u
- m
- i
- o
- .
- c
- :
- 7
- 6
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- v
- i
- n
- u
- m
- /
- v
- i
- n
- u
- m
- i
- o
- .
- c
- :
- 7
- 6
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- v
- i
- n
- u
- m
- /
- v
- i
- n
- u
- m
- i
- o
- .
- c
- :
- 7
- 7
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- v
- i
- n
- u
- m
- /
- v
- i
- n
- u
- m
- i
- o
- .
- c
- :
- 7
- 9
- 6
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- v
- i
- n
- u
- m
- /
- v
- i
- n
- u
- m
- i
- o
- .
- c
- :
- 7
- 9
- 9
Detail
Exploit chain
Root-only trigger (vinum is a geom class accessed via vinum(8)/VINUM_CREATE ioctl). The realistic threat is an admin who mounts/makes mountable a crafted vinum drive image (vfs.usermount=1 + attacker-owned-image is a valid precondition). Full heap corruption with attacker-shaped content; uid0 escalation moot (trigger is already root). Harness in harness.c.
Evidence (decisive lines)
Vulnerable vinumio.c:799-800 copies 131072 bytes into a 2048-byte buffer Overflow amount: 129024 bytes past buffer end (attacker-controlled) VERDICT: BUG CONFIRMED. Inner copy loop has no upper bound on eptr; a single non-newline-terminated line in the vinum config area overflows the 2 KB config_line by ~129 KB. Root-only trigger (vinum geom), but full heap corruption with attacker-shaped content.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty. Initial harness segfaulted because the demo overflow actually wrote past the malloc'd buffer; fixed by counting bytes instead of writing them in the vulnerable-loop emulator.
Verified recommended fix
fix.diff bounds both loops: outer cptr bounded by config_text + MAXCONFIG2, inner eptr bounded by config_line + MAXCONFIGLINE2 - 1 (reserving a byte for the NUL terminator). Minimal targeted change.
Verdict
REPRODUCED (logic/harness). vinumio.c:761 allocates config_text=131072 bytes, :763 allocates config_line=2048 bytes. :775 read_drive reads MAXCONFIG2 attacker-controlled disk bytes into config_text. :796-800 inner copy loop 'for (eptr=config_line; (cptr!=newline)&&(cptr!=NUL);) eptr++=*cptr++;' has NO bound on eptr. A single config line >2047 bytes overflows config_line by up to ~129 KB of attacker-controlled bytes. The outer cptr loop is also unbounded. Harness reproduces the overflow arithmetic.
No comments yet.