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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4803 Β· 7 files
FileTypeDescriptionSize
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 (parityrebuild mysize), :386/:393 (Malloc of 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 probe
  • run_vbd1.sh β€” the exact executed trigger (vbd1 partitions)
  • panic.log β€” full serial-console panic
  • env.txt

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.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
per-fix-DF-4803

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).