# DF-0872 — NTFS BPB bpmftrec divide-by-zero / undefined-shift

**Finding:** `Divide-by-zero and undefined-shift in BPB bpmftrec computation from crafted boot sector`
**Severity:** Medium (mount-time kernel DoS via crafted filesystem image)
**CWE:** CWE-369 (Integer Divide-by-Zero)
**CVSS:** `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H`

## Reproduce

```sh
./build.sh &&
# as root on a DragonFlyBSD guest with vnconfig + mount_ntfs:
./run.sh
```

**Expected on the unpatched `6.5-DEVELOPMENT #0` kernel:** the guest
kernel-panics with `Fatal trap 18: integer divide fault ... Stopped at
ntfs_mountfs.isra.0+0x5ca: idivl %ecx,%eax` and drops to the `db>` DDB prompt.
The ssh session dies; the guest must be reset.

**Expected on the patched kernel (fixed `ntfs.ko`):** `mount_ntfs` prints
`mount_ntfs: /dev/vn1: Invalid argument` and exits 71 (`EINVAL`); the guest
stays up.

## How it works

`ntfs_mountfs()` reads the NTFS boot sector (BPB) and computes the
bytes-per-MFT-record divisor at `sys/vfs/ntfs/ntfs_vfsops.c:349-355`:

```c
int8_t cpr = ntmp->ntm_mftrecsz;          /* u8 from BPB offset 0x40 */
if( cpr > 0 )
    ntmp->ntm_bpmftrec = ntmp->ntm_spc * cpr;
else
    ntmp->ntm_bpmftrec = (1 << (-cpr)) / ntmp->ntm_bps;   /* bps==0 => #DE */
```

`bf_bps` (bytes per sector) and `bf_spc` (sectors per cluster) are taken
directly from attacker-controlled disk content with **no validation** — only
the 8-byte OEM id (`"NTFS    "`) is checked (`:343`). A crafted image with
`bf_bps = 0` and `bf_mftrecsz <= 0` (signed) drives the `(1 << (-cpr)) /
ntm_bps` division by zero → CPU `#DE` → non-resumable kernel trap → panic.

Two related sub-bugs:
- `bf_mftrecsz == 0x80` (`cpr = INT8_MIN = -128`) → `1 << 128`, undefined shift.
- `bf_spc == 0` with `cpr > 0` → `bpmftrec = 0`, which later divides by zero in
  `ntfs_statfs()` at `:620` / `ntfs_statvfs()` at `:646`.

## Files

| file | purpose |
|---|---|
| `craft_ntfs.c` | minimal trigger — crafts `bf_bps=0, bf_mftrecsz=0xF6` image |
| `craft_variants.c` | emits 4 images exercising all three sub-bugs + a control |
| `build.sh` | `cc -O2 -o craft_ntfs craft_ntfs.c` |
| `run.sh` | vnconfig + mount_ntfs the crafted image (expects panic on unpatched) |
| `fix.diff` | git-apply-able fix — validates BPB divisors/shift before use |
| `VERDICT.md` | full narrative with path:line citations |
| `panic.txt` | the `Fatal trap 18 ... ntfs_mountfs+0x5ca` serial-console excerpt |
| `build.log` / `run.log` / `run.2.log` | full untrimmed logs |
| `fix_build.log` | full single-fix kernel build output (rc=0) |
| `fix_run.log` / `fix_run.2.log` | patched-kernel PoC re-run (EINVAL, no panic) |
| `fix_variants.log` | all 4 variants rejected by the fix |
| `env.txt` | guest `uname`, `cc`, sysctls |
| `manifest.json` | machine-readable catalog |

## Threat model

A local user who can cause a crafted NTFS image to be mounted instantly
kernel-panics the machine. With `vfs.usermount=1` and a root-created vnode disk
owned by the attacker, the trigger is fully unprivileged. The standard model for
this class (admin mounts an untrusted filesystem image) also applies. Impact is
pure DoS — no memory corruption, no escalation.
