vfs-7.3-rc1.kfunc

Please consider pulling these changes from the signed vfs-7.3-rc1.kfunc tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCan7RJQAKCRCRxhvAZXjc
 oqBnAQDzCTHIa0wColluZLaFVuL4pvipQzC7tjUUQCwPmy85EwEA2cyeguEkNYJZ
 SK3m4z8kfuZjO2tEOig+XCkmtVrSMQQ=
 =6gwq
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.3-rc1.kfunc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs bpf access updates from Christian Brauner:
 "This adds a bpf_sock_read_xattr() kfunc so a BPF LSM program can read
  a user.* extended attribute from a socket's sockfs inode locklessly.

  userspace already uses user.* xattrs on sockets to implement socket
  rate limiting and to tag sockets for other purposes such as a varlink
  registry. There has been no efficient way for a BPF program to read
  those labels back. With this a listening socket marked from userspace
  with fsetxattr() can be read back during bind or connect and acted
  upon on the connecting socket. That lets userspace mark sockets and
  later rediscover them or implement policy on them"

* tag 'vfs-7.3-rc1.kfunc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  selftests/bpf: Add test for bpf_sock_read_xattr() kfunc
  fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
This commit is contained in:
Linus Torvalds 2026-08-17 11:02:10 -07:00
commit de03b17ec0
6 changed files with 187 additions and 0 deletions

View File

@ -12,6 +12,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();
@ -360,6 +361,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_data_inode - get the real inode hosting a file's data
* @file: file to resolve
@ -391,6 +425,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_data_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)
/* Side-effecting kfuncs that stay exclusive to LSM programs. */

View File

@ -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);

View File

@ -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,

View File

@ -364,6 +364,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

View File

@ -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();
}

View File

@ -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;
}