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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1732 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download 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)

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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.
↓ fix.diffvinum.ko module rebuild (loadable .ko) - applied fix.diff to /usr/src, 'make' rc=0, vinum.ko built clean

Confirmed kernel references

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.