# DF-5768 — card-derived `erase_sector == 0` → integer divide-by-zero kernel panic in `mmcsd_delete()` (TRIM path)

**File:** `sys/bus/mmc/mmc.c` (pass 2, GLM 5.3) · sink in `sys/dev/disk/mmcsd/mmcsd.c`
**Class:** CWE-369 (divide by zero on externally-influenced data) + CWE-20

## What

`mmc_discover_cards()` derives the card erase-sector size with truncating
integer division from two card-supplied CSD fields:

* SD: `sys/bus/mmc/mmc.c:1476-1477`
  `ivar->erase_sector = csd.erase_sector * csd.write_bl_len / MMC_SECTOR_SIZE;`
* MMC: `sys/bus/mmc/mmc.c:1595-1596` (identical expression)

`csd.erase_sector >= 1` (WP_GRP/ERASE_GRP fields, +1 in the decoder) and
`csd.write_bl_len = 1 << WRITE_BL_LEN_bits` with a 4-bit, unvalidated
field, so a crafted CSD with `WRITE_BL_LEN bits == 0` gives
`write_bl_len == 1` and `ivar->erase_sector == (1 * 1) / 512 == 0`.
The EXT_CSD override (`mmc.c:1669-1671`, `1024 * byte`, only when byte
≠ 0) and the SD-STATUS override (`mmc.c:1538-1540`, `16 << au_size`,
only when ≠ 0) never repair a zero — a hostile card simply reports
`EXT_CSD[224] == 0` / `au_size == 0`.

The zero flows to mmcsd(4) via `MMC_IVAR_ERASE_SECTOR`
(`mmc.c:1949-1951`) and the first BIO_FREEBLKS (TRIM) against the disk
executes, in production `mmcsd_delete()`
(`sys/dev/disk/mmcsd/mmcsd.c:407-411`):

```c
erase_sector = mmc_get_erase_sector(dev);	/* == 0 */
start = block + erase_sector - 1;
start -= start % erase_sector;			/* %0 → #DE → panic */
```

The kernel issues BIO_FREEBLKS through `VOP_FREEBLKS` /
`devfs_spec_freeblks()` (`sys/vfs/devfs/devfs_vnops.c:2028-2049`) — i.e.
deleting blocks on a UFS filesystem mounted with TRIM, softdep
freeblks processing, etc. Raw disk nodes are `root:operator 0640`
(`sys/kern/subr_disk.c:701`). Threat model: malicious/mis-programmed
SD/MMC card inserted into a machine (the DF-5339/5430/5603 family
threat model); the panic also fires at mount/first-TRIM time with no
user interaction beyond normal filesystem use.

## How to reproduce (no SD hardware needed)

The harness KLD compiles the **production** `sys/bus/mmc/mmc.c` into the
module (verbatim `#include`) and registers it on a fake `mmcbr` bridge
(`dfhbr`) that answers enumeration with a synthetic MMC card:

* CSD: `spec_vers = 5`, `read_bl_len = 512`, erase group fields 0,
  **`WRITE_BL_LEN bits = 0`** (→ `write_bl_len = 1` byte);
* EXT_CSD: `SEC_CNT = 4096`, `ERASE_GRP_SIZE = 0` (keeps the CSD-derived
  value), `CARD_TYPE = 0`;
* a protocol-conformant state machine (CMD2 stops answering once the
  card owns an RCA, so production discovery terminates after one card).

The builtin **production** mmcsd(4) then attaches `/dev/mmcsd0`, and the
module issues a `BUF_CMD_FREEBLKS` bio that is a line-for-line mirror of
`devfs_spec_freeblks()` (devfs_vnops.c:2031-2045).

```sh
# guest (root), from a clean boot:
cd /tmp/df5768 && SYSDIR=/usr/src/sys make
kldload ./df5768.ko
```

**Expected (stock kernel):** serial console shows

```
DF-5768:   csd.erase_sector=1 csd.write_bl_len=1 -> ivar->erase_sector=0 (mmc.c:1595-1596)
DF-5768: issuing BUF_CMD_FREEBLKS to mmcsd0 ...
Fatal trap 18: integer divide fault while in kernel mode
...
Stopped at      mmcsd_task+0x6cb:       idivl   %ecx,%eax
```

and the guest goes down. With the fix (fix.diff), the same run prints
`ivar->erase_sector=1`, the TRIM completes, `kldunload` is clean and the
guest stays up.
