# DF-0880 — PoC evidence pack

Heap over-read (CWE-125) in `udf_vget()` via unbounded File-Entry `l_ea`/`l_ad`.
`sys/vfs/udf/udf_vfsops.c:527` computes `size = UDF_FENTRY_SIZE + fe->l_ea +
fe->l_ad` from on-disk `uint32_t` fields that are never validated against
`bsize`; `:530` `bcopy(bp->b_data, unode->fentry, size)` then reads `size`
bytes from the `bsize`-byte (2048) buffer. With `l_ad=0xFFFF`, `size=65711` =
a **63663-byte (63 KB) heap over-read**. Fires on first `ls`/`stat` after mount
(`udf_root` → `udf_vget`). Also reachable via NFS `fhtovp`.

## Files

| file | purpose |
|------|---------|
| `craft_img.py`   | builds `df0880.udf` — a valid UDF image whose root File Entry has `l_ad=0xFFFF` |
| `harness.c`      | deterministic transcription of the `udf_vget` arithmetic + poisoned allocator (proves 63KB over-read) |
| `run.sh`         | guest-side: `vnconfig` + `mount_udf` (root) then `ls` as maxx → panic |
| `build.sh`       | host-side: generate `df0880.udf` + compile `harness` |
| `df0880.udf`     | crafted UDF image (1 MB, 512 sectors) |
| `fix.diff`       | bounds check `size > bsize` after `:527`, before `:530` — `git apply`-able |
| `VERDICT.md`     | full root-cause + reproduction + fix-validation narrative |
| `run.log`        | harness deterministic over-read proof (full output) |
| `panic.txt`      | kernel panic signature + backtrace from `boot.log` |
| `fix_build.log`  | patched `udf.ko` module build output |
| `fix_run.log`    | patched-module validation (mount OK, ls/stat → EINVAL, no panic) |
| `env.txt`        | guest `uname` / `kern.version` / cc version / udf.ko sha256 |

## How to reproduce

The image is generated on a host with `python3` (the guest has none); the
harness builds on either. `run.sh` runs on the DragonFly guest as root (the
trigger itself is unprivileged — `ls`/`stat` as any user).

```sh
# host
./build.sh                                        # -> df0880.udf, harness
scp df0880.udf run.sh dfbsd:/root/poc/

# guest (DragonFly 6.5-DEVELOPMENT #0 GENERIC, INVARIANTS ON)
kldload udf
vnconfig -c vn0 /root/poc/df0880.udf
mount_udf -o ro /dev/vn0 /mnt
su maxx -c 'ls /mnt'                              # -> kernel panic (memmove read-fault)
```

**Expected (bug present):** kernel page-fault in `memmove`/`bcopy` called from
`udf_root` → `udf_vget`; `fault code = supervisor read data, page not present`
(a READ over-read). Guest frozen / rebooting.

**Expected (after `fix.diff` applied, `udf.ko` rebuilt):** `ls`/`stat` return
`Invalid argument` (EINVAL); no panic; guest stays up. `dmesg` shows
`udf_vget: file entry too large (65711 > 2048)`.

## Deterministic harness (no image needed)

```sh
cc -O2 -Wall -o harness harness.c && ./harness
```

Proves the exact arithmetic (`size=65711` from a `2048`-byte buffer) and
faults the `bcopy` at the 2048-byte source boundary via a `PROT_NONE` guard
page, demonstrating the read is unbounded by `bsize`.

## Impact

Read-only OOB (CWE-125) — **no write primitive, no escalation chain** (valid
Phase-6 hard blocker). Deterministic manifestation: **panic (DoS)**. Secondary
(unreliable) info-leak ceiling if the over-read completes without faulting.
Root-only mount + unprivileged `ls`/`stat` trigger (or `vfs.usermount=1` +
root-created image owned by attacker).
