* [PATCH bpf-next 0/6] Introduce bpf_ksock
@ 2026-07-06 9:35 Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper Mahe Tardy
` (5 more replies)
0 siblings, 6 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, Mahe Tardy
This patch series introduces bpf_ksock, a set of BPF kfuncs to allow BPF
programs to create UDP sockets and send data. This provides a mechanism
for BPF LSM progs to emit telemetry over UDP independently of userspace.
The main use case is to be able to completely dispense with
agents/daemons for BPF programs after startup. In the case of
Isovalent's Tetragon, the idea would be to be able to emit security
alerts or export data from BPF even when the agent is down. For meta,
according to Liam presentation[^2], this could replace logging via
ringbuffers which created cross-binary versioning issues.
The implementation follows the established kfunc lifecycle pattern
(create/acquire/release with refcounting, kptr map storage, dtor
registration), for example used by the network bpf_crypto kfuncs.
For reference, this was discussed at LSF/MM/BPF 2025[^1] in Montreal,
again at Plumbers 2025 in Tokyo. Liam Wisehart mentioned this work
during his presentation of BpfJailer[^2]. Then it was also discussed
during LSF/MM/BPF 2026 in Zagreb.
A first version of it, called bpf_netpoll was submitted to the mailing
list but eventually NACKED by Jakub Kicinski[^3]. The discussion
eventually reached an agreement that we should use regular kernel
sockets if we want to do network from BPF programs[^4]. This was
fundamentally more complex to implement but here is a first proposition
of how it could look like after several automated reviews using sashiko.
For more details, here are some of the main adjustements I had to make
during the preparation of these patches:
Initially, the goal was to register the ksock_kfunc_set with
BPF_PROG_TYPE_UNSPEC to allow send to be called from any programs. This
introduces significant challenges (but might be doable). The limitation
is still that the programs should be able to sleep but combining this
with bpf workqueue allows to send from virtually anywhere. However, not
by-passing LSM socket hooks make it impossible to be called from the
workqueue context as the credential of the initial caller would not be
preserved. Also, it would be easy for users to shoot themselves in the
foot and attach a program that sends asynchronously over the network on
a network hook. So the idea for now is to restrict the ksock_kfunc_set
(which is acquire, release and send) to SYSCALL and LSM to make it
simpler. Also to make the patch set easier to start with, the sockets
are restricted to UDP.
v0 updates (from local sashiko iterations):
- do not bypass the LSM and thus add send re-enter protection;
- limit the number of socket creation through the kfunc per ns;
- copy the arg values to avoid TOCTOU race since kfunc can sleep;
- prevent calling bpf_ksock_create from workqueue with improper creds.
[^1]: https://lwn.net/Articles/1022034/
[^2]: https://lpc.events/event/19/contributions/2159/
[^3]: https://lore.kernel.org/bpf/20260511182019.69ebc7c6@kernel.org/
[^4]: https://lore.kernel.org/bpf/CAPhsuW71P58XqsXrLbqsShgnozg66TA=T_c=fYrqSSzvL1tTWA@mail.gmail.com/
Mahe Tardy (6):
net: Add __sys_connect_socket() helper
bpf: Add ksock kfuncs
selftests/bpf: Add ksock kfunc test
selftests/bpf: Add ksock LSM recursion test
selftests/bpf: Add ksock net ns quota tests
selftests/bpf: Add ksock test for async callback guard
Documentation/admin-guide/sysctl/net.rst | 12 +
include/linux/bpf_ksock.h | 50 ++
include/linux/socket.h | 2 +
kernel/bpf/verifier.c | 3 +
net/core/Makefile | 3 +
net/core/bpf_ksock.c | 516 ++++++++++++++++++
net/core/sysctl_net_core.c | 11 +
net/socket.c | 32 +-
.../testing/selftests/bpf/prog_tests/ksock.c | 239 ++++++++
.../selftests/bpf/prog_tests/ksock_quota.c | 139 +++++
.../selftests/bpf/prog_tests/ksock_wq.c | 34 ++
.../testing/selftests/bpf/progs/ksock_basic.c | 37 ++
.../selftests/bpf/progs/ksock_common.h | 110 ++++
.../testing/selftests/bpf/progs/ksock_quota.c | 167 ++++++
.../selftests/bpf/progs/ksock_recursion.c | 69 +++
tools/testing/selftests/bpf/progs/ksock_wq.c | 62 +++
16 files changed, 1472 insertions(+), 14 deletions(-)
create mode 100644 include/linux/bpf_ksock.h
create mode 100644 net/core/bpf_ksock.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_quota.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_wq.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_basic.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_common.h
create mode 100644 tools/testing/selftests/bpf/progs/ksock_quota.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_recursion.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_wq.c
--
2.34.1
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
` (4 subsequent siblings)
5 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, 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] 24+ messages in thread
* [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
2026-07-06 10:01 ` sashiko-bot
` (4 more replies)
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
` (3 subsequent siblings)
5 siblings, 5 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, 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 six kfuncs:
bpf_ksock_create() - Create a socket (sleepable)
bpf_ksock_bind() - Bind socket to local address (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_bind, 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.
A bpf_ksock_max sysctl is added to limit the maximum number of BPF
kernel sockets that may exist in each network namespace. Out of
simplicity for now, the settings is host wide but the counters are per
network namespace.
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>
---
Documentation/admin-guide/sysctl/net.rst | 12 +
include/linux/bpf_ksock.h | 50 +++
kernel/bpf/verifier.c | 3 +
net/core/Makefile | 3 +
net/core/bpf_ksock.c | 516 +++++++++++++++++++++++
net/core/sysctl_net_core.c | 11 +
6 files changed, 595 insertions(+)
create mode 100644 include/linux/bpf_ksock.h
create mode 100644 net/core/bpf_ksock.c
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index e586e17fc7a5..8bcdc1bfd711 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -128,6 +128,18 @@ compiler in order to reject unprivileged JIT requests once it has
been surpassed. bpf_jit_limit contains the value of the global limit
in bytes.
+bpf_ksock_max
+-------------
+
+Maximum number of BPF kernel sockets that may exist in each network namespace.
+This host-wide setting is exposed in the initial network namespace and applies
+the same limit independently to every network namespace. Sockets awaiting
+deferred RCU and workqueue cleanup remain counted until the underlying socket
+has been released. A value of 0 disables creation of BPF kernel sockets in
+every network namespace.
+
+Default: 1024
+
dev_weight
----------
diff --git a/include/linux/bpf_ksock.h b/include/linux/bpf_ksock.h
new file mode 100644
index 000000000000..0485b8eddc1f
--- /dev/null
+++ b/include/linux/bpf_ksock.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _BPF_KSOCK_H
+#define _BPF_KSOCK_H
+
+#include <linux/types.h>
+
+#define BPF_KSOCK_MAX_DEFAULT 1024
+
+extern int sysctl_bpf_ksock_max;
+
+/**
+ * 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/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 25aea4271cd0..52f6a89546e4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4424,6 +4424,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..8bde734bc917
--- /dev/null
+++ b/net/core/bpf_ksock.c
@@ -0,0 +1,516 @@
+// 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/cache.h>
+#include <linux/hash.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/list_bl.h>
+#include <linux/net.h>
+#include <linux/refcount.h>
+#include <linux/rcupdate.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/ipv6.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.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;
+};
+
+struct bpf_ksock_send_guard {
+ struct hlist_bl_node node;
+ struct task_struct *task;
+};
+
+#define BPF_KSOCK_SEND_GUARD_HASH_BITS 6
+
+struct bpf_ksock_send_bucket {
+ struct hlist_bl_head head;
+} ____cacheline_aligned_in_smp;
+
+static struct bpf_ksock_send_bucket
+ bpf_ksock_send_buckets[1 << BPF_KSOCK_SEND_GUARD_HASH_BITS];
+
+static struct bpf_ksock_send_bucket *
+bpf_ksock_send_bucket(const struct task_struct *task)
+{
+ u32 bucket_idx = hash_ptr(task, BPF_KSOCK_SEND_GUARD_HASH_BITS);
+
+ return &bpf_ksock_send_buckets[bucket_idx];
+}
+
+static bool bpf_ksock_send_enter(struct bpf_ksock_send_guard *guard)
+{
+ struct bpf_ksock_send_guard *entry;
+ struct hlist_bl_node *pos;
+ struct bpf_ksock_send_bucket *bucket;
+
+ bucket = bpf_ksock_send_bucket(current);
+ hlist_bl_lock(&bucket->head);
+ hlist_bl_for_each_entry(entry, pos, &bucket->head, node) {
+ if (entry->task == current) {
+ hlist_bl_unlock(&bucket->head);
+ return false;
+ }
+ }
+
+ guard->task = current;
+ INIT_HLIST_BL_NODE(&guard->node);
+ hlist_bl_add_head(&guard->node, &bucket->head);
+ hlist_bl_unlock(&bucket->head);
+ return true;
+}
+
+static void bpf_ksock_send_exit(struct bpf_ksock_send_guard *guard)
+{
+ struct bpf_ksock_send_bucket *bucket;
+
+ bucket = bpf_ksock_send_bucket(guard->task);
+ hlist_bl_lock(&bucket->head);
+ hlist_bl_del(&guard->node);
+ hlist_bl_unlock(&bucket->head);
+}
+
+struct bpf_ksock_net {
+ atomic_t count;
+};
+
+static unsigned int bpf_ksock_net_id;
+int sysctl_bpf_ksock_max __read_mostly = BPF_KSOCK_MAX_DEFAULT;
+
+static struct bpf_ksock_net *bpf_ksock_pernet(const struct net *net)
+{
+ return net_generic(net, bpf_ksock_net_id);
+}
+
+static struct pernet_operations bpf_ksock_net_ops = {
+ .id = &bpf_ksock_net_id,
+ .size = sizeof(struct bpf_ksock_net),
+};
+
+static bool bpf_ksock_net_try_charge(struct net *net)
+{
+ struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
+ int count = atomic_read(&kn->count);
+ int max;
+
+ do {
+ max = READ_ONCE(sysctl_bpf_ksock_max);
+ if (count >= max)
+ return false;
+ } while (!atomic_try_cmpxchg(&kn->count, &count, count + 1));
+
+ return true;
+}
+
+static void bpf_ksock_net_uncharge(struct net *net)
+{
+ struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
+
+ WARN_ON_ONCE(atomic_dec_return(&kn->count) < 0);
+}
+
+static void ksock_release_work_fn(struct work_struct *work)
+{
+ struct bpf_ksock *ks =
+ container_of(to_rcu_work(work), struct bpf_ksock, rwork);
+ struct net *net = get_net(sock_net(ks->sock->sk));
+
+ sock_release(ks->sock);
+ bpf_ksock_net_uncharge(net);
+ put_net(net);
+ kfree(ks);
+}
+
+static int bpf_ksock_get_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 socket is charged against the active network namespace's BPF kernel
+ * socket quota.
+ *
+ * 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;
+ struct net *net;
+ 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;
+ }
+
+ net = current->nsproxy->net_ns;
+ if (!bpf_ksock_net_try_charge(net)) {
+ err = -ENOSPC;
+ goto err_free;
+ }
+
+ /*
+ * 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_uncharge;
+
+ 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_uncharge:
+ bpf_ksock_net_uncharge(net);
+err_free:
+ kfree(ks);
+err_out:
+ put_unaligned(err, err__uninit);
+ return NULL;
+}
+
+/**
+ * bpf_ksock_bind() - Bind a BPF kernel socket to a local address.
+ * @ks: The BPF kernel socket context.
+ * @opts: Pointer to struct bpf_ksock_addr_opts with local address.
+ * @opts__sz: Size of the opts struct.
+ *
+ * Binds the socket to the specified local address and port.
+ * This is optional; if not called, the kernel will auto-assign.
+ *
+ * This function may sleep while binding 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_bind(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_get_addr(opts, opts__sz, &addr);
+ if (addrlen < 0)
+ return addrlen;
+
+ return __sys_bind_socket(ks->sock, &addr, addrlen);
+}
+
+/**
+ * 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_get_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 bpf_ksock_send_guard guard;
+ 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 (!bpf_ksock_send_enter(&guard))
+ return -EBUSY;
+
+ ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz);
+ bpf_ksock_send_exit(&guard);
+
+ 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_bind, 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_pernet_subsys(&bpf_ksock_net_ops);
+ if (ret)
+ return ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
+ &ksock_init_kfunc_set);
+ if (ret) {
+ unregister_pernet_subsys(&bpf_ksock_net_ops);
+ return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
+ ARRAY_SIZE(bpf_ksock_dtors),
+ THIS_MODULE);
+}
+
+late_initcall(bpf_ksock_kfunc_init);
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc12..d221a180c877 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -7,6 +7,7 @@
*/
#include <linux/filter.h>
+#include <linux/bpf_ksock.h>
#include <linux/mm.h>
#include <linux/sysctl.h>
#include <linux/module.h>
@@ -525,6 +526,16 @@ static struct ctl_table net_core_table[] = {
.extra1 = SYSCTL_LONG_ONE,
.extra2 = &bpf_jit_limit_max,
},
+#endif
+#if IS_ENABLED(CONFIG_BPF_SYSCALL) && IS_ENABLED(CONFIG_INET)
+ {
+ .procname = "bpf_ksock_max",
+ .data = &sysctl_bpf_ksock_max,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_ZERO,
+ },
#endif
{
.procname = "netdev_tstamp_prequeue",
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
2026-07-06 10:10 ` sashiko-bot
2026-07-06 10:16 ` bot+bpf-ci
2026-07-06 9:35 ` [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
` (2 subsequent siblings)
5 siblings, 2 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, Mahe Tardy
Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
bpf setup program creates a ksock context and connect the socket.
Another syscall bpf program lookup the context and send test data.
The userspace harness create 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 | 165 ++++++++++++++++++
.../testing/selftests/bpf/progs/ksock_basic.c | 68 ++++++++
.../selftests/bpf/progs/ksock_common.h | 73 ++++++++
3 files changed, 306 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..085ddb59067e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
@@ -0,0 +1,165 @@
+// 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);
+ if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
+ return false;
+
+ 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);
+ if (!ASSERT_OK_FD(pfd, "ksock_send fd"))
+ goto fail;
+
+ 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..87d92372d28d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -0,0 +1,73 @@
+/* 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_bind(struct bpf_ksock *ks,
+ const struct bpf_ksock_addr_opts *opts, u32 opts__sz) __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 local, *v;
+ struct bpf_ksock *old;
+ u32 key = 0;
+ int err;
+
+ local.ctx = NULL;
+ err = bpf_map_update_elem(&__ksock_ctx_map, &key, &local, 0);
+ if (err) {
+ bpf_ksock_release(ctx);
+ return err;
+ }
+
+ v = bpf_map_lookup_elem(&__ksock_ctx_map, &key);
+ 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] 24+ messages in thread
* [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
` (2 preceding siblings ...)
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
5 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, 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 085ddb59067e..8b14fd98d410 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);
if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
return false;
@@ -140,7 +143,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 */
@@ -163,3 +168,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 87d92372d28d..7cf64319b901 100644
--- a/tools/testing/selftests/bpf/progs/ksock_common.h
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -70,4 +70,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] 24+ messages in thread
* [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
` (3 preceding siblings ...)
2026-07-06 9:35 ` [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
5 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, Mahe Tardy
The new bpf_ksock_create() kfunc is limited by quota enforced at the
network namespace layer. This test verifies that the quota are enforced
and the acquire/release patch increase/reduce the counters.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../selftests/bpf/prog_tests/ksock_quota.c | 139 +++++++++++++++
.../testing/selftests/bpf/progs/ksock_quota.c | 167 ++++++++++++++++++
2 files changed, 306 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_quota.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_quota.c
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_quota.c b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
new file mode 100644
index 000000000000..c86d9b0a9513
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <sys/stat.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "sysctl_helpers.h"
+#include "ksock_quota.skel.h"
+
+#define NS_NET_QUOTA_TEST1 "ksock_net_quota_ns1"
+#define NS_NET_QUOTA_TEST2 "ksock_net_quota_ns2"
+#define KSOCK_MAX_SYSCTL "/proc/sys/net/core/bpf_ksock_max"
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NS1_OFFSET 0
+#define KSOCK_QUOTA_NS2_OFFSET KSOCK_QUOTA_NS_SLOTS
+
+static int ksock_run_prog(struct bpf_program *prog)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int err;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ if (err)
+ return err;
+ return opts.retval;
+}
+
+static __u64 ksock_quota_release_completed(struct ksock_quota *skel)
+{
+ return __atomic_load_n(&skel->bss->quota_release_completed,
+ __ATOMIC_ACQUIRE);
+}
+
+static int ksock_release_quota_slots(struct ksock_quota *skel, int slot_offset)
+{
+ __u64 completed;
+ int err;
+
+ completed = ksock_quota_release_completed(skel);
+ skel->bss->quota_slot_offset = slot_offset;
+ err = ksock_run_prog(skel->progs.ksock_quota_release);
+ if (!ASSERT_OK(err, "ksock_quota_release"))
+ return -1;
+
+ while (ksock_quota_release_completed(skel) - completed <
+ skel->bss->quota_released)
+ usleep(1000);
+
+ return skel->bss->quota_released;
+}
+
+static bool ksock_expect_quota(struct ksock_quota *skel, int slot_offset,
+ const char *name)
+{
+ int err;
+
+ skel->bss->quota_slot_offset = slot_offset;
+ err = ksock_run_prog(skel->progs.ksock_quota_create);
+ if (!ASSERT_OK(err, name))
+ return false;
+ if (!ASSERT_EQ(skel->bss->quota_created, 2, name))
+ return false;
+ return ASSERT_EQ(skel->bss->quota_err, -ENOSPC, name);
+}
+
+static bool record_current_ns(struct ksock_quota *skel, int target)
+{
+ struct stat st;
+ int err;
+
+ err = stat("/proc/self/ns/net", &st);
+ if (!ASSERT_OK(err, "stat netns"))
+ return false;
+ skel->bss->target_netns_inum[target] = (__u32)st.st_ino;
+ return true;
+}
+
+void serial_test_ksock_net_quota(void)
+{
+ char old_max[16] = {};
+ struct netns_obj *netns1 = NULL, *netns2 = NULL;
+ struct ksock_quota *skel;
+ int released;
+
+ skel = ksock_quota__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "ksock quota skeleton"))
+ return;
+ skel->links.ksock_release_work_enter =
+ bpf_program__attach_trace(skel->progs.ksock_release_work_enter);
+ if (!ASSERT_OK_PTR(skel->links.ksock_release_work_enter,
+ "attach ksock release work entry"))
+ goto out;
+ skel->links.ksock_release_work_exit =
+ bpf_program__attach_trace(skel->progs.ksock_release_work_exit);
+ if (!ASSERT_OK_PTR(skel->links.ksock_release_work_exit,
+ "attach ksock release work exit"))
+ goto out;
+
+ if (sysctl_set_or_fail(KSOCK_MAX_SYSCTL, old_max, "2"))
+ goto out;
+
+ netns1 = netns_new(NS_NET_QUOTA_TEST1, true);
+ if (!ASSERT_OK_PTR(netns1, "create first netns"))
+ goto out;
+ if (!record_current_ns(skel, 0))
+ goto out;
+
+ if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS1_OFFSET,
+ "first netns quota"))
+ goto out;
+
+ /* The host-wide setting grants the full allowance to each netns. */
+ netns2 = netns_new(NS_NET_QUOTA_TEST2, true);
+ if (!ASSERT_OK_PTR(netns2, "create second netns"))
+ goto out;
+ if (!record_current_ns(skel, 1))
+ goto out;
+
+ if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET,
+ "second netns quota"))
+ goto out;
+
+ released = ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+ if (!ASSERT_EQ(released, 2, "second netns quota released"))
+ goto out;
+
+ /* Both released slots must become available again. */
+ ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET, "recovered quota");
+
+out:
+ ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+ ksock_release_quota_slots(skel, KSOCK_QUOTA_NS1_OFFSET);
+ netns_free(netns2);
+ netns_free(netns1);
+ if (old_max[0])
+ sysctl_set_or_fail(KSOCK_MAX_SYSCTL, NULL, old_max);
+ ksock_quota__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_quota.c b/tools/testing/selftests/bpf/progs/ksock_quota.c
new file mode 100644
index 000000000000..91854ea333c1
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_quota.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NETNS 2
+#define KSOCK_QUOTA_SLOTS (KSOCK_QUOTA_NS_SLOTS * KSOCK_QUOTA_NETNS)
+
+struct ksock_quota_value {
+ struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, u32);
+ __type(value, struct ksock_quota_value);
+ __uint(max_entries, KSOCK_QUOTA_SLOTS);
+} quota_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, u64);
+ __type(value, u8);
+ __uint(max_entries, KSOCK_QUOTA_SLOTS);
+} release_workers SEC(".maps");
+
+u32 quota_created;
+u32 quota_released;
+u32 quota_slot_offset;
+u32 target_netns_inum[KSOCK_QUOTA_NETNS];
+u64 quota_release_completed;
+int quota_err;
+
+static __always_inline bool
+ksock_quota_create_one(u32 i,
+ const struct bpf_ksock_create_opts *create_opts)
+{
+ struct ksock_quota_value *v;
+ struct bpf_ksock *ks, *old;
+ int err = 0;
+
+ i += quota_slot_offset;
+ if (i >= KSOCK_QUOTA_SLOTS) {
+ quota_err = -ERANGE;
+ return true;
+ }
+
+ barrier_var(err);
+ ks = bpf_ksock_create(create_opts, sizeof(*create_opts), &err);
+ if (!ks) {
+ quota_err = err;
+ return true;
+ }
+
+ v = bpf_map_lookup_elem("a_map, &i);
+ if (!v) {
+ bpf_ksock_release(ks);
+ quota_err = -ENOENT;
+ return true;
+ }
+
+ old = bpf_kptr_xchg(&v->ctx, ks);
+ if (old)
+ bpf_ksock_release(old);
+ quota_created++;
+ return false;
+}
+
+static __always_inline void ksock_quota_release_one(u32 i)
+{
+ struct ksock_quota_value *v;
+ struct bpf_ksock *ks;
+
+ i += quota_slot_offset;
+ if (i >= KSOCK_QUOTA_SLOTS)
+ return;
+
+ v = bpf_map_lookup_elem("a_map, &i);
+ if (!v)
+ return;
+
+ ks = bpf_kptr_xchg(&v->ctx, NULL);
+ if (ks) {
+ bpf_ksock_release(ks);
+ quota_released++;
+ }
+}
+
+SEC("syscall")
+int ksock_quota_create(void *ctx)
+{
+ struct bpf_ksock_create_opts create_opts = {};
+
+ create_opts.family = AF_INET;
+ create_opts.type = SOCK_DGRAM;
+ create_opts.protocol = IPPROTO_UDP;
+ quota_created = 0;
+ quota_err = 0;
+
+ if (ksock_quota_create_one(0, &create_opts))
+ return 0;
+ if (ksock_quota_create_one(1, &create_opts))
+ return 0;
+ ksock_quota_create_one(2, &create_opts);
+
+ return 0;
+}
+
+SEC("syscall")
+int ksock_quota_release(void *ctx)
+{
+ quota_released = 0;
+ ksock_quota_release_one(0);
+ ksock_quota_release_one(1);
+ ksock_quota_release_one(2);
+
+ return 0;
+}
+
+SEC("fentry/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_enter, struct work_struct *work)
+{
+ struct rcu_work *rwork = container_of(work, struct rcu_work, work);
+ struct bpf_ksock *ks = container_of(rwork, struct bpf_ksock, rwork);
+ u64 pid_tgid = bpf_get_current_pid_tgid();
+ struct net *net;
+ u32 inum;
+ u8 tracked = 1;
+
+ /* ksock_release_work_fn() frees ks before the fexit program runs. */
+ net = BPF_CORE_READ(ks, sock, sk, __sk_common.skc_net.net);
+ if (!net)
+ return 0;
+
+ inum = BPF_CORE_READ(net, ns.inum);
+ if (inum != target_netns_inum[0] &&
+ inum != target_netns_inum[1])
+ return 0;
+
+ bpf_map_update_elem(&release_workers, &pid_tgid, &tracked, BPF_ANY);
+ return 0;
+}
+
+SEC("fexit/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_exit, struct work_struct *work)
+{
+ u64 pid_tgid = bpf_get_current_pid_tgid();
+ u8 *tracked;
+
+ tracked = bpf_map_lookup_elem(&release_workers, &pid_tgid);
+ if (!tracked)
+ return 0;
+ bpf_map_delete_elem(&release_workers, &pid_tgid);
+
+ /* The worker has released the socket and returned its quota charge. */
+ __sync_fetch_and_add("a_release_completed, 1);
+
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
` (4 preceding siblings ...)
2026-07-06 9:35 ` [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests Mahe Tardy
@ 2026-07-06 9:35 ` Mahe Tardy
5 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 9:35 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, 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] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
@ 2026-07-06 10:01 ` sashiko-bot
2026-07-06 15:42 ` Mahe Tardy
2026-07-06 10:30 ` bot+bpf-ci
` (3 subsequent siblings)
4 siblings, 1 reply; 24+ messages in thread
From: sashiko-bot @ 2026-07-06 10:01 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing rollback of pernet subsystem on initialization error paths in `bpf_ksock_kfunc_init`.
--
commit c26355c87f06c9eb5cc9cceccb20cfce50aa1e1e
Author: Mahe Tardy <mahe.tardy@gmail.com>
bpf: Add ksock kfuncs
This commit introduces BPF kfuncs to allow BPF LSM and SYSCALL programs to
create and use sockets for sending data. It adds several lifecycle and usage
kfuncs for kernel sockets, limits socket creation via a per-namespace sysctl,
and registers the necessary BTF kfuncs.
> diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
> new file mode 100644
> index 0000000000000..8bde734bc917d
> --- /dev/null
> +++ b/net/core/bpf_ksock.c
[ ... ]
> +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_pernet_subsys(&bpf_ksock_net_ops);
> + if (ret)
> + return ret;
> +
> + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
> + &ksock_init_kfunc_set);
> + if (ret) {
> + unregister_pernet_subsys(&bpf_ksock_net_ops);
> + return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> + ARRAY_SIZE(bpf_ksock_dtors),
> + THIS_MODULE);
[Severity: Medium]
If the later register_btf_kfunc_id_set() or register_btf_id_dtor_kfuncs()
calls fail in bpf_ksock_kfunc_init(), does this leak the pernet operations
registration?
The earlier error path for BPF_PROG_TYPE_SYSCALL registration properly
calls unregister_pernet_subsys(&bpf_ksock_net_ops). If these subsequent
registrations fail, the function returns the error directly without cleaning
up the pernet subsystem.
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260706093525.13030-1-mahe.tardy@gmail.com?part=2
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
@ 2026-07-06 10:10 ` sashiko-bot
2026-07-06 16:11 ` Mahe Tardy
2026-07-06 10:16 ` bot+bpf-ci
1 sibling, 1 reply; 24+ messages in thread
From: sashiko-bot @ 2026-07-06 10:10 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Dead code and flawed insert logic in `ksock_ctx_insert()` due to unconditional map element overwriting.
- [Low] Unnecessary manual validation of BPF program FDs after successful skeleton load.
--
commit 559d17f26b2fc7936f58b27cbc4a4f83dacd3a73
Author: Mahe Tardy <mahe.tardy@gmail.com>
selftests/bpf: Add ksock kfunc test
Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
bpf setup program creates a ksock context and connect the socket.
Another syscall bpf program lookup the context and send test data.
> 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 0000000000000..085ddb59067e8
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
[ ... ]
> +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);
> + if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
> + return false;
[Severity: Low]
This isn't a bug, but is it necessary to manually validate the program
file descriptor here?
According to the BPF subsystem selftest guidelines, after a successful
skeleton load, skeleton-generated program and map FDs are guaranteed to be
valid. Since this is called after a successful skeleton load, the
ASSERT_OK_FD check might be redundant.
> +
> + err = bpf_prog_test_run_opts(pfd, &opts);
> + if (!ASSERT_OK(err, "ksock_setup run"))
> + return false;
[ ... ]
> +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);
> + if (!ASSERT_OK_FD(pfd, "ksock_send fd"))
> + goto fail;
[Severity: Low]
This isn't a bug, but similarly to the check in ksock_setup_ctx, is this
ASSERT_OK_FD necessary since the skeleton is already successfully loaded?
> +
> + err = bpf_prog_test_run_opts(pfd, &opts);
> + if (!ASSERT_OK(err, "ksock_send run"))
> + goto fail;
[ ... ]
> 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 0000000000000..87d92372d28dc
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/ksock_common.h
[ ... ]
> +static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
> +{
> + struct __ksock_ctx_value local, *v;
> + struct bpf_ksock *old;
> + u32 key = 0;
> + int err;
> +
> + local.ctx = NULL;
> + err = bpf_map_update_elem(&__ksock_ctx_map, &key, &local, 0);
[Severity: Medium]
Will this unconditional bpf_map_update_elem call defeat the intended
collision detection logic below?
Because __ksock_ctx_map is a BPF_MAP_TYPE_ARRAY map, calling
bpf_map_update_elem with a zero-initialized value (local.ctx = NULL)
will clear the existing entry and implicitly free any pre-existing kptr.
Consequently, the subsequent bpf_kptr_xchg will always see a NULL pointer,
making the if (old) check dead code and causing the function to silently
overwrite existing entries instead of returning -EEXIST.
Could the logic be adjusted to perform the lookup and exchange without
overwriting the element first?
> + if (err) {
> + bpf_ksock_release(ctx);
> + return err;
> + }
> +
> + v = bpf_map_lookup_elem(&__ksock_ctx_map, &key);
> + 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;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260706093525.13030-1-mahe.tardy@gmail.com?part=3
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
2026-07-06 10:10 ` sashiko-bot
@ 2026-07-06 10:16 ` bot+bpf-ci
2026-07-06 15:27 ` Mahe Tardy
1 sibling, 1 reply; 24+ messages in thread
From: bot+bpf-ci @ 2026-07-06 10:16 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 976 bytes --]
> Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
> bpf setup program creates a ksock context and connect the socket.
> Another syscall bpf program lookup the context and send test data.
> The userspace harness create 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.
This isn't a bug, but would a quick pass over the changelog wording help
here? A few spots read a little off:
"One sycall" -> "One syscall"
"creates a ksock context and connect the socket" -> connects
"Another syscall bpf program lookup the context" -> looks up
"The userspace harness create a network namespace" -> creates
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28783010022
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
2026-07-06 10:01 ` sashiko-bot
@ 2026-07-06 10:30 ` bot+bpf-ci
2026-07-06 15:28 ` Mahe Tardy
2026-07-06 16:58 ` Stanislav Fomichev
` (2 subsequent siblings)
4 siblings, 1 reply; 24+ messages in thread
From: bot+bpf-ci @ 2026-07-06 10:30 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1707 bytes --]
> diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
> new file mode 100644
> index 000000000000..8bde734bc917
> --- /dev/null
> +++ b/net/core/bpf_ksock.c
[ ... ]
> @@ -0,0 +1,516 @@
[ ... ]
> +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_pernet_subsys(&bpf_ksock_net_ops);
> + if (ret)
> + return ret;
> +
> + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
> + &ksock_init_kfunc_set);
> + if (ret) {
> + unregister_pernet_subsys(&bpf_ksock_net_ops);
> + return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> + ARRAY_SIZE(bpf_ksock_dtors),
> + THIS_MODULE);
^^^^
This isn't a bug, but in the final return statement the assignment
to ret looks like a dead store since the value is returned right
away. Could this just be:
return ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
ARRAY_SIZE(bpf_ksock_dtors),
THIS_MODULE);
> +}
> +
> +late_initcall(bpf_ksock_kfunc_init);
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28783010022
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
2026-07-06 10:16 ` bot+bpf-ci
@ 2026-07-06 15:27 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 15:27 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, martin.lau, yonghong.song, clm, ihor.solodrai
On Mon, Jul 06, 2026 at 10:16:33AM +0000, bot+bpf-ci@kernel.org wrote:
> > Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
> > bpf setup program creates a ksock context and connect the socket.
> > Another syscall bpf program lookup the context and send test data.
> > The userspace harness create 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.
>
> This isn't a bug, but would a quick pass over the changelog wording help
> here? A few spots read a little off:
>
> "One sycall" -> "One syscall"
> "creates a ksock context and connect the socket" -> connects
> "Another syscall bpf program lookup the context" -> looks up
> "The userspace harness create a network namespace" -> creates
Will fix these typos in next iteration.
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28783010022
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 10:30 ` bot+bpf-ci
@ 2026-07-06 15:28 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 15:28 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev, martin.lau, yonghong.song, clm, ihor.solodrai
On Mon, Jul 06, 2026 at 10:30:04AM +0000, bot+bpf-ci@kernel.org wrote:
> > diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
> > new file mode 100644
> > index 000000000000..8bde734bc917
> > --- /dev/null
> > +++ b/net/core/bpf_ksock.c
>
> [ ... ]
>
> > @@ -0,0 +1,516 @@
>
> [ ... ]
>
> > +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_pernet_subsys(&bpf_ksock_net_ops);
> > + if (ret)
> > + return ret;
> > +
> > + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
> > + &ksock_init_kfunc_set);
> > + if (ret) {
> > + unregister_pernet_subsys(&bpf_ksock_net_ops);
> > + return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> > + ARRAY_SIZE(bpf_ksock_dtors),
> > + THIS_MODULE);
> ^^^^
>
> This isn't a bug, but in the final return statement the assignment
> to ret looks like a dead store since the value is returned right
> away. Could this just be:
>
> return ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> ARRAY_SIZE(bpf_ksock_dtors),
> THIS_MODULE);
Indeed, this is relic of changes trying to get the cleanup part right,
see the other message from sashiko on the same patch.
>
> > +}
> > +
> > +late_initcall(bpf_ksock_kfunc_init);
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28783010022
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 10:01 ` sashiko-bot
@ 2026-07-06 15:42 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 15:42 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Mon, Jul 06, 2026 at 10:01:41AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Missing rollback of pernet subsystem on initialization error paths in `bpf_ksock_kfunc_init`.
> --
>
> commit c26355c87f06c9eb5cc9cceccb20cfce50aa1e1e
> Author: Mahe Tardy <mahe.tardy@gmail.com>
>
> bpf: Add ksock kfuncs
>
> This commit introduces BPF kfuncs to allow BPF LSM and SYSCALL programs to
> create and use sockets for sending data. It adds several lifecycle and usage
> kfuncs for kernel sockets, limits socket creation via a per-namespace sysctl,
> and registers the necessary BTF kfuncs.
>
> > diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
> > new file mode 100644
> > index 0000000000000..8bde734bc917d
> > --- /dev/null
> > +++ b/net/core/bpf_ksock.c
>
> [ ... ]
>
> > +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_pernet_subsys(&bpf_ksock_net_ops);
> > + if (ret)
> > + return ret;
> > +
> > + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
> > + &ksock_init_kfunc_set);
> > + if (ret) {
> > + unregister_pernet_subsys(&bpf_ksock_net_ops);
> > + return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> > + ARRAY_SIZE(bpf_ksock_dtors),
> > + THIS_MODULE);
>
> [Severity: Medium]
> If the later register_btf_kfunc_id_set() or register_btf_id_dtor_kfuncs()
> calls fail in bpf_ksock_kfunc_init(), does this leak the pernet operations
> registration?
>
> The earlier error path for BPF_PROG_TYPE_SYSCALL registration properly
> calls unregister_pernet_subsys(&bpf_ksock_net_ops). If these subsequent
> registrations fail, the function returns the error directly without cleaning
> up the pernet subsystem.
Since there's no way of unregistering kfuncs for a certain prog type if
one step fails, I took the decision to unregister the per net ns count
if the first fails. Not sure how important this is and if there's a
cleaner way of doing this?
The last return line could be adjusted as mentioned in a separate thread
on the same message.
>
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706093525.13030-1-mahe.tardy@gmail.com?part=2
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test
2026-07-06 10:10 ` sashiko-bot
@ 2026-07-06 16:11 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 16:11 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Mon, Jul 06, 2026 at 10:10:40AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Dead code and flawed insert logic in `ksock_ctx_insert()` due to unconditional map element overwriting.
> - [Low] Unnecessary manual validation of BPF program FDs after successful skeleton load.
> --
>
> commit 559d17f26b2fc7936f58b27cbc4a4f83dacd3a73
> Author: Mahe Tardy <mahe.tardy@gmail.com>
>
> selftests/bpf: Add ksock kfunc test
>
> Add a selftest that exercises the ksock kfuncs end-to-end. One sycall
> bpf setup program creates a ksock context and connect the socket.
> Another syscall bpf program lookup the context and send test data.
>
> > 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 0000000000000..085ddb59067e8
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/ksock.c
>
> [ ... ]
>
> > +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);
> > + if (!ASSERT_OK_FD(pfd, "ksock_setup fd"))
> > + return false;
>
> [Severity: Low]
> This isn't a bug, but is it necessary to manually validate the program
> file descriptor here?
>
> According to the BPF subsystem selftest guidelines, after a successful
> skeleton load, skeleton-generated program and map FDs are guaranteed to be
> valid. Since this is called after a successful skeleton load, the
> ASSERT_OK_FD check might be redundant.
Sure, can be removed.
>
> > +
> > + err = bpf_prog_test_run_opts(pfd, &opts);
> > + if (!ASSERT_OK(err, "ksock_setup run"))
> > + return false;
>
> [ ... ]
>
> > +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);
> > + if (!ASSERT_OK_FD(pfd, "ksock_send fd"))
> > + goto fail;
>
> [Severity: Low]
> This isn't a bug, but similarly to the check in ksock_setup_ctx, is this
> ASSERT_OK_FD necessary since the skeleton is already successfully loaded?
Dito.
>
> > +
> > + err = bpf_prog_test_run_opts(pfd, &opts);
> > + if (!ASSERT_OK(err, "ksock_send run"))
> > + goto fail;
>
> [ ... ]
>
> > 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 0000000000000..87d92372d28dc
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/progs/ksock_common.h
>
> [ ... ]
>
> > +static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
> > +{
> > + struct __ksock_ctx_value local, *v;
> > + struct bpf_ksock *old;
> > + u32 key = 0;
> > + int err;
> > +
> > + local.ctx = NULL;
> > + err = bpf_map_update_elem(&__ksock_ctx_map, &key, &local, 0);
>
> [Severity: Medium]
> Will this unconditional bpf_map_update_elem call defeat the intended
> collision detection logic below?
>
> Because __ksock_ctx_map is a BPF_MAP_TYPE_ARRAY map, calling
> bpf_map_update_elem with a zero-initialized value (local.ctx = NULL)
> will clear the existing entry and implicitly free any pre-existing kptr.
>
> Consequently, the subsequent bpf_kptr_xchg will always see a NULL pointer,
> making the if (old) check dead code and causing the function to silently
> overwrite existing entries instead of returning -EEXIST.
>
> Could the logic be adjusted to perform the lookup and exchange without
> overwriting the element first?
So this is not erasing the content since local.ctx is a kptr, so the
feedback is kinda wrong, it will be ignored by the map_update part. It
was copied from crypto_common.h but is kinda doing nothing so could be
simplified to only lookup & kptr_xchg.
>
> > + if (err) {
> > + bpf_ksock_release(ctx);
> > + return err;
> > + }
> > +
> > + v = bpf_map_lookup_elem(&__ksock_ctx_map, &key);
> > + 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;
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706093525.13030-1-mahe.tardy@gmail.com?part=3
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
2026-07-06 10:01 ` sashiko-bot
2026-07-06 10:30 ` bot+bpf-ci
@ 2026-07-06 16:58 ` Stanislav Fomichev
2026-07-06 17:26 ` Mahe Tardy
2026-07-06 21:33 ` Amery Hung
2026-07-06 23:01 ` Kuniyuki Iwashima
4 siblings, 1 reply; 24+ messages in thread
From: Stanislav Fomichev @ 2026-07-06 16:58 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev
On 07/06, Mahe Tardy wrote:
> 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 six kfuncs:
>
> bpf_ksock_create() - Create a socket (sleepable)
[..]
> bpf_ksock_bind() - Bind socket to local address (sleepable)
> bpf_ksock_connect() - Connect socket to remote address (sleepable)
Since you're doing only UDP for now, maybe you don't need bind/connect? The
kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
> 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_bind, 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.
[..]
> A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> kernel sockets that may exist in each network namespace. Out of
> simplicity for now, the settings is host wide but the counters are per
> network namespace.
What is this guarding against? Rogue bpf programs creating too many sockets?
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 16:58 ` Stanislav Fomichev
@ 2026-07-06 17:26 ` Mahe Tardy
2026-07-06 20:21 ` Amery Hung
2026-07-06 21:02 ` Stanislav Fomichev
0 siblings, 2 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-06 17:26 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev
On Mon, Jul 06, 2026 at 09:58:09AM -0700, Stanislav Fomichev wrote:
> On 07/06, Mahe Tardy wrote:
> > 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 six kfuncs:
> >
> > bpf_ksock_create() - Create a socket (sleepable)
>
> [..]
>
> > bpf_ksock_bind() - Bind socket to local address (sleepable)
> > bpf_ksock_connect() - Connect socket to remote address (sleepable)
>
> Since you're doing only UDP for now, maybe you don't need bind/connect? The
> kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
Yep indeed, I kinda overlooked that as I started with UDP & TCP supports
and mostly added the args checks. Another thing is that send is simpler
since only used on connected sockets, so you just pass the struct
bpf_ksock and data. So on one side it would simplify the current
UDP-only API for now by removing the kfuncs but we might need a more
complex send kfunc (something like sendto).
>
> > 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_bind, 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.
>
> [..]
>
> > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > kernel sockets that may exist in each network namespace. Out of
> > simplicity for now, the settings is host wide but the counters are per
> > network namespace.
>
> What is this guarding against? Rogue bpf programs creating too many sockets?
Yes. AI review raised this because users are prevented from creating too
many sockets by bumping against the max number of fd and this would
allow them to create way more sockets. I kind of agreed that having "a
limit" on resource creation would make sense but maybe it doesn't and we
can simplify this!
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 17:26 ` Mahe Tardy
@ 2026-07-06 20:21 ` Amery Hung
2026-07-06 21:02 ` Stanislav Fomichev
1 sibling, 0 replies; 24+ messages in thread
From: Amery Hung @ 2026-07-06 20:21 UTC (permalink / raw)
To: Mahe Tardy
Cc: Stanislav Fomichev, bpf, andrew+netdev, andrii, ast, daniel,
davem, eddyz87, edumazet, john.fastabend, kuba, liamwisehart,
martin.lau, pabeni, song, netdev
On Mon, Jul 6, 2026 at 12:04 PM Mahe Tardy <mahe.tardy@gmail.com> wrote:
>
> On Mon, Jul 06, 2026 at 09:58:09AM -0700, Stanislav Fomichev wrote:
> > On 07/06, Mahe Tardy wrote:
> > > 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 six kfuncs:
> > >
> > > bpf_ksock_create() - Create a socket (sleepable)
> >
> > [..]
> >
> > > bpf_ksock_bind() - Bind socket to local address (sleepable)
> > > bpf_ksock_connect() - Connect socket to remote address (sleepable)
> >
> > Since you're doing only UDP for now, maybe you don't need bind/connect? The
> > kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
>
> Yep indeed, I kinda overlooked that as I started with UDP & TCP supports
> and mostly added the args checks. Another thing is that send is simpler
> since only used on connected sockets, so you just pass the struct
> bpf_ksock and data. So on one side it would simplify the current
> UDP-only API for now by removing the kfuncs but we might need a more
> complex send kfunc (something like sendto).
I also have the same question about the necessity of bind(), but
connect() + send() make sense to me. In the stated use case, the dst
addr probably doesn't change often and I think avoiding route lookup
everytime should be a good thing.
>
> >
> > > 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_bind, 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.
> >
> > [..]
> >
> > > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > > kernel sockets that may exist in each network namespace. Out of
> > > simplicity for now, the settings is host wide but the counters are per
> > > network namespace.
> >
> > What is this guarding against? Rogue bpf programs creating too many sockets?
>
> Yes. AI review raised this because users are prevented from creating too
> many sockets by bumping against the max number of fd and this would
> allow them to create way more sockets. I kind of agreed that having "a
> limit" on resource creation would make sense but maybe it doesn't and we
> can simplify this!
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 17:26 ` Mahe Tardy
2026-07-06 20:21 ` Amery Hung
@ 2026-07-06 21:02 ` Stanislav Fomichev
2026-07-06 22:50 ` Kuniyuki Iwashima
1 sibling, 1 reply; 24+ messages in thread
From: Stanislav Fomichev @ 2026-07-06 21:02 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev
On 07/06, Mahe Tardy wrote:
> On Mon, Jul 06, 2026 at 09:58:09AM -0700, Stanislav Fomichev wrote:
> > On 07/06, Mahe Tardy wrote:
> > > 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 six kfuncs:
> > >
> > > bpf_ksock_create() - Create a socket (sleepable)
> >
> > [..]
> >
> > > bpf_ksock_bind() - Bind socket to local address (sleepable)
> > > bpf_ksock_connect() - Connect socket to remote address (sleepable)
> >
> > Since you're doing only UDP for now, maybe you don't need bind/connect? The
> > kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
>
> Yep indeed, I kinda overlooked that as I started with UDP & TCP supports
> and mostly added the args checks. Another thing is that send is simpler
> since only used on connected sockets, so you just pass the struct
> bpf_ksock and data. So on one side it would simplify the current
> UDP-only API for now by removing the kfuncs but we might need a more
> complex send kfunc (something like sendto).
Since you were targeting bpf_netpoll_send_udp originally, maybe sendto
is a better fit? You get the payload and the destination and you
bpf_sendto() it? We can later move to stateful bind/connect if needed.
(mostly coming from the pow of minimizing api exposure initially, but
not a strong preference)
> >
> > > 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_bind, 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.
> >
> > [..]
> >
> > > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > > kernel sockets that may exist in each network namespace. Out of
> > > simplicity for now, the settings is host wide but the counters are per
> > > network namespace.
> >
> > What is this guarding against? Rogue bpf programs creating too many sockets?
>
> Yes. AI review raised this because users are prevented from creating too
> many sockets by bumping against the max number of fd and this would
> allow them to create way more sockets. I kind of agreed that having "a
> limit" on resource creation would make sense but maybe it doesn't and we
> can simplify this!
I believe even the kernel sockets go via lsm layer, so this enforcement
can be done in an lsm bpf program. Seems like that should be enough?
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
` (2 preceding siblings ...)
2026-07-06 16:58 ` Stanislav Fomichev
@ 2026-07-06 21:33 ` Amery Hung
2026-07-07 9:48 ` Mahe Tardy
2026-07-06 23:01 ` Kuniyuki Iwashima
4 siblings, 1 reply; 24+ messages in thread
From: Amery Hung @ 2026-07-06 21:33 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev
On Mon, Jul 6, 2026 at 3:26 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
>
> 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 six kfuncs:
>
> bpf_ksock_create() - Create a socket (sleepable)
> bpf_ksock_bind() - Bind socket to local address (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_bind, 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.
Are these the first BPF kfuncs that invoke (attachable) security
hooks? Some fs kfuncs seem to deliberately skip them. Can we avoid the
limiting recursion check by skipping the security hook in
bpf_ksock_send()? There is still security_socket_connect() in
bpf_ksock_connect() to prevent undesired send.
>
> A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> kernel sockets that may exist in each network namespace. Out of
> simplicity for now, the settings is host wide but the counters are per
> network namespace.
>
> 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>
> ---
> Documentation/admin-guide/sysctl/net.rst | 12 +
> include/linux/bpf_ksock.h | 50 +++
> kernel/bpf/verifier.c | 3 +
> net/core/Makefile | 3 +
> net/core/bpf_ksock.c | 516 +++++++++++++++++++++++
> net/core/sysctl_net_core.c | 11 +
> 6 files changed, 595 insertions(+)
> create mode 100644 include/linux/bpf_ksock.h
> create mode 100644 net/core/bpf_ksock.c
>
> diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
> index e586e17fc7a5..8bcdc1bfd711 100644
> --- a/Documentation/admin-guide/sysctl/net.rst
> +++ b/Documentation/admin-guide/sysctl/net.rst
> @@ -128,6 +128,18 @@ compiler in order to reject unprivileged JIT requests once it has
> been surpassed. bpf_jit_limit contains the value of the global limit
> in bytes.
>
> +bpf_ksock_max
> +-------------
> +
> +Maximum number of BPF kernel sockets that may exist in each network namespace.
> +This host-wide setting is exposed in the initial network namespace and applies
> +the same limit independently to every network namespace. Sockets awaiting
> +deferred RCU and workqueue cleanup remain counted until the underlying socket
> +has been released. A value of 0 disables creation of BPF kernel sockets in
> +every network namespace.
> +
> +Default: 1024
> +
> dev_weight
> ----------
>
> diff --git a/include/linux/bpf_ksock.h b/include/linux/bpf_ksock.h
> new file mode 100644
> index 000000000000..0485b8eddc1f
> --- /dev/null
> +++ b/include/linux/bpf_ksock.h
> @@ -0,0 +1,50 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (c) 2026 Isovalent */
> +
> +#ifndef _BPF_KSOCK_H
> +#define _BPF_KSOCK_H
> +
> +#include <linux/types.h>
> +
> +#define BPF_KSOCK_MAX_DEFAULT 1024
> +
> +extern int sysctl_bpf_ksock_max;
> +
> +/**
> + * 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/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 25aea4271cd0..52f6a89546e4 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -4424,6 +4424,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..8bde734bc917
> --- /dev/null
> +++ b/net/core/bpf_ksock.c
> @@ -0,0 +1,516 @@
> +// 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/cache.h>
> +#include <linux/hash.h>
> +#include <linux/in.h>
> +#include <linux/in6.h>
> +#include <linux/list_bl.h>
> +#include <linux/net.h>
> +#include <linux/refcount.h>
> +#include <linux/rcupdate.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/ipv6.h>
> +#include <net/net_namespace.h>
> +#include <net/netns/generic.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;
> +};
> +
> +struct bpf_ksock_send_guard {
> + struct hlist_bl_node node;
> + struct task_struct *task;
> +};
> +
> +#define BPF_KSOCK_SEND_GUARD_HASH_BITS 6
> +
> +struct bpf_ksock_send_bucket {
> + struct hlist_bl_head head;
> +} ____cacheline_aligned_in_smp;
> +
> +static struct bpf_ksock_send_bucket
> + bpf_ksock_send_buckets[1 << BPF_KSOCK_SEND_GUARD_HASH_BITS];
> +
> +static struct bpf_ksock_send_bucket *
> +bpf_ksock_send_bucket(const struct task_struct *task)
> +{
> + u32 bucket_idx = hash_ptr(task, BPF_KSOCK_SEND_GUARD_HASH_BITS);
> +
> + return &bpf_ksock_send_buckets[bucket_idx];
> +}
> +
> +static bool bpf_ksock_send_enter(struct bpf_ksock_send_guard *guard)
> +{
> + struct bpf_ksock_send_guard *entry;
> + struct hlist_bl_node *pos;
> + struct bpf_ksock_send_bucket *bucket;
> +
> + bucket = bpf_ksock_send_bucket(current);
> + hlist_bl_lock(&bucket->head);
> + hlist_bl_for_each_entry(entry, pos, &bucket->head, node) {
> + if (entry->task == current) {
> + hlist_bl_unlock(&bucket->head);
> + return false;
> + }
> + }
> +
> + guard->task = current;
> + INIT_HLIST_BL_NODE(&guard->node);
> + hlist_bl_add_head(&guard->node, &bucket->head);
> + hlist_bl_unlock(&bucket->head);
> + return true;
> +}
> +
> +static void bpf_ksock_send_exit(struct bpf_ksock_send_guard *guard)
> +{
> + struct bpf_ksock_send_bucket *bucket;
> +
> + bucket = bpf_ksock_send_bucket(guard->task);
> + hlist_bl_lock(&bucket->head);
> + hlist_bl_del(&guard->node);
> + hlist_bl_unlock(&bucket->head);
> +}
> +
> +struct bpf_ksock_net {
> + atomic_t count;
> +};
> +
> +static unsigned int bpf_ksock_net_id;
> +int sysctl_bpf_ksock_max __read_mostly = BPF_KSOCK_MAX_DEFAULT;
> +
> +static struct bpf_ksock_net *bpf_ksock_pernet(const struct net *net)
> +{
> + return net_generic(net, bpf_ksock_net_id);
> +}
> +
> +static struct pernet_operations bpf_ksock_net_ops = {
> + .id = &bpf_ksock_net_id,
> + .size = sizeof(struct bpf_ksock_net),
> +};
> +
> +static bool bpf_ksock_net_try_charge(struct net *net)
> +{
> + struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
> + int count = atomic_read(&kn->count);
> + int max;
> +
> + do {
> + max = READ_ONCE(sysctl_bpf_ksock_max);
> + if (count >= max)
> + return false;
> + } while (!atomic_try_cmpxchg(&kn->count, &count, count + 1));
> +
> + return true;
> +}
> +
> +static void bpf_ksock_net_uncharge(struct net *net)
> +{
> + struct bpf_ksock_net *kn = bpf_ksock_pernet(net);
> +
> + WARN_ON_ONCE(atomic_dec_return(&kn->count) < 0);
> +}
> +
> +static void ksock_release_work_fn(struct work_struct *work)
> +{
> + struct bpf_ksock *ks =
> + container_of(to_rcu_work(work), struct bpf_ksock, rwork);
> + struct net *net = get_net(sock_net(ks->sock->sk));
> +
> + sock_release(ks->sock);
> + bpf_ksock_net_uncharge(net);
> + put_net(net);
> + kfree(ks);
> +}
> +
> +static int bpf_ksock_get_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 socket is charged against the active network namespace's BPF kernel
> + * socket quota.
> + *
> + * 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;
> + struct net *net;
> + 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;
> + }
> +
> + net = current->nsproxy->net_ns;
> + if (!bpf_ksock_net_try_charge(net)) {
> + err = -ENOSPC;
> + goto err_free;
> + }
> +
> + /*
> + * 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_uncharge;
> +
> + 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_uncharge:
> + bpf_ksock_net_uncharge(net);
> +err_free:
> + kfree(ks);
> +err_out:
> + put_unaligned(err, err__uninit);
> + return NULL;
> +}
> +
> +/**
> + * bpf_ksock_bind() - Bind a BPF kernel socket to a local address.
> + * @ks: The BPF kernel socket context.
> + * @opts: Pointer to struct bpf_ksock_addr_opts with local address.
> + * @opts__sz: Size of the opts struct.
> + *
> + * Binds the socket to the specified local address and port.
> + * This is optional; if not called, the kernel will auto-assign.
> + *
> + * This function may sleep while binding 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_bind(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_get_addr(opts, opts__sz, &addr);
> + if (addrlen < 0)
> + return addrlen;
> +
> + return __sys_bind_socket(ks->sock, &addr, addrlen);
> +}
> +
> +/**
> + * 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_get_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 bpf_ksock_send_guard guard;
> + 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 (!bpf_ksock_send_enter(&guard))
> + return -EBUSY;
> +
> + ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz);
> + bpf_ksock_send_exit(&guard);
> +
> + 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_bind, 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_pernet_subsys(&bpf_ksock_net_ops);
> + if (ret)
> + return ret;
> +
> + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL,
> + &ksock_init_kfunc_set);
> + if (ret) {
> + unregister_pernet_subsys(&bpf_ksock_net_ops);
> + return 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 = ret ?: register_btf_id_dtor_kfuncs(bpf_ksock_dtors,
> + ARRAY_SIZE(bpf_ksock_dtors),
> + THIS_MODULE);
> +}
> +
> +late_initcall(bpf_ksock_kfunc_init);
> diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
> index b508618bfc12..d221a180c877 100644
> --- a/net/core/sysctl_net_core.c
> +++ b/net/core/sysctl_net_core.c
> @@ -7,6 +7,7 @@
> */
>
> #include <linux/filter.h>
> +#include <linux/bpf_ksock.h>
> #include <linux/mm.h>
> #include <linux/sysctl.h>
> #include <linux/module.h>
> @@ -525,6 +526,16 @@ static struct ctl_table net_core_table[] = {
> .extra1 = SYSCTL_LONG_ONE,
> .extra2 = &bpf_jit_limit_max,
> },
> +#endif
> +#if IS_ENABLED(CONFIG_BPF_SYSCALL) && IS_ENABLED(CONFIG_INET)
> + {
> + .procname = "bpf_ksock_max",
> + .data = &sysctl_bpf_ksock_max,
> + .maxlen = sizeof(int),
> + .mode = 0644,
> + .proc_handler = proc_dointvec_minmax,
> + .extra1 = SYSCTL_ZERO,
> + },
> #endif
> {
> .procname = "netdev_tstamp_prequeue",
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 21:02 ` Stanislav Fomichev
@ 2026-07-06 22:50 ` Kuniyuki Iwashima
2026-07-07 9:41 ` Mahe Tardy
0 siblings, 1 reply; 24+ messages in thread
From: Kuniyuki Iwashima @ 2026-07-06 22:50 UTC (permalink / raw)
To: sdf.kernel
Cc: andrew+netdev, andrii, ast, bpf, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, mahe.tardy, martin.lau,
netdev, pabeni, song
From: Stanislav Fomichev <sdf.kernel@gmail.com>
Date: Mon, 6 Jul 2026 14:02:39 -0700
> On 07/06, Mahe Tardy wrote:
> > On Mon, Jul 06, 2026 at 09:58:09AM -0700, Stanislav Fomichev wrote:
> > > On 07/06, Mahe Tardy wrote:
> > > > 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 six kfuncs:
> > > >
> > > > bpf_ksock_create() - Create a socket (sleepable)
> > >
> > > [..]
> > >
> > > > bpf_ksock_bind() - Bind socket to local address (sleepable)
> > > > bpf_ksock_connect() - Connect socket to remote address (sleepable)
> > >
> > > Since you're doing only UDP for now, maybe you don't need bind/connect? The
> > > kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
> >
> > Yep indeed, I kinda overlooked that as I started with UDP & TCP supports
> > and mostly added the args checks. Another thing is that send is simpler
> > since only used on connected sockets, so you just pass the struct
> > bpf_ksock and data. So on one side it would simplify the current
> > UDP-only API for now by removing the kfuncs but we might need a more
> > complex send kfunc (something like sendto).
>
> Since you were targeting bpf_netpoll_send_udp originally, maybe sendto
> is a better fit? You get the payload and the destination and you
> bpf_sendto() it? We can later move to stateful bind/connect if needed.
>
> (mostly coming from the pow of minimizing api exposure initially, but
> not a strong preference)
+1, small start would be better.
>
> > >
> > > > 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_bind, 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.
> > >
> > > [..]
> > >
> > > > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > > > kernel sockets that may exist in each network namespace. Out of
> > > > simplicity for now, the settings is host wide but the counters are per
> > > > network namespace.
> > >
> > > What is this guarding against? Rogue bpf programs creating too many sockets?
> >
> > Yes. AI review raised this because users are prevented from creating too
> > many sockets by bumping against the max number of fd and this would
> > allow them to create way more sockets. I kind of agreed that having "a
> > limit" on resource creation would make sense but maybe it doesn't and we
> > can simplify this!
>
> I believe even the kernel sockets go via lsm layer, so this enforcement
> can be done in an lsm bpf program. Seems like that should be enough?
Right, I don't think the per-netns limit is useful for CAP_BPF users.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
` (3 preceding siblings ...)
2026-07-06 21:33 ` Amery Hung
@ 2026-07-06 23:01 ` Kuniyuki Iwashima
4 siblings, 0 replies; 24+ messages in thread
From: Kuniyuki Iwashima @ 2026-07-06 23:01 UTC (permalink / raw)
To: mahe.tardy
Cc: andrew+netdev, andrii, ast, bpf, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, netdev, pabeni,
song
From: Mahe Tardy <mahe.tardy@gmail.com>
Date: Mon, 6 Jul 2026 09:35:21 +0000
> diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
[...]
> +static bool bpf_ksock_send_enter(struct bpf_ksock_send_guard *guard)
Can we do this better with assigning a single bit to task_struct ?
> +{
> + struct bpf_ksock_send_guard *entry;
> + struct hlist_bl_node *pos;
> + struct bpf_ksock_send_bucket *bucket;
> +
> + bucket = bpf_ksock_send_bucket(current);
> + hlist_bl_lock(&bucket->head);
> + hlist_bl_for_each_entry(entry, pos, &bucket->head, node) {
> + if (entry->task == current) {
> + hlist_bl_unlock(&bucket->head);
> + return false;
> + }
> + }
> +
> + guard->task = current;
> + INIT_HLIST_BL_NODE(&guard->node);
> + hlist_bl_add_head(&guard->node, &bucket->head);
> + hlist_bl_unlock(&bucket->head);
> + return true;
> +}
> +
> +static void bpf_ksock_send_exit(struct bpf_ksock_send_guard *guard)
> +{
> + struct bpf_ksock_send_bucket *bucket;
> +
> + bucket = bpf_ksock_send_bucket(guard->task);
> + hlist_bl_lock(&bucket->head);
> + hlist_bl_del(&guard->node);
> + hlist_bl_unlock(&bucket->head);
> +}
[...]
> +__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;
> + struct net *net;
> + 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;
> + }
> +
> + net = current->nsproxy->net_ns;
> + if (!bpf_ksock_net_try_charge(net)) {
> + err = -ENOSPC;
> + goto err_free;
> + }
> +
> + /*
> + * 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);
This also triggers LSM in __sock_create() so needs the loop
detection, no ?
Same for __sys_bind_socket() and __sys_connect_socket().
> + if (err)
> + goto err_uncharge;
> +
> + 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_uncharge:
> + bpf_ksock_net_uncharge(net);
> +err_free:
> + kfree(ks);
> +err_out:
> + put_unaligned(err, err__uninit);
> + return NULL;
> +}
> +
> +/**
> + * bpf_ksock_bind() - Bind a BPF kernel socket to a local address.
> + * @ks: The BPF kernel socket context.
> + * @opts: Pointer to struct bpf_ksock_addr_opts with local address.
> + * @opts__sz: Size of the opts struct.
> + *
> + * Binds the socket to the specified local address and port.
> + * This is optional; if not called, the kernel will auto-assign.
> + *
> + * This function may sleep while binding 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_bind(struct bpf_ksock *ks,
> + const struct bpf_ksock_addr_opts *opts,
> + u32 opts__sz)
> +{
> + struct sockaddr_storage addr = {};
nit: we don't need to initialise here since bpf_ksock_get_addr() does.
> + int addrlen;
> +
> + if (!bpf_ksock_has_user_task_context())
> + return -EOPNOTSUPP;
> +
> + addrlen = bpf_ksock_get_addr(opts, opts__sz, &addr);
> + if (addrlen < 0)
> + return addrlen;
> +
> + return __sys_bind_socket(ks->sock, &addr, addrlen);
> +}
> +
> +/**
> + * 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 = {};
Same.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 22:50 ` Kuniyuki Iwashima
@ 2026-07-07 9:41 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-07 9:41 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: sdf.kernel, andrew+netdev, andrii, ast, bpf, daniel, davem,
eddyz87, edumazet, john.fastabend, kuba, liamwisehart, martin.lau,
netdev, pabeni, song
On Mon, Jul 06, 2026 at 10:50:33PM +0000, Kuniyuki Iwashima wrote:
> From: Stanislav Fomichev <sdf.kernel@gmail.com>
> Date: Mon, 6 Jul 2026 14:02:39 -0700
> > On 07/06, Mahe Tardy wrote:
> > > On Mon, Jul 06, 2026 at 09:58:09AM -0700, Stanislav Fomichev wrote:
> > > > On 07/06, Mahe Tardy wrote:
> > > > > 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 six kfuncs:
> > > > >
> > > > > bpf_ksock_create() - Create a socket (sleepable)
> > > >
> > > > [..]
> > > >
> > > > > bpf_ksock_bind() - Bind socket to local address (sleepable)
> > > > > bpf_ksock_connect() - Connect socket to remote address (sleepable)
> > > >
> > > > Since you're doing only UDP for now, maybe you don't need bind/connect? The
> > > > kernel should autobind (by default) when you sendmsg over UDP socket (IIRC).
> > >
> > > Yep indeed, I kinda overlooked that as I started with UDP & TCP supports
> > > and mostly added the args checks. Another thing is that send is simpler
> > > since only used on connected sockets, so you just pass the struct
> > > bpf_ksock and data. So on one side it would simplify the current
> > > UDP-only API for now by removing the kfuncs but we might need a more
> > > complex send kfunc (something like sendto).
> >
> > Since you were targeting bpf_netpoll_send_udp originally, maybe sendto
> > is a better fit? You get the payload and the destination and you
> > bpf_sendto() it? We can later move to stateful bind/connect if needed.
> >
> > (mostly coming from the pow of minimizing api exposure initially, but
> > not a strong preference)
>
> +1, small start would be better.
Okay I feel at least we can confidently remove bind for now. Indeed for
netpoll it was kinda obvious that it was going to be sendto-style but
now with the sockets I'm unsure what's best:
As Amery Hung wrote in a parallel thread:
> [...] but connect() + send() make sense to me. In the stated use case,
> the dst addr probably doesn't change often and I think avoiding route
> lookup everytime should be a good thing.
Looks like there's benefit to both approaches, I'll experiment.
> > > > > 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)
> > > > >
> > > > [..]
> > > >
> > > > > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > > > > kernel sockets that may exist in each network namespace. Out of
> > > > > simplicity for now, the settings is host wide but the counters are per
> > > > > network namespace.
> > > >
> > > > What is this guarding against? Rogue bpf programs creating too many sockets?
> > >
> > > Yes. AI review raised this because users are prevented from creating too
> > > many sockets by bumping against the max number of fd and this would
> > > allow them to create way more sockets. I kind of agreed that having "a
> > > limit" on resource creation would make sense but maybe it doesn't and we
> > > can simplify this!
> >
> > I believe even the kernel sockets go via lsm layer, so this enforcement
> > can be done in an lsm bpf program. Seems like that should be enough?
>
> Right, I don't think the per-netns limit is useful for CAP_BPF users.
Ok, agree, let's remove all this then for the next version.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH bpf-next 2/6] bpf: Add ksock kfuncs
2026-07-06 21:33 ` Amery Hung
@ 2026-07-07 9:48 ` Mahe Tardy
0 siblings, 0 replies; 24+ messages in thread
From: Mahe Tardy @ 2026-07-07 9:48 UTC (permalink / raw)
To: Amery Hung
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, liamwisehart, martin.lau, pabeni, song,
netdev
On Mon, Jul 06, 2026 at 02:33:43PM -0700, Amery Hung wrote:
> On Mon, Jul 6, 2026 at 3:26 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
> >
> > 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 six kfuncs:
> >
> > bpf_ksock_create() - Create a socket (sleepable)
> > bpf_ksock_bind() - Bind socket to local address (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_bind, 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.
>
> Are these the first BPF kfuncs that invoke (attachable) security
> hooks? Some fs kfuncs seem to deliberately skip them. Can we avoid the
> limiting recursion check by skipping the security hook in
> bpf_ksock_send()? There is still security_socket_connect() in
> bpf_ksock_connect() to prevent undesired send.
FYI I found some piece of documentation around kfuncs and LSM hooks[^1],
people tried to avoid the recursion to happen in the first place.
Initially I wrote this with kernel sockets, which are not subject to LSM
hooks on the send path iirc. I migrated away from this thinking that
those "bpf sockets" should still be subject to the LSMs and not bypass
them, but we can change that or also have it partially as you mentioned
with connect but not send? Note that this would maybe change if we go
the sendto route as suggested on the other thread.
[^1]: https://docs.kernel.org/bpf/fs_kfuncs.html
>
> >
> > A bpf_ksock_max sysctl is added to limit the maximum number of BPF
> > kernel sockets that may exist in each network namespace. Out of
> > simplicity for now, the settings is host wide but the counters are per
> > network namespace.
> >
[...]
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-07-07 9:48 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
2026-07-06 10:01 ` sashiko-bot
2026-07-06 15:42 ` Mahe Tardy
2026-07-06 10:30 ` bot+bpf-ci
2026-07-06 15:28 ` Mahe Tardy
2026-07-06 16:58 ` Stanislav Fomichev
2026-07-06 17:26 ` Mahe Tardy
2026-07-06 20:21 ` Amery Hung
2026-07-06 21:02 ` Stanislav Fomichev
2026-07-06 22:50 ` Kuniyuki Iwashima
2026-07-07 9:41 ` Mahe Tardy
2026-07-06 21:33 ` Amery Hung
2026-07-07 9:48 ` Mahe Tardy
2026-07-06 23:01 ` Kuniyuki Iwashima
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
2026-07-06 10:10 ` sashiko-bot
2026-07-06 16:11 ` Mahe Tardy
2026-07-06 10:16 ` bot+bpf-ci
2026-07-06 15:27 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox