Lockdep reports an inconsistent lock state (`{INITIAL USE} -> {IN-NMI}`) when a BPF program deletes an element from a `BPF_MAP_TYPE_RHASH` map in NMI context: inconsistent {INITIAL USE} -> {IN-NMI} usage. ... Possible unsafe locking scenario: CPU0 ==== lock(rhashtable_bucket); lock(rhashtable_bucket); *** DEADLOCK *** The `{INITIAL USE}` of the lock is registered during process creation when `pidfs_add_pid()` inserts a new PID into the `pidfs_ino_ht` rhashtable and acquires the bucket lock in normal process context. Later, a BPF program attached to a hardware breakpoint executes in NMI context and calls `bpf_map_delete_elem()` on a `BPF_MAP_TYPE_RHASH` map. This calls `rhashtable_remove_fast()`, which acquires the bucket lock in NMI context, triggering the lockdep warning. This is a false positive caused by all `rhashtable` instances in the kernel sharing the exact same lock class for their bucket locks. The bucket table allocation function `bucket_table_alloc()` initializes the lockdep map for the bucket locks using a single static key. Thus, lockdep assumes that the bucket lock acquired by the BPF program in NMI context is the same lock acquired by `pidfs` in normal process context, leading to a perceived deadlock. In reality, they operate on completely different `rhashtable` instances. Furthermore, a real deadlock on the same `BPF_MAP_TYPE_RHASH` instance is explicitly prevented by the BPF subsystem using `bpf_disable_instrumentation()`, which increments the per-CPU `bpf_prog_active` counter and prevents reentrancy. To fix this, update the `rhashtable` API to allow different lock classes for the bucket locks of different `rhashtable` instances. Extend the initialization macros to generate a unique static `lock_class_key` for the bucket locks and pass it down to `bucket_table_alloc()`. For BPF maps, use `__lockdep_no_track__` to completely bypass lockdep tracking for the bucket locks. This avoids issues with shared global keys like `__lockdep_no_validate__` (which can cause invalid wait context warnings if previously registered as a mutex by another subsystem) while relying on BPF's internal NMI deadlock prevention mechanism. Fixes: 16b4d3e2fb24 ("bpf: Implement resizable hashmap basic functions") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+2fb31ade2d0e920fea9a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2fb31ade2d0e920fea9a Link: https://syzkaller.appspot.com/ai_job?id=31a9ea31-9998-4618-b127-6dbc2e463ee9 To: "Andrew Morton" To: "Andrii Nakryiko" To: "Alexei Starovoitov" To: To: "Daniel Borkmann" To: "Eduard Zingerman" To: "Herbert Xu" To: To: "Kumar Kartikeya Dwivedi" To: "Thomas Graf" To: "Mykyta Yatsenko" Cc: "Emil Tsalapatis" Cc: "Jiri Olsa" Cc: Cc: "Martin KaFai Lau" Cc: "Song Liu" Cc: "Yonghong Song" --- diff --git a/include/linux/rhashtable-types.h b/include/linux/rhashtable-types.h index 57c11ec9d..059f21e37 100644 --- a/include/linux/rhashtable-types.h +++ b/include/linux/rhashtable-types.h @@ -94,6 +94,9 @@ struct rhashtable { struct mutex mutex; spinlock_t lock; atomic_t nelems; +#ifdef CONFIG_LOCKDEP + struct lock_class_key *dep_key; +#endif #ifdef CONFIG_MEM_ALLOC_PROFILING struct alloc_tag *alloc_tag; #endif @@ -138,23 +141,27 @@ struct rhashtable_iter { int __rhashtable_init_noprof(struct rhashtable *ht, const struct rhashtable_params *params, - struct lock_class_key *key); + struct lock_class_key *key, + struct lock_class_key *bucket_key); #define rhashtable_init_noprof(ht, params) \ ({ \ static struct lock_class_key __key; \ + static struct lock_class_key __bucket_key; \ \ - __rhashtable_init_noprof(ht, params, &__key); \ + __rhashtable_init_noprof(ht, params, &__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 *key, + struct lock_class_key *bucket_key); #define rhltable_init_noprof(hlt, params) \ ({ \ static struct lock_class_key __key; \ + static struct lock_class_key __bucket_key; \ \ - __rhltable_init_noprof(hlt, params, &__key); \ + __rhltable_init_noprof(hlt, params, &__key, &__bucket_key); \ }) #define rhltable_init(...) alloc_hooks(rhltable_init_noprof(__VA_ARGS__)) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 9f394e1aa..d7e2a601c 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -2822,7 +2822,19 @@ static struct bpf_map *rhtab_map_alloc(union bpf_attr *attr) params.obj_cmpfn = rhtab_key_cmp_long; } - err = rhashtable_init(&rhtab->ht, ¶ms); +#ifdef CONFIG_LOCKDEP +#define bpf_rhashtable_init(ht, params) \ + ({ \ + static struct lock_class_key __key; \ + \ + __rhashtable_init_noprof(ht, params, &__key, \ + &__lockdep_no_track__); \ + }) +#else +#define bpf_rhashtable_init(ht, params) rhashtable_init_noprof(ht, params) +#endif + err = alloc_hooks(bpf_rhashtable_init(&rhtab->ht, ¶ms)); +#undef bpf_rhashtable_init if (err) goto free_rhtab; diff --git a/lib/rhashtable.c b/lib/rhashtable.c index d459bef24..f872136ca 100644 --- a/lib/rhashtable.c +++ b/lib/rhashtable.c @@ -205,7 +205,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); +#ifdef CONFIG_LOCKDEP + if (ht->dep_key) + lockdep_init_map(&tbl->dep_map, "rhashtable_bucket", ht->dep_key, 0); + else +#endif + lockdep_init_map(&tbl->dep_map, "rhashtable_bucket", &__key, 0); tbl->size = size; @@ -1162,7 +1167,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 *key, + struct lock_class_key *bucket_key) { struct bucket_table *tbl; size_t size; @@ -1176,6 +1182,10 @@ int __rhashtable_init_noprof(struct rhashtable *ht, spin_lock_init(&ht->lock); memcpy(&ht->p, params, sizeof(*params)); +#ifdef CONFIG_LOCKDEP + ht->dep_key = bucket_key; +#endif + alloc_tag_record(ht->alloc_tag); if (params->min_size) @@ -1237,11 +1247,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 *key, + struct lock_class_key *bucket_key) { int err; - err = __rhashtable_init_noprof(&hlt->ht, params, key); + err = __rhashtable_init_noprof(&hlt->ht, params, key, bucket_key); hlt->ht.rhlist = true; return err; } base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff -- This is an AI-generated patch subject to moderation. Reply with '#syz upstream' to Sign-off the patch as a human author and send it to the upstream kernel mailing lists. Reply with '#syz reject' to reject it ('#syz unreject' to undo). See https://goo.gle/syzbot-ai-patches for information about AI-generated patches. You can comment on the patch as usual, syzbot will try to address the comments and send a new version of the patch if necessary. syzbot engineers can be reached at syzkaller@googlegroups.com.