# DF-3082 VERDICT

## Bottom line

**REPRODUCED.** On the stock INVARIANTS kernel (#0), an unprivileged user
(`maxx`) creating **one** file inside an htree-indexed directory of a valid,
e2fsck-clean, Linux-toolchain-produced ext2 image (mounted read-write by
root) caused `ext2_direnter()` to **truncate the directory to the middle of
the index tree**: `ls` collapsed from 100 to 36 entries — 65 pre-existing
entries silently destroyed — and the next lookup of a vanished name panicked
the kernel.

## Why (code trace)

1. `ext2_lookup_ino()` dispatches CREATE to `ext2_htree_lookup()`
   (sys/vfs/ext2fs/ext2_lookup.c:377-382) because the dir has
   `EXT2_INDEX_FL` → `IN_E3INDEX` (sys/vfs/ext2fs/ext2_inode_cnv.c:170).
2. Name not found → `ext2_htree_lookup()` returns ENOENT after searching
   only the hash-path leaves; `ss` holds a slot inside one leaf and
   `*endusefulp` is leaf-local (sys/vfs/ext2fs/ext2_htree.c:381-418).
3. `case ENOENT:` (ext2_lookup.c:387-389) → `notfound:` builds
   `dp->i_endoff = roundup2(enduseful, DIRBLKSIZ)` from that leaf-local
   enduseful (ext2_lookup.c:495-505).
4. `ext2_direnter()` (ext2_lookup.c:948-954): `ext2_add_entry()` then
   `ext2_truncate(dvp, dp->i_endoff)` — frees every directory block after
   the searched leaf while the dx root still indexes them.
5. Vanished name lookup → dx leaf at offset 3072 ≥ truncated i_size →
   bread returns a zero-filled hole → `ext2_check_direntry()` rejects
   `rec_len == 0` → `ext2_dirbad()` (ext2_lookup.c:771-784) → **panic**
   (writable mount).

DragonFly disabled the htree add path (`#if 0`, ext2_lookup.c:920-941) but
kept the linear compact-truncate — FreeBSD keeps htree adds enabled and
never runs this truncate for indexed dirs, so this is a dfly-specific
regression that fires on ordinary Linux-created ext3 images.

## Baseline run (stock #0)

```
entries before: 100
after touch zzq001: entries=36      <- one create destroyed 65 entries
panic: ext2_dirbad: : bad dir ino 12 at offset 3072: mangled entry
ext2_dirbad() / ext2_search_dirblock() / ext2_htree_lookup() / ext2_lookup()
```
(run.log, panic.txt; mount via vntool attach + `mount -t ext2fs /dev/vn0`.)

## Fix validation (kernel #1, built in-guest from fix.diff)

`make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0, 4299 cc steps,
full log build.log.gz) + `make installkernel` + reboot.

Same image, same user (`fix_run.sh`): 40 creates grow the entry count
monotonically 100 → 140, **zero** vanished pre-existing entries, stat sweep
over 40 surviving indexed entries all found, no panic, guest up:
```
entries after 40 touches: 140
vanished pre-existing entries: 0
FIX-OK: no truncate, no data loss
stat sweep: ok=40 notfound=0 (no panic = fix holds)
```

## Exploit chain

Not a privilege-escalation primitive: the bug is silent mass data
destruction (integrity) reachable by an unprivileged create, followed by a
reliable kernel panic (availability) on any subsequent lookup of the
destroyed namespace. No kernel memory corruption is involved —
`ext2_truncate` uses the normal truncate machinery; the panic is the
fail-stop `ext2_dirbad()` policy (DF-3083).

## Notes

* The `rm payloadname0X` steps were observed to silently no-op on this
  image (lookup ENOENT for names that exist — consistent with the dx
  binary-search resolving low hashes to the count/limit overlay when no
  leading sentinel entry exists, cf. the upstream "lost dirents" XXX at
  ext2_lookup.c:908-919). This quirk is *not* needed for the trigger: the
  truncate fired on the very first create, using only e2fsck packing slack.
* Guest was reset to the clean `with-src` snapshot after the runs.
