# DF-0831 — PoC evidence pack

Heap overflow in `udf_getfid()` via a negative `frag_size` produced by 4-byte
FID alignment overshoot.  `sys/vfs/udf/udf_vnops.c:605` alignment can advance
`ds->off` up to 3 bytes past `ds->size`; the next call's fragmented-FID branch
then computes `frag_size = ds->size - ds->off` (negative), the signed guard at
`:544` (`frag_size >= bsize`) is bypassed, and `bcopy(..., frag_size)` at
`:555` sign-extends `int(-3)` to `size_t(0xFFFFFFFFFFFFFFFD)` → catastrophic
kernel heap write → page fault → panic.

## Files

| file | purpose |
|------|---------|
| `craft_img.py`   | builds `df0831.udf` — a valid UDF image whose root dir spans **2 extents** with a non-4-aligned FID at the extent-0 boundary |
| `harness.c`      | deterministic transcription of the `udf_getfid()` arithmetic + poison allocator (proves `size_t(-3)` overrun) |
| `run.sh`         | guest-side: `vnconfig` + `mount_udf` (root) then `ls` as maxx → panic |
| `build.sh`       | host-side: generate `df0831.udf` + compile `harness` |
| `fix.diff`       | one-line clamp (`if (frag_size < 0) frag_size = 0;`) — `git apply`-able |
| `VERDICT.md`     | full root-cause + reproduction + fix-validation narrative |
| `run.log`        | decisive unpatched run (panic trace) |
| `fix_run.log`    | decisive patched run (clean readdir, no panic) |
| `panic.txt`      | kernel panic signature from `boot.log` |
| `fix_build.log`  | patched `udf.ko` build output |
| `env.txt`        | guest `uname` / `kern.version` / cc version |

## How to reproduce

The image must be 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 `readdir` trigger itself is unprivileged).

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

# guest (DragonFly 6.5-DEVELOPMENT #0 GENERIC)
sh /root/poc/DF-0831/run.sh                  # -> kernel panic (memmove+0x24f)
```

**Expected (bug present):** kernel page-fault in `memmove`/`bcopy` called
from `udf_readdir`; guest frozen at the `db>` prompt.

**Expected (after fix.diff):** `ls /mnt` returns the directory entries
(`.`, `..`, `AB`, `X`) with no panic.

## Determinism

Reproduced 5× across 3 image revisions; the fault address varies per run
(`0xfffff800566b5ff9`, `0xfffff80054ec5ff9`, `0xfffff80118132000`,
`0xfffff80118274000`, …), confirming a real unbounded read and not a
fixed-address artifact.
