* [PATCH 0/2] Add bpf_sock_read_xattr() kfunc to read socket xattrs
@ 2026-06-17 11:18 Christian Brauner
2026-06-17 11:18 ` [PATCH 1/2] fs: " Christian Brauner
2026-06-17 11:18 ` [PATCH 2/2] selftests/bpf: Add test for bpf_sock_read_xattr() kfunc Christian Brauner
0 siblings, 2 replies; 5+ messages in thread
From: Christian Brauner @ 2026-06-17 11:18 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann
Cc: Alexander Viro, Jan Kara, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, linux-fsdevel, netdev, bpf, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Christian Brauner (Amutable)
In c8db08110cbe ("Merge tag 'vfs-7.1-rc1.xattr' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs")
we added support for extended attributes for sockets. This comes in two
flavors: sockfs and non-sockfs/filesystem sockets. Filesystem sockets
are actual filesystem objects so reading xattrs must use dedicated fs
helpers such as bpf_get_dentry_xattr() and bpf_get_file_xattr(). Those
are inherently sleeping operations. Sockfs sockets on the other hand
don't need to use sleeping operations as the underlying data structure
is lockless. In addition, retrieval of sockfs extended attributes often
happens from LSM hooks that only provide struct socket and it's
completely nonsensical to grab a reference to a file, then force a
sleeping operation to retrieve the xattr and drop the reference. We know
that the sockfs file cannot go away while the LSM hook runs.
This series adds a bpf_sock_read_xattr() kfunc that, given a struct
socket, reads a user.* extended attribute from the socket's sockfs inode
into a bpf_dynptr. Together with fsetxattr() from userspace this lets a
process label a socket with a user.* xattr and have a BPF LSM program
retrieve that label locklessly. The kfunc mirrors the existing
bpf_cgroup_read_xattr(), including the restriction to the user.*
namespace.
systemd uses user.* xattrs on sockets to implement socket rate limiting
and to tag sockets for other purposes [1] such as implementing a varlink
registry. There is currently no efficient way for a BPF program to read
those labels back. The new helper allows a listening socket marked with
an extended attribute to be read back during bind/connect and then act
on the connect()ing socket. Extended attributes make it possible to
allow an unprivileged user manager such as systemd --user to mark
sockets from userspace and then rediscover them or implement policies.
The kfunc is registered KF_RCU and only for BPF LSM programs. A struct
socket is only guaranteed to live in sockfs when an LSM socket hook hands
it out, which is what keeps SOCK_INODE() valid. Sockets that embed struct
socket outside sockfs (tun, tap) are only reachable from tracing programs
and are excluded by the registration. (Btw, for consistency it would
be nice to force allocation of struct socket from sockfs instead of
simply embedding it in e.g., struct tun_file which makes the SOCKFS_I()
pattern a hazard - at least outside of sockfs functions.)
The read never sleeps and takes no lock. For sockfs the value lives in
the inode's in-memory xattr store and simple_xattr_get() resolves it
with an RCU-protected rhashtable lookup, taking neither the inode lock
nor any xattr lock. The kfunc is therefore usable from both sleepable
and non-sleepable LSM hooks.
Link: https://github.com/systemd/systemd/pull/40559 [1]
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Christian Brauner (2):
fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
selftests/bpf: Add test for bpf_sock_read_xattr() kfunc
fs/bpf_fs_kfuncs.c | 37 ++++++++++++
include/linux/net.h | 1 +
net/socket.c | 25 ++++++++
tools/testing/selftests/bpf/bpf_experimental.h | 3 +
.../testing/selftests/bpf/prog_tests/sock_xattr.c | 67 ++++++++++++++++++++++
.../testing/selftests/bpf/progs/sock_read_xattr.c | 54 +++++++++++++++++
6 files changed, 187 insertions(+)
---
base-commit: 6b5a2b7d9bc156e505f09e698d85d6a1547c1206
change-id: 20260617-work-bpf-sock-xattr-37ec4c991886
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
2026-06-17 11:18 [PATCH 0/2] Add bpf_sock_read_xattr() kfunc to read socket xattrs Christian Brauner
@ 2026-06-17 11:18 ` Christian Brauner
2026-06-17 11:32 ` sashiko-bot
2026-06-17 11:18 ` [PATCH 2/2] selftests/bpf: Add test for bpf_sock_read_xattr() kfunc Christian Brauner
1 sibling, 1 reply; 5+ messages in thread
From: Christian Brauner @ 2026-06-17 11:18 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann
Cc: Alexander Viro, Jan Kara, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, linux-fsdevel, netdev, bpf, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Christian Brauner (Amutable)
In c8db08110cbe ("Merge tag 'vfs-7.1-rc1.xattr' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs")
we added support for extended attributes for sockets. This comes in two
flavors: sockfs and non-sockfs/filesystem sockets. Filesystem sockets
are actual filesystem objects so reading xattrs must use dedicated fs
helpers such as bpf_get_dentry_xattr() and bpf_get_file_xattr(). Those
are inherently sleeping operations. Sockfs sockets on the other hand
don't need to use sleeping operations as the underlying data structure
is lockless. In addition, retrieval of sockfs extended attributes often
happens from LSM hooks that only provide struct socket and it's
completely nonsensical to grab a reference to a file, then force a
sleeping operation to retrieve the xattr and drop the reference. We know
that the sockfs file cannot go away while the LSM hook runs.
This series adds a bpf_sock_read_xattr() kfunc that, given a struct
socket, reads a user.* extended attribute from the socket's sockfs inode
into a bpf_dynptr. Together with fsetxattr() from userspace this lets a
process label a socket with a user.* xattr and have a BPF LSM program
retrieve that label locklessly. The kfunc mirrors the existing
bpf_cgroup_read_xattr(), including the restriction to the user.*
namespace.
systemd uses user.* xattrs on sockets to implement socket rate limiting
and to tag sockets for other purposes [1] such as implementing a varlink
registry. There is currently no efficient way for a BPF program to read
those labels back. The new helper allows a listening socket marked with
an extended attribute to be read back during bind/connect and then act
on the connect()ing socket. Extended attributes make it possible to
allow an unprivileged user manager such as systemd --user to mark
sockets from userspace and then rediscover them or implement policies.
The kfunc is registered KF_RCU and only for BPF LSM programs. A struct
socket is only guaranteed to live in sockfs when an LSM socket hook hands
it out, which is what keeps SOCK_INODE() valid. Sockets that embed struct
socket outside sockfs (tun, tap) are only reachable from tracing programs
and are excluded by the registration. (Btw, for consistency it would
be nice to force allocation of struct socket from sockfs instead of
simply embedding it in e.g., struct tun_file which makes the SOCKFS_I()
pattern a hazard - at least outside of sockfs functions.)
The read never sleeps and takes no lock. For sockfs the value lives in
the inode's in-memory xattr store and simple_xattr_get() resolves it
with an RCU-protected rhashtable lookup, taking neither the inode lock
nor any xattr lock. The kfunc is therefore usable from both sleepable
and non-sleepable LSM hooks.
Link: https://github.com/systemd/systemd/pull/40559 [1]
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/bpf_fs_kfuncs.c | 37 +++++++++++++++++++++++++++++++++++++
include/linux/net.h | 1 +
net/socket.c | 25 +++++++++++++++++++++++++
3 files changed, 63 insertions(+)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 11841c3d4260..85fc9519d1ff 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -11,6 +11,7 @@
#include <linux/file.h>
#include <linux/kernfs.h>
#include <linux/mm.h>
+#include <linux/net.h>
#include <linux/xattr.h>
__bpf_kfunc_start_defs();
@@ -359,6 +360,39 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s
}
#endif /* CONFIG_CGROUPS */
+#ifdef CONFIG_NET
+/**
+ * bpf_sock_read_xattr - read xattr of a socket's inode in sockfs
+ * @sock: socket to get xattr from
+ * @name__str: name of the xattr
+ * @value_p: output buffer of the xattr value
+ *
+ * Get xattr *name__str* of *sock* and store the output in *value_p*.
+ *
+ * For security reasons, only *name__str* with prefix "user." is allowed.
+ *
+ * Return: length of the xattr value on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_sock_read_xattr(struct socket *sock, const char *name__str,
+ struct bpf_dynptr *value_p)
+{
+ struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
+ u32 value_len;
+ void *value;
+
+ /* Only allow reading "user.*" xattrs */
+ if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+ return -EPERM;
+
+ value_len = __bpf_dynptr_size(value_ptr);
+ value = __bpf_dynptr_data_rw(value_ptr, value_len);
+ if (!value)
+ return -EINVAL;
+
+ return sock_read_xattr(sock, name__str, value, value_len);
+}
+#endif /* CONFIG_NET */
+
/**
* bpf_real_inode - get the real inode backing a dentry
* @dentry: dentry to resolve
@@ -385,6 +419,9 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)
+#ifdef CONFIG_NET
+BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)
+#endif
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
diff --git a/include/linux/net.h b/include/linux/net.h
index f268f395ce47..fdcf9956805c 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -285,6 +285,7 @@ int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags);
struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname);
struct socket *sockfd_lookup(int fd, int *err);
struct socket *sock_from_file(struct file *file);
+int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size);
#define sockfd_put(sock) fput(sock->file)
int net_ratelimit(void);
diff --git a/net/socket.c b/net/socket.c
index 9e8dc769ff7a..3566f8c8ea3f 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -465,6 +465,31 @@ static const struct xattr_handler sockfs_user_xattr_handler = {
.set = sockfs_user_xattr_set,
};
+/**
+ * sock_read_xattr - read a user.* xattr from a socket's sockfs inode
+ * @sock: socket whose inode holds the xattr
+ * @name: full xattr name, e.g. "user.bpf_test"
+ * @value: output buffer
+ * @size: size of @value in bytes
+ *
+ * SOCK_INODE() is valid only for sockfs sockets; sock_from_file() rejects
+ * anything else (e.g. tun, tap).
+ * Lockless: simple_xattr_get() looks up the value under RCU, no inode lock.
+ *
+ * Return: length of the value on success, a negative errno on error.
+ */
+int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size)
+{
+ struct file *file = sock->file;
+ struct sockfs_inode *si;
+
+ if (!file || sock_from_file(file) != sock)
+ return -EOPNOTSUPP;
+
+ si = SOCKFS_I(SOCK_INODE(sock));
+ return simple_xattr_get(&sockfs_xa_cache, &si->xattrs, name, value, size);
+}
+
static const struct xattr_handler * const sockfs_xattr_handlers[] = {
&sockfs_xattr_handler,
&sockfs_security_xattr_handler,
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/2] selftests/bpf: Add test for bpf_sock_read_xattr() kfunc
2026-06-17 11:18 [PATCH 0/2] Add bpf_sock_read_xattr() kfunc to read socket xattrs Christian Brauner
2026-06-17 11:18 ` [PATCH 1/2] fs: " Christian Brauner
@ 2026-06-17 11:18 ` Christian Brauner
1 sibling, 0 replies; 5+ messages in thread
From: Christian Brauner @ 2026-06-17 11:18 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann
Cc: Alexander Viro, Jan Kara, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, linux-fsdevel, netdev, bpf, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Christian Brauner (Amutable)
Add a selftest that loads the kfunc in sleepable and non-sleepable
lsm/socket_connect programs and checks that a value set via fsetxattr()
on a socket is read back.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tools/testing/selftests/bpf/bpf_experimental.h | 3 +
.../testing/selftests/bpf/prog_tests/sock_xattr.c | 67 ++++++++++++++++++++++
.../testing/selftests/bpf/progs/sock_read_xattr.c | 54 +++++++++++++++++
3 files changed, 124 insertions(+)
diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h
index 2234bd6bc9d3..5b825157b125 100644
--- a/tools/testing/selftests/bpf/bpf_experimental.h
+++ b/tools/testing/selftests/bpf/bpf_experimental.h
@@ -446,6 +446,9 @@ extern void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) __weak __ksym;
extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str,
struct bpf_dynptr *value_p) __weak __ksym;
+extern int bpf_sock_read_xattr(struct socket *sock, const char *name__str,
+ struct bpf_dynptr *value_p) __weak __ksym;
+
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
diff --git a/tools/testing/selftests/bpf/prog_tests/sock_xattr.c b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c
new file mode 100644
index 000000000000..b5816e90f01a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Christian Brauner */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/xattr.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <test_progs.h>
+
+#include "sock_read_xattr.skel.h"
+
+static const char xattr_value[] = "bpf_sock_value";
+static const char xattr_name[] = "user.bpf_test";
+
+static void test_read_sock_xattr(void)
+{
+ struct sockaddr_in addr = {};
+ struct sock_read_xattr *skel = NULL;
+ struct bpf_link *link = NULL;
+ int sock_fd = -1, err;
+
+ sock_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (!ASSERT_OK_FD(sock_fd, "socket"))
+ return;
+
+ err = fsetxattr(sock_fd, xattr_name, xattr_value, sizeof(xattr_value), 0);
+ if (!ASSERT_OK(err, "fsetxattr"))
+ goto out;
+
+ skel = sock_read_xattr__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "sock_read_xattr__open_and_load"))
+ goto out;
+
+ skel->bss->monitored_pid = sys_gettid();
+
+ /* Only attach the functional program; the verifier-only programs
+ * above are not pid-gated and would clobber the shared globals.
+ */
+ link = bpf_program__attach(skel->progs.read_sock_xattr);
+ if (!ASSERT_OK_PTR(link, "attach read_sock_xattr"))
+ goto out;
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(1234);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ /* Only the lsm/socket_connect hook matters; the connect may fail. */
+ connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
+
+ ASSERT_EQ(skel->data->read_ret, sizeof(xattr_value), "read_ret");
+ ASSERT_STREQ(skel->bss->value, xattr_value, "value");
+
+out:
+ bpf_link__destroy(link);
+ if (sock_fd >= 0)
+ close(sock_fd);
+ sock_read_xattr__destroy(skel);
+}
+
+void test_sock_xattr(void)
+{
+ RUN_TESTS(sock_read_xattr);
+
+ if (test__start_subtest("read_sock_xattr"))
+ test_read_sock_xattr();
+}
diff --git a/tools/testing/selftests/bpf/progs/sock_read_xattr.c b/tools/testing/selftests/bpf/progs/sock_read_xattr.c
new file mode 100644
index 000000000000..c4a8eae8cc3c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_read_xattr.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Christian Brauner */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_experimental.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+char value[16];
+int read_ret = -1;
+__u32 monitored_pid = 0;
+
+static __always_inline void read_xattr(struct socket *sock)
+{
+ struct bpf_dynptr value_ptr;
+
+ bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr);
+ bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr);
+}
+
+SEC("lsm.s/socket_connect")
+__success
+int BPF_PROG(trusted_sock_ptr_sleepable, struct socket *sock)
+{
+ read_xattr(sock);
+ return 0;
+}
+
+SEC("lsm/socket_connect")
+__success
+int BPF_PROG(trusted_sock_ptr_non_sleepable, struct socket *sock)
+{
+ read_xattr(sock);
+ return 0;
+}
+
+SEC("lsm.s/socket_connect")
+__success
+int BPF_PROG(read_sock_xattr, struct socket *sock)
+{
+ struct bpf_dynptr value_ptr;
+ __u32 pid = bpf_get_current_pid_tgid() >> 32;
+
+ if (pid != monitored_pid)
+ return 0;
+
+ bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr);
+ read_ret = bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr);
+ return 0;
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
2026-06-17 11:18 ` [PATCH 1/2] fs: " Christian Brauner
@ 2026-06-17 11:32 ` sashiko-bot
2026-06-17 14:03 ` Christian Brauner
0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-06-17 11:32 UTC (permalink / raw)
To: Christian Brauner; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing READ_ONCE() on lockless access to sock->file can lead to a TOCTOU NULL pointer dereference.
--
commit 9bf8c99c6cda345fa08537f5df5f36bc0aa367b9
Author: Christian Brauner <brauner@kernel.org>
fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
This commit introduces the bpf_sock_read_xattr() kfunc to allow BPF LSM
programs to locklessly read user.* extended attributes from a socket's
sockfs inode. The kfunc is registered as KF_RCU, and retrieves the
attributes using an RCU-protected rhashtable lookup.
Link: https://github.com/systemd/systemd/pull/40559 [1]
> diff --git a/net/socket.c b/net/socket.c
> index 9e8dc769ff7af..3566f8c8ea3fb 100644
> --- a/net/socket.c
> +++ b/net/socket.c
[ ... ]
> @@ -465,6 +465,31 @@ static const struct xattr_handler sockfs_user_xattr_handler = {
> .set = sockfs_user_xattr_set,
> };
>
> +/**
> + * sock_read_xattr - read a user.* xattr from a socket's sockfs inode
[ ... ]
> +int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size)
> +{
> + struct file *file = sock->file;
> + struct sockfs_inode *si;
> +
> + if (!file || sock_from_file(file) != sock)
[Severity: High]
Can a concurrent close cause a NULL pointer dereference here?
Since bpf_sock_read_xattr() is registered with KF_RCU, BPF programs can
call it under RCU protection where the socket might not have a held file
reference. If the last file descriptor is closed concurrently,
__sock_release() can set sock->file = NULL without waiting for an RCU grace
period.
Because sock->file is read without READ_ONCE(), the compiler is permitted
to reload the pointer from memory. If the initial read for the !file check
sees a valid pointer, but a subsequent read for the inlined
sock_from_file(file) call sees NULL due to the concurrent modification,
sock_from_file() will attempt to dereference file->f_op on a NULL pointer.
Does this need struct file *file = READ_ONCE(sock->file) to prevent a
TOCTOU issue?
> + return -EOPNOTSUPP;
> +
> + si = SOCKFS_I(SOCK_INODE(sock));
> + return simple_xattr_get(&sockfs_xa_cache, &si->xattrs, name, value, size);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260617-work-bpf-sock-xattr-v1-0-a1276f7c9da3@kernel.org?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
2026-06-17 11:32 ` sashiko-bot
@ 2026-06-17 14:03 ` Christian Brauner
0 siblings, 0 replies; 5+ messages in thread
From: Christian Brauner @ 2026-06-17 14:03 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Wed, Jun 17, 2026 at 11:32:08AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Missing READ_ONCE() on lockless access to sock->file can lead to a TOCTOU NULL pointer dereference.
> --
>
> commit 9bf8c99c6cda345fa08537f5df5f36bc0aa367b9
> Author: Christian Brauner <brauner@kernel.org>
>
> fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
>
> This commit introduces the bpf_sock_read_xattr() kfunc to allow BPF LSM
> programs to locklessly read user.* extended attributes from a socket's
> sockfs inode. The kfunc is registered as KF_RCU, and retrieves the
> attributes using an RCU-protected rhashtable lookup.
>
> Link: https://github.com/systemd/systemd/pull/40559 [1]
>
> > diff --git a/net/socket.c b/net/socket.c
> > index 9e8dc769ff7af..3566f8c8ea3fb 100644
> > --- a/net/socket.c
> > +++ b/net/socket.c
> [ ... ]
> > @@ -465,6 +465,31 @@ static const struct xattr_handler sockfs_user_xattr_handler = {
> > .set = sockfs_user_xattr_set,
> > };
> >
> > +/**
> > + * sock_read_xattr - read a user.* xattr from a socket's sockfs inode
> [ ... ]
> > +int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size)
> > +{
> > + struct file *file = sock->file;
> > + struct sockfs_inode *si;
> > +
> > + if (!file || sock_from_file(file) != sock)
>
> [Severity: High]
> Can a concurrent close cause a NULL pointer dereference here?
>
> Since bpf_sock_read_xattr() is registered with KF_RCU, BPF programs can
> call it under RCU protection where the socket might not have a held file
> reference. If the last file descriptor is closed concurrently,
> __sock_release() can set sock->file = NULL without waiting for an RCU grace
> period.
>
> Because sock->file is read without READ_ONCE(), the compiler is permitted
> to reload the pointer from memory. If the initial read for the !file check
> sees a valid pointer, but a subsequent read for the inlined
> sock_from_file(file) call sees NULL due to the concurrent modification,
> sock_from_file() will attempt to dereference file->f_op on a NULL pointer.
>
> Does this need struct file *file = READ_ONCE(sock->file) to prevent a
> TOCTOU issue?
The function is intentionally restricted to BPF LSM hooks where the file
pointer on struct socket is either NULL or stable. This invariant is
also relied upon by other LSMs. The RCU makes it possible to use it in
non-sleeping contexts and the simple xattr interface was specifically
designed for that.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-17 14:03 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-17 11:18 [PATCH 0/2] Add bpf_sock_read_xattr() kfunc to read socket xattrs Christian Brauner
2026-06-17 11:18 ` [PATCH 1/2] fs: " Christian Brauner
2026-06-17 11:32 ` sashiko-bot
2026-06-17 14:03 ` Christian Brauner
2026-06-17 11:18 ` [PATCH 2/2] selftests/bpf: Add test for bpf_sock_read_xattr() kfunc Christian Brauner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox