set_drive_parms() re-Mallocs drive->freelist without freeing the previous allocation (leak on drive re-init)
Summary
close_locked_drive only drops the vnode; only free_drive frees the freelist - a drive transitioned up->down keeps it, and the next init_drive for the same device overwrites the pointer, orphaning 16*sizeof(drive_freelist) per cycle. Slow admin/boot-triggered heap leak, no UAF. Free-before-Malloc fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4596 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 1.5 KB | β raw |
DF-4596 β set_drive_parms() overwrites drive->freelist without freeing the previous allocation (re-init leak)
- File:
sys/dev/raid/vinum/vinumio.c:143-148 - Severity: Low Β· Confidence: certain (code) / impact small (256 B per cycle)
What
drive->freelist_size = INITIAL_DRIVE_FREELIST;
drive->freelist = (struct drive_freelist *)
Malloc(INITIAL_DRIVE_FREELIST * sizeof(struct drive_freelist)); /* old pointer clobbered */
set_drive_parms() is reached from init_drive() β read_drive_label() β
check_drive()/config_drive(). Nothing here checks whether
drive->freelist is already allocated. A drive that was previously
initialized and later closed keeps its freelist: close_drive() /
close_locked_drive() (vinumio.c:202-230) only drop the vnode; only
free_drive() (vinumconfig.c:572-588) frees the freelist, and it is not
called on the state transitions drive_up β drive_down (e.g.
vinum setstate d1 down, I/O failure, daemon_save_config downing a drive).
Re-initializing such a drive β vinum read /dev/β¦ on the same device, or the
boot scan β runs set_drive_parms() again, and the old
INITIAL_DRIVE_FREELIST(16) * sizeof(struct drive_freelist) allocation
leaks. Loopable by an administrator (or the boot scan against flapping
drives); 256 bytes per cycle. Kernel memory exhaustion over time; no
use-after-free (the old block is simply orphaned).
Fix
drive->freelist_size = INITIAL_DRIVE_FREELIST;
+ if (drive->freelist)
+ Free(drive->freelist);
drive->freelist = (struct drive_freelist *)
Malloc(INITIAL_DRIVE_FREELIST * sizeof(struct drive_freelist));
Not run on the guest
The leak is 256 B/cycle into M_DEVBUF; the guest has no per-allocation-site
accounting to observe it cheaply, and the code path (down β re-read) is plain
from source. Recorded as untested.
DF-4596 β VERDICT
Status: untested (code-certain; no guest run β the leak is 256 B per re-init cycle into M_DEVBUF and not observably attributable on the guest without allocation-site accounting).
Analysis
sys/dev/raid/vinum/vinumio.c:144-145β unconditionaldrive->freelist = Malloc(16 * sizeof(struct drive_freelist)).- Lifetime facts verified in source:
close_locked_drive()(vinumio.c:218-230) drops only the vnode;free_drive()(vinumconfig.c:572-588) is the only freer offreelistand it alsobzeros the drive β it runs on removal, not on state-down transitions;- therefore a drive that goes
up β downwhile staying allocated (set_drive_state(β¦, drive_down, β¦)from e.g.daemon_save_config()write errors orvinum setstate) keepsfreelist != NULL, and the nextread_drive_label()βinit_drive()βset_drive_parms()for the same device name (re-scan,vinum read, boot scan) overwrites the pointer.
Classification: slow kernel heap leak, administrator/boot-triggered β Low.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md analysis with path:line citations
Verified recommended fix
Free any existing drive->freelist (or reuse it) before the Malloc in set_drive_parms().
Verdict
Code-certain leak: set_drive_parms() (vinumio.c:144-145) unconditionally re-Mallocs drive->freelist; drives transitioned up->down keep their freelist (close_locked_drive() only drops the vnode; only free_drive() frees), so a later re-init of the same device (vinum read / boot scan) orphans the previous 16-entry allocation. 256 bytes per cycle, administrator/boot-triggered; no UAF (block is orphaned, not reused).
No comments yet.