# DF-3178 — hpfs_mountfs performs no super/spareblock census validation:
# `su_btotal` flows unvalidated into bminit sizes (mount-time kernel panic
# or 32 MiB wired heap + 128 GiB statfs lie per crafted image)

**File:** `sys/vfs/hpfs/hpfs_vfsops.c` (finding filed at the mount layer)
**Sink:** `sys/vfs/hpfs/hpfs_subr.c:109-120` (`hpfs_bminit`), `hpfs_vfsops.c:408` (`hpfs_statfs`)
**Severity:** Medium (crafted-image, root/auto-mount threat model — hpfs is
never user-mountable: `get_fscap("hpfs")` = `SYSCAP_RESTRICTEDROOT`,
sys/kern/vfs_syscalls.c:646-655)
**Verification:** reproduced on the QEMU guest — panic variant 1/1 runs,
wiring variant 2/2 runs.

## What this is

After the two magic checks (`hpfs_vfsops.c:282-291`) the mount layer does
**zero** validation of the volume census.  `su_btotal` (a `u_int32_t` read
straight off the image at `hpfs_vfsops.c:268`) is consumed as a size/index
in three places with no bound:

1. `hpfs_bminit` (`hpfs_subr.c:109`): `hpm_dbnum = (su_btotal + 0x3FFF) / 0x4000`
   then a single **bmind-list bread of `((dbnum + 0x7F) & ~0x7F) << 2` bytes**
   (`hpfs_subr.c:119-120`).  For `su_btotal >= 0x10000001` (any volume
   claiming > 128 GiB in 512-byte sectors) the bread size exceeds
   `MAXBSIZE` (64 KiB) and `getblk` panics the kernel **at mount time**:

       panic: getblk: size(1048576) > MAXBSIZE(65536)
       getblk() at getblk+0x4e5
       breadnx() at breadnx+0x284
       hpfs_bminit() at hpfs_bminit+0xb0
       hpfs_mount() at hpfs_mount+0x4b7
       vfs_mount() at vfs_mount+0x14f

2. Same function: `kmalloc(dbnum * BMSIZE)` + `kmalloc(dbnum * 4)` —
   for any `su_btotal` in `(0x1000 … 0x10000000]` the mount **succeeds**
   and wires `dbnum × 2048 + dbnum × 4` bytes of kernel heap per mount
   (demonstrated: 32.1 MiB from a 1.25 MiB image claiming 0x0FFFC000
   sectors; ceiling without panicking = 32 MiB; the 512 MiB
   `su_btotal=0xFFFFC000` case panics per (1), and `su_btotal >= 0xFFFFC001`
   wraps the u32 addition and is DF-0862's undersize-OOB regime).

3. `hpfs_statfs` (`hpfs_vfsops.c:408`): `f_blocks = su_btotal` — the mount
   advertises `su_btotal × 512` bytes of capacity regardless of the backing
   device (demonstrated value: 0x0FFFC000 × 512 ≈ 128 GiB on a 1.25 MiB
   image; would be 2.0 TiB for the panic-regime value).

This is the hpfs twin of DF-3128 (msdosfs `mountmsdosfs()` never bounds
BPB geometry by the backing device size) and the mount-layer enabler of the
already-filed subr-side findings (DF-0862 wrap regime, DF-0861/3144/3145
`sp_cpinum` family — note `sp_cpinum`/`sp_cpi` are likewise unvalidated
here).

## Evidence in this pack

* `craft_img.py` — builds both variants (same 1.25 MiB image, only
  `su_btotal` differs).
* `mount_ro.c`, `build.sh`, `statfs_probe.c`, `run_full.sh`, `run_wiring.sh` —
  harness.
* `panic.txt`, `boot_panic_console.log`, `run_panic_attempt.log` — the
  deterministic mount-time panic (`su_btotal = 0xFFFFC000`, 1st attempt).
* `run.log` (run_full) and `run_wiring_partial_df0927_trip.log` — two
  independent successful wiring-variant mounts: `HPFS_mount … 32.1M`,
  `vm.stats.vm.v_free_count` 848021→839002 and 847502→838428 (≈9k pages
  ≈ 35 MiB total kernel footprint), `MOUNT_RC=0 wall=0s`.
* `VERDICT.md` — full narrative, including the **side observation** that
  entering the crafted mount by path (`df`/`open(2)`) wedges the whole
  system (consistent with the filed DF-0927/DF-3143 unbounded-walk family;
  root vnode/dirblk content is attacker data — NOT claimed by this finding,
  logs preserved).
* `fix.diff` — validate `su_btotal`/`su_rootfno` against `dev_dpsize()`
  in `hpfs_mountfs` (same pattern as DF-3128's verified fix).

## Reproduce

```
./build.sh                      # craft image, push, build mount_ro in guest
python3 craft_img.py df3178p.img panic   # panic variant
# guest: vnconfig vn0 df3178p.img && ./mount_ro /dev/vn0 /tmp/hpfsmp
#   -> panic: getblk: size(1048576) > MAXBSIZE(65536)
python3 craft_img.py df3178w.img wiring  # wiring variant
# guest: vnconfig vn0 df3178w.img && ./mount_ro /dev/vn0 /tmp/hpfsmp
#   -> mount OK; vmstat -m | grep HPFS -> 32.1M; v_free_count -~9k pages
```

Success criterion: (panic variant) `Fatal trap`/`panic: getblk:` on the
serial console with the `hpfs_bminit → hpfs_mount` trace; (wiring variant)
`MOUNT_RC=0`, `HPFS_mount 3 32.1M`, free-page drop ≈9,000, released on
umount.
