# DF-3083 — ext2_dirbad() panics a writable-mounted ext2 on any mangled directory entry (local DoS from a crafted image)

## Summary

`ext2_dirbad()` (sys/vfs/ext2fs/ext2_lookup.c:771-784) reacts to a
directory-entry consistency failure with `panic()` **whenever the mount is
not read-only**:

```c
if ((mp->mnt_flag & MNT_RDONLY) == 0)
        panic("ext2_dirbad: %s: bad dir ino %ju at offset %ld: %s\n", ...);
else
        SDT_PROBE4(...);           /* read-only: log and continue */
```

It is called from the dirent walkers whenever `ext2_check_direntry()`
(ext2_lookup.c:794-819) rejects an on-disk entry — i.e. any of:

* `rec_len < EXT2_DIR_REC_LEN(1)`, `rec_len % 4 != 0`,
  `rec_len < EXT2_DIR_REC_LEN(namelen)`, entry crosses block boundary,
  `ino > s_inodes_count` (ext2_lookup.c:801-810);
* the "i_size too small" fixup path (ext2_lookup.c:532-537).

Call sites: `ext2_search_dirblock()` "mangled entry"
(ext2_lookup.c:698-706), reached from the linear `searchloop`
(ext2_lookup.c:444) and from `ext2_htree_lookup()`
(sys/vfs/ext2fs/ext2_htree.c:399). Any `stat`/`open`/`ls -l` whose lookup
walks past the corrupt byte panics the kernel.

A single flipped byte in a directory block (e.g. `rec_len` 16 → 17) turns
any writable-mounted ext2 image into an immediate kernel panic; the
read-only branch proves the non-fatal handling already exists.

## Reachability

* crafted ext2 image (one corrupt dirent byte) mounted **read-write**
  — by root, or by an unprivileged user with `vfs.usermount=1`
    (the image may also arrive on removable media);
* trigger is a plain path lookup — unprivileged, e.g. `ls -l` or `stat`
  of any name at/after the corrupt entry;
* also fires as the *follow-on* of DF-3082 on a **valid** image (freed
  leaf blocks read back zero-filled → "mangled entry" → panic).

## Reproduced (baseline, stock INVARIANTS kernel #0)

```
mount -t ext2fs -o ro /dev/vn0 /mnt/e2 ; ls -l /mnt/e2/d   # control: survives
umount /mnt/e2
mount -t ext2fs /dev/vn0 /mnt/e2                            # read-write
stat /mnt/e2/d/file4
panic: ext2_dirbad: /mnt/e2: bad dir ino 12 at offset 56: mangled entry
ext2_dirbad() at ext2_dirbad+0x32
ext2_search_dirblock() at ext2_search_dirblock+0x194
ext2_lookup() at ext2_lookup+0x1fe
vop_compat_nresolve() at vop_compat_nresolve+0x82
```

offset 56 = the patched entry (rec_len 16→17, `rec_len % 4 != 0`).

## Fix

`fix.diff` (shared with DF-3082's pack) — always log, never panic;
the walker already contains the recovery (skip to the next block,
ext2_lookup.c:702-706). Validated on a patched kernel: the RW mount
survives the same `stat` (printf on console instead of panic).

## Contents

* `mangle.patch.py` (embedded in craft steps) — flips one rec_len byte
* `run.sh` — guest root: RO control + RW trigger
* `run.log`, `panic.txt` — baseline evidence
* `fix.diff`, `fix_run.log` — fix validation
* `verdict.json`, `manifest.json`, `VERDICT.md`
