# DF-0795 — Stack buffer overflow in VFAT long-filename reassembly

## TL;DR

**REPRODUCED.** The OOB write is real (CWE-787): `mbnambuf_write()` writes
4 bytes (ASCII variant) or 17 bytes (KICONV variant) past `nb_buf[256]`
when a crafted FAT directory entry has `weCnt = 0x54` (= `WIN_LAST | 20`,
so `id = 19`) and all 13 name positions are non-null. The finding's
path:line analysis is correct.

**NOT EXPLOITABLE to `uid=0`.** The overflow lands in `struct dirent
dirbuf` (in `msdosfs_readdir`) or in unused stack padding (in
`msdosfs_lookup`); it does **not** reach the saved frame pointer, return
address, or any function pointer / refcount / credential field. The
`dirbuf` fields it does corrupt are unconditionally overwritten before
use. There is therefore **no control-flow primitive** to convert — this
is a valid Phase-6 hard blocker. The "no kernel stack canary = code
execution" theory in the finding markdown is **wrong**: it conflates
"no canary" with "the overflow reaches the saved RIP", and on this
compilation the overflow does not reach the saved RIP.

**Impact:** integrity-only OOB write of attacker-controlled bytes into
a stack neighbor that is discarded before use. Real bug, defense-in-depth
fix warranted, but not a privilege escalation. Reported impact:
`corruption` (silent, contained), **not** `uid0` and not even `panic`.

## Files in this evidence pack

| File | Purpose |
|------|---------|
| `craft_img.py`         | FAT image patcher: injects a `weCnt=0x54` LFN slot at root-dir offset 0 |
| `run.sh`               | Mount+ls trigger (must run as root: needs vnconfig/mount) |
| `df0795_harness.c`     | Deterministic userland replication of `mbnambuf_write` showing exact overflow bytes and what they hit (poisoned stack frame) |
| `df0795_harness_fixed.c` | Same harness with the fix applied; ORIG-vs-FIXED before/after comparison |
| `build.sh`             | no-op for the trigger (Python); harnesses built with `cc -O2` |
| `run_baseline_kernel.log` | Decisive run on unpatched kernel `#0` (silent, no panic) |
| `run_patched_kernel.log`  | Same run on patched kernel `#1` (no regression) |
| `harness_before_fix.log`  | ORIG `mbnambuf_write` output (4 bytes corrupted) |
| `harness_after_fix.log`   | FIXED `mbnambuf_write` output (0 bytes corrupted, returns ENAMETOOLONG) |
| `env.txt`              | Guest environment (uname, kern.version, msdos module id) |
| `fix.diff`             | `git apply`-able fix adding the absolute-offset bounds check |
| `fix_build.log`        | module-only build log (fast path) |
| `fix_build_kernel.log` | full single-fix nativekernel build log (`#1`) |
| `VERDICT.md`           | Full narrative + stack-frame disassembly + byte map |

## How to reproduce

```sh
# on host (has python3)
ssh dfbsd 'newfs_msdos -F 16 -C 16m /tmp/df0795.img'   # make a fresh image
scp dfbsd:/tmp/df0795.img /tmp/df0795.img
python3 findings/poc/DF-0795/craft_img.py /tmp/df0795.img  # patch it
scp /tmp/df0795.img dfbsd:/tmp/df0795.img

# on guest (root)
ssh dfbsd 'cd /tmp/df0795 && sh ./run.sh'   # mount+ls the crafted image

# harness (build on guest)
ssh dfbsd 'cc -O2 -o /tmp/h /tmp/df0795/df0795_harness.c && /tmp/h'
```

## Expected on the unpatched kernel

No panic. No kernel message. Empty directory listing (the LFN slot has no
matching 8.3 alias, so msdosfs_readdir skips it). The overflow happens
silently — 4 bytes of `nb_buf` overwrite `dirbuf.d_ino` low 32 bits,
which is then overwritten by `dirbuf.d_fileno = ...` before use. This
silent behavior is confirmed by the deterministic harness.

## Expected on the patched kernel (with fix.diff applied)

Same observable behavior (no panic, empty listing), but the overflow is
prevented: `mbnambuf_write` returns `ENAMETOOLONG` for the `id=19,
count=13` input. The harness shows the byte-level difference.
