# DF-0874 — Unbounded attr_indexentry walk in ntfs_readdir

**Medium · CWE-125 OOB Read · `sys/vfs/ntfs/ntfs_vnops.c:585-586`**

`ntfs_readdir` walks `attr_indexentry` records using the attacker-controlled
on-disk `ie_reclen` as stride, with no bound check against the INDEX block
buffer (`fp->f_dirblbuf`). A crafted NTFS image walks the pointer into kernel
heap → OOB read / info leak / DoS. The sibling walk in `ntfs_ntreaddir`
(`ntfs_subr.c:1176`) *is* bounded; `ntfs_readdir` is not.

## Reproduce

```sh
./build.sh                       # cc trigger.c + harness.c (guest, maxx)
# root mounts the crafted image (vfs.usermount=0); maxx readdir's it:
ssh dfbsd 'vnconfig -c vn0 /root/poc/DF-0874/ntfs_reclen0.img &&
           mount -t ntfs -o ro,-u=1001,-g=1001 /dev/vn0 /mnt/t'
ssh dfbsd-maxx 'cd poc/DF-0874 && ./trigger /mnt/t'    # 257 duplicate dirents (BUG)
ssh dfbsd-maxx 'cd poc/DF-0874 && ./harness 0xFFF0 0 1' # code-level OOB proof
```

**Expected (bug present, unpatched #0):** `readdir` returns 257 entries (`..` +
256× `HHHH`) because `reclen=0` makes the walk never advance. The harness shows
the walk dereferences 64 KB past the 4 KB buffer.

**Expected (fixed):** `readdir` returns `EINVAL` (0 entries); the harness walk
is bounded; a clean image still returns the correct dirents.

## Files

| file | what |
|------|------|
| `trigger.c` | unprivileged readdir driver (opendir/readdir + hex dump) |
| `craft_img.py` | builds crafted NTFS images (panic/leak modes) |
| `harness.c` | deterministic code-level proof (replicates both kernel walks) |
| `ntfs_reclen0.img` | reclen=0 → dirent flood (live-GENERIC trigger) |
| `ntfs_oob.img` / `ntfs_leak.img` | reclen=0xFFF0 / 0x60 → OOB read variants |
| `ntfs_clean.img` | valid image for regression check |
| `fix.diff` | bounds the walk against `fp->f_dirblbuf`/`f_dirblsz` |
| `VERDICT.md` | full analysis, before/after, mechanism |
| `run.log` / `run_reclen0.out` / `harness_*.out` | full untrimmed logs |
| `fix_build.log` / `fix_run.log` | Phase-8 module build + validation |

## Impact

Read-only primitive — no escalation. Ceiling: kernel heap info leak / DoS
(dirent flood or page-fault panic if the OOB offset escapes mapped kmem).
On default GENERIC (INVARIANTS ON) the slab poison typically masks observable
exfiltration; on non-INVARIANTS the leak is directly observable.
