# DF-3063 VERDICT

## Reproduced? YES (decisive second measurement; first run's samples were
already-zero blocks on a fresh image — inconclusive measurement, not a
failed attempt; the loop demonstrably ran in both)

Guest: DragonFly 6.5-DEVELOPMENT #0 Jul 2 2026 (stock ext2fs.ko).

Craft (`craft3063.py`): mke2fs ext2 (REV0) -b 1024 -I 128 -N 16 on 4MB;
patched: s_rev_level=0 (already), RAW s_inode_size=0xFFFF (unvalidated at
REV0), s_inodes_per_group=16 (>= ipb=8 → legal), s_free_inodes_count=16,
ro_compat |= GDT_CSUM (REV0 skips the feature-mask gate at
sys/vfs/ext2fs/ext2_vfsops.c:306-331), gd0: nifree=16, INODE_ZEROED clear,
itable_unused=16 (→ used_blks=0), gd crc16 fixed.

Decisive run (trigger3063b.sh):

```
--- pre-fill blocks 100..1100 with 0xAA ---
--- BEFORE ---                --- AFTER (touch) ---
blk   5: 0f343b09…  (zero)    blk   5: 0f343b09…  (unchanged)
blk 500 : c599594b…  (0xAA)   blk 500 : 0f343b09…  ZEROED
blk 1042: c599594b…  (0xAA)   blk 1042: 0f343b09…  ZEROED (last loop block)
blk 1043: c599594b…  (0xAA)   blk 1043: c599594b…  (unchanged)
MOUNT-RC=0 / TOUCH-OK
```

The zeroed range [20 .. 1042] matches `i_tables .. i_tables+all_blks-1`
exactly (itable=20, all_blks = 0xFFFF*16/1024 = 1023). Mount validated only
`i_tables + itpg - 1` = block 21 (`ext2_cg_validate`,
sys/vfs/ext2fs/ext2_vfsops.c:439-440). One file-create wiped 1023 blocks
(~1MB) of the device past the inode table; with ipg=8192 the same craft
zeroes ~512MB per group.

## Root cause chain (path:line)

1. `sys/vfs/ext2fs/ext2_vfsops.c:306` — feature-mask check only runs for
   `rev > E2FS_REV0` → REV0 + GDT_CSUM is mountable.
2. `sys/vfs/ext2fs/ext2_vfsops.c:522-546` — inode_size validated only in the
   REV1 branch; REV0 leaves the raw field arbitrary.
3. `sys/vfs/ext2fs/ext2_alloc.c:1251` — `all_blks` computed from the RAW
   field (contrast line 1256 which correctly uses the validated
   EXT2_INODE_SIZE(fs)); at REV1 the two provably agree (all_blks <= itpg),
   at REV0 they diverge by up to 512x.
4. `sys/vfs/ext2fs/ext2_alloc.c:1258-1267` — getblk/clrbuf/bawrite loop over
   `all_blks - used_blks` blocks starting at i_tables+used_blks.

## Impact ceiling

Destructive device writes beyond the fs's validated extent (zero-fill), I/O
storm DoS (up to ~524k blocks per affected group per create). All writes are
buffer-cache-sized — no kernel-memory corruption. Not a uid0 primitive.

## Fix

fix.diff: `all_blks = fs->e2fs_isize * fs->e2fs_ipg / fs->e2fs_bsize;`
(applies clean, fuzz 2). Not built/validated in-guest (budget spent on
DF-3062's mandatory fix cycle; this is device-level corruption, not kernel
memory corruption, so a fix build was not mandatory). Companion hardening
recommended: validate `features_{rocompat,incompat} == 0` (or gate them) at
REV0.

impact=dos (destructive device zero-fill + I/O storm from crafted image);
confidence=certain; attempts=2 (1 inconclusive measurement + decisive run).
