# DF-4829 — VERDICT

**Status: not_reproduced / latent (defect certain, reachability dead in current tree).**
**Impact: none today; memory-corruption-class (CWE-190→CWE-787) if ever reached.**

## The defect (proven by code inspection, certain)

`mdstrategy_malloc()` truncates the 64-bit bio offset to 32 bits and then
computes the secp-array growth size in 32-bit unsigned arithmetic:

```
md.c:253   secno = (unsigned)(bio->bio_offset >> DEV_BSHIFT);
md.c:303   secpp = kmalloc((secno + nsec + 1) * sizeof(u_char *), M_MD, M_WAITOK|M_ZERO);
md.c:306   bcopy(sc->secp, secpp, sc->nsecp * sizeof(u_char *));
md.c:307   kfree(sc->secp, M_MD); sc->secp = secpp;
md.c:309   sc->nsecp = secno + nsec + 1;
md.c:310   secpp = &sc->secp[secno];
md.c:315/323  *secpp = ... (wild write)
```

With `pwrite(fd, nonuniform_512B, 0x1FFFFFFE00)`: secno = 0xFFFFFFFF, nsec = 0
(after the `while (nsec--)` decrement), sum = 0x100000000 → wraps to 0 →
`kmalloc(0)`. Per `sys/kern/kern_slaballoc.c:884-890`, kmalloc(0) returns
`ZERO_LENGTH_PTR = (void *)-8` — so md.c:306 would bcopy `nsecp*8` bytes to
VA −8 → fatal fault. With a 1024-byte transfer the sum wraps to 2 → a real
16-byte chunk → `nsecp*8`-byte heap overflow (content: old secp array —
kernel heap pointers / small ints), followed by a mandatory wild write at
`secp + 0xFFFFFFFF*8` (≈ +32 GB). Every wrapped growth ends in the wild
write inside the same request — there is no post-corruption execution
window, so even if reachable this is panic-with-heap-smash, not a clean
privesc primitive.

## Why it did not reproduce (traced, with the gate)

Attempted on the live guest (6 vCPU, kernel `DragonFly dfbsd 6.5-DEVELOPMENT
#0: Thu Jul 2 06:02:54 UTC 2026`): trigger returns **EINVAL**, no crash.

Trace of the gate:

1. `/dev/mdN` is the `disk1_ops` covering device, not the raw `md_ops`
   cdev — `_disk_create_named` (sys/kern/subr_disk.c:691-727) creates the
   raw cdev with `make_only_dev`, which never calls `devfs_create_dev`
   (compare kern_conf.c:196-220 `make_dev` vs kern_conf.c:271-293
   `make_only_dev`), so no raw-md node exists in devfs (confirmed on
   guest: only md0, md0s0 — and after our opens, md1, md1s0).
2. Userland I/O on the covering device dispatches to `diskstrategy`
   (subr_disk.c:1222-1257) → `dscheck` (sys/kern/subr_diskslice.c:91-236):
   `secno + nsec > endsecno` → EINVAL / EOF / truncate; `endsecno` for the
   whole-disk slice = `d_media_blocks` = MD_NSECT = 20000 (md.c:31-33, 423).
   Boundary verified live: pwrite @ 0x400000 → 512 OK; @ 0x9C4000±,
   0x1FFFFFFE00, 0x7FFFFFFE00 → EINVAL.
3. The single unbounded dscheck branch — WHOLE_DISK_SLICE, part ≥ 128
   (subr_diskslice.c:159-176: `nbio->bio_offset = bio->bio_offset | part << 56`
   passed through "with no further interpretation") — requires a cdev whose
   minor encodes part ≥ 128. Partition cdevs are only manufactured from
   on-disk disklabel entries (subr_disk.c:255-268, 440-455; label partition
   indices < 128); devfs cannot resolve mknod'd nodes to the unpublished raw
   cdev. No in-tree creator (grep: only sys/dev/disk/fd/fd.c:1085 mentions
   the mechanism, commented out).
4. In-kernel bios (buffer cache via mounted md, swap) route through the same
   dscheck-bounded slice strategy.

Therefore mdstrategy_malloc can only ever observe secno < 20000 and
nsec ≤ 256 (MAXPHYS/512) → max sum 20257 → the wrap is unreachable.

## What WAS reproduced

- The growth code path itself executes in-bounds (write @ 4 MiB grows the
  array to 8193 entries — sum arithmetic working normally).
- The bounds gate behavior matrix (probe3 in run.log).
- (Side observation: opening /dev/md0 auto-created md1 on the guest — live
  confirmation of known DF-2478.)

## Exploit chain

Empty — no reachable primitive. If the gate were ever bypassed the chain
would be: open /dev/mdN (root/operator 0640) → pwrite non-uniform 512B at
0x1FFFFFFE00 → bcopy to (void*)-8 → panic; or 1024B variant → 16-byte-chunk
heap overflow → wild write at +32 GB → panic. The mandatory wild write in
the same request forecloses controlled post-corruption execution.

## Fix

`fix.diff` — 64-bit sector number + explicit growth cap (defense-in-depth;
not build-validated since nothing is reproducible to validate against:
fix_status = not_testable).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
