Re: [PATCH v2 2/6] rcu: Make Tiny call_rcu() safe to call from any context
Puranjay Mohan <[email protected]> Mon, 3 Aug 2026 15:40:32 +0100
| Newsgroups | dev.linux.lists.linux-rt-devel,dev.linux.lists.sashiko-reviews,org.kernel.vger.bpf,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CANk7y0g+Yf0Vy0jZCoD6NicDXdJ=wo4WpmiBLo3MPqKaNUXO-w@mail.gmail.com> |
On Mon, Aug 3, 2026 at 3:35=E2=80=AFPM <[email protected]> wrote: > > Thank you for your contribution! Sashiko AI review found 2 potential issu= e(s) to consider: > - [High] Relocating `debug_rcu_head_queue()` into `rcu_do_enqueue()` bypa= sses double-free detection for deferred callbacks, silently corrupting the = lockless list and inducing a permanent hard livelock during `rcu_defer_drai= n()`. > - [Medium] Invalid `WARN_ON_ONCE` in `call_rcu()` falsely flags safe NMI = executions due to a flawed concurrency assumption. > -- > > --- Patch [2]: [PATCH v2 2/6] rcu: Make Tiny call_rcu() safe to call from= any context --- > commit 866e1596ae5a7314657b9b0164dfabf922016c70 > Author: Puranjay Mohan <[email protected]> > > rcu: Make Tiny call_rcu() safe to call from any context > > Give Tiny call_rcu() the same treatment as Tree RCU. When interrupts are > disabled and the scheduler is up, stage the callback on a lockless list > that an irq_work re-issues later. One global list and irq_work suffice > since Tiny RCU is uniprocessor, and there is no CPU-offline drain. > > > diff --git a/kernel/rcu/tiny.c b/kernel/rcu/tiny.c > > index dccccd6be9411..5736b964d8ee1 100644 > > --- a/kernel/rcu/tiny.c > > +++ b/kernel/rcu/tiny.c > > [ ... ] > > > @@ -42,8 +44,86 @@ static struct rcu_ctrlblk rcu_ctrlblk =3D { > > [ ... ] > > > +static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func) > > +{ > > + /* A re-entrant call_rcu() during the drain would livelock it; dr= op it. */ > > + if (rcu_defer_draining && !in_nmi()) { > > + WARN_ONCE(1, "call_rcu() re-entered during callback drain= ; leaking callback\n"); > > + return; > > + } > > + head->func =3D func; > > + if (llist_add((struct llist_node *)head, &rcu_defer_list)) > > [Severity: High] > Does moving the debug_rcu_head_queue() check into rcu_do_enqueue() bypass > double-free detection for deferred callbacks? > > If a double-free occurs, llist_add() will be called twice on the same nod= e here, > corrupting the lockless list by creating a circular link (head->next =3D = head). > > When the irq_work executes rcu_defer_drain(), won't the llist_for_each_sa= fe() > iteration infinitely hang on the circular list before the delayed debug c= heck > can prevent the livelock? > > > + irq_work_queue(&rcu_defer_iw); > > +} > > [ ... ] > > > @@ -157,29 +237,15 @@ EXPORT_SYMBOL_GPL(synchronize_rcu); > > */ > > void call_rcu(struct rcu_head *head, rcu_callback_t func) > > { > > - static atomic_t doublefrees; > > - unsigned long flags; > > [ ... ] > > > + if (should_rcu_defer()) { > > + call_rcu_defer(head, func); > > return; > > } > > > > - head->func =3D func; > > - head->next =3D NULL; > > - > > - local_irq_save(flags); > > - *rcu_ctrlblk.curtail =3D head; > > - rcu_ctrlblk.curtail =3D &head->next; > > - local_irq_restore(flags); > > + /* An NMI reaching here entered with irqs enabled, so the enqueue= can race. */ > > + WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi()); > > [Severity: Medium] > Is this concurrency assumption backwards? > > If an NMI interrupted a context where IRQs were enabled, that interrupted > context could not have been in the middle of rcu_do_enqueue() because > rcu_do_enqueue() disables IRQs via local_irq_save(). > > Since it's impossible for the NMI to race with rcu_do_enqueue() in this > scenario, will this falsely trigger kernel warnings during safe NMI > executions? This code assumes that irqs_disabled() returns true in NMI handlers, so the execution should not reach this warning in NMI handlers.