[PATCH] rhashtable: use per-init-site lockdep classes for bucket locks
quanyeyang <[email protected]> Sat, 01 Aug 2026 18:13:27 +0800
| Newsgroups | org.kernel.feeds.b4-sent,org.kernel.vger.linux-crypto,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <20260801-fix-rhashtable-bucket-lockdep-v1-1-15a0f8ae094c@gmail.com> |
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]>