# DF-0820 — `ffs_mountfs` missing superblock-geometry validation

**Status:** REPRODUCED (panic / heap-corruption DoS, root-reachable). NOT exploitable
to `uid=0` — two valid hard blockers (see VERDICT.md §3).

**Impact:** panic / kernel heap corruption from a crafted UFS filesystem image. The
finding's `vfs.usermount=1` unprivileged-trigger claim is **incorrect** for UFS.

## What this is

`ffs_mountfs` (sys/vfs/ufs/ffs_vfsops.c) validates only `fs_magic` and `fs_bsize`
(L642-646). Every other geometry field is consumed raw from the attacker-controlled
on-disk superblock — as a divisor (`fs_fsize`/`fs_ipg`/`fs_fpg`), an allocation size
(`fs_sbsize`, the `int size` accumulation at L680-685), and a loop bound (`fs_frag`
L687, `fs_ncg` L703). A crafted image triggers, depending on the field:

| Field | Effect | Confirmed |
|---|---|---|
| `fs_fsize=0` | divide-by-zero @ L681 `howmany(size,fs_fsize)` | ✅ `Fatal trap 18 @ ffs_mountfs+0x2ee` |
| `fs_ncg=-1` | `bzero(fs_contigdirs, (size_t)-1)` @ L709 — unbounded heap zero-fill | ✅ `panic: vm_fault: fault on stack guard` (memset←ffs_mount) |
| `fs_ncg=858993460` | `int size` wraps small → `*lp++=contigsumsize` loop @ L703-704 writes 3.4 GiB of controlled value into 2 KiB | ✅ `Fatal trap 12 @ ffs_mountfs+0x58e` (`movl %ecx,-0x4(%rsi)`) |
| `fs_frag=0` | L687 loop never advances → infinite mount loop | ✅ (covered by fix) |

The bzero and the controlled-value loop are genuine write primitives, but both are
**unbounded synchronous stores that page-fault and panic before the mount syscall
returns**, and the primitive is **reachable only from a root-context mount** (UFS is
not in the `vfs.usermount` allow-list). Both are valid Phase-6 hard blockers — no
unprivileged→root chain exists. See VERDICT.md for the full escalation analysis.

## Files

- `craft.c` — superblock patcher. `craft base.img out.img <field=value>...`
- `harness.c` — transcribes the L680-709 size arithmetic + write loops verbatim with a
  poisoned allocator; proves, per variant, exactly which line overflows and by how much.
- `reproduce.sh` — guest-side reproducer (builds base image, patches, mounts).
- `build.sh` / `run.sh` — exact build & run commands.
- `offsets.c` — prints `struct fs` field offsets (used to locate patch sites).
- `VERDICT.md` — full narrative: source trace, all panic signatures, escalation
  analysis + blockers, fix, Phase-8 before/after.
- `panic_fsize0.txt` / `panic_ncgneg1.txt` / `panic_ncghuge.txt` — crash signatures
  from `dfbsd-qemu/boot.log`.
- `harness_output.txt` — full harness characterization of all variants.
- `fix.diff` — git-apply-able fix (validation block + `int size`→`size_t`).
- `fix_build.log` — full single-fix kernel build output (rc=0).
- `fix_run.log` — Phase-8 before/after on all variants + regression.
- `env.txt` — guest environment.

## How to reproduce

The guest must be on the `with-src` base (default `#0` GENERIC, INVARIANTS ON).
Run as **root** (UFS mount is not user-mountable):

```sh
# host
scp -F dfbsd-qemu/config -r findings/poc/DF-0820/. dfbsd:/root/
./dfbsd-qemu/vm.sh run_root 'cd /root && sh build.sh'
# each variant panics the guest — capture then reset between
./dfbsd-qemu/vm.sh run_root 'cd /root && sh run.sh fsize0'    # Fatal trap 18
grep -iE 'fatal trap|stopped at' dfbsd-qemu/boot.log | tail
./dfbsd-qemu/vm.sh reset with-src
./dfbsd-qemu/vm.sh run_root 'cd /root && sh run.sh ncgneg1'   # bzero panic
./dfbsd-qemu/vm.sh reset with-src
./dfbsd-qemu/vm.sh run_root 'cd /root && sh run.sh ncghuge'   # overflow panic
```

## Fix

Apply `fix.diff` to `/usr/src`, `make -j6 nativekernel KERNCONF=X86_64_GENERIC &&
make installkernel`, reboot. All crafted variants are then rejected with
`EINVAL` ("incorrect super block"); legitimate filesystems still mount.
Validated on single-fix kernel `6.5-DEVELOPMENT #1` (fix_run.log).
