[PATCH bpf-next v2 17/43] bpf: Hold the socket in the sk_storage map iterator
flat view
WARM1d
IN BPF-NEXT
From: Alexei Starovoitov <hidden>
Date: 2026-10-17 16:39:32
Also in:
bpf
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v2 of 2 in this series; queued in bpf-next as 070a96b2a98f on 2026-10-17.
Revisions (2)
- v1 [diff vs current]
- v2 current
From: Josef Bacik <josef@toxicpanda.com>
The iterator over a sk_storage map passes the owner of an element to its
program as ctx->sk, without a reference. All that it holds is the RCU read
lock, which keeps the element and the storage, not the socket.
An element is unlinked by bpf_sk_storage_free(), which __sk_destruct()
calls right before it frees the socket. That is no better for a socket
with SOCK_RCU_FREE: the grace period comes in front of __sk_destruct(),
so a reader which starts behind call_rcu() still finds the element. The
program and the helpers it hands ctx->sk to, such as
bpf_sk_storage_get() or bpf_sk_storage_delete(), use a socket which is
being freed on another CPU. It takes nothing but reading the iterator
while sockets with an element in the map are closed, by whomever.
Take a reference for the time of the program, and skip the element if
there is none to be had. To look at the owner at all, do what
bpf_selem_unlink_nofail() does for bpf_local_storage_map_free(): as long
as the element is linked under the lock of the storage,
bpf_local_storage_destroy() has not got past it, unless it has given up
on the lock, and then it waits for owner_refcnt.
The last reference may now be dropped by the iterator. The end of a socket
may want process context: rfcomm_sock_destruct() takes a mutex. So only
drop the reference under RCU, and free a socket whose last one it was when
the RCU read lock has been released, which is at the end of a bucket and
of every read() at the latest.
Fixes: c69d2ddb2072 ("bpf: Using rcu_read_lock for bpf_sk_storage_map iterator")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
net/core/bpf_sk_storage.c | 73 ++++++++++++++++++++++++++++++++++++---
1 file changed, 68 insertions(+), 5 deletions(-)
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index aea67b446c7c..ea22ec5ef5b1 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c@@ -699,8 +699,37 @@ struct bpf_iter_seq_sk_storage_map_info { struct bpf_map *map; unsigned int bucket_id; unsigned skip_elems; + struct rcu_head *dead_sks; }; +/* + * sock_put() in two halves. The end of a socket may want process context: + * rfcomm_sock_destruct() takes a mutex. If the iterator drops the last + * reference, it keeps the socket until it has left the RCU read side critical + * section. Nobody uses sk_rcu before sk_destruct(). + */ +static void +bpf_sk_storage_map_seq_put_sk(struct bpf_iter_seq_sk_storage_map_info *info, + struct sock *sk) +{ + if (refcount_dec_and_test(&sk->sk_refcnt)) { + sk->sk_rcu.next = info->dead_sks; + info->dead_sks = &sk->sk_rcu; + } +} + +static void +bpf_sk_storage_map_seq_free_sks(struct bpf_iter_seq_sk_storage_map_info *info) +{ + struct sock *sk; + + while (info->dead_sks) { + sk = container_of(info->dead_sks, struct sock, sk_rcu); + info->dead_sks = sk->sk_rcu.next; + sk_free(sk); + } +} + static struct bpf_local_storage_elem * bpf_sk_storage_map_seq_find_next(struct bpf_iter_seq_sk_storage_map_info *info, struct bpf_local_storage_elem *prev_selem)
@@ -729,6 +758,7 @@ bpf_sk_storage_map_seq_find_next(struct bpf_iter_seq_sk_storage_map_info *info, /* not found, unlock and go to the next bucket */ b = &smap->buckets[bucket_id++]; rcu_read_unlock(); + bpf_sk_storage_map_seq_free_sks(info); skip_elems = 0; break; }
@@ -796,12 +826,44 @@ DEFINE_BPF_ITER_FUNC(bpf_sk_storage_map, struct bpf_iter_meta *meta, struct bpf_map *map, struct sock *sk, void *value) +/* + * Take a reference on the socket which owns @selem. RCU keeps the element, not + * its owner. As long as the element is linked under the lock of the storage, + * the owner has not got past bpf_sk_storage_free(), unless that has given up + * on the lock. Then it still waits for owner_refcnt. + */ +static struct sock * +bpf_sk_storage_selem_get_sk(struct bpf_local_storage_elem *selem) +{ + struct bpf_local_storage *sk_storage; + struct sock *sk = NULL; + unsigned long flags; + + sk_storage = rcu_dereference(selem->local_storage); + /* reset since ->next() looked: the socket goes away */ + if (!sk_storage) + return NULL; + + if (raw_res_spin_lock_irqsave(&sk_storage->lock, flags)) + return NULL; + + if (!hlist_unhashed(&selem->snode) && + refcount_inc_not_zero(&sk_storage->owner_refcnt)) { + sk = sk_storage->owner; + if (!refcount_inc_not_zero(&sk->sk_refcnt)) + sk = NULL; + bpf_local_storage_unpin_owner(sk_storage); + } + raw_res_spin_unlock_irqrestore(&sk_storage->lock, flags); + + return sk; +} + static int __bpf_sk_storage_map_seq_show(struct seq_file *seq, struct bpf_local_storage_elem *selem) { struct bpf_iter_seq_sk_storage_map_info *info = seq->private; struct bpf_iter__bpf_sk_storage_map ctx = {}; - struct bpf_local_storage *sk_storage; struct bpf_iter_meta meta; struct bpf_prog *prog; int ret = 0;
@@ -812,14 +874,14 @@ static int __bpf_sk_storage_map_seq_show(struct seq_file *seq, ctx.meta = &meta; ctx.map = info->map; if (selem) { - sk_storage = rcu_dereference(selem->local_storage); - /* reset since ->next() looked: the socket goes away */ - if (!sk_storage) + ctx.sk = bpf_sk_storage_selem_get_sk(selem); + if (!ctx.sk) return SEQ_SKIP; - ctx.sk = sk_storage->owner; ctx.value = SDATA(selem)->data; } ret = bpf_iter_run_prog(prog, &ctx); + if (ctx.sk) + bpf_sk_storage_map_seq_put_sk(info, ctx.sk); } return ret;
@@ -837,6 +899,7 @@ static void bpf_sk_storage_map_seq_stop(struct seq_file *seq, void *v) (void)__bpf_sk_storage_map_seq_show(seq, v); else rcu_read_unlock(); + bpf_sk_storage_map_seq_free_sks(seq->private); } static int bpf_iter_init_sk_storage_map(void *priv_data,
--
2.55.0