Skip to content

Introduce bpf_cgroup_read_xattr #5503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: bpf-next_base
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions fs/bpf_fs_kfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/file.h>
#include <linux/kernfs.h>
#include <linux/mm.h>
#include <linux/xattr.h>

Expand Down Expand Up @@ -322,6 +323,39 @@ __bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name_
return ret;
}

#ifdef CONFIG_CGROUPS
/**
* bpf_cgroup_read_xattr - read xattr of a cgroup's node in cgroupfs
* @cgroup: cgroup to get xattr from
* @name__str: name of the xattr
* @value_p: output buffer of the xattr value
*
* Get xattr *name__str* of *cgroup* and store the output in *value_ptr*.
*
* 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_cgroup_read_xattr(struct cgroup *cgroup, 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 kernfs_xattr_get(cgroup->kn, name__str, value, value_len);
}
#endif /* CONFIG_CGROUPS */

__bpf_kfunc_end_defs();

BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
Expand Down
74 changes: 40 additions & 34 deletions fs/kernfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,46 @@ static const struct inode_operations kernfs_iops = {
.listxattr = kernfs_iop_listxattr,
};

static struct kernfs_iattrs *__kernfs_iattrs(struct kernfs_node *kn, int alloc)
static struct kernfs_iattrs *__kernfs_iattrs(struct kernfs_node *kn, bool alloc)
{
static DEFINE_MUTEX(iattr_mutex);
struct kernfs_iattrs *ret;
struct kernfs_iattrs *ret __free(kfree) = NULL;
struct kernfs_iattrs *attr;

mutex_lock(&iattr_mutex);
attr = READ_ONCE(kn->iattr);
if (attr || !alloc)
return attr;

if (kn->iattr || !alloc)
goto out_unlock;

kn->iattr = kmem_cache_zalloc(kernfs_iattrs_cache, GFP_KERNEL);
if (!kn->iattr)
goto out_unlock;
ret = kmem_cache_zalloc(kernfs_iattrs_cache, GFP_KERNEL);
if (!ret)
return NULL;

/* assign default attributes */
kn->iattr->ia_uid = GLOBAL_ROOT_UID;
kn->iattr->ia_gid = GLOBAL_ROOT_GID;

ktime_get_real_ts64(&kn->iattr->ia_atime);
kn->iattr->ia_mtime = kn->iattr->ia_atime;
kn->iattr->ia_ctime = kn->iattr->ia_atime;

simple_xattrs_init(&kn->iattr->xattrs);
atomic_set(&kn->iattr->nr_user_xattrs, 0);
atomic_set(&kn->iattr->user_xattr_size, 0);
out_unlock:
ret = kn->iattr;
mutex_unlock(&iattr_mutex);
return ret;
ret->ia_uid = GLOBAL_ROOT_UID;
ret->ia_gid = GLOBAL_ROOT_GID;

ktime_get_real_ts64(&ret->ia_atime);
ret->ia_mtime = ret->ia_atime;
ret->ia_ctime = ret->ia_atime;

simple_xattrs_init(&ret->xattrs);
atomic_set(&ret->nr_user_xattrs, 0);
atomic_set(&ret->user_xattr_size, 0);

/* If someone raced us, recognize it. */
if (!try_cmpxchg(&kn->iattr, &attr, ret))
return READ_ONCE(kn->iattr);

return no_free_ptr(ret);
}

static struct kernfs_iattrs *kernfs_iattrs(struct kernfs_node *kn)
{
return __kernfs_iattrs(kn, 1);
return __kernfs_iattrs(kn, true);
}

static struct kernfs_iattrs *kernfs_iattrs_noalloc(struct kernfs_node *kn)
{
return __kernfs_iattrs(kn, 0);
return __kernfs_iattrs(kn, false);
}

int __kernfs_setattr(struct kernfs_node *kn, const struct iattr *iattr)
Expand Down Expand Up @@ -141,9 +142,9 @@ ssize_t kernfs_iop_listxattr(struct dentry *dentry, char *buf, size_t size)
struct kernfs_node *kn = kernfs_dentry_node(dentry);
struct kernfs_iattrs *attrs;

attrs = kernfs_iattrs(kn);
attrs = kernfs_iattrs_noalloc(kn);
if (!attrs)
return -ENOMEM;
return -ENODATA;

return simple_xattr_list(d_inode(dentry), &attrs->xattrs, buf, size);
}
Expand All @@ -166,9 +167,10 @@ static inline void set_inode_attr(struct inode *inode,

static void kernfs_refresh_inode(struct kernfs_node *kn, struct inode *inode)
{
struct kernfs_iattrs *attrs = kn->iattr;
struct kernfs_iattrs *attrs;

inode->i_mode = kn->mode;
attrs = kernfs_iattrs_noalloc(kn);
if (attrs)
/*
* kernfs_node has non-default attributes get them from
Expand Down Expand Up @@ -306,7 +308,9 @@ int kernfs_xattr_set(struct kernfs_node *kn, const char *name,
const void *value, size_t size, int flags)
{
struct simple_xattr *old_xattr;
struct kernfs_iattrs *attrs = kernfs_iattrs(kn);
struct kernfs_iattrs *attrs;

attrs = kernfs_iattrs(kn);
if (!attrs)
return -ENOMEM;

Expand Down Expand Up @@ -345,8 +349,9 @@ static int kernfs_vfs_user_xattr_add(struct kernfs_node *kn,
struct simple_xattrs *xattrs,
const void *value, size_t size, int flags)
{
atomic_t *sz = &kn->iattr->user_xattr_size;
atomic_t *nr = &kn->iattr->nr_user_xattrs;
struct kernfs_iattrs *attr = kernfs_iattrs_noalloc(kn);
atomic_t *sz = &attr->user_xattr_size;
atomic_t *nr = &attr->nr_user_xattrs;
struct simple_xattr *old_xattr;
int ret;

Expand Down Expand Up @@ -384,8 +389,9 @@ static int kernfs_vfs_user_xattr_rm(struct kernfs_node *kn,
struct simple_xattrs *xattrs,
const void *value, size_t size, int flags)
{
atomic_t *sz = &kn->iattr->user_xattr_size;
atomic_t *nr = &kn->iattr->nr_user_xattrs;
struct kernfs_iattrs *attr = kernfs_iattrs(kn);
atomic_t *sz = &attr->user_xattr_size;
atomic_t *nr = &attr->nr_user_xattrs;
struct simple_xattr *old_xattr;

old_xattr = simple_xattr_set(xattrs, full_name, value, size, flags);
Expand Down
3 changes: 3 additions & 0 deletions kernel/bpf/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -3397,6 +3397,9 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB
BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
#endif
BTF_ID_FLAGS(func, __bpf_trap)
#ifdef CONFIG_CGROUPS
BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU)
#endif
BTF_KFUNCS_END(common_btf_ids)

static const struct btf_kfunc_id_set common_kfunc_set = {
Expand Down
5 changes: 5 additions & 0 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -7058,6 +7058,10 @@ BTF_TYPE_SAFE_RCU(struct css_set) {
struct cgroup *dfl_cgrp;
};

BTF_TYPE_SAFE_RCU(struct cgroup_subsys_state) {
struct cgroup *cgroup;
};

/* RCU trusted: these fields are trusted in RCU CS and can be NULL */
BTF_TYPE_SAFE_RCU_OR_NULL(struct mm_struct) {
struct file __rcu *exe_file;
Expand Down Expand Up @@ -7108,6 +7112,7 @@ static bool type_is_rcu(struct bpf_verifier_env *env,
BTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct task_struct));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct cgroup));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct css_set));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct cgroup_subsys_state));

return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id, "__safe_rcu");
}
Expand Down
3 changes: 3 additions & 0 deletions tools/testing/selftests/bpf/bpf_experimental.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,4 +596,7 @@ extern int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it) __weak __ksym;
extern struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it) __weak __ksym;
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;

#endif
145 changes: 145 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/cgroup_xattr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */

#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/xattr.h>

#include <test_progs.h>

#include "read_cgroupfs_xattr.skel.h"
#include "cgroup_read_xattr.skel.h"

#define CGROUP_FS_ROOT "/sys/fs/cgroup/"
#define CGROUP_FS_PARENT CGROUP_FS_ROOT "foo/"
#define CGROUP_FS_CHILD CGROUP_FS_PARENT "bar/"

static int move_pid_to_cgroup(const char *cgroup_folder, pid_t pid)
{
char filename[128];
char pid_str[64];
int procs_fd;
int ret;

snprintf(filename, sizeof(filename), "%scgroup.procs", cgroup_folder);
snprintf(pid_str, sizeof(pid_str), "%d", pid);

procs_fd = open(filename, O_WRONLY | O_APPEND);
if (!ASSERT_OK_FD(procs_fd, "open"))
return -1;

ret = write(procs_fd, pid_str, strlen(pid_str));
close(procs_fd);
if (!ASSERT_GT(ret, 0, "write cgroup.procs"))
return -1;
return 0;
}

static void reset_cgroups_and_lo(void)
{
rmdir(CGROUP_FS_CHILD);
rmdir(CGROUP_FS_PARENT);
system("ip addr del 1.1.1.1/32 dev lo");
system("ip link set dev lo down");
}

static const char xattr_value_a[] = "bpf_selftest_value_a";
static const char xattr_value_b[] = "bpf_selftest_value_b";
static const char xattr_name[] = "user.bpf_test";

static int setup_cgroups_and_lo(void)
{
int err;

err = mkdir(CGROUP_FS_PARENT, 0755);
if (!ASSERT_OK(err, "mkdir 1"))
goto error;
err = mkdir(CGROUP_FS_CHILD, 0755);
if (!ASSERT_OK(err, "mkdir 2"))
goto error;

err = setxattr(CGROUP_FS_PARENT, xattr_name, xattr_value_a,
strlen(xattr_value_a) + 1, 0);
if (!ASSERT_OK(err, "setxattr 1"))
goto error;

err = setxattr(CGROUP_FS_CHILD, xattr_name, xattr_value_b,
strlen(xattr_value_b) + 1, 0);
if (!ASSERT_OK(err, "setxattr 2"))
goto error;

err = system("ip link set dev lo up");
if (!ASSERT_OK(err, "lo up"))
goto error;

err = system("ip addr add 1.1.1.1 dev lo");
if (!ASSERT_OK(err, "lo addr v4"))
goto error;

err = write_sysctl("/proc/sys/net/ipv4/ping_group_range", "0 0");
if (!ASSERT_OK(err, "write_sysctl"))
goto error;

return 0;
error:
reset_cgroups_and_lo();
return err;
}

static void test_read_cgroup_xattr(void)
{
struct sockaddr_in sa4 = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
struct read_cgroupfs_xattr *skel = NULL;
pid_t pid = gettid();
int sock_fd = -1;
int connect_fd = -1;

if (!ASSERT_OK(setup_cgroups_and_lo(), "setup_cgroups_and_lo"))
return;
if (!ASSERT_OK(move_pid_to_cgroup(CGROUP_FS_CHILD, pid),
"move_pid_to_cgroup"))
goto out;

skel = read_cgroupfs_xattr__open_and_load();
if (!ASSERT_OK_PTR(skel, "read_cgroupfs_xattr__open_and_load"))
goto out;

skel->bss->target_pid = pid;

if (!ASSERT_OK(read_cgroupfs_xattr__attach(skel), "read_cgroupfs_xattr__attach"))
goto out;

sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (!ASSERT_OK_FD(sock_fd, "sock create"))
goto out;

connect_fd = connect(sock_fd, &sa4, sizeof(sa4));
if (!ASSERT_OK_FD(connect_fd, "connect 1"))
goto out;
close(connect_fd);

ASSERT_TRUE(skel->bss->found_value_a, "found_value_a");
ASSERT_TRUE(skel->bss->found_value_b, "found_value_b");

out:
close(connect_fd);
close(sock_fd);
read_cgroupfs_xattr__destroy(skel);
move_pid_to_cgroup(CGROUP_FS_ROOT, pid);
reset_cgroups_and_lo();
}

void test_cgroup_xattr(void)
{
RUN_TESTS(cgroup_read_xattr);

if (test__start_subtest("read_cgroupfs_xattr"))
test_read_cgroup_xattr();
}
Loading
Loading