* [PATCH bpf-next v2 1/5] net: Add __sys_connect_socket() helper
2026-07-22 10:44 [PATCH bpf-next v2 0/5] Introduce bpf_ksock Mahe Tardy
@ 2026-07-22 10:44 ` Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 2/5] bpf: Add ksock kfuncs Mahe Tardy
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-07-22 10:44 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, sdf.kernel, ameryhung, kuniyu, Mahe Tardy
Add a helper that connects an existing socket while invoking the LSM
hook. Reuse it in __sys_connect_file() to avoid duplicating the connect
logic. Other socket operations have equivalent helpers that trigger the
appropriate LSM hooks that can be reused, this one was the only one
missing.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
include/linux/socket.h | 2 ++
net/socket.c | 32 ++++++++++++++++++--------------
2 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f1d1..48f1eb8193de 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -461,6 +461,8 @@ extern struct file *__sys_socket_file(int family, int type, int protocol);
extern int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen);
extern int __sys_bind_socket(struct socket *sock, struct sockaddr_storage *address,
int addrlen);
+extern int __sys_connect_socket(struct socket *sock, struct sockaddr_storage *addr,
+ int addrlen, int flags);
extern int __sys_connect_file(struct file *file, struct sockaddr_storage *addr,
int addrlen, int file_flags);
extern int __sys_connect(int fd, struct sockaddr __user *uservaddr,
diff --git a/net/socket.c b/net/socket.c
index 63c69a0fa74e..c7427dd4dd52 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -2115,27 +2115,31 @@ SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
* include the -EINPROGRESS status for such sockets.
*/
+int __sys_connect_socket(struct socket *sock, struct sockaddr_storage *address,
+ int addrlen, int flags)
+{
+ int err;
+
+ err = security_socket_connect(sock, (struct sockaddr *)address, addrlen);
+ if (err)
+ return err;
+
+ return READ_ONCE(sock->ops)->connect(sock,
+ (struct sockaddr_unsized *)address,
+ addrlen, flags);
+}
+
int __sys_connect_file(struct file *file, struct sockaddr_storage *address,
int addrlen, int file_flags)
{
struct socket *sock;
- int err;
sock = sock_from_file(file);
- if (!sock) {
- err = -ENOTSOCK;
- goto out;
- }
-
- err =
- security_socket_connect(sock, (struct sockaddr *)address, addrlen);
- if (err)
- goto out;
+ if (!sock)
+ return -ENOTSOCK;
- err = READ_ONCE(sock->ops)->connect(sock, (struct sockaddr_unsized *)address,
- addrlen, sock->file->f_flags | file_flags);
-out:
- return err;
+ return __sys_connect_socket(sock, address, addrlen,
+ sock->file->f_flags | file_flags);
}
int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 2/5] bpf: Add ksock kfuncs
2026-07-22 10:44 [PATCH bpf-next v2 0/5] Introduce bpf_ksock Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 1/5] net: Add __sys_connect_socket() helper Mahe Tardy
@ 2026-07-22 10:44 ` Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 3/5] selftests/bpf: Add ksock kfunc test Mahe Tardy
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-07-22 10:44 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, sdf.kernel, ameryhung, kuniyu, Mahe Tardy
Add BPF kfuncs that allow BPF LSM programs to create and use sockets for
sending data. This provides a mechanism for BPF programs to emit
telemetry. For this first patch set, it's restricted to SOCK_DGRAM
socket types with IPPROTO_UDP protocol but could be easily extended to
SOCK_STREAM and IPPROTO_TCP in the future.
The API consists of five kfuncs:
bpf_ksock_create() - Create a socket (sleepable)
bpf_ksock_connect() - Connect socket to remote address (sleepable)
bpf_ksock_send() - Send data through the socket (sleepable)
bpf_ksock_acquire() - Acquire a reference to a socket context
bpf_ksock_release() - Release a reference (cleanup via
queue_rcu_work since sock_release sleeps)
The setup kfuncs bpf_ksock_create, bpf_ksock_connect, can be called from
SYSCALL programs only. While bpf_ksock_acquire, bpf_ksock_release and
bpf_ksock_send can be called from SYSCALL and LSM programs.
The implementation follows the established kfunc lifecycle pattern
(create/acquire/release with refcounting, kptr map storage, dtor
registration). The kernel socket is wrapped in a refcounted bpf_ksock
struct. Cleanup is deferred via queue_rcu_work() because sock_release()
may sleep.
The kfuncs are only compiled when CONFIG_INET is enabled, as they
specifically support AF_INET and AF_INET6 sockets.
The socket operations go through the expected LSM hooks instead of
by-passing them like many kernel sockets since those are created by BPF
programs and thus system users. Thus bpf_ksock_send() kfunc, which is
exposed to LSM progs, has a re-entering protection to avoid recursion.
Also, because of the LSM checks, we prevent the use of the kfuncs from
asynchronous workqueue as the current value would then be invalid.
In bpf_ksock_create(), we copy the arg values to avoid TOCTOU races
since the kfunc can sleep and the arg values could be stored in a map
that could be re-written by BPF progs or even userspace programs if the
map is mmaped.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
include/linux/bpf_ksock.h | 46 +++++
include/linux/sched.h | 4 +
kernel/bpf/verifier.c | 3 +
net/core/Makefile | 3 +
net/core/bpf_ksock.c | 361 ++++++++++++++++++++++++++++++++++++++
5 files changed, 417 insertions(+)
create mode 100644 include/linux/bpf_ksock.h
create mode 100644 net/core/bpf_ksock.c
diff --git a/include/linux/bpf_ksock.h b/include/linux/bpf_ksock.h
new file mode 100644
index 000000000000..a2e06e3d604d
--- /dev/null
+++ b/include/linux/bpf_ksock.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _BPF_KSOCK_H
+#define _BPF_KSOCK_H
+
+#include <linux/types.h>
+
+/**
+ * struct bpf_ksock_create_opts - BPF kernel socket creation parameters
+ * @family: Address family: AF_INET or AF_INET6.
+ * @type: Socket type: only SOCK_DGRAM supported for now.
+ * @protocol: Protocol number (e.g. IPPROTO_UDP), or 0 for the default protocol
+ * of the given type.
+ * @reserved: Must be zero. Reserved for future use.
+ */
+struct bpf_ksock_create_opts {
+ __u8 family;
+ __u8 type;
+ __u8 protocol;
+ __u8 reserved;
+};
+
+/**
+ * struct bpf_ksock_addr_opts - BPF kernel socket address parameters
+ * @family: Address family: AF_INET or AF_INET6.
+ * @reserved: Must be zero. Reserved for future use.
+ * @port: Port in host byte order.
+ * @scope_id: IPv6 scope ID for scoped AF_INET6 addresses, or zero.
+ * Must be zero when family=AF_INET.
+ * @ipv4_addr: IPv4 address in network byte order. Used when family=AF_INET.
+ * @ipv6_addr: IPv6 address (16 bytes, network byte order). Used when family=AF_INET6.
+ */
+struct bpf_ksock_addr_opts {
+ __u8 family;
+ __u8 reserved;
+ __u16 port;
+ __u32 scope_id;
+
+ union {
+ __be32 ipv4_addr;
+ __u32 ipv6_addr[4]; /* in6_addr; network order */
+ };
+};
+
+#endif /* _BPF_KSOCK_H */
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 373bcc0598d1..334846df9238 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1050,6 +1050,10 @@ struct task_struct {
/* Recursion prevention for eventfd_signal() */
unsigned in_eventfd:1;
#endif
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_INET)
+ /* Recursion prevention for bpf_ksock_send() */
+ unsigned in_bpf_ksock_send:1;
+#endif
#ifdef CONFIG_ARCH_HAS_CPU_PASID
unsigned pasid_activated:1;
#endif
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 52be0a118cce..4f3de0a4b0c1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4445,6 +4445,9 @@ BTF_ID(struct, task_struct)
#ifdef CONFIG_CRYPTO
BTF_ID(struct, bpf_crypto_ctx)
#endif
+#ifdef CONFIG_INET
+BTF_ID(struct, bpf_ksock)
+#endif
BTF_SET_END(rcu_protected_types)
static bool rcu_protected_object(const struct btf *btf, u32 btf_id)
diff --git a/net/core/Makefile b/net/core/Makefile
index b3fdcb4e355f..a9295b785901 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -44,6 +44,9 @@ obj-$(CONFIG_FAILOVER) += failover.o
obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o
obj-$(CONFIG_BPF_SYSCALL) += sock_map.o
obj-$(CONFIG_BPF_SYSCALL) += bpf_sk_storage.o
+ifneq ($(CONFIG_INET),)
+obj-$(CONFIG_BPF_SYSCALL) += bpf_ksock.o
+endif
obj-$(CONFIG_OF) += of_net.o
obj-$(CONFIG_NET_TEST) += net_test.o
obj-$(CONFIG_NET_DEVMEM) += devmem.o
diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
new file mode 100644
index 000000000000..eb7d3ab4401f
--- /dev/null
+++ b/net/core/bpf_ksock.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Isovalent */
+
+#include <linux/bpf.h>
+#include <linux/bpf_ksock.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/net.h>
+#include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+#include <linux/ip.h>
+#include <net/sock.h>
+
+/**
+ * struct bpf_ksock - refcounted BPF kernel socket context
+ * @sock: The underlying kernel socket.
+ * @usage: Reference counter.
+ * @rwork: RCU work for deferred cleanup (sock_release may sleep).
+ */
+struct bpf_ksock {
+ struct socket *sock;
+ refcount_t usage;
+ struct rcu_work rwork;
+};
+
+static void ksock_release_work_fn(struct work_struct *work)
+{
+ struct bpf_ksock *ks =
+ container_of(to_rcu_work(work), struct bpf_ksock, rwork);
+
+ sock_release(ks->sock);
+ kfree(ks);
+}
+
+static int bpf_ksock_parse_addr(const struct bpf_ksock_addr_opts *opts,
+ u32 opts__sz, struct sockaddr_storage *addr)
+{
+ struct bpf_ksock_addr_opts opts_copy;
+
+ if (!opts || opts__sz != sizeof(*opts))
+ return -EINVAL;
+
+ /* Kfunc memory arguments are not guaranteed to be naturally aligned. */
+ memcpy(&opts_copy, opts, sizeof(opts_copy));
+
+ if (opts_copy.reserved)
+ return -EINVAL;
+
+ switch (opts_copy.family) {
+ case AF_INET: {
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
+
+ if (opts_copy.scope_id)
+ return -EINVAL;
+
+ *addr4 = (struct sockaddr_in){
+ .sin_family = AF_INET,
+ .sin_port = htons(opts_copy.port),
+ .sin_addr.s_addr = opts_copy.ipv4_addr,
+ };
+ return sizeof(*addr4);
+ }
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6: {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
+
+ *addr6 = (struct sockaddr_in6){
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(opts_copy.port),
+ .sin6_scope_id = opts_copy.scope_id,
+ };
+ memcpy(&addr6->sin6_addr, opts_copy.ipv6_addr,
+ sizeof(struct in6_addr));
+ return sizeof(*addr6);
+ }
+#endif
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+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();
+
+/**
+ * bpf_ksock_create() - Create a BPF kernel socket.
+ *
+ * Allocates and creates a kernel socket.
+ *
+ * The returned context must either be stored in a map as a kptr, or
+ * freed with bpf_ksock_release().
+ *
+ * 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.
+ *
+ * @opts: Pointer to struct bpf_ksock_create_opts with socket parameters.
+ * @opts__sz: Size of the opts struct.
+ * @err__uninit: Integer to store error code when NULL is returned.
+ */
+__bpf_kfunc struct bpf_ksock *
+bpf_ksock_create(const struct bpf_ksock_create_opts *opts, u32 opts__sz,
+ int *err__uninit)
+{
+ struct bpf_ksock_create_opts opts_copy;
+ struct bpf_ksock *ks;
+ 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.
+ */
+ if (!bpf_ksock_has_user_task_context()) {
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
+
+ if (!opts || opts__sz != sizeof(struct bpf_ksock_create_opts)) {
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ opts_copy = (struct bpf_ksock_create_opts){
+ .family = READ_ONCE(opts->family),
+ .type = READ_ONCE(opts->type),
+ .protocol = READ_ONCE(opts->protocol),
+ .reserved = READ_ONCE(opts->reserved),
+ };
+
+ if (opts_copy.reserved) {
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ if (opts_copy.family != AF_INET && opts_copy.family != AF_INET6) {
+ err = -EAFNOSUPPORT;
+ goto err_out;
+ }
+
+ if (opts_copy.type != SOCK_DGRAM) {
+ err = -EPROTONOSUPPORT;
+ goto err_out;
+ }
+
+ if (opts_copy.protocol != IPPROTO_UDP && opts_copy.protocol != 0) {
+ err = -EPROTONOSUPPORT;
+ goto err_out;
+ }
+
+ ks = kzalloc_obj(*ks);
+ if (!ks) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ /*
+ * Use the normal current-task socket path so LSM/cgroup policy,
+ * socket labels, and the active netns reference match a socket(2)
+ * created by the BPF program's caller.
+ */
+ err = sock_create(opts_copy.family, opts_copy.type, opts_copy.protocol,
+ &ks->sock);
+ if (err)
+ goto err_free;
+
+ ks->sock->sk->sk_rcvbuf = SOCK_MIN_RCVBUF;
+ ks->sock->sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
+
+ refcount_set(&ks->usage, 1);
+ put_unaligned(0, err__uninit);
+ return ks;
+
+err_free:
+ kfree(ks);
+err_out:
+ put_unaligned(err, err__uninit);
+ return NULL;
+}
+
+/**
+ * bpf_ksock_connect() - Connect a BPF kernel socket to a remote address.
+ * @ks: The BPF kernel socket context.
+ * @opts: Pointer to struct bpf_ksock_addr_opts with remote address.
+ * @opts__sz: Size of the opts struct.
+ *
+ * Connects the socket to the specified remote address and port.
+ *
+ * This function may sleep while connecting the socket, so it can only be used
+ * in sleepable BPF programs (SYSCALL).
+ *
+ * Return: 0 on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks,
+ const struct bpf_ksock_addr_opts *opts,
+ u32 opts__sz)
+{
+ struct sockaddr_storage addr;
+ int addrlen;
+
+ if (!bpf_ksock_has_user_task_context())
+ return -EOPNOTSUPP;
+
+ addrlen = bpf_ksock_parse_addr(opts, opts__sz, &addr);
+ if (addrlen < 0)
+ return addrlen;
+
+ return __sys_connect_socket(ks->sock, &addr, addrlen, 0);
+}
+
+/**
+ * bpf_ksock_acquire() - Acquire a reference to a BPF kernel socket.
+ * @ks: The BPF kernel socket context to acquire. Must be a
+ * trusted pointer (e.g. RCU-protected kptr from a map).
+ *
+ * The acquired context must either be stored in a map as a kptr, or
+ * freed with bpf_ksock_release().
+ */
+__bpf_kfunc struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks)
+{
+ if (!refcount_inc_not_zero(&ks->usage))
+ return NULL;
+ return ks;
+}
+
+/**
+ * bpf_ksock_release() - Release a BPF kernel socket.
+ * @ks: The BPF kernel socket context to release.
+ *
+ * When the final reference is released, the socket is cleaned up via
+ * queue_rcu_work() (since sock_release may sleep).
+ */
+__bpf_kfunc void bpf_ksock_release(struct bpf_ksock *ks)
+{
+ if (refcount_dec_and_test(&ks->usage)) {
+ INIT_RCU_WORK(&ks->rwork, ksock_release_work_fn);
+ queue_rcu_work(system_dfl_wq, &ks->rwork);
+ }
+}
+
+__bpf_kfunc void bpf_ksock_release_dtor(void *ks)
+{
+ bpf_ksock_release(ks);
+}
+CFI_NOSEAL(bpf_ksock_release_dtor);
+
+/**
+ * bpf_ksock_send() - Send data through a BPF kernel socket.
+ * @ks: The BPF kernel socket context. Must be an acquired reference.
+ * @data: Pointer to the data to send.
+ * @data__sz: Size of the data to send (max 65535 bytes).
+ *
+ * Sends data on a connected socket, best-effort and nonblocking. This may sleep
+ * (kernel_sendmsg), so it can only be called from sleepable BPF programs.
+ *
+ * Return: Number of bytes sent on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data,
+ u32 data__sz)
+{
+ struct msghdr msg = {
+ .msg_flags = MSG_DONTWAIT,
+ };
+ struct kvec iov = {
+ .iov_base = (void *)data,
+ .iov_len = data__sz,
+ };
+ int ret;
+
+ if (!bpf_ksock_has_user_task_context())
+ return -EOPNOTSUPP;
+
+ /* Early check for UDP. Exact limits enforced by kernel_sendmsg(). */
+ if (data__sz > IP_MAX_MTU)
+ return -EMSGSIZE;
+
+ if (current->in_bpf_ksock_send)
+ return -EBUSY;
+
+ current->in_bpf_ksock_send = true;
+ ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz);
+ current->in_bpf_ksock_send = false;
+
+ return ret;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(ksock_init_kfunc_btf_ids)
+BTF_ID_FLAGS(func, bpf_ksock_create, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_ksock_connect, KF_SLEEPABLE)
+BTF_KFUNCS_END(ksock_init_kfunc_btf_ids)
+
+static const struct btf_kfunc_id_set ksock_init_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &ksock_init_kfunc_btf_ids,
+};
+
+BTF_KFUNCS_START(ksock_kfunc_btf_ids)
+BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE)
+BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE)
+BTF_KFUNCS_END(ksock_kfunc_btf_ids)
+
+static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&ksock_kfunc_btf_ids, kfunc_id) ||
+ prog->type == BPF_PROG_TYPE_SYSCALL ||
+ prog->type == BPF_PROG_TYPE_LSM)
+ return 0;
+
+ return -EACCES;
+}
+
+static const struct btf_kfunc_id_set ksock_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &ksock_kfunc_btf_ids,
+ .filter = bpf_ksock_kfunc_filter,
+};
+
+BTF_ID_LIST(bpf_ksock_dtor_ids)
+BTF_ID(struct, bpf_ksock)
+BTF_ID(func, bpf_ksock_release_dtor)
+
+static int __init bpf_ksock_kfunc_init(void)
+{
+ int ret;
+ const struct btf_id_dtor_kfunc bpf_ksock_dtors[] = {
+ {
+ .btf_id = bpf_ksock_dtor_ids[0],
+ .kfunc_btf_id = bpf_ksock_dtor_ids[1],
+ },
+ };
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &ksock_init_kfunc_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &ksock_kfunc_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM,
+ &ksock_kfunc_set);
+ return ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
+ ARRAY_SIZE(bpf_ksock_dtors),
+ THIS_MODULE);
+}
+
+late_initcall(bpf_ksock_kfunc_init);
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 3/5] selftests/bpf: Add ksock kfunc test
2026-07-22 10:44 [PATCH bpf-next v2 0/5] Introduce bpf_ksock Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 1/5] net: Add __sys_connect_socket() helper Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 2/5] bpf: Add ksock kfuncs Mahe Tardy
@ 2026-07-22 10:44 ` Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 4/5] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 5/5] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-07-22 10:44 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, sdf.kernel, ameryhung, kuniyu, Mahe Tardy
Add a selftest that exercises the ksock kfuncs end-to-end. One syscall
bpf setup program creates a ksock context and connects the socket.
Another syscall bpf program looks up the context and send test data.
The userspace harness creates a network namespace and a new socket on
loopback, run the setup and send syscall bpf progs then check that the
userspace socket received the data from bpf.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../testing/selftests/bpf/prog_tests/ksock.c | 159 ++++++++++++++++++
.../testing/selftests/bpf/progs/ksock_basic.c | 68 ++++++++
.../selftests/bpf/progs/ksock_common.h | 62 +++++++
3 files changed, 289 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_basic.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_common.h
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock.c b/tools/testing/selftests/bpf/prog_tests/ksock.c
new file mode 100644
index 000000000000..5b22ece4be4e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <arpa/inet.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "ksock_basic.skel.h"
+
+#define NS_TEST "ksock_basic_ns"
+#define LOOPBACK_IP "127.0.0.1"
+#define RECV_PORT 7777
+#define RECV_TIMEOUT_SEC 5
+
+struct ksock_test_env {
+ const char *netns;
+ bool netns_created;
+ struct nstoken *nstoken;
+ struct sockaddr_in addr;
+ int rfd;
+ char buf[32];
+};
+
+static bool ksock_test_env_setup(struct ksock_test_env *env, const char *netns)
+{
+ struct timeval tv;
+ int err;
+
+ memset(env, 0, sizeof(*env));
+ env->netns = netns;
+ env->rfd = -1;
+
+ SYS(fail, "ip netns add %s", netns);
+ env->netns_created = true;
+ SYS(fail, "ip -net %s link set lo up", netns);
+
+ env->nstoken = open_netns(netns);
+ if (!ASSERT_OK_PTR(env->nstoken, "open_netns"))
+ goto fail;
+
+ env->rfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (!ASSERT_OK_FD(env->rfd, "receiver socket"))
+ goto fail;
+
+ env->addr.sin_family = AF_INET;
+ env->addr.sin_addr.s_addr = inet_addr(LOOPBACK_IP);
+ env->addr.sin_port = htons(RECV_PORT);
+
+ err = bind(env->rfd, (struct sockaddr *)&env->addr, sizeof(env->addr));
+ if (!ASSERT_OK(err, "bind receiver"))
+ goto fail;
+
+ tv.tv_sec = RECV_TIMEOUT_SEC;
+ tv.tv_usec = 0;
+ err = setsockopt(env->rfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ if (!ASSERT_OK(err, "set rcvtimeo"))
+ goto fail;
+
+ return true;
+
+fail:
+ if (env->rfd >= 0) {
+ close(env->rfd);
+ env->rfd = -1;
+ }
+ if (env->nstoken) {
+ close_netns(env->nstoken);
+ env->nstoken = NULL;
+ }
+ if (env->netns_created) {
+ SYS_NOFAIL("ip netns del %s >/dev/null 2>&1", netns);
+ env->netns_created = false;
+ }
+ return false;
+}
+
+static void ksock_test_env_cleanup(struct ksock_test_env *env)
+{
+ if (env->rfd >= 0)
+ close(env->rfd);
+ if (env->nstoken)
+ close_netns(env->nstoken);
+ if (env->netns_created) {
+ SYS_NOFAIL("ip netns del %s >/dev/null 2>&1", env->netns);
+ env->netns_created = false;
+ }
+}
+
+static void ksock_assert_recv(struct ksock_test_env *env, const char *data,
+ size_t data_sz)
+{
+ ssize_t n;
+
+ memset(env->buf, 0, sizeof(env->buf));
+ n = recvfrom(env->rfd, env->buf, sizeof(env->buf), 0, NULL, NULL);
+ if (!ASSERT_EQ(n, data_sz, "recvfrom len"))
+ return;
+ ASSERT_MEMEQ(env->buf, data, data_sz, "payload match");
+}
+
+static bool ksock_setup_ctx(struct ksock_basic *skel)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int err, pfd;
+
+ skel->bss->ipv4_remote = inet_addr(LOOPBACK_IP);
+ skel->bss->remote_port = RECV_PORT;
+
+ pfd = bpf_program__fd(skel->progs.ksock_setup);
+ err = bpf_prog_test_run_opts(pfd, &opts);
+ if (!ASSERT_OK(err, "ksock_setup run"))
+ return false;
+ if (!ASSERT_OK(opts.retval, "ksock_setup retval"))
+ return false;
+
+ return true;
+}
+
+void test_ksock_basic(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct ksock_test_env env;
+ struct ksock_basic *skel;
+ int err, pfd;
+
+ skel = ksock_basic__open();
+ if (!ASSERT_OK_PTR(skel, "skel open"))
+ return;
+
+ err = ksock_basic__load(skel);
+ if (!ASSERT_OK(err, "skel load")) {
+ ksock_basic__destroy(skel);
+ return;
+ }
+
+ if (!ksock_test_env_setup(&env, NS_TEST))
+ goto fail;
+
+ /* Step 1: Run the setup SYSCALL prog to create ksock */
+ if (!ksock_setup_ctx(skel))
+ goto fail;
+
+ /* Step 2: Run the send SYSCALL prog */
+ pfd = bpf_program__fd(skel->progs.ksock_send);
+ err = bpf_prog_test_run_opts(pfd, &opts);
+ if (!ASSERT_OK(err, "ksock_send run"))
+ goto fail;
+ if (!ASSERT_EQ(opts.retval,
+ sizeof(skel->data->send_data), "sendmsg bytes"))
+ goto fail;
+
+ /* Step 3: Receive and verify the data */
+ ksock_assert_recv(&env, skel->data->send_data,
+ sizeof(skel->data->send_data));
+
+fail:
+ ksock_test_env_cleanup(&env);
+ ksock_basic__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_basic.c b/tools/testing/selftests/bpf/progs/ksock_basic.c
new file mode 100644
index 000000000000..e8131932d97b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_basic.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+__be32 ipv4_remote;
+__u16 remote_port;
+
+char send_data[32] = "hello from bpf ksock";
+
+SEC("syscall")
+int ksock_setup(void *ctx)
+{
+ struct bpf_ksock_create_opts create_opts = {};
+ struct bpf_ksock_addr_opts addr_opts = {};
+ struct bpf_ksock *ks;
+ int err = 0;
+
+ create_opts.family = AF_INET;
+ create_opts.type = SOCK_DGRAM;
+ create_opts.protocol = IPPROTO_UDP;
+
+ ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
+ if (!ks)
+ return err;
+
+ addr_opts.family = AF_INET;
+ addr_opts.port = remote_port;
+ addr_opts.ipv4_addr = ipv4_remote;
+
+ err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
+ if (err) {
+ bpf_ksock_release(ks);
+ return err;
+ }
+
+ err = ksock_ctx_insert(ks);
+ if (err && err != -EEXIST)
+ return err;
+ return 0;
+}
+
+SEC("syscall")
+int ksock_send(void *ctx)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *ks;
+ int send = -1;
+
+ v = ksock_ctx_value_lookup();
+ if (!v)
+ return -ENOENT;
+
+ ks = bpf_kptr_xchg(&v->ctx, NULL);
+ if (!ks)
+ return -ENOENT;
+
+ send = bpf_ksock_send(ks, send_data, sizeof(send_data));
+ bpf_ksock_release(ks);
+ return send;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h
new file mode 100644
index 000000000000..77f30a42be60
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _KSOCK_COMMON_H
+#define _KSOCK_COMMON_H
+
+#include "errno.h"
+#include <stdbool.h>
+
+#define SOCK_STREAM 1
+#define SOCK_DGRAM 2
+#define IPPROTO_TCP 6
+#define IPPROTO_UDP 17
+
+struct bpf_ksock *bpf_ksock_create(const struct bpf_ksock_create_opts *opts,
+ u32 opts__sz, int *err__uninit) __ksym;
+int bpf_ksock_connect(struct bpf_ksock *ks,
+ const struct bpf_ksock_addr_opts *opts, u32 opts__sz) __ksym;
+struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks) __ksym;
+void bpf_ksock_release(struct bpf_ksock *ks) __ksym;
+int bpf_ksock_send(struct bpf_ksock *ks,
+ const void *data, u32 data__sz) __ksym;
+
+struct __ksock_ctx_value {
+ struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct __ksock_ctx_value);
+ __uint(max_entries, 1);
+} __ksock_ctx_map SEC(".maps");
+
+static inline struct __ksock_ctx_value *ksock_ctx_value_lookup(void)
+{
+ u32 key = 0;
+
+ return bpf_map_lookup_elem(&__ksock_ctx_map, &key);
+}
+
+static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *old;
+
+ v = ksock_ctx_value_lookup();
+ if (!v) {
+ bpf_ksock_release(ctx);
+ return -ENOENT;
+ }
+
+ old = bpf_kptr_xchg(&v->ctx, ctx);
+ if (old) {
+ bpf_ksock_release(old);
+ return -EEXIST;
+ }
+
+ return 0;
+}
+
+#endif /* _KSOCK_COMMON_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 4/5] selftests/bpf: Add ksock LSM recursion test
2026-07-22 10:44 [PATCH bpf-next v2 0/5] Introduce bpf_ksock Mahe Tardy
` (2 preceding siblings ...)
2026-07-22 10:44 ` [PATCH bpf-next v2 3/5] selftests/bpf: Add ksock kfunc test Mahe Tardy
@ 2026-07-22 10:44 ` Mahe Tardy
2026-07-22 10:44 ` [PATCH bpf-next v2 5/5] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-07-22 10:44 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, sdf.kernel, ameryhung, kuniyu, Mahe Tardy
The bpf_ksock_send() kfunc triggers the security_socket_sendmsg() LSM
hook via kernel_sendmsg(), thus attaching an LSM program triggering that
kfunc on the same hook would provoke recursion. This test exercises that
path and make sure that recursion is guarded and prevented.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../testing/selftests/bpf/prog_tests/ksock.c | 84 +++++++++++++++++--
.../testing/selftests/bpf/progs/ksock_basic.c | 33 +-------
.../selftests/bpf/progs/ksock_common.h | 37 ++++++++
.../selftests/bpf/progs/ksock_recursion.c | 69 +++++++++++++++
4 files changed, 186 insertions(+), 37 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/ksock_recursion.c
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock.c b/tools/testing/selftests/bpf/prog_tests/ksock.c
index 5b22ece4be4e..f66660fc191d 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksock.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
@@ -6,8 +6,10 @@
#include "test_progs.h"
#include "network_helpers.h"
#include "ksock_basic.skel.h"
+#include "ksock_recursion.skel.h"
#define NS_TEST "ksock_basic_ns"
+#define NS_LSM_RECURSION_TEST "ksock_lsm_recursion_ns"
#define LOOPBACK_IP "127.0.0.1"
#define RECV_PORT 7777
#define RECV_TIMEOUT_SEC 5
@@ -98,15 +100,16 @@ static void ksock_assert_recv(struct ksock_test_env *env, const char *data,
ASSERT_MEMEQ(env->buf, data, data_sz, "payload match");
}
-static bool ksock_setup_ctx(struct ksock_basic *skel)
+static bool ksock_setup_ctx(struct bpf_program *prog, __be32 *ipv4_remote,
+ __u16 *remote_port)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
int err, pfd;
- skel->bss->ipv4_remote = inet_addr(LOOPBACK_IP);
- skel->bss->remote_port = RECV_PORT;
+ *ipv4_remote = inet_addr(LOOPBACK_IP);
+ *remote_port = RECV_PORT;
- pfd = bpf_program__fd(skel->progs.ksock_setup);
+ pfd = bpf_program__fd(prog);
err = bpf_prog_test_run_opts(pfd, &opts);
if (!ASSERT_OK(err, "ksock_setup run"))
return false;
@@ -137,7 +140,9 @@ void test_ksock_basic(void)
goto fail;
/* Step 1: Run the setup SYSCALL prog to create ksock */
- if (!ksock_setup_ctx(skel))
+ if (!ksock_setup_ctx(skel->progs.ksock_setup,
+ &skel->bss->ipv4_remote,
+ &skel->bss->remote_port))
goto fail;
/* Step 2: Run the send SYSCALL prog */
@@ -157,3 +162,72 @@ void test_ksock_basic(void)
ksock_test_env_cleanup(&env);
ksock_basic__destroy(skel);
}
+
+void test_ksock_lsm_recursion(void)
+{
+ struct ksock_test_env env;
+ struct ksock_recursion *skel;
+ char trigger = 'x';
+ int tfd = -1;
+ int err;
+ ssize_t n;
+
+ skel = ksock_recursion__open();
+ if (!ASSERT_OK_PTR(skel, "skel open"))
+ return;
+
+ err = ksock_recursion__load(skel);
+ if (!ASSERT_OK(err, "skel load")) {
+ ksock_recursion__destroy(skel);
+ return;
+ }
+
+ if (!ksock_test_env_setup(&env, NS_LSM_RECURSION_TEST))
+ goto fail;
+
+ /* Step 1: Run the setup SYSCALL prog to create the ksock */
+ if (!ksock_setup_ctx(skel->progs.ksock_setup,
+ &skel->bss->ipv4_remote,
+ &skel->bss->remote_port))
+ goto fail;
+
+ /* Step 2: Attach LSM prog and trigger socket_sendmsg from userspace */
+ skel->links.ksock_socket_sendmsg =
+ bpf_program__attach_lsm(skel->progs.ksock_socket_sendmsg);
+ if (!ASSERT_OK_PTR(skel->links.ksock_socket_sendmsg,
+ "attach socket_sendmsg lsm"))
+ goto fail;
+
+ tfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (!ASSERT_OK_FD(tfd, "trigger socket"))
+ goto fail;
+
+ skel->bss->target_pid = getpid();
+ skel->bss->trigger_send = 1;
+ n = sendto(tfd, &trigger, sizeof(trigger), 0,
+ (struct sockaddr *)&env.addr, sizeof(env.addr));
+ skel->bss->target_pid = 0;
+ skel->bss->trigger_send = 0;
+ if (!ASSERT_EQ(n, sizeof(trigger), "trigger sendto"))
+ goto fail;
+
+ /* Step 3: The nested bpf_ksock_send() must hit the recursion guard */
+ if (!ASSERT_EQ(skel->bss->rec_count, 2,
+ "socket_sendmsg recursion count"))
+ goto fail;
+ if (!ASSERT_EQ(skel->data->rec_kfunc_rets[0], -EBUSY,
+ "recursive send status"))
+ goto fail;
+ if (!ASSERT_EQ(skel->data->rec_kfunc_rets[1],
+ sizeof(skel->data->send_data), "outer send bytes"))
+ goto fail;
+
+ ksock_assert_recv(&env, skel->data->send_data,
+ sizeof(skel->data->send_data));
+
+fail:
+ if (tfd >= 0)
+ close(tfd);
+ ksock_test_env_cleanup(&env);
+ ksock_recursion__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_basic.c b/tools/testing/selftests/bpf/progs/ksock_basic.c
index e8131932d97b..189b0fc15770 100644
--- a/tools/testing/selftests/bpf/progs/ksock_basic.c
+++ b/tools/testing/selftests/bpf/progs/ksock_basic.c
@@ -8,41 +8,10 @@
#include "bpf_tracing_net.h"
#include "ksock_common.h"
-__be32 ipv4_remote;
-__u16 remote_port;
-
-char send_data[32] = "hello from bpf ksock";
-
SEC("syscall")
int ksock_setup(void *ctx)
{
- struct bpf_ksock_create_opts create_opts = {};
- struct bpf_ksock_addr_opts addr_opts = {};
- struct bpf_ksock *ks;
- int err = 0;
-
- create_opts.family = AF_INET;
- create_opts.type = SOCK_DGRAM;
- create_opts.protocol = IPPROTO_UDP;
-
- ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
- if (!ks)
- return err;
-
- addr_opts.family = AF_INET;
- addr_opts.port = remote_port;
- addr_opts.ipv4_addr = ipv4_remote;
-
- err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
- if (err) {
- bpf_ksock_release(ks);
- return err;
- }
-
- err = ksock_ctx_insert(ks);
- if (err && err != -EEXIST)
- return err;
- return 0;
+ return do_ksock_setup();
}
SEC("syscall")
diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h
index 77f30a42be60..dc3eed78fb2f 100644
--- a/tools/testing/selftests/bpf/progs/ksock_common.h
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -59,4 +59,41 @@ static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
return 0;
}
+/* Globals for passing config from userspace */
+__be32 ipv4_remote;
+__u16 remote_port;
+
+char send_data[32] = "hello from bpf ksock";
+
+static inline int do_ksock_setup(void)
+{
+ struct bpf_ksock_create_opts create_opts = {};
+ struct bpf_ksock_addr_opts addr_opts = {};
+ struct bpf_ksock *ks;
+ int err = 0;
+
+ create_opts.family = AF_INET;
+ create_opts.type = SOCK_DGRAM;
+ create_opts.protocol = IPPROTO_UDP;
+
+ ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
+ if (!ks)
+ return err;
+
+ addr_opts.family = AF_INET;
+ addr_opts.port = remote_port;
+ addr_opts.ipv4_addr = ipv4_remote;
+
+ err = bpf_ksock_connect(ks, &addr_opts, sizeof(addr_opts));
+ if (err) {
+ bpf_ksock_release(ks);
+ return err;
+ }
+
+ err = ksock_ctx_insert(ks);
+ if (err && err != -EEXIST)
+ return err;
+ return 0;
+}
+
#endif /* _KSOCK_COMMON_H */
diff --git a/tools/testing/selftests/bpf/progs/ksock_recursion.c b/tools/testing/selftests/bpf/progs/ksock_recursion.c
new file mode 100644
index 000000000000..52f5200943e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_recursion.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
+int target_pid;
+int trigger_send;
+
+unsigned int rec_count;
+int rec_kfunc_rets[] = { -1, -1 };
+
+SEC("syscall")
+int ksock_setup(void *ctx)
+{
+ return do_ksock_setup();
+}
+
+SEC("lsm.s/socket_sendmsg")
+int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg,
+ int size, int ret)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *ks, *tmp;
+ u32 pid = bpf_get_current_pid_tgid() >> 32;
+ int kfunc_ret;
+
+ if (ret || !trigger_send || pid != target_pid)
+ return ret;
+
+ v = ksock_ctx_value_lookup();
+ if (!v) {
+ kfunc_ret = -ENOENT;
+ goto out;
+ }
+
+ ks = NULL;
+ bpf_rcu_read_lock();
+ tmp = v->ctx;
+ if (tmp)
+ ks = bpf_ksock_acquire(tmp);
+ bpf_rcu_read_unlock();
+
+ if (!ks) {
+ kfunc_ret = -ENOENT;
+ goto out;
+ }
+
+ kfunc_ret = bpf_ksock_send(ks, send_data, sizeof(send_data));
+ bpf_ksock_release(ks);
+
+out:
+ rec_kfunc_rets[rec_count & 1] = kfunc_ret;
+ __sync_fetch_and_add(&rec_count, 1);
+
+ if (kfunc_ret != -EBUSY)
+ trigger_send = 0;
+
+ return ret;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 5/5] selftests/bpf: Add ksock test for async callback guard
2026-07-22 10:44 [PATCH bpf-next v2 0/5] Introduce bpf_ksock Mahe Tardy
` (3 preceding siblings ...)
2026-07-22 10:44 ` [PATCH bpf-next v2 4/5] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
@ 2026-07-22 10:44 ` Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-07-22 10:44 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, sdf.kernel, ameryhung, kuniyu, Mahe Tardy
Because the kfuncs are going through LSM hooks, allowing their use via
workqueue callbacks would expose the wrong credentials. This test
ensures the kfunc are preventing any use from these contexts.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../selftests/bpf/prog_tests/ksock_wq.c | 34 ++++++++++
tools/testing/selftests/bpf/progs/ksock_wq.c | 62 +++++++++++++++++++
2 files changed, 96 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_wq.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_wq.c
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_wq.c b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
new file mode 100644
index 000000000000..184b134f3823
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_wq.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <unistd.h>
+
+#include "test_progs.h"
+#include "ksock_wq.skel.h"
+
+void test_ksock_wq(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct ksock_wq *skel;
+ int err;
+
+ skel = ksock_wq__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "ksock_wq open and load"))
+ return;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.ksock_wq_start),
+ &opts);
+ if (!ASSERT_OK(err, "run ksock_wq_start"))
+ goto out;
+ if (!ASSERT_OK(opts.retval, "ksock_wq_start retval"))
+ goto out;
+
+ while (!__atomic_load_n(&skel->bss->callback_done, __ATOMIC_ACQUIRE))
+ usleep(1000);
+
+ ASSERT_EQ(skel->bss->create_err, -EOPNOTSUPP,
+ "workqueue create rejected");
+
+out:
+ ksock_wq__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_wq.c b/tools/testing/selftests/bpf/progs/ksock_wq.c
new file mode 100644
index 000000000000..16a1873d132e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_wq.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include "bpf_tracing_net.h"
+#include "errno.h"
+#include "ksock_common.h"
+
+struct ksock_wq_value {
+ struct bpf_wq work;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, u32);
+ __type(value, struct ksock_wq_value);
+} work_map SEC(".maps");
+
+int create_err;
+u32 callback_done;
+
+static int ksock_wq_callback(void *map, int *key, void *value)
+{
+ struct bpf_ksock_create_opts opts = {
+ .family = AF_INET,
+ .type = SOCK_DGRAM,
+ .protocol = IPPROTO_UDP,
+ };
+ struct bpf_ksock *ks;
+ int err = 0;
+
+ ks = bpf_ksock_create(&opts, sizeof(opts), &err);
+ if (ks)
+ bpf_ksock_release(ks);
+ create_err = err;
+ __sync_fetch_and_add(&callback_done, 1);
+ return 0;
+}
+
+SEC("syscall")
+int ksock_wq_start(void *ctx)
+{
+ struct ksock_wq_value *value;
+ u32 key = 0;
+ int err;
+
+ 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;
+ err = bpf_wq_set_callback(&value->work, ksock_wq_callback, 0);
+ if (err)
+ return err;
+ return bpf_wq_start(&value->work, 0);
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread