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

Heap overflow in plex/subdisk auto-naming via strcat past filled 64-byte name field

Summary

config_plex at vinumconfig.c:1455-1464: strcpy(plex->name,VOL[volno].name) fills 64-byte name exactly (ksnprintf max 63+NUL). strcat(plex->name,".pN") writes 3-5 bytes past end into plex->organization. Same at :1269-1278 for sd->name into sd->state. Triggered by 63-char volume name + unnamed plex. Via vinum create (root) or on-disk config (vinum_scandisk autoconfig at boot). Fix: ksnprintf with sizeof(name) bound or length check before strcat.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1397 Β· 11 files
FileTypeDescriptionSize
vinum_overflow.c trigger-source byte-exact harness replicating config_plex strcpy+strcat overflow 3.7 KB view raw
live_repro.sh trigger-source live kernel repro: kldload vinum + vinum create with 63-char volume name 822 B view raw
build.sh build-script cc -O2 -o vinum_overflow vinum_overflow.c 216 B view raw
run.sh run-script run harness + note live repro 260 B view raw
run.log run-log harness output: OVERFLOW CONFIRMED, name=66, organization corrupted 683 B view raw
env.txt environment uname, cc version 227 B view raw
fix.diff suggested-fix replace strcpy+strcat with bounded ksnprintf at plex+sd sites 1.8 KB view raw
VERDICT.md verdict full analysis incl live before/after (66->63 chars) 5.3 KB ↓ raw
README.md readme reproduce guide 2.1 KB ↓ 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
README.md readme reproduce guide
↓ download raw

DF-1397 β€” vinum config_plex auto-naming strcpy/strcat overflow (PoC)

Summary

A 63-char vinum volume name + an unnamed plex makes config_plex (sys/dev/raid/vinum/vinumconfig.c:1459-1464) do strcpy(plex->name, VOL[].name) then strcat(plex->name, ".pN"), overflowing the 64-byte plex->name into the adjacent enum plexorg organization field. Symmetric subdisk site at :1274-1278 (strcpy+strcat(".sN") into sd->state).

Reachability

vinum is a loadable module (vinum.ko, prebuilt at /boot/kernel/), not in GENERIC. The bug is live-reproducible here: kldload vinum + vinum create (root) with a 63-char volume name + unnamed plex. Realistic untrusted vector: on-disk vinum config auto-parsed at boot.

Build / run

Byte-exact harness (userspace, any user)

./build.sh && ./run.sh      # prints the 66-char name + corrupted organization

Live kernel repro (needs root)

kldload vinum
sh live_repro.sh            # creates a 63-char volume + unnamed plex

Expected output (bug present)

  • Harness: OVERFLOW CONFIRMED: strcat(".p%d") wrote past the 64-byte name into the organization enum field. (printed name walks to 66 chars; organization corrupted from plex_concat(1) to 12400.)
  • Live: vinum l shows the plex name printed as 66 chars (AAAA...AAA.p0) β€” proof the NUL now lives inside organization.

Fix validation (before/after, live)

  • Before (unpatched #0): plex name = 66 chars (overflow present).
  • After (rebuilt vinum.ko with fix.diff, -Werror): plex name = 63 chars (ksnprintf bounded; overflow gone). fix_status = fixed.

Files

  • vinum_overflow.c β€” byte-exact harness (struct plex from vinumvar.h:548).
  • live_repro.sh β€” live kernel reproduction.
  • build.sh / run.sh.
  • run.log β€” harness output.
  • env.txt β€” guest environment.
  • fix.diff β€” replace strcpy+ksprintf+strcat with bounded ksnprintf at both sites.
  • VERDICT.md / manifest.json.

Fix

Replace the strcpy+ksprintf+strcat sequence at both sites with a single bounded ksnprintf(.., sizeof(name), "%s.p%d", volname, pindex) / "%s.s%d" (fix.diff).

VERDICT.md verdict full analysis incl live before/after (66->63 chars)
↓ download raw

DF-1397 β€” vinum config_plex auto-naming strcpy/strcat overflow

Verdict: REPRODUCED (live kernel + byte-exact harness). Fix VALIDATED (live before/after).

A 63-character vinum volume name combined with an unnamed plex causes config_plex to overflow the 64-byte plex->name buffer into the adjacent organization enum field. This was reproduced live on the DragonFly guest (kldload vinum + vinum create) and deterministically with a byte-exact userspace harness, and the authored fix was validated with a true before/after kernel-module rebuild + re-test.

Mechanism (trigger β†’ primitive β†’ effect)

struct plex (sys/dev/raid/vinum/vinumvar.h:548-550):

struct plex {
    char name[MAXPLEXNAME];        /* MAXPLEXNAME = 64  (vinumvar.h:103) */
    enum plexorg organization;     /* IMMEDIATELY AFTER name -> overflow target */
    ...
};

Volume names are bounded to MAXVOLNAME-1 = 63 chars + NUL by ksnprintf(vol->name, sizeof(vol->name), "%s", name) at vinumconfig.c:925. When an unnamed plex is auto-named, config_plex runs (vinumconfig.c:1459-1464):

strcpy(plex->name, VOL[plex->volno].name);   /* 63 chars + NUL fills name[0..63] exactly */
ksprintf(plexsuffix, ".p%d", pindex);          /* ".p0" */
strcat(plex->name, plexsuffix);                /* writes ".p0\0" at name[63..66] -> OOB */

strcat finds the NUL at name[63], writes .,p,0,\0 at indices 63,64,65,66 β€” indices 64/65/66 are past the 64-byte buffer, corrupting the organization enum. (A symmetric site exists for subdisks at :1274-1278: strcpy(sd->name, PLEX[].name) + strcat(".sN").)

The same primitive appears for subdisks (config_sd, :1269-1278): strcpy(sd->name, PLEX[sd->plexno].name) + strcat(".s%d") with a 63-char plex name; the sd->state field is the overflow target.

Primitive

  • Class: heap (in-object) overwrite β€” 3-5 bytes past a 64-byte name into the adjacent enum plexorg organization (or enum sdstate state) field of the same kmalloc'd struct plex/struct sd array element.
  • Content: low-attacker-controlled ASCII (.pN\0 / .sN\0).
  • Effect: corrupts plex organization β†’ the immediately-following if (isstriped(plex)) (:1466) reads the corrupted enum; misbehavior / potential panic. Because the overflow lands inside the SAME struct (not a slab boundary), INVARIANTS slab checks do NOT trip β€” corruption is silent.

Reachability / threat model

  • vinum is a loadable module (vinum.ko, prebuilt at /boot/kernel/), NOT in X86_64_GENERIC.
  • The live trigger is vinum create (root) with a 63-char volume name + unnamed plex. The realistic untrusted-input vector is on-disk vinum config auto-parsed at boot/autoconfig: an attacker who can write a vinum config to a disk (e.g. via vfs.usermount + a user-owned image, or a USB drive) can place a 63-char volume name that the kernel parses on autoconfig.
  • This is NOT a privilege boundary the bright-line rule forbids for demonstration (kldload is used only to load the vulnerable module; the bug path itself is vinum create / on-disk config). No uid=0 chain is claimed β€” the realistic impact is memory corruption (DoS / misbehavior), not priv-esc, because the controlled bytes are limited ASCII into a single enum.

Live reproduction (proof)

On unpatched 6.5-DEVELOPMENT #0:

# kldload vinum ; vinum create -f vc.conf   (vc.conf: 63-char "volume AAAA..." + "plex org concat")
P AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.p0   ...
# plex name printed length = 66   (buffer is 64 -> 2 bytes overflowed into `organization`)

The plex name printing as 66 characters is direct proof: the terminating NUL now lives inside the organization field, so the char* walk runs past the 64-byte name. See run.log (harness) and the live output in this VERDICT.

Fix validation (before/after, live)

Authored fix.diff: replace strcpy+ksprintf+strcat at both sites with a single bounded ksnprintf(.., sizeof(name), "%s.p%d", volname, pindex) / "%s.s%d".

  • Baseline (unpatched #0): rebuilt module is the stock vinum.ko; live repro β†’ plex name = 66 chars (overflow present).
  • Patched (rebuilt vinum.ko with fix, cc ... -Werror, RC=0): kldload + re-run repro β†’ plex name = 63 chars (ksnprintf bounded; overflow GONE).
fixed module: /usr/obj/usr/src/sys/dev/raid/vinum/vinum.ko   (rebuilt, -Werror clean)
BEFORE (stock): plex name length: 66
AFTER  (fixed): plex name length: 63   -> overflow into organization eliminated

fix_status = fixed. Both the compile (-Werror) and the runtime before/after confirm the fix closes the bug.

PoC changes

  • vinum_overflow.c β€” byte-exact userspace harness replicating the struct plex layout and the strcpy+ksprintf+strcat logic; shows the printed name walking to 66 chars and organization corrupted from plex_concat(1) to 12400.
  • live_repro.sh β€” the live kernel reproduction (kldload vinum + vinum create with a 63-char volume name).
  • fix.diff β€” the verified fix.

Kernel references

Fix verification

fixed

compile+harness validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live+harness). vinum config_plex strcpy+strcat 63-char vol name -> 3B overflow into organization enum. vinum.ko module. Fixed: ksnprintf.