/* ============================================================== * DF-4482 fix.diff — BEST ATTEMPT, DEMONSTRABLY INSUFFICIENT. * Fixes the two provable UAF arms in the waitlist machinery: * (1) drain walks/relaunches nodes freed by inline completion * (2) append walks the same freed chain * but the panic ALSO persists with this applied (run.4.log) — at * least one more lifetime bug remains in the revive-conflict * relaunch path (see VERDICT.md, arm analysis). The real fix is a * redesign: drained requests must not be launched while the walker * still references them, and freerq() must be waitlist-aware (or * completions must be deferred). Kept for the record. * ============================================================== */ diff -urN a/sys/dev/raid/vinum/vinumrequest.c b/sys/dev/raid/vinum/vinumrequest.c --- a/sys/dev/raid/vinum/vinumrequest.c 2026-09-20 18:17:04.340371043 +0000 +++ b/sys/dev/raid/vinum/vinumrequest.c 2026-09-20 18:17:10.540292314 +0000 @@ -327,16 +327,18 @@ if ((rq->flags & XFR_REVIVECONFLICT) /* possible revive conflict */ &&(!reviveok)) { /* and we don't want to do it now, */ struct sd *sd; - struct request *waitlist; /* point to the waitlist */ sd = &SD[rq->sdno]; - if (sd->waitlist != NULL) { /* something there already, */ - waitlist = sd->waitlist; - while (waitlist->next != NULL) /* find the end */ - waitlist = waitlist->next; - waitlist->next = rq; /* hook our request there */ - } else - sd->waitlist = rq; /* hook our request at the front */ + /* + * Push at the head with a cmpset loop. NEVER walk the chain: + * revive_block() steals and frees these requests while draining, + * so chain nodes can vanish at any time. + */ + do { + rq->next = sd->waitlist; + } while (atomic_cmpset_long( + (volatile unsigned long *)&sd->waitlist, + (unsigned long)rq->next, (unsigned long)rq) == 0); #ifdef VINUMDEBUG if (debug & DEBUG_REVIVECONFLICT) { diff -urN a/sys/dev/raid/vinum/vinumrevive.c b/sys/dev/raid/vinum/vinumrevive.c --- a/sys/dev/raid/vinum/vinumrevive.c 2026-09-20 18:17:04.300371551 +0000 +++ b/sys/dev/raid/vinum/vinumrevive.c 2026-09-20 18:17:04.328371196 +0000 @@ -69,6 +69,7 @@ int paritysd = 0; /* set if this is the parity stripe */ struct rangelock *lock; /* for locking */ vinum_off_t stripeoffset; /* offset in stripe */ + struct request *rqwait; /* drained waitlist */ plexblkno = 0; /* to keep the compiler happy */ sd = &SD[sdno]; @@ -195,9 +196,22 @@ } if (lock) /* we took a lock, */ unlockrange(sd->plexno, lock); /* give it back */ - while (sd->waitlist) { /* we have waiting requests */ + /* + * Atomically steal the entire waitlist. Concurrent + * launch_requests() (XFR_REVIVECONFLICT) appends requests with a + * cmpset loop on the list head and never walks the chain, so the + * stolen chain is private to us: nothing else can reference these + * requests while we launch (and synchronously complete and free) + * them. + */ + rqwait = (struct request *) + atomic_swap_long((volatile unsigned long *)&sd->waitlist, 0); + while (rqwait) { /* we have waiting requests */ + struct request *rq = rqwait; /* dequeue head first */ + + rqwait = rq->next; /* unlink before launch */ + rq->next = NULL; #ifdef VINUMDEBUG - struct request *rq = sd->waitlist; cdev_t dev; if (debug & DEBUG_REVIVECONFLICT) { @@ -213,8 +227,7 @@ rq->bio->bio_buf->b_bcount); } #endif - launch_requests(sd->waitlist, 1); /* do them now */ - sd->waitlist = sd->waitlist->next; /* and move on to the next */ + launch_requests(rq, 1); /* do it now */ } } Free(bp->b_data);