# DF-2619 — hammer2 dirent namlen unbounded by chain->bytes (OOB read past 64KB DIO buffer)

* Cited: `sys/vfs/hammer2/hammer2_chain.c:5781-5785` (`hammer2_chain_dirent_test`)
* Verified additionally in: `sys/vfs/hammer2/hammer2_vnops.c:723-737` (`hammer2_vop_readdir`)
* Severity as filed: Low. **Verified impact is higher: kernel-memory disclosure to
  userspace via `getdents(2)` (127 bytes), and a repeatable kernel panic when the
  adjacent page is unmapped.**

## What this pack contains

| file | what |
|---|---|
| `h2common.py` + `forge_2619.py` | image forger (volhdr→sroot→PFS walk, DF-2616 technique) |
| `getdents_dump.c` | getdents(2) dumper that hexdumps every d_name (leak capture) |
| `statprobe.c` | stat(2) probe printing errno |
| `run_2619.sh` / `run_2619_instr.sh` | guest trigger scripts (stock / instrumented kernel) |
| `run_stock.log` | **the leak**: full getdents output on stock kernel #0, forged vs base |
| `console_excerpts.txt` | panic texts (stock `memcpy+0x19`; instr. `dirent_test+0x89 cmpb`; instr. backtrace `hammer2_vop_readdir+0x5c8`) |
| `fix.diff` | the fix (dirent_test clamp + readdir skip) |
| `fix_run.log` | fix validation on kernel #2 |
| `instr_build*.log`, `fix_build.log`, `fix_install.log`, `env.txt` | full untrimmed build/env logs |

## Build

1. Guest (root): base image per `run_2619.sh` prerequisites —
   `newfs_hammer2 -L testvol` on a 64M vn image, create ONE file with a
   255-char name (`head -c 255 /dev/zero | tr '\0' 'A'`), sync, umount,
   pull `base2619.img` to the host.
2. Host: `python3 forge_2619.py base2619.img craft2619.img`
   (relocates the file's DIRENT bref to `0x210FF87` = window `0x2100000`,
   offset `0xFF80`, radix 7 → `chain->bytes`=128 < namlen=255; writes the
   first 128 name bytes into the block; sets CHECK_NONE on the dirent,
   PFS-inode and sroot brefs; recomputes the volhdr CRC32Cs).
3. Guest: `cc -O -o getdents_dump getdents_dump.c && cc -O -o statprobe statprobe.c`,
   push `craft2619.img`.

## Run

```sh
vnconfig -c vn0 craft2619.img
mount -o ro -t hammer2 /dev/vn0@testvol /mnt/h2x
./getdents_dump /mnt/h2x          # leak (or panic, layout-dependent)
./statprobe "/mnt/h2x/$(head -c 255 /dev/zero | tr '\0' 'A')"   # OOB compare
umount /mnt/h2x; vnconfig -u vn0
```

## Expected

* **Stock kernel, adjacent page mapped** (observed): the 255-char dirent
  name returned by `getdents` ends with 127 bytes of kernel memory from
  past the end of the 64KB DIO buffer (see `run_stock.log` hexdump:
  `...4141` ×128 then `01 00 … 6f54f378235a0600 …`).
* **Stock kernel, adjacent page unmapped** (observed): fatal trap 12 in
  `memcpy+0x19` (see `console_excerpts.txt` [A]).
* Control (`base2619.img`): everything normal, stat succeeds.
* **Fixed kernel #2**: malformed entry skipped — no garbage tail, no
  panic, `stat` → ENOENT; legit long-name dirents on the base image still
  list and stat fine (`fix_run.log`).

Trigger privilege: mounting the crafted image requires root (or
`vfs.usermount=1` with a user-owned vn device); the *leak itself*
(`getdents` on a world-readable directory of the mounted fs) is available
to any unprivileged user afterwards, exactly like prior art DF-2627.
