bpf_prog_assoc_struct_ops() and bpf_prog_disassoc_struct_ops() update prog->aux->st_ops_assoc under st_ops_assoc_mutex, and the latter puts the map of a non-struct_ops prog while holding it.
A struct_ops map is about to dissociate its progs from map_free_pre_rcu(), which bpf_map_put() calls in whatever context drops the last reference, including softirq, e.g. bpf_struct_ops_put() from tcp_cleanup_congestion_control(). A mutex can't be taken there, and the put of the last reference under the lock of one prog would take the lock of other progs. Replace the mutex with a spinlock taken with interrupts disabled, and put the map after dropping it. Signed-off-by: Qiliang Yuan <[email protected]> --- include/linux/bpf.h | 2 +- kernel/bpf/bpf_struct_ops.c | 19 ++++++++++--------- kernel/bpf/core.c | 3 +-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 54144372281cb..2a51acd34f9a9 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1960,7 +1960,7 @@ struct bpf_prog_aux { struct rcu_head rcu; }; struct bpf_stream *stream[2]; - struct mutex st_ops_assoc_mutex; + spinlock_t st_ops_assoc_lock; /* protects st_ops_assoc */ struct bpf_map __rcu *st_ops_assoc; }; diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c index 1178acd72296e..a1ae30b145a03 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -1541,10 +1541,10 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map) { struct bpf_map *st_ops_assoc; - guard(mutex)(&prog->aux->st_ops_assoc_mutex); + guard(spinlock_irqsave)(&prog->aux->st_ops_assoc_lock); st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc, - lockdep_is_held(&prog->aux->st_ops_assoc_mutex)); + lockdep_is_held(&prog->aux->st_ops_assoc_lock)); if (st_ops_assoc && st_ops_assoc == map) return 0; @@ -1569,19 +1569,20 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map) void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog) { + struct bpf_prog_aux *aux = prog->aux; struct bpf_map *st_ops_assoc; - guard(mutex)(&prog->aux->st_ops_assoc_mutex); + scoped_guard(spinlock_irqsave, &aux->st_ops_assoc_lock) { + st_ops_assoc = rcu_dereference_protected(aux->st_ops_assoc, + lockdep_is_held(&aux->st_ops_assoc_lock)); + if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON) + return; - st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc, - lockdep_is_held(&prog->aux->st_ops_assoc_mutex)); - if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON) - return; + RCU_INIT_POINTER(aux->st_ops_assoc, NULL); + } if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) bpf_map_put(st_ops_assoc); - - RCU_INIT_POINTER(prog->aux->st_ops_assoc, NULL); } /* diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 05c89396119ad..fd4e4dc4acc8c 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -141,7 +141,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag mutex_init(&fp->aux->used_maps_mutex); mutex_init(&fp->aux->ext_mutex); mutex_init(&fp->aux->dst_mutex); - mutex_init(&fp->aux->st_ops_assoc_mutex); + spin_lock_init(&fp->aux->st_ops_assoc_lock); return fp; } @@ -291,7 +291,6 @@ void __bpf_prog_free(struct bpf_prog *fp) #endif mutex_destroy(&fp->aux->used_maps_mutex); mutex_destroy(&fp->aux->dst_mutex); - mutex_destroy(&fp->aux->st_ops_assoc_mutex); kfree(fp->aux->poke_tab); kfree(fp->aux); } -- 2.43.0

