# DF-5120 — vtrnd_harvest: unbounded `virtqueue_poll()` busy-wait runs in the softclock callout thread

File: `sys/dev/virtual/virtio/random/virtio_random.c`
Class: robustness / denial of service (CWE-835 — loop with unreachable exit condition)
Severity: Low · Confidence: likely (mechanism is code-certain; trigger requires a host-side fault)

## What the code does

- `vtrnd_attach()` arms a periodic callout every 5 s: `callout_reset(&sc->vtrnd_callout, 5 * hz, vtrnd_timer, sc)`
  (virtio_random.c:142), re-armed unconditionally by `vtrnd_timer()` (virtio_random.c:229).
- `vtrnd_harvest()` enqueues one 4-byte *writable* descriptor pointing at a stack variable,
  notifies the device, then calls `virtqueue_poll(vq, NULL)` (virtio_random.c:205-215).
- `virtqueue_poll()` is an **unbounded** busy-wait:

  ```c
  void *
  virtqueue_poll(struct virtqueue *vq, uint32_t *len)
  {
      void *cookie;
      /* We only poll the virtqueue when dumping to virtio-blk */
      while ((cookie = virtqueue_dequeue(vq, len)) == NULL)
          ;
  ```
  (sys/dev/virtual/virtio/virtio/virtqueue.c:572-580)

  It was designed for the `virtio-blk` **dump path** (crash-dump I/O, where sleeping is
  impossible and the device is assumed responsive). `vtrnd` reuses it from a periodic
  callout that runs in the softclock thread.

## Why that is a hazard

If the host entropy backend never advances the used ring, the softclock thread for the
arming CPU spins forever with preemption effectively pinned:

- every future callout on that CPU starves (timers, watchdogs, protocol ticks);
- the guest becomes unusable — a hard hang with no panic, no watchdog, no recovery
  except a forced reset.

Realistic ways the used ring never advances while guest timers keep running:

1. QEMU/KVM `virtio-rng` backed by a **blocking EGD chardev** on the host that stalls
   (this is a documented operational failure mode of virtio-rng backends).
2. Host-side device wedged/reset out-of-band while the guest keeps scheduling
   (vhost restart, live-migration stall of the rng backend, host suspend/resume race).
3. Theoretical in-guest window: `vtpci_shutdown()` (virtio_pci.c:347-349) forcibly stops
   the device while the vtrnd callout is still armed — vtrnd has no shutdown method to
   disarm it — but the shutdown→cpu_reset window is normally far shorter than 5 s, so
   this variant needs a stalled post-shutdown handler to matter.

The virtio threat model treats the *host* as trusted, so this is not exploitable by a
guest-local unprivileged user; it is a fault-tolerance defect: **one wedged host
entropy backend turns into a full guest kernel hang instead of a degraded-but-alive
system.**

## Why not reproduced here

The audit guest has no virtio-rng device at all (dfbsd-qemu/vm.sh attaches only
`virtio-blk` and `virtio-net`; guest dmesg shows no `vtrnd`), and reproducing the hang
requires host-side fault injection (a stalled rng backend), which is exactly the
trigger under test. Analysis is complete at the source level; see VERDICT.md.

## Recommended fix (see fix.diff)

Bound the wait in `vtrnd_harvest()` (~10 ms cap using `virtqueue_pending()` +
`DELAY()`), and on timeout simply stop harvesting: the still-queued descriptor keeps
`virtqueue_empty()` false, which already gates every future `vtrnd_harvest()` call
(virtio_random.c:205-206), so the driver degrades to "no virtio entropy" instead of
hanging softclock.
