# DF-5108 — VERDICT

**Status: not_reproduced (race-gated heap OOB write; code path certain, race
window not reached within guest budget). Impact class: memcorrupt.**

## Defect (static, vinumlock.c)
The rangelock allocator can commit `pos` = one-past-the-end of the
256-entry lock table:

1. `:139` `while (plex->usedlocks == PLEX_LOCKS)` — equality only. Any
   usedlocks > PLEX_LOCKS (produced by the bug itself, or by the
   unprotected `usedlocks++`/`--` RMW pair across CPUs — `:190` vs
   `:219` in interrupt-context unlockrange) permanently defeats the wait
   AND the full-table wakeup at `:220` (waiters then sleep in
   `tsleep("vlock")` forever, no PCATCH).
2. The conflict path `:155-171`: a requester that finds its stripe held
   by another request tsleeps on that lock entry. tsleep drops the MP
   token (vinum is non-MPSAFE — kern_device.c:121 wraps every entry point
   with get_mplock), so while it sleeps, other CPUs' requests consume
   free slots. unlockrange frees the conflicting entry and wakes it, but
   a third request can retake that entry before the sleeper is scheduled.
3. The sleeper then rescans (`:168-170`, no fullness recheck) with all
   256 slots in use: the scan runs to `lock == &plex->lock[PLEX_LOCKS]`,
   `pos` stays NULL, and `:181-182` sets `pos = lock` — one past the end.
4. `:188-190` writes `pos->stripe` (attacker-influenced 64-bit disk
   offset) and `pos->bp` (kernel pointer) 16 bytes past the
   `kmalloc(PLEX_LOCKS * sizeof(struct rangelock))` = 4096-byte object
   (vinumconfig.c:1467), and increments usedlocks past 256 — feeding (1):
   every subsequent lockrange on this plex repeats the OOB write.

Reachability: rangelocks are acquired only for RAID-5 plex request groups
(vinumraid5.c:592 `rqg->lockbase = m.stripebase` → vinumrequest.c:396-397
inside launch_requests' crit section — whose own comment admits the bottom
half completes concurrently). Any local user generating concurrent I/O on
a vinum-backed filesystem reaches lockrange; the race needs the table
exactly full at a wake boundary.

## What was attempted (guest, 6 vCPU)
* Built a live RAID-5 volume (3 × 100 MB vn-backed drives, 32 KB stripes,
  forced up) and hammered `/dev/vinum/vol/rv` with the included
  lockrange_race (pwrite from N threads at random offsets from a
  P-stripe pool to force same-stripe conflicts + near-full table).
* Three completed runs (see run.log): 400×32K×300 stripes × 300 s;
  800×32K×270 × 300 s; 300×128K×258 × 300 s — ≈11 M write ops, 0 errors,
  no panic, no "vlock" wedge, dmesg clean.
* Why it did not fire: aggregate throughput × service time implies
  ~80-160 concurrently-held slots; the table never reached 255+1 at a
  wake boundary. The vn-on-virtio backing completes too quickly to
  accumulate ≥256 in-flight request groups; the MP token serializes
  submissions further. A production array with real disk latency and
  deep queues (or a slower backing device) reaches fullness routinely —
  that is exactly the regime the striped-lock design was written for.
* Instrumentation attempt (ulmon.c) to read plex->usedlocks via
  VINUM_PLEXCONFIG failed on the ioctl size-encoding probe; not pursued
  further within budget.

## Conclusion
Not reproduced in budget — the interleaving is real (every ingredient is
individually present: token-dropping sleeps, interrupt-context decrements,
equality checks, the unconditional `pos = lock`) but requires ≥256
in-flight RAID-5 groups at a wake boundary, which this guest's I/O stack
does not sustain. The finding stands as a race-gated heap OOB write with
permanent repeat-after-first-hit semantics (usedlocks overrun). Severity
Medium, confidence likely.

## Fix
fix.diff (same file as DF-5109's fix): `==`→`>=` both fullness checks,
post-tsleep `goto restart` recheck, defensive end-of-table guard before
commit, crit_enter/crit_exit around unlockrange's update. Validated to
build and boot (see DF-5109 fix validation); no behavioral regression in
the hammer runs' I/O path (runs completed identically on the patched
kernel during validation).

## 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.
