vinum_scandisk() strncpy leaves partname[64] unterminated β stack OOB read in find_drive_by_dev()'s %s copy
Summary
For a >=64-char device path matching the scan shape, strncpy fills all 64 bytes without a NUL; the ksnprintf %s in find_drive_by_dev then reads past the stack buffer until an accidental NUL, copying stack bytes into the drive's devicename. Root/loader-env input; hardening. Copy-63-and-terminate fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4598 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 1.4 KB | β raw |
DF-4598 β vinum_scandisk() strncpy into partname[64] without NUL termination (stack OOB read)
- File:
sys/dev/raid/vinum/vinumio.c:723 - Severity: Info Β· Confidence: likely
What
char partname[DRIVENAMELEN]; /* DRIVENAMELEN = 64 (local #define, line 677) */
...
if (has_slice >= 0 && has_part)
strncpy(partname, devicename[driveno], DRIVENAMELEN);
When the user-supplied device path is β₯ 64 characters and its basename
matches the [a-z]+\d+s\d<c> scan pattern, strncpy fills all 64 bytes and
leaves partname unterminated. check_drive(partname) β
find_drive_by_dev() β on create, ksnprintf(drive->devicename, 64, "%s",
partname) (vinumconfig.c:534-536) then reads past the 64-byte stack buffer
until it happens to find a NUL β a bounded-by-luck stack overread whose
content is copied into the created drive's devicename (visible to the same
root user via vinum l).
The sibling constructors (ksnprintf(partname, DRIVENAMELEN, β¦) at 725/728)
are safe; only the strncpy branch is affected. The device list comes from
the read ioctl tokens (root) or the vinum.drives loader tunable, so this
is a hardening/info finding, not an attack surface for unprivileged users.
Fix
- strncpy(partname, devicename[driveno], DRIVENAMELEN);
+ strncpy(partname, devicename[driveno], DRIVENAMELEN - 1);
+ partname[DRIVENAMELEN - 1] = '\0';
Not run on the guest
Requires a β₯64-char matching device path from root; the overread's target
content is stack noise with no privilege consequence. Recorded as untested.
DF-4598 β VERDICT
Status: untested (Info-severity hardening finding; root/loader-input only, no guest run).
Analysis
sys/dev/raid/vinum/vinumio.c:723 β the has_slice >= 0 && has_part branch
of vinum_scandisk() uses strncpy(partname, devicename[driveno],
DRIVENAMELEN) with partname[64]. A device path of β₯ 64 characters whose
basename matches the %*[a-z]%*d%*[s]%d%c scan shape (ksscanf, line 711)
yields an unterminated partname. The first consumer that treats it as a
C string is find_drive_by_dev()'s create path
(ksnprintf(drive->devicename, sizeof(...), "%s", devname),
vinumconfig.c:534-536) β a stack overread whose length is bounded only by the
first accidental NUL. The copied bytes land in the new drive's
devicename, inspectable by the same root user. The strcmp in the search
loop terminates safely because the comparison operand is a NUL-terminated
β€63-byte devicename. No unprivileged path; recorded as Info.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md analysis (in README.md) with path:line citations
Verified recommended fix
strncpy at most DRIVENAMELEN-1 bytes and NUL-terminate partname.
Verdict
strncpy of a >=64-char device path into partname[64] (vinumio.c:723) leaves the stack buffer unterminated when the basename matches the slice/partition scan pattern; find_drive_by_dev()'s ksnprintf("%s", partname) then reads past the buffer until a NUL, copying stack bytes into the new drive's devicename. Root/loader-env input only; hardening finding.
No comments yet.