# DF-0881 — Heap OOB read in sparing-table scan: `rt_l` unbounded by `st_size`

## Summary

In `udf_find_partmaps()` (`sys/vfs/udf/udf_vfsops.c:692`), the sparing-table
entry scan loop iterates `rt_l` times (a `uint16_t` from the on-disk table),
but `rt_l` is never bounded by `st_size` (the declared table size used for
`kmalloc`). A crafted UDF image with `rt_l >> st_size` causes the loop to read
`entries[i]` far past the allocation — a heap OOB read / info-leak / panic.

## Reproduce

```sh
# On the DragonFlyBSD guest (root):
./build.sh                    # crafts evil.udf (st_size=32768, rt_l=65535)
kldload udf                   # load UDF module
mkdir -p /mnt/udf
./run.sh                      # vnconfig + mount → panic on unpatched kernel
```

### Expected behavior

- **Unpatched kernel (`#0`):** mount hangs / guest panics with
  `Fatal trap 12: page fault ... Stopped at udf_mount.part.2+0x8c9`
- **Fixed kernel (`#1`):** mount returns `EINVAL` cleanly, guest stays up

## Files

| File | Description |
|------|-------------|
| `craft_evil_udf.py` | Python UDF image crafter (parameterized st_size/rt_l) |
| `evil.udf` | Crafted image: st_size=32768, rt_l=65535 |
| `build.sh` | Runs the crafter |
| `run.sh` | vnconfig + mount commands |
| `fix.diff` | git-apply-able fix: bound rt_l by st_size |
| `VERDICT.md` | Full analysis: mechanism, evidence, fix validation |
| `run.log` | Unpatched kernel panic output |
| `fix_run.log` | Patched kernel clean output |
| `fix_build.log` | Single-fix kernel build log |
| `panic.txt` | Panic signature from serial console |
| `env.txt` | Guest environment |
| `manifest.json` | Machine-readable catalog |
