# DF-4140 — vinum drive free-list grows without bound: OOB write past the # 16-entry `drive->freelist` heap array with attacker-chosen u64 pairs ## Impact Unbounded kernel heap buffer overflow (16 bytes per extra entry, unlimited entry count, content = two attacker-influenced `u_int64_t`s) from **ordinary vinum configuration operations** — placing subdisks at specific `driveoffset`s, or returning fragmented space. Reproduced on the guest: the overflow value (0x163 = one of our subdisk end offsets) was later dereferenced as a kernel pointer by `prop_object_release` → page fault. ## Root cause * `sys/dev/raid/vinum/vinumio.c:143-145` — the free list is allocated **once** with `INITIAL_DRIVE_FREELIST` = 16 entries (vinumvar.h:191) = `Malloc(16 * sizeof(struct drive_freelist))` = 256 bytes. `drive->freelist_size` is set to 16 and **never referenced again anywhere in the driver** (no growth, no capacity check). * `sys/dev/raid/vinum/vinumconfig.c:429-439` (`give_sd_to_drive`, "specific offset" case 3) — placing a subdisk strictly inside a free entry splits it and does `drive->freelist_entries++; drive->freelist[fe+1].offset = sdend; drive->freelist[fe+1].sectors = dend - sdend;` with no bound. * `sys/dev/raid/vinum/vinumconfig.c:696-705` (`return_drive_space`, case 3) — returning non-contiguous space inserts an entry the same way. Every fragmented placement adds one 16-byte entry past the 16th. `sdend` is fully attacker-chosen (the `driveoffset` + `len` from the config line); `sectors` is derived. 17th placement = first OOB write; N placements ≈ 16·(N−16) bytes past a kmalloc(256). ## Reachability * root/operator `vinum create` with multiple `sd ... driveoffset Xs len Ys detached` lines at interior offsets (exactly how the PoC drives it), * any crafted vinum-labelled disk — the identical `config_subdisk` → `give_sd_to_drive` path runs in VF_READING_CONFIG mode, * space return paths (`remove sd`, `vinum move`) fragment via `return_drive_space` the same way. ## Build / run `cfgfrag2.sec` (60 one-sector sds at 2-sector pitch) goes to the vinum config area of a crafted drive (see DF-4133 pack for label crafting), then: ``` vinum read /dev/vn0s1a # freelist_entries: 1 -> ~61 in a 16-slot array # repeat read after VINUM_RESETCONFIG + relabel to consume the smashed zone: ./vreset; dd if=lbl.bin of=/dev/vn0s1a bs=512 seek=8 conv=notrunc vinum read /dev/vn0s1a # -> panic (see panic.txt) ``` ## Expected (vulnerable kernel) ``` Fatal trap 12: page fault while in kernel mode fault virtual address = 0x163 <- our sdend value (300+2*27+1) Stopped at prop_object_release+0x3d: movq (%rbx),%rax ``` (the first read usually survives — the corruption is dormant until the 256-byte zone object adjacent to freelist[] is used; a second cycle crashes deterministically in this run.) ## Fix Grow the array when entries reach capacity (the field for it already exists): ```diff --- a/sys/dev/raid/vinum/vinumconfig.c +++ b/sys/dev/raid/vinum/vinumconfig.c @@ -429,6 +429,15 @@ } else { /* 3 or 4 */ drive->freelist[fe].sectors = sd->driveoffset - drive->freelist[fe].offset; if (dend > sdend) { /* 3: space at the end as well */ + if (drive->freelist_entries >= drive->freelist_size) { + EXPAND(drive->freelist, struct drive_freelist, + drive->freelist_size, INITIAL_DRIVE_FREELIST); + if (drive->freelist == NULL) + return; + } if (fe < (drive->freelist_entries - 1)) /* not the last one */ ``` (the same guard is needed in `return_drive_space` before the insert at :696-705.)