# DF-3128 — msdosfs mount accepts BPB geometry far beyond the backing device (dropped FreeBSD media-size check) → mount-time kernel memory exhaustion + non-interruptible FAT scan

- **File**: `sys/vfs/msdosfs/msdosfs_vfsops.c`
- **Lines**: 432-437 (missing check, in-tree comment admits it), 463-464, 484-490, 565-566, 578-579
- **Severity**: Medium (local DoS; unprivileged when `vfs.usermount=1`, otherwise malicious-image/root)
- **Bucket**: base:vfs (DoS)
- **Verdict**: reproduced (impact `dos`), fix validated (`fixed`)

## Reproduce

Guest: DragonFly 6.5-DEVELOPMENT stock `#0` kernel (INVARIANTS), 4 GB RAM.

```
# on the audit host
dfbsd-qemu/vm.sh up                      # guest up, clean
# copy craftfat32.c build.sh run.sh into /root/df3128/ (ssh -F dfbsd-qemu/config dfbsd)
# in the guest
cd /root/df3128 && sh build.sh           # cc -O2 -o craftfat32 craftfat32.c
sh run.sh                                # ~90 s total
```

## Expected (vulnerable, stock kernel)

1. **TEST 1**: `mount_msdos -m 644 /dev/vn3 /mnt/DF-3128a` returns **rc=0 after ~38 s**
   of non-interruptible kernel work — the image claims a **1.5 TiB** FAT32 volume
   (HugeSectors=0xC0000000) with a 4 GiB FAT (BigFATsecs=0x00800000) on a
   **9 GiB sparse** device. FreeBSD rejects this image instantly
   (`> cp->provider->mediasize /* past end of vol */`); DragonFly has no such check —
   the in-tree comment `/* XXX FreeBSD also checks media size in above */`
   (msdosfs_vfsops.c:434) admits the omission.
2. `df` on the mount reports **536,870,912 1K-blocks (512 GiB) free** — statfs
   blocks come from `pm_maxcluster+1` (2^30 clusters × 512 B) derived entirely
   from the forged BPB, not from the device.
3. `vmstat -m` shows `MSDOSFS_FAT 1 128M` — the `pm_inusemap`
   `kmalloc(M_WAITOK)` (vfsops.c:565) is attacker-sized via BigFATsecs.
4. **TEST 2**: SIGKILL of `mount_msdos` 3 s into the mount does **not** stop it:
   the kernel finishes the 38 s `fillinusemap()` scan and the mount lands anyway
   (SIGKILL is only acted on at the syscall boundary).
5. **TEST 3**: a second concurrent mount of the same image adds another 128 MiB
   (`MSDOSFS_FAT 2 256M`). BigFATsecs is 32-bit: up to **512 MiB wired per
   mount** (2^32 clusters) before the uint32 `pm_HugeSectors` store truncates;
   N concurrent mounts × attacker-chosen size bounded only by kmem.

## Fixed kernel (`fix.diff`: bound `pm_HugeSectors * pm_BlkPerSec` by `dev_dpsize(dev)`)

- Crafted image: `mount rc=71 (EINVAL) elapsed=0s`, zero `MSDOSFS_FAT` allocation.
- Regression: `newfs_msdos -F 32 -c 1` (FAT32, HugeSectors == device size) and
  `newfs_msdos` (FAT16) both mount read-write normally (`run.fixed2.log`).

## Artifacts

- `craftfat32.c` — image crafter (FAT[0] media-ID word 0x0FFFFFF8 required by
  fillinusemap's FAT[0]-vs-bpbMedia check, fat.c:933-938)
- `build.sh` / `build.log` — guest compile
- `run.sh` / `run.log` — baseline run on stock `#0` (decisive evidence)
- `run.fixed2.log` — patched kernel #2: attack rejected + FAT32/FAT16 regression OK
- `fix_build.log` — nativekernel build/install tails (full logs were in-guest)
- `env.txt` — guest uname/sysctl/vmstat baseline
- `fix.diff` — git-apply-able patch (also applies to the repo `sys/` tree)
- `VERDICT.md`, `manifest.json`, `verdict.json`
