Drive free list grows without bound: OOB write past the 16-entry (256-byte) drive->freelist heap array with attacker-chosen u64 pairs from ordinary config
Summary
drive->freelist is allocated once with 16 entries; freelist_size is never referenced again. give_sd_to_drive and return_drive_space increment freelist_entries and write freelist[fe(+1)] = {u64 offset, u64 sectors} with no capacity check. Every fragmented subdisk placement adds one 16-byte entry past the 16th. LIVE-REPRODUCED: 60 one-sector sds at 2-sector pitch -> freelist grows to ~61 in a 16-slot array; after the next zone reuse: Fatal trap 12 with fault address 0x163 == exactly the attacker-written sdend - the overflow content lands in live adjacent objects and is dereferenced as a pointer. EXPAND-if-full fix (the field already exists for exactly this purpose).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4140 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| panic.txt | β | 986 B | view raw | |
| serial_full.txt | β | 3.3 KB | view raw | |
| cfgfrag2.sec | β | 3.5 KB | β download | |
| verdict.json | β | 2.8 KB | view raw |
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_ts) from ordinary
vinum configuration operations β placing subdisks at specific
driveoffsets, 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 withINITIAL_DRIVE_FREELIST= 16 entries (vinumvar.h:191) =Malloc(16 * sizeof(struct drive_freelist))= 256 bytes.drive->freelist_sizeis 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 doesdrive->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 createwith multiplesd ... driveoffset Xs len Ys detachedlines at interior offsets (exactly how the PoC drives it), - any crafted vinum-labelled disk β the identical
config_subdiskβgive_sd_to_drivepath runs in VF_READING_CONFIG mode, - space return paths (
remove sd,vinum move) fragment viareturn_drive_spacethe 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):
--- 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.)
Fix verification
not_testableFix not built in this run (fix-validation kernel carried DF-4132/4133 only); proposed EXPAND guard in README.
panic.txt baseline.
Confirmed kernel references
Detail
Exploit chain
config with N fragmented sd placements -> freelist_entries grows 1->N+1 in a 16-slot array -> 16-byte OOB writes of chosen u64 pairs past kmalloc(256) -> adjacent 256-byte zone objects (e.g. libprop objects) corrupted with small-integer 'pointers' -> crash on use (0x163 deref). Ceiling: controlled heap corruption; the written offsets/lengths are config-chosen u64s.
Evidence (decisive lines)
panic.txt (fault VA == our sdend value), serial_full.txt, cfgfrag2.sec, README.md
PoC changes
Used space-separated kernel config grammar; sizespec suffix s=sectors (first attempt with len 512b created 512-sector sds that overlapped); crash needed a second resetconfig+read cycle to consume the corrupted zone.
Verified recommended fix
EXPAND drive->freelist when freelist_entries reaches freelist_size before both insertion sites (field already exists for this).
Verdict
drive->freelist is allocated once with 16 entries (256 bytes, vinumio.c:143-145, INITIAL_DRIVE_FREELIST=16) and freelist_size is never checked again; give_sd_to_drive case 3 (vinumconfig.c:429-439) and return_drive_space case 3 (:696-705) increment freelist_entries and write 16-byte {u64 offset, u64 sectors} entries with no bound. Reproduced: a crafted config placing 60 one-sector subdisks at 2-sector pitch grows the list to ~61 entries (~720 bytes past the 256-byte kmalloc); after the following zone reuse the kernel dereferenced one of the attacker-written values (fault VA 0x163 == sdend of sd f27) in prop_object_release -> Fatal trap 12. Root/operator config path and crafted-disk read path both reach it.
No comments yet.