# DF-0789 — Reproduce

## Bug
Unbounded run-list walk in `ntfs_runtovrun` (`sys/vfs/ntfs/ntfs_subr.c:582-634`).
The function takes NO length parameter; both loops walk `run[off]` until a zero
byte (`:595-598` count, `:605-629` decode) with **no bound check** against the
attribute's data-extent length. A crafted NTFS image with a non-resident
attribute whose run list has no zero terminator causes an OOB read past the
MFT record buffer into adjacent kernel heap, or (on default GENERIC with
INVARIANTS) an infinite loop into 0xdeadc0de-poisoned freed slab.

The disabled `ntfs_parserun` at `:1745-1780` shows the correct pattern: it
takes a `len` parameter and bounds-checks at `:1760` and `:1770`.

## Files
| file | purpose |
|---|---|
| `harness.c`        | userspace replication of `ntfs_runtovrun:582-634` with a guard page; accepts `clean|oob_short|oob_fill|oob_infinite` and `apply_fix` |
| `gen_ntfs_0789.py` | builds a crafted NTFS image with ino 0's non-resident $DATA run list filled with 0x11 (no terminator) to the end of the MFT record |
| `ntfs_0789.img`    | the crafted image (regenerated by build.sh if python3 present) |
| `build.sh`         | compiles the harness + (if python3 available) regenerates the image |
| `run.sh`           | runs the harness; `run.sh live` (root) also runs the live mount |
| `fix.diff`         | git-apply-able fix: thread a `runlen` parameter and bound both loops |
| `VERDICT.md`       | full narrative + evidence |
| `manifest.json`    | artifact catalog |

## Build
```sh
./build.sh            # builds ./harness (+ image if python3 present)
```

## Run
```sh
./run.sh              # userspace harness, deterministic (no root needed)
./run.sh live         # + live mount test (REQUIRES ROOT, panics/hangs the guest)
```

## Expected
- **Harness, buggy mode:** `clean` → rc=0 (terminator found); `oob_short` → rc=0
  (stops at zero byte after entry — proves no internal bound, but adjacent zeros
  save it); `oob_fill` / `oob_infinite` → SIGSEGV (rc=2, OOB read past guard page)
  or ITERATION CAP (rc=1, infinite loop).
- **Harness, fix mode:** `oob_fill` / `oob_infinite` → "FIX REJECTED input"
  (EINVAL); clean/oob_short unchanged.
- **Live (unpatched kernel):** `mount_ntfs` on `ntfs_0789.img` causes kernel
  hang (infinite loop into poisoned slab) or panic (OOB page fault). Guest dies.
- **Live (patched kernel):** `mount_ntfs` returns `Invalid argument` (RC=71),
  guest stays UP.

## Preconditions
- `mount_ntfs` is root-only (`vfs.usermount=0` on stock DragonFly). Threat model:
  admin mounts attacker-supplied NTFS image / USB stick.
- Reachable at mount time during `VFS_VGET(NTFS_MFTINO=0)` → `ntfs_loadntnode`
  → `ntfs_attrtontvattr` → `ntfs_runtovrun`. Does NOT require the post-mount
  lookup path (so the DF-0786 lockmgr panic does not block this finding).
