# DF-2742 VERDICT — dp->d_slice replacement/free vs unserialized diskstrategy

**Status: not_reproduced (no memory corruption observed). The
unsynchronized access itself was demonstrated live. Impact: none
demonstrated beyond transient I/O failure. Confidence: likely (race is
statically certain; allocator behavior currently masks the UAF).**

## The defect (by inspection)
- `diskstrategy()` (sys/kern/subr_disk.c:1222-1252) reads `dp->d_slice`
  and passes it to `dscheck()` with **no ds_token** (line 1246).
- `disk_probe()` on the disk_msg_core thread replaces `dp->d_slice`
  (subr_disk.c:367-368) and frees the old struct (`dsgone(&osp)`,
  subr_disk.c:373,494) while label reads inside the probe block and
  release ds_token — so strategy-path bios can interleave.
- `disk_invalidate()` (subr_disk.c:950-954) → `dsgone(&disk->d_slice)` with
  no token at all, from driver media-change/detach contexts
  (sys/bus/cam/scsi/scsi_da.c:534, sys/dev/disk/fd/fd.c:1087,
  sys/dev/disk/nata/*).
- `dscheck()` dereferences and even writes the object
  (`sp->ds_flags |= DSF_REPROBE`, subr_diskslice.c:182) — a stale pointer
  is a use-after-free read/write.

## What was observed (live)
- Run 1: 660,136 forced reprobes (DIOCSYNCSLICEINFO with nonzero arg on
  /dev/vn2) over 50 s with 8 concurrent reader threads on /dev/vn2s1:
  7 of 8 readers hit `pread: Invalid argument` storms — dscheck reading
  the transient 2-slice replacement struct mid-probe
  (`slice >= dss_nslices`, subr_diskslice.c:115-119). This is direct
  evidence that strategy-path bios consume dp->d_slice values that
  disk_probe is concurrently swapping.
- Run 2 (dual-disk interleave, vn2+vn3 both reprobed to make the freed
  4128-byte ssp claimable by the other disk's dsmakeslicestruct):
  25,210 reprobes, again no corruption.
- Why no UAF crash: dsgone's kfree(old ssp, 4128 B, M_DEVBUF) is followed
  within the same probe by dsmakeslicestruct(MAX_SLICES) = same type/size
  class, so the chunk is immediately recycled into the new ssp; the stale
  pointer therefore almost always still points at validly-initialized
  memory. Corruption requires an interposer allocation of exactly that
  size class between free and reuse, which we could not arrange from the
  available knobs.

## Honesty note
This is a real missing-serialization defect (any future allocator change,
heap pressure pattern, or different nslices mix turns it into a UAF), but
on this kernel and allocator we could not convert it into observed memory
corruption, so it is filed at Low severity with not_reproduced status.

## Suggested fix
Serialize the strategy path against slice-struct replacement: in
`diskstrategy()`, acquire `ds_token` around the `dp->d_slice` fetch +
`dscheck()` (the subsequent `dev_dstrategy()` is an async queue operation
and can run outside the token), and make `disk_invalidate()` take
`ds_token` around `dsgone()`.
