[PATCH bpf-next v5] bpf: allow ksock connect/send calls from wq
DORMANTno replies
REVIEWED: 1 (0M)
From: Mahe Tardy <hidden>
Date: 2026-10-15 10:45:59
Also in:
bpf
Subsystem:
bpf [general] (safe dynamic programs and tools), bpf [selftests] (test runners & infrastructure), kernel selftest framework, networking [general], networking [sockets], the rest · Maintainers:
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Shuah Khan, Shuah Khan, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima, Willem de Bruijn, Linus Torvalds
1 review trailer.
The initial ksock implementation, commit 7ae4eb14c5f9 ("bpf: Add ksock
kfuncs"), kept LSM checks from the kfunc calls and disallowed
asynchronous workqueues because worker credentials differ from those of
the triggering task. However, discussions at Plumbers 2026[^1] revealed
that bypassing LSM checks for BPF-created sockets could be acceptable.
This patch allows workqueue use while retaining LSM checks for now,
paving the way to call bpf_ksock_connect() and bpf_ksock_send() from
non-sleepable programs, such as tracing programs, in future patches.
Also adapt test_ksock_wq() added in 'c93cbdb13f99 ("selftests/bpf: Add
ksock test for async callback guard")' to test that the connect() and
send() kfuncs are now allowed and work when called from workqueues.
Note that bpf_ksock_create() still checks:
* for current to not be a kernel thread (thus excluding bpf workqueue
callbacks) because the socket is created within a userspace task
network namespace. It should, most of the time, be the process that
triggered the SYSCALL program, but could be another task when called
from bpf_task_work callbacks.
* for current->nsproxy to be non-NULL since when called from a
bpf_task_work callback and the task is in the do_exit() phase, nsproxy
is torn down just before the remaining task work is executed.
[^1]: https://lpc.events/event/20/contributions/2430/
Tested-by: Alan Maguire (Oracle) <almagui@kernel.org>
Signed-off-by: Mahe Tardy <redacted>
---
net/core/bpf_ksock.c | 24 +-----
.../selftests/bpf/prog_tests/ksock_wq.c | 29 +++++--
tools/testing/selftests/bpf/progs/ksock_wq.c | 77 +++++++++++++++----
3 files changed, 92 insertions(+), 38 deletions(-)
diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
index e9943aeeadd3..7bf796193ab6 100644
--- a/net/core/bpf_ksock.c
+++ b/net/core/bpf_ksock.c@@ -37,15 +37,6 @@ static void ksock_release_work_fn(struct work_struct *work) kfree(ks); } -static bool bpf_ksock_has_user_task_context(void) -{ - /* - * Task work can run from do_exit() after exit_nsproxy_namespaces() - * cleared current->nsproxy, while current is still not a kthread. - */ - return !(current->flags & PF_KTHREAD) && current->nsproxy; -} - __bpf_kfunc_start_defs(); /**
@@ -59,7 +50,7 @@ __bpf_kfunc_start_defs(); * This function may sleep (sock_create), so it can only be used * in sleepable BPF programs (SYSCALL). * It cannot be called from a BPF workqueue callback because that callback - * does not retain the invoking task's namespace or security context. + * does not retain the invoking task's namespace. * * @opts: Pointer to struct bpf_ksock_create_opts with socket parameters. * @opts__sz: Size of the opts struct.
@@ -74,11 +65,10 @@ bpf_ksock_create(const struct bpf_ksock_create_opts *opts, u32 opts__sz, int err; /* - * sock_create() derives the network namespace, credentials, and cgroup - * from current. Kernel threads, including BPF workqueue callbacks, do - * not carry the context of the task that invoked the BPF program. + * sock_create() derives the network namespace from current and task + * work can run from do_exit() after exit_nsproxy_namespaces() */ - if (!bpf_ksock_has_user_task_context()) { + if ((current->flags & PF_KTHREAD) || !current->nsproxy) { err = -EOPNOTSUPP; goto err_out; }
@@ -165,9 +155,6 @@ __bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks, struct sockaddr_storage sa; int addrlen; - if (!bpf_ksock_has_user_task_context()) - return -EOPNOTSUPP; - if (!addr || addr__sz != sizeof(*addr)) return -EINVAL;
@@ -247,9 +234,6 @@ __bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data, }; int ret; - if (!bpf_ksock_has_user_task_context()) - return -EOPNOTSUPP; - ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz); return ret;
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_wq.c b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
index d6dc20b8f95b..12c2275e8177 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c@@ -4,6 +4,7 @@ #include <unistd.h> #include "test_progs.h" +#include "network_helpers.h" #include "ksock_wq.skel.h" #define CALLBACK_WAIT_RETRIES 1000
@@ -13,13 +14,22 @@ void test_ksock_wq(void) { LIBBPF_OPTS(bpf_test_run_opts, opts); struct ksock_wq *skel; + char recv_data[sizeof(skel->data->send_data)]; u32 callback_done; - int err, i; + int err, i, port, rfd; skel = ksock_wq__open_and_load(); if (!ASSERT_OK_PTR(skel, "ksock_wq open and load")) return; + rfd = start_server(AF_INET, SOCK_DGRAM, "127.0.0.1", 0, 1000); + if (!ASSERT_OK_FD(rfd, "receiver socket")) + goto out; + port = get_socket_local_port(rfd); + if (!ASSERT_GE(port, 0, "receiver port")) + goto out; + skel->bss->remote_port = port; + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.ksock_wq_start), &opts); if (!ASSERT_OK(err, "run ksock_wq_start"))
@@ -28,18 +38,27 @@ void test_ksock_wq(void) goto out; for (i = 0; i < CALLBACK_WAIT_RETRIES; i++) { - if (__atomic_load_n(&skel->bss->callback_done, __ATOMIC_ACQUIRE)) + if (__atomic_load_n(&skel->bss->callback_done, __ATOMIC_ACQUIRE) == 2) break; usleep(CALLBACK_WAIT_US); } callback_done = __atomic_load_n(&skel->bss->callback_done, __ATOMIC_ACQUIRE); - if (!ASSERT_EQ(callback_done, 1, "workqueue callback completed")) + if (!ASSERT_EQ(callback_done, 2, "workqueue callbacks completed")) goto out; - ASSERT_EQ(skel->bss->create_err, -EOPNOTSUPP, - "workqueue create rejected"); + ASSERT_EQ(skel->bss->create_ret, -EOPNOTSUPP, "workqueue create rejected"); + + if (!ASSERT_EQ(skel->bss->connect_send_ret, sizeof(recv_data), + "workqueue send bytes")) + goto out; + err = recv(rfd, recv_data, sizeof(recv_data), 0); + if (ASSERT_EQ(err, sizeof(recv_data), "recv len")) + ASSERT_MEMEQ(recv_data, skel->data->send_data, sizeof(recv_data), + "payload match"); out: + if (rfd >= 0) + close(rfd); ksock_wq__destroy(skel); }
diff --git a/tools/testing/selftests/bpf/progs/ksock_wq.c b/tools/testing/selftests/bpf/progs/ksock_wq.c
index 16a1873d132e..6ec6103e2518 100644
--- a/tools/testing/selftests/bpf/progs/ksock_wq.c
+++ b/tools/testing/selftests/bpf/progs/ksock_wq.c@@ -2,9 +2,11 @@ /* Copyright (c) 2026 Isovalent */ #include "vmlinux.h" +#include <bpf/bpf_endian.h> #include <bpf/bpf_helpers.h> #include "bpf_experimental.h" #include "bpf_tracing_net.h" +#include "bpf_misc.h" #include "errno.h" #include "ksock_common.h"
@@ -14,15 +16,19 @@ struct ksock_wq_value { struct { __uint(type, BPF_MAP_TYPE_ARRAY); - __uint(max_entries, 1); + __uint(max_entries, 2); __type(key, u32); __type(value, struct ksock_wq_value); } work_map SEC(".maps"); -int create_err; +char send_data[] = "hello from bpf workqueue"; + +__be16 remote_port; +int connect_send_ret; +int create_ret; u32 callback_done; -static int ksock_wq_callback(void *map, int *key, void *value) +static int ksock_wq_create(void *map, int *key, void *value) { struct bpf_ksock_create_opts opts = { .family = AF_INET,
@@ -35,7 +41,32 @@ static int ksock_wq_callback(void *map, int *key, void *value) ks = bpf_ksock_create(&opts, sizeof(opts), &err); if (ks) bpf_ksock_release(ks); - create_err = err; + create_ret = err; + __sync_fetch_and_add(&callback_done, 1); + return 0; +} + +static int ksock_wq_connect_send(void *map, int *key, void *value) +{ + union bpf_ksock_addr addr = { + .sin = { + .sin_family = AF_INET, + .sin_port = remote_port, + .sin_addr.s_addr = bpf_htonl(0x7f000001), + }, + }; + struct bpf_ksock *ks; + int err = -ENOENT; + + ks = ksock_ctx_get(); + if (!ks) + goto out; + err = bpf_ksock_connect(ks, &addr, sizeof(addr)); + if (!err) + err = bpf_ksock_send(ks, send_data, sizeof(send_data)); + bpf_ksock_release(ks); +out: + connect_send_ret = err; __sync_fetch_and_add(&callback_done, 1); return 0; }
@@ -43,20 +74,40 @@ static int ksock_wq_callback(void *map, int *key, void *value) SEC("syscall") int ksock_wq_start(void *ctx) { + struct bpf_ksock_create_opts opts = { + .family = AF_INET, + .type = SOCK_DGRAM, + .protocol = IPPROTO_UDP, + }; struct ksock_wq_value *value; - u32 key = 0; - int err; + u32 key __aligned(8) = 0; + struct bpf_ksock *ks; + int err = 0; - value = bpf_map_lookup_elem(&work_map, &key); - if (!value) - return -ENOENT; - err = bpf_wq_init(&value->work, &work_map, 0); - if (err) + ks = bpf_ksock_create(&opts, sizeof(opts), &err); + if (!ks) return err; - err = bpf_wq_set_callback(&value->work, ksock_wq_callback, 0); + err = ksock_ctx_insert(ks); if (err) return err; - return bpf_wq_start(&value->work, 0); + for (key = 0; key < 2; key++) { + value = bpf_map_lookup_elem(&work_map, &key); + if (!value) + return -ENOENT; + err = bpf_wq_init(&value->work, &work_map, 0); + if (err) + return err; + if (key) + err = bpf_wq_set_callback(&value->work, ksock_wq_create, 0); + else + err = bpf_wq_set_callback(&value->work, ksock_wq_connect_send, 0); + if (err) + return err; + err = bpf_wq_start(&value->work, 0); + if (err) + return err; + } + return 0; } char __license[] SEC("license") = "GPL"; --
2.43.0