[PATCH v2 08/10] srcutiny: Detect duplicate callback enqueue
From: Kunwu Chan
Date: Tue Oct 27 2026 - 03:15:17 EST
Detect duplicate callback enqueue attempts at call_srcu()
entry, before the deferred list can be corrupted. Use
__ratelimit() for the diagnostic output group.
Suggested-by: Paul E. McKenney <paulmck@xxxxxxxxxx>
Signed-off-by: Kunwu Chan <chentao@xxxxxxxxxx>
---
kernel/rcu/srcutiny.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
index 98864977bed2..4c1e8d8595b3 100644
--- a/kernel/rcu/srcutiny.c
+++ b/kernel/rcu/srcutiny.c
@@ -12,9 +12,11 @@
#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>
+#include <linux/ratelimit.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/srcu.h>
@@ -178,6 +180,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 +238,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 +277,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);
@@ -288,6 +295,10 @@ void srcu_defer_drain(struct irq_work *iw)
}
EXPORT_SYMBOL_GPL(srcu_defer_drain);
+static DEFINE_RATELIMIT_STATE(rcu_srcutiny_dup_rs,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
rcu_callback_t func)
{
@@ -298,6 +309,17 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
if (WARN_ON_ONCE(!func))
return;
+ if (debug_rcu_head_queue(rhp)) {
+ if (__ratelimit(&rcu_srcutiny_dup_rs)) {
+ pr_err("%s(): Duplicate call_srcu() CB %p->%pS()\n",
+ __func__, rhp, rhp->func);
+ mem_dump_obj(rhp);
+ dump_stack();
+ }
+ 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 +328,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