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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4596 Β· 2 files
FileTypeDescriptionSize
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)

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.

VERDICT.md
↓ download raw

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 β€” unconditional drive->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 of freelist and it also bzeros the drive β€” it runs on removal, not on state-down transitions;
  • therefore a drive that goes up β†’ down while staying allocated (set_drive_state(…, drive_down, …) from e.g. daemon_save_config() write errors or vinum setstate) keeps freelist != NULL, and the next read_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_testable
baseline no→ patch + rebuild →patched clean
per-fix-DF-4596

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