# DF-5172 — vcdstrategy() drains sc->bio_queue with no serialization (latent race)

**vkernel virtual CD-ROM driver** — `sys/dev/virtual/vkernel/cdrom/vcd.c`

## What

`vcdstrategy()` (vcd.c:148-149) manipulates `sc->bio_queue` with
`bioqdisksort()` (sorted TAILQ insertion, sys/kern/subr_disk.c:1353) and
`bioq_takefirst()` (sys/sys/buf2.h:167) **with no lock, crit section, or
cothread** — and then performs a blocking `pread`/`pwrite` *inside* the
drain loop, which holds the queue open for the full duration of the host
I/O. Two concurrent strategy calls on different vCPUs would corrupt the
TAILQ (lost/double-removed bios → double `biodone` → buffer-cache state
corruption inside the vkernel process).

The vdisk sibling was reworked precisely to fix this shape: vkd moved all
queue manipulation under `cothread_lock()` (vdisk.c:228-231, 250-255,
279-298). vcd never got the rework.

## Why it is only *latent* (the shield, verified)

`vcd_ops` is declared `{ "vcd", 0, D_DISK }` — **without `D_MPSAFE`**
(vcd.c:66-73). For any device whose ops lack D_MPSAFE,
`dev_dstrategy()` takes the global kernel lock around the strategy call:

```c
dev_needmplock()  =>  (dev->si_ops->head.flags & D_MPSAFE) == 0   /* kern_device.c:119-121 */
...
if (needmplock) get_mplock();                                     /* kern_device.c:384 */
(void)dev->si_ops->d_strategy(&ap);
if (needmplock) rel_mplock();                                     /* kern_device.c:388 */
```

So today every `vcdstrategy()` call on the system executes under the
mplock — concurrent entries are impossible and the unlocked bio_queue
cannot be raced. Exactly like DF-5010's dscheck shield, this is an
external, incidental guard: the day someone marks vcd D_MPSAFE (the
usual modernization step for vkernel drivers — note the mplock is a
known scalability bottleneck), the race goes live with a window that
includes a full host-side `pread`.

## Verification (guest, 2026-09-30)

Static + dynamic:

* **Dynamic attempt:** 4-vCPU vkernel (`-n 4`; the default boots 2), PID1
  forked 4 children hammering `/dev/vcd0` with 20,000 mixed 64 KB
  reads/writes each (offsets 2048-aligned; the first attempt with 64-byte
  alignment was bounced by `dscheck` with ~2000 "not on a sector
  boundary" messages — those bios never reached the driver, so the test
  was fixed). Result: **no crash, clean exit** (`RACE SURVIVED`,
  run.log / run.2.log) — consistent with the mplock shield, not with a
  racy driver.
* **Shield trace:** kern_device.c:119-121 + 384-388 (above); vcd.c:67 has
  no D_MPSAFE. The vkernel is fully SMP (`-n N`, MAXCPU; cpu0..cpu3
  observed in run logs).

Not reproduced *because it cannot currently happen*; filed as a latent
hardening defect in the same spirit as DF-5010 (Info).

## Recommended fix

When touching vcd (e.g. for DF-5171), either adopt vdisk's cothread
structure or, minimally, wrap the bioq manipulation + drain in a
`crit_enter()/crit_exit()` (single-CPU-per-thread) or a spinlock — and
only *then* consider D_MPSAFE. Not validated in this run (no live bug to
observe); the validated diff in this batch is DF-5171's.
