β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4802

initsd() verify loop retries a mismatched block forever (verified=0 with no bound): unbounded uninterruptible kernel loop in the ioctl thread

Summary

The while(!verified) loop rewrites/re-reads on every mismatch with no retry counter; biowaits are PCATCH-less so a persistent mismatch spins the ioctl thread unkillably. Loop code-certain; trigger NOT reachable on the md guest (buffer-cache coherence shields the verify across 3 full verified inits under a hammering writer - documented negative result). Bound-the-retry fix drafted.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4802 Β· 5 files
FileTypeDescriptionSize
initlivelock.c β€” 4.0 KB view raw
env.txt β€” 312 B view raw
README.md β€” 2.6 KB ↓ raw
verdict.json β€” 2.7 KB view raw
manifest.json β€” 1.0 KB view raw

DF-4802 β€” initsd() verify loop retries a mismatched block forever: unbounded uninterruptible kernel loop (CWE-835); the mismatch was not reachable on this guest

  • File: sys/dev/raid/vinum/vinumrevive.c:526-565 (initsd verify path)
  • Severity: Low (speculative; needs a device that returns data other than what was just written while the subdisk is sd_initializing)
  • Bucket: base:raid
  • Status: not reproduced on the md-backed guest (see analysis)

The defect

verified = 0;
while (!verified) {                 /* vinumrevive.c:526 */
    ... write zeroes; biowait;              /* :527-536  */
    if (error) ...
    if ((error == 0) && verify) {           /* :539 */
        ... read back; biowait;         /* :540-548 */
        else if ((*bp->b_data != 0)
        ||(bcmp(bp->b_data, &bp->b_data[1], bp->b_bcount - 1))) {
            kprintf("vinum: init error ...");
            verified = 0;               /* :560  <-- retry forever */
        } else
            verified = 1;
    } else
        verified = 1;
}

There is no retry bound: a persistent readback mismatch (flaky media, a lying device, or any concurrent writer that lands between the zero-write and the readback) spins this loop in the ioctl thread forever. The biowaits have no PCATCH, so the thread is unkillable while it loops. (initsd is reached per-block from start_object/VINUM_SETSTATE(object_up, verify=1), vinumstate.c:883-887.)

Why it did not reproduce here (negative result with evidence)

The concurrent-writer angle was tested thoroughly on the guest (initlivelock.c): a tight pwrite loop on the raw subdisk device (/dev/vinum/sd/vol.p0.s0, vinumstrategy VINUM_SD_TYPE β†’ sdio directly, vinumrequest.c:135-139) hammering the exact block being initialized β€” zero mismatches in 3 full 3 MB inits; the readback never saw the concurrent garbage. Reason: sdio() dispatches via vn_strategy(drive->vp, ...) (vinumrequest.c:981) β€” the init's write+readback pair is served coherently from the vnode/buffer-cache view, while the raw physio writer bypasses it; the verify effectively re-reads its own just-written zeros. A raw concurrent writer therefore cannot desync the pair on this backing, and the mismatch arm stayed unreachable (no vinum: init error line was ever produced).

On real hardware with media that returns corrupted data (the case the verify exists for), the loop is provably unbounded: there is no counter and no error exit on the mismatch branch.

Files

  • initlivelock.c β€” the attempted trigger (init ioctls with verify=1 + concurrent raw-sd garbage writer; documents the buffer-cache shielding)
  • verdict.json, README.md, env.txt

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not validated - the trigger itself was not reachable on this guest, so there is no baseline behavior to flip

per-fix-DF-4802

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md analysis; initlivelock.c (the attempted trigger); dmesg in the guest session showed zero 'vinum: init error' lines across three full verified inits with the hammer active

PoC changes

driver had to re-fill the ioctl msg template every iteration (the IOC_OUT reply overlays the request buffer, clobbering type/state)

Verified recommended fix

bound the retry: after 3 consecutive mismatches of the same block set error=EIO and verified=1 (draft hunk included in the DF-4800/4801 combined fixwork, not validated separately)

Verdict

initsd()'s verify loop (vinumrevive.c:526-565) sets verified=0 on a readback mismatch (:555-560) with no retry bound, so a persistent mismatch spins the ioctl thread in an unbounded, uninterruptible (PCATCH-less biowait) kernel loop. The loop and the missing bound are certain from the code; the mismatch itself was NOT reachable on this guest: a tight pwrite hammer on the raw subdisk device hammering the exact block being initialized produced zero mismatches across three full 3 MB verified inits, because sdio() dispatches via vn_strategy(drive->vp) (vinumrequest.c:981) and the init's write+readback pair is served coherently from the buffer-cache view while the raw physio writer bypasses it. On hardware whose media returns corrupted data (the case verify exists for) the loop provably never exits. Filed Low/speculative: code-certain defect, trigger not demonstrated.