# DF-3064 VERDICT

## Reproduced? YES — single decisive run

- Guest: DragonFly 6.5-DEVELOPMENT #0 Jul 2 2026, stock ext2fs.ko.
- Image: craft3064.py — plain rev1 ext2 (no features, so no gd checksums);
  group-0 inode bitmap bit 1 (inode 2, the LIVE root) cleared; gd nifree=1;
  s_free_inodes_count=1.
- Trigger: `touch /mnt/e4/x`
  - returned `File exists` (TOUCH-RC=1)
  - console: `ext2_valloc: vp 0xfffff80118106c00 exists for inode 2`
- Aftermath: `ls /mnt/e4` and `umount /mnt/e4` wedge in uninterruptible D
  state (ps: PIDs 982, 1001 `ls`, 1022 `umount`; `timeout 15` could not kill
  them). Only namecache-served reads still work. Reboot required.

## Mechanism (path:line)

- ext2_nodealloccg (sys/vfs/ext2fs/ext2_alloc.c:1358 memcchr scan / 1395
  return) hands out the first clear bitmap bit — the crafted-clear root bit —
  and returns ino 2 with nifree/ficount consumed.
- ext2_valloc (sys/vfs/ext2fs/ext2_alloc.c:423-426) then calls
  ext2_ihashget() which returns the root vnode vget()'d
  (sys/vfs/ext2fs/ext2_ihash.c:100, vget LK_EXCLUSIVE + vref; recursive
  against the create path's own parent lock) — and the EEXIST return path
  never vput()s it. The vnode's lock recursion and vref are permanently
  leaked: the mount cannot be locked for directory ops nor flushed by
  umount; every triggering create also permanently consumes an inode.

## Impact

Local DoS (crafted image → mount-wide uninterruptible wedge, un-unmountable
mount, inode leak). Not a privilege escalation.

## Fix

One line (fix.diff): `vput(vp);` before `return (EEXIST);`. Applies clean;
not rebuilt in-guest (DoS class — fix-build budget spent on DF-3062's
mandatory memcorrupt cycle). Companion hardening: check the inode hash
before consuming the bitmap bit so the allocation itself is rolled back.

impact=dos; confidence=certain; attempts=1.
