Re: [PATCH 8/9] srcutiny: Detect duplicate callback enqueue
From: Paul E. McKenney
Date: Fri Oct 23 2026 - 15:32:13 EST
On Fri, Oct 23, 2026 at 06:18:40PM +0800, Kunwu Chan wrote:
> Detect duplicate callback enqueue attempts at call_srcu()
> entry, before the deferred list can be corrupted.
>
> Signed-off-by: Kunwu Chan <chentao@xxxxxxxxxx>
Same questions as for the previous patch. ;-)
Thanx, Paul
> ---
> kernel/rcu/srcutiny.c | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
> index 98864977bed2..4400fd2b41b7 100644
> --- a/kernel/rcu/srcutiny.c
> +++ b/kernel/rcu/srcutiny.c
> @@ -12,6 +12,7 @@
> #include <linux/irq_work.h>
> #include <linux/kasan.h>
> #include <linux/llist.h>
> +#include <linux/mm.h>
> #include <linux/mutex.h>
> #include <linux/preempt.h>
> #include <linux/rcupdate_wait.h>
> @@ -178,6 +179,7 @@ void srcu_drive_gp(struct work_struct *wp)
> while (lh) {
> rhp = lh;
> lh = lh->next;
> + debug_rcu_head_unqueue(rhp);
> debug_rcu_head_callback(rhp);
> local_bh_disable();
> rhp->func(rhp);
> @@ -235,6 +237,8 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp)
> * Also called by __srcu_defer_drain() to re-issue a deferred callback, so it
> * must not re-check the deferral condition.
> */
> +static void srcu_leak_callback(struct rcu_head *rhp) { }
> +
> static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
> rcu_callback_t func)
> {
> @@ -272,6 +276,8 @@ static void __srcu_defer_drain(struct srcu_struct *ssp)
> llist_for_each_safe(node, next, llist_del_all(&ssp->defer_cbs)) {
> struct rcu_head *rhp = (struct rcu_head *)node;
>
> + /* Bounds a node self-linked by a double call_srcu(). */
> + rhp->next = NULL;
> srcu_do_enqueue(ssp, rhp, rhp->func);
> }
> local_irq_restore(flags);
> @@ -291,6 +297,8 @@ EXPORT_SYMBOL_GPL(srcu_defer_drain);
> void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
> rcu_callback_t func)
> {
> + static atomic_t doublefrees;
> +
> /* Misaligned rcu_head! */
> WARN_ON_ONCE((unsigned long)rhp & (sizeof(void *) - 1));
>
> @@ -298,6 +306,16 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
> if (WARN_ON_ONCE(!func))
> return;
>
> + if (debug_rcu_head_queue(rhp)) {
> + if (atomic_inc_return(&doublefrees) < 4) {
> + pr_err("%s(): Duplicate call_srcu() CB %p->%pS()\n",
> + __func__, rhp, rhp->func);
> + mem_dump_obj(rhp);
> + }
> + WRITE_ONCE(rhp->func, srcu_leak_callback);
> + return;
> + }
> +
> /* Record the caller: the irq_work's stack says nothing about it. */
> kasan_record_aux_stack(rhp);
>
> @@ -306,6 +324,7 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
> if (READ_ONCE(srcu_defer_draining) && !in_nmi()) {
> WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU),
> "call_srcu() re-entered during callback drain; leaking callback\n");
> + debug_rcu_head_unqueue(rhp);
> return;
> }
> rhp->func = func;
> --
> 2.43.0
>