parityops()/parityrebuild() compute the transfer size with an overflowing 'stripesize << DEV_BSHIFT': one checkparity/rebuildparity ioctl on a >=2 GiB-stripe RAID-5 plex panics the kernel (kmem_slab_alloc exhaustion)
Summary
stripesize is an unbounded int in sectors ('plex org raid5 3g' is accepted); >=2GiB overflows the shift, 3GiB wraps negative, imin selects it, and parityrebuild Mallocs the sign-extended ~1.8e19 value - kmalloc(M_WAITOK) cannot fail, first parity ioctl panics. REPRODUCED deterministic on stock kernel: 'vinum checkparity rv.p0' -> panic kmem_slab_alloc ran out of space (full serial log). Distinct sink from DF-1886 div0 in the same family. u64-min clamp fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4803 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| run.sh | β | 221 B | view raw | |
| run_vbd1.sh | β | 1.6 KB | view raw | |
| panic.log | β | 4.4 KB | view raw | |
| env.txt | β | 312 B | view raw | |
| README.md | β | 2.5 KB | β raw | |
| verdict.json | β | 2.5 KB | view raw | |
| manifest.json | β | 834 B | view raw |
DF-4803 β parityops()/parityrebuild() compute the transfer size with an overflowing stripesize << DEV_BSHIFT: one vinum checkparity/rebuildparity ioctl on a β₯2 GiB-stripe RAID-5 plex panics the kernel
- File:
sys/dev/raid/vinum/vinumrevive.c:274-275(parityops),:382(parityrebuildmysize),:386/:393(Mallocof the wrapped value) - Severity: Low (root-gated deterministic kernel panic from a legal config string; same class/file as DF-1886's div0 but a distinct sink β signed integer overflow into a kernel allocation)
- Bucket: base:raid / dos
- Status: reproduced (panic on the first ioctl)
Root cause
size = imin(DEFAULT_REVIVE_BLOCKSIZE, /* one block at a time */
plex->stripesize << DEV_BSHIFT); /* vinumrevive.c:274 */
plex->stripesize is an int in sectors. The config parser
(plex org raid5 3g) accepts any size, so a stripesize β₯ 2 GiB
(β₯ 2^22 sectors) makes stripesize << 9 overflow int: 3 GiB
(6291456 sectors) wraps to 0xC0000000 (negative). imin picks the
negative value; parityrebuild() then computes
mysize = u64min(size, (plex->stripesize * (stripe + 1) - pstripe) << DEV_BSHIFT);
...
bpp[sdno]->b_data = Malloc(mysize); /* vinumrevive.c:393 */
where the wrapped value sign-extends to ~1.8e19 as size_t; Malloc
is kmalloc(M_WAITOK) (vinumext.h:258), which cannot fail, and the
gigantic request panics in the allocator.
Reproduction (guest, stock kernel)
run_vbd1.sh: legal config plex org raid5 3g (3Γ2 MB subdisks on
md/vbd partitions), all objects forced up, then ONE
vinum checkparity:
Sep 24 ... vinum: Correcting length of rv.p0: was 0, is 8192 panic: kmem_slab_alloc(): kernel_map ran out of space! _kmalloc() at _kmalloc+0x5be parityrebuild() at parityrebuild+0x162 parityops() at parityops+0x82 vinumioctl() at vinumioctl+0x63b
(panic.log β first ioctl, deterministic.) The 16k-stripe control
(DF-4714 pack) behaves normally.
Fix
Compute the stripe cap in 64-bit and clamp before the int demotion
(same shape in parityrebuild):
- size = imin(DEFAULT_REVIVE_BLOCKSIZE,
- plex->stripesize << DEV_BSHIFT);
+ {
+ vinum_off_t s64 = (vinum_off_t)plex->stripesize << DEV_BSHIFT;
+ size = (int)u64min((u_int64_t)s64, DEFAULT_REVIVE_BLOCKSIZE);
+ }
Files
run.shβ md-partition variant of the proberun_vbd1.shβ the exact executed trigger (vbd1 partitions)panic.logβ full serial-console panicenv.txt
Fix verification
not_testablefix.diff not built this run (Low-severity root-gated panic; the 64-bit clamp is mechanical and risk-free) - baseline panic captured, patch recommended
panic.log
Confirmed kernel references
Detail
Exploit chain
root: config string 'plex org raid5 3g' (or any stripesize >= 2 GiB) + one checkparity/rebuildparity ioctl -> immediate kernel panic (local DoS)
Evidence (decisive lines)
panic.log (full serial console: the panic backtrace through parityrebuild/parityops/vinumioctl, plus the config acceptance lines)
PoC changes
none - shell-only trigger
Verified recommended fix
compute the stripe cap in 64-bit (vinum_off_t) and clamp to DEFAULT_REVIVE_BLOCKSIZE before demoting to int, in both parityops (:274) and parityrebuild (:382)
Verdict
parityops() computes its transfer size as imin(DEFAULT_REVIVE_BLOCKSIZE, plex->stripesize << DEV_BSHIFT) (vinumrevive.c:274-275) with stripesize an int in sectors and no upper bound from the config parser ('plex org raid5 3g' is accepted). For stripesize >= 2 GiB the shift overflows int; 3 GiB wraps to 0xC0000000 (negative), imin selects it, and parityrebuild() derives mysize via the same overflowing expression (:382) and Malloc()s the sign-extended ~1.8e19 value (:393) -- kmalloc(M_WAITOK) cannot fail, so the first parity ioctl panics in kmem_slab_alloc. Reproduced deterministically on the stock kernel: one 'vinum checkparity' on the 3g-stripe plex -> 'panic: kmem_slab_alloc(): kernel_map ran out of space!' with backtrace _kmalloc <- parityrebuild+0x162 <- parityops+0x82 <- vinumioctl (panic.log).
No comments yet.