[PATCH] rhashtable: use per-init-site lockdep classes for bucket locks

quanyeyang via B4 Relay <[email protected]> Sat, 01 Aug 2026 18:13:27 +0800
Newsgroups org.kernel.vger.linux-crypto,org.kernel.feeds.b4-sent,org.kernel.vger.linux-kernel
Message-ID <20260801-fix-rhashtable-bucket-lockdep-v1-1-15a0f8ae094c@gmail.com>
From: quanyeyang <[email protected]>

All bucket tables currently share a single lockdep class. This makes
lockdep conflate bucket locks from unrelated rhashtable instances.

A BPF program attached to lock_release can expose this when pidfs
inserts a pid. The tracepoint runs before lockdep removes the pidfs
bucket lock from the task's held-lock stack. Deleting an element from
a BPF RHASH map then acquires a bucket lock belonging to a different
rhashtable. Since both tables use the same class, lockdep reports
possible recursive locking.

Declare a separate bucket lock class key at each rhashtable_init() and
rhltable_init() call site, alongside the mutex class key. Store the
bucket key in struct rhashtable so tables created during resize keep
using the same class.

A targeted reproducer triggers the warning reliably before this change.
After the change, the nested BPF RHASH deletion still executes, but
lockdep no longer reports recursive locking.

Fixes: 149212f07856 ("rhashtable: add lockdep tracking to bucket bit-spin-locks.")
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=ef8d17bae14efb960935
Assisted-by: Cursor:GPT-5.6 Sol
Signed-off-by: quanyeyang <[email protected]>
---
 include/linux/rhashtable-types.h | 20 ++++++++++++++------
 lib/rhashtable.c                 | 19 +++++++++++++------
 2 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/include/linux/rhashtable-types.h b/include/linux/rhashtable-types.h
index 57c11ec9dc64..4dea91a49ec8 100644
--- a/include/linux/rhashtable-types.h
+++ b/include/linux/rhashtable-types.h
@@ -82,6 +82,7 @@ struct rhashtable_params {
  * @mutex: Mutex to protect current/future table swapping
  * @lock: Spin lock to protect walker list
  * @nelems: Number of elements in table
+ * @bucket_lock_key: Per-init-site lockdep class for bucket bit-locks
  */
 struct rhashtable {
 	struct bucket_table __rcu	*tbl;
@@ -94,6 +95,7 @@ struct rhashtable {
 	struct mutex                    mutex;
 	spinlock_t			lock;
 	atomic_t			nelems;
+	struct lock_class_key		*bucket_lock_key;
 #ifdef CONFIG_MEM_ALLOC_PROFILING
 	struct alloc_tag		*alloc_tag;
 #endif
@@ -138,23 +140,29 @@ struct rhashtable_iter {
 
 int __rhashtable_init_noprof(struct rhashtable *ht,
 		    const struct rhashtable_params *params,
-		    struct lock_class_key *key);
+		    struct lock_class_key *mutex_key,
+		    struct lock_class_key *bucket_key);
 #define rhashtable_init_noprof(ht, params)				\
 ({									\
-	static struct lock_class_key __key;				\
+	static struct lock_class_key __mutex_key;			\
+	static struct lock_class_key __bucket_key;			\
 									\
-	__rhashtable_init_noprof(ht, params, &__key);			\
+	__rhashtable_init_noprof(ht, params, &__mutex_key,		\
+				 &__bucket_key);			\
 })
 #define rhashtable_init(...)	alloc_hooks(rhashtable_init_noprof(__VA_ARGS__))
 
 int __rhltable_init_noprof(struct rhltable *hlt,
 		  const struct rhashtable_params *params,
-		  struct lock_class_key *key);
+		  struct lock_class_key *mutex_key,
+		  struct lock_class_key *bucket_key);
 #define rhltable_init_noprof(hlt, params)				\
 ({									\
-	static struct lock_class_key __key;				\
+	static struct lock_class_key __mutex_key;			\
+	static struct lock_class_key __bucket_key;			\
 									\
-	__rhltable_init_noprof(hlt, params, &__key);			\
+	__rhltable_init_noprof(hlt, params, &__mutex_key,		\
+			       &__bucket_key);				\
 })
 #define rhltable_init(...)	alloc_hooks(rhltable_init_noprof(__VA_ARGS__))
 
diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index d459bef245f4..e047ad912f0e 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -189,7 +189,6 @@ static struct bucket_table *bucket_table_alloc(struct rhashtable *ht,
 	struct bucket_table *tbl = NULL;
 	size_t size;
 	int i;
-	static struct lock_class_key __key;
 
 	tbl = alloc_hooks_tag(ht->alloc_tag,
 			kvmalloc_node_align_noprof(struct_size(tbl, buckets, nbuckets),
@@ -205,7 +204,12 @@ static struct bucket_table *bucket_table_alloc(struct rhashtable *ht,
 	if (tbl == NULL)
 		return NULL;
 
-	lockdep_init_map(&tbl->dep_map, "rhashtable_bucket", &__key, 0);
+	/*
+	 * Keep all bucket tables belonging to the same rhashtable in the
+	 * per-init-site lock class, including tables created during resize.
+	 */
+	lockdep_init_map(&tbl->dep_map, "rhashtable_bucket",
+			 ht->bucket_lock_key, 0);
 
 	tbl->size = size;
 
@@ -1162,7 +1166,8 @@ static u32 rhashtable_jhash2(const void *key, u32 length, u32 seed)
  */
 int __rhashtable_init_noprof(struct rhashtable *ht,
 		    const struct rhashtable_params *params,
-		    struct lock_class_key *key)
+		    struct lock_class_key *mutex_key,
+		    struct lock_class_key *bucket_key)
 {
 	struct bucket_table *tbl;
 	size_t size;
@@ -1172,7 +1177,8 @@ int __rhashtable_init_noprof(struct rhashtable *ht,
 		return -EINVAL;
 
 	memset(ht, 0, sizeof(*ht));
-	mutex_init_with_key(&ht->mutex, key);
+	mutex_init_with_key(&ht->mutex, mutex_key);
+	ht->bucket_lock_key = bucket_key;
 	spin_lock_init(&ht->lock);
 	memcpy(&ht->p, params, sizeof(*params));
 
@@ -1237,11 +1243,12 @@ EXPORT_SYMBOL_GPL(__rhashtable_init_noprof);
  */
 int __rhltable_init_noprof(struct rhltable *hlt,
 			   const struct rhashtable_params *params,
-			   struct lock_class_key *key)
+			   struct lock_class_key *mutex_key,
+			   struct lock_class_key *bucket_key)
 {
 	int err;
 
-	err = __rhashtable_init_noprof(&hlt->ht, params, key);
+	err = __rhashtable_init_noprof(&hlt->ht, params, mutex_key, bucket_key);
 	hlt->ht.rhlist = true;
 	return err;
 }

---
base-commit: 0131b508c0e2489eac6e121135988f6eeb716f19
change-id: 20260801-fix-rhashtable-bucket-lockdep-95e25abebeea

Best regards,
--  
quanyeyang <[email protected]>