# DF-4829 — md(4) mdstrategy_malloc sector-array growth integer wrap (LATENT — unreachable in current tree)

## What the code bug is

`sys/dev/disk/md/md.c` `mdstrategy_malloc()`:

- md.c:253  `secno = (unsigned)(bio->bio_offset >> DEV_BSHIFT);`
  A 64-bit byte offset is truncated to a 32-bit sector number.
- md.c:303  `secpp = kmalloc((secno + nsec + 1) * sizeof(u_char *), ...);`
  The growth size is computed in **32-bit unsigned** arithmetic and wraps for
  any transfer whose last sector index is ≥ 2^32 (byte offset ≥ 2^41 ≈ 2 TB,
  e.g. `pwrite(fd, buf, 512, 0x1FFFFFFE00)` gives secno = 0xFFFFFFFF,
  sum = 0 → `kmalloc(0)`).
- `kmalloc(0)` does **not** return NULL — `sys/kern/kern_slaballoc.c:884-890`
  returns `ZERO_LENGTH_PTR == (void *)-8`.
- md.c:306  `bcopy(sc->secp, secpp, sc->nsecp * sizeof(u_char *));`
  → variant A: bcopy to VA −8 (instant kernel fault);
  → variant B (1024-byte transfer: sum wraps to 2 → real 16-byte chunk):
    heap overflow of `sc->nsecp * 8` bytes with kernel-pointer content.
- md.c:310+315/323  `secpp = &sc->secp[secno]; ... *secpp = secp;`
  Every wrapped growth ends in a mandatory wild write at `secp + secno*8`
  (≥ +16 GB) — unavoidable within the same request.

## Reachability verdict (why it is latent)

Every path that can feed a bio to `mdstrategy` goes through the disk layer:

1. `/dev/mdN` is the `disk1_ops` *covering* cdev (`_disk_create_named`,
   sys/kern/subr_disk.c:691-727: `make_only_dev` creates the raw cdev without
   publishing it in devfs; only the covering `make_dev_covering` node appears).
2. All userland I/O on the covering device → `diskstrategy`
   (subr_disk.c:1222) → `dscheck()` (sys/kern/subr_diskslice.c:91) which
   bounds `secno + nsec <= endsecno <= d_media_blocks` and returns
   EINVAL/EOF/truncates otherwise. For auto-created md units
   `d_media_blocks = MD_NSECT = 20000` (10 MiB) — the wrap offset
   (0x1FFFFFFE00, ≈2 TB) can never pass.
3. The only *unbounded* branch in dscheck — WHOLE_DISK_SLICE with
   partition number ≥ 128 ("kernel raw", subr_diskslice.c:166-176, offset
   OR'd with `part << 56` and passed straight through) — has **no
   userland-openable cdev**: partition cdevs are only created from
   disklabel entries (indices < 128; subr_disk.c:265,448), the raw md cdev
   is never devfs-published, and mknod cannot resolve to it.
4. In-kernel writers (mounted FS buffer cache, swap) also route through the
   dscheck-bounded slice/disk strategy.

## Live verification (guest, this run)

- Trigger built and fired (variant A and boundary probes): **no crash** —
  `pwrite @ 0x1FFFFFFE00` returns `EINVAL` from dscheck; mdstrategy never
  sees the offset.
- In-bounds growth works normally (pwrite @ 4 MiB → nsecp grows, sum 8193,
  no wrap) — proving the growth code itself executes and stays small.
- Boundary: media end is 0x9C4000 (20000 sectors); everything ≥ that
  returns EINVAL.

Conclusion: **real arithmetic defect, dead code path today**. Reported as
Info/defense-in-depth. It becomes memory corruption the moment any future
path feeds mdstrategy an unclamped bio_offset (e.g. a raw-access node like
the part≥128 mechanism being exposed, or an ioctl-attached unit added to
this driver).

## Build / run

```
cc -O2 -o mdwrap mdwrap.c     # on the guest
./mdwrap A                    # 512B non-uniform pwrite @ 0x1FFFFFFE00
./mdwrap B                    # pre-grow + 1024B pwrite (heap-overflow shape)
./probe3                      # boundary matrix
```

Expected on an unmodified kernel: "survived - pwrite errno=22" (EINVAL);
guest stays up. A panic here would mean the bounds gate was bypassed.
