Commit 81427a62 authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'bpf-add-support-for-cgroup1-bpf-part'

Yafang Shao says:

====================
bpf: Add support for cgroup1, BPF part

This is the BPF part of the series "bpf, cgroup: Add BPF support for
cgroup1 hierarchy" with adjustment in the last two patches compared
to the previous one.

v3->v4:
  - use subsys_name instead of cgrp_name in get_cgroup_hierarchy_id()
    (Tejun)
  - use local bpf_link instead of modifying the skeleton in the
    selftests
v3: https://lwn.net/Articles/949264/
====================

Link: https://lore.kernel.org/r/20231111090034.4248-1-laoar.shao@gmail.comSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 727a92d6 36076923
......@@ -2228,6 +2228,25 @@ __bpf_kfunc long bpf_task_under_cgroup(struct task_struct *task,
rcu_read_unlock();
return ret;
}
/**
* bpf_task_get_cgroup1 - Acquires the associated cgroup of a task within a
* specific cgroup1 hierarchy. The cgroup1 hierarchy is identified by its
* hierarchy ID.
* @task: The target task
* @hierarchy_id: The ID of a cgroup1 hierarchy
*
* On success, the cgroup is returen. On failure, NULL is returned.
*/
__bpf_kfunc struct cgroup *
bpf_task_get_cgroup1(struct task_struct *task, int hierarchy_id)
{
struct cgroup *cgrp = task_get_cgroup1(task, hierarchy_id);
if (IS_ERR(cgrp))
return NULL;
return cgrp;
}
#endif /* CONFIG_CGROUPS */
/**
......@@ -2534,6 +2553,7 @@ BTF_ID_FLAGS(func, bpf_cgroup_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_cgroup_ancestor, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_cgroup_from_id, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_under_cgroup, KF_RCU)
BTF_ID_FLAGS(func, bpf_task_get_cgroup1, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
#endif
BTF_ID_FLAGS(func, bpf_task_from_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_throw)
......
......@@ -45,9 +45,12 @@
#define format_parent_cgroup_path(buf, path) \
format_cgroup_path_pid(buf, path, getppid())
#define format_classid_path(buf) \
snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH, \
CGROUP_WORK_DIR)
#define format_classid_path_pid(buf, pid) \
snprintf(buf, sizeof(buf), "%s%s%d", NETCLS_MOUNT_PATH, \
CGROUP_WORK_DIR, pid)
#define format_classid_path(buf) \
format_classid_path_pid(buf, getpid())
static __thread bool cgroup_workdir_mounted;
......@@ -419,26 +422,23 @@ int create_and_get_cgroup(const char *relative_path)
}
/**
* get_cgroup_id() - Get cgroup id for a particular cgroup path
* @relative_path: The cgroup path, relative to the workdir, to join
* get_cgroup_id_from_path - Get cgroup id for a particular cgroup path
* @cgroup_workdir: The absolute cgroup path
*
* On success, it returns the cgroup id. On failure it returns 0,
* which is an invalid cgroup id.
* If there is a failure, it prints the error to stderr.
*/
unsigned long long get_cgroup_id(const char *relative_path)
unsigned long long get_cgroup_id_from_path(const char *cgroup_workdir)
{
int dirfd, err, flags, mount_id, fhsize;
union {
unsigned long long cgid;
unsigned char raw_bytes[8];
} id;
char cgroup_workdir[PATH_MAX + 1];
struct file_handle *fhp, *fhp2;
unsigned long long ret = 0;
format_cgroup_path(cgroup_workdir, relative_path);
dirfd = AT_FDCWD;
flags = 0;
fhsize = sizeof(*fhp);
......@@ -474,6 +474,14 @@ unsigned long long get_cgroup_id(const char *relative_path)
return ret;
}
unsigned long long get_cgroup_id(const char *relative_path)
{
char cgroup_workdir[PATH_MAX + 1];
format_cgroup_path(cgroup_workdir, relative_path);
return get_cgroup_id_from_path(cgroup_workdir);
}
int cgroup_setup_and_join(const char *path) {
int cg_fd;
......@@ -523,10 +531,20 @@ int setup_classid_environment(void)
return 1;
}
if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls") &&
errno != EBUSY) {
log_err("mount cgroup net_cls");
return 1;
if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls")) {
if (errno != EBUSY) {
log_err("mount cgroup net_cls");
return 1;
}
if (rmdir(NETCLS_MOUNT_PATH)) {
log_err("rmdir cgroup net_cls");
return 1;
}
if (umount(CGROUP_MOUNT_DFLT)) {
log_err("umount cgroup base");
return 1;
}
}
cleanup_classid_environment();
......@@ -541,15 +559,16 @@ int setup_classid_environment(void)
/**
* set_classid() - Set a cgroupv1 net_cls classid
* @id: the numeric classid
*
* Writes the passed classid into the cgroup work dir's net_cls.classid
* Writes the classid into the cgroup work dir's net_cls.classid
* file in order to later on trigger socket tagging.
*
* We leverage the current pid as the classid, ensuring unique identification.
*
* On success, it returns 0, otherwise on failure it returns 1. If there
* is a failure, it prints the error to stderr.
*/
int set_classid(unsigned int id)
int set_classid(void)
{
char cgroup_workdir[PATH_MAX - 42];
char cgroup_classid_path[PATH_MAX + 1];
......@@ -565,7 +584,7 @@ int set_classid(unsigned int id)
return 1;
}
if (dprintf(fd, "%u\n", id) < 0) {
if (dprintf(fd, "%u\n", getpid()) < 0) {
log_err("Setting cgroup classid");
rc = 1;
}
......@@ -607,3 +626,66 @@ void cleanup_classid_environment(void)
join_cgroup_from_top(NETCLS_MOUNT_PATH);
nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
}
/**
* get_classid_cgroup_id - Get the cgroup id of a net_cls cgroup
*/
unsigned long long get_classid_cgroup_id(void)
{
char cgroup_workdir[PATH_MAX + 1];
format_classid_path(cgroup_workdir);
return get_cgroup_id_from_path(cgroup_workdir);
}
/**
* get_cgroup1_hierarchy_id - Retrieves the ID of a cgroup1 hierarchy from the cgroup1 subsys name.
* @subsys_name: The cgroup1 subsys name, which can be retrieved from /proc/self/cgroup. It can be
* a named cgroup like "name=systemd", a controller name like "net_cls", or multi-contollers like
* "net_cls,net_prio".
*/
int get_cgroup1_hierarchy_id(const char *subsys_name)
{
char *c, *c2, *c3, *c4;
bool found = false;
char line[1024];
FILE *file;
int i, id;
if (!subsys_name)
return -1;
file = fopen("/proc/self/cgroup", "r");
if (!file) {
log_err("fopen /proc/self/cgroup");
return -1;
}
while (fgets(line, 1024, file)) {
i = 0;
for (c = strtok_r(line, ":", &c2); c && i < 2; c = strtok_r(NULL, ":", &c2)) {
if (i == 0) {
id = strtol(c, NULL, 10);
} else if (i == 1) {
if (!strcmp(c, subsys_name)) {
found = true;
break;
}
/* Multiple subsystems may share one single mount point */
for (c3 = strtok_r(c, ",", &c4); c3;
c3 = strtok_r(NULL, ",", &c4)) {
if (!strcmp(c, subsys_name)) {
found = true;
break;
}
}
}
i++;
}
if (found)
break;
}
fclose(file);
return found ? id : -1;
}
......@@ -20,6 +20,7 @@ int get_root_cgroup(void);
int create_and_get_cgroup(const char *relative_path);
void remove_cgroup(const char *relative_path);
unsigned long long get_cgroup_id(const char *relative_path);
int get_cgroup1_hierarchy_id(const char *subsys_name);
int join_cgroup(const char *relative_path);
int join_root_cgroup(void);
......@@ -29,8 +30,9 @@ int setup_cgroup_environment(void);
void cleanup_cgroup_environment(void);
/* cgroupv1 related */
int set_classid(unsigned int id);
int set_classid(void);
int join_classid(void);
unsigned long long get_classid_cgroup_id(void);
int setup_classid_environment(void);
void cleanup_classid_environment(void);
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2023 Yafang Shao <laoar.shao@gmail.com> */
#include <sys/types.h>
#include <unistd.h>
#include <test_progs.h>
#include "cgroup_helpers.h"
#include "test_cgroup1_hierarchy.skel.h"
static void bpf_cgroup1(struct test_cgroup1_hierarchy *skel)
{
struct bpf_link *lsm_link, *fentry_link;
int err;
/* Attach LSM prog first */
lsm_link = bpf_program__attach_lsm(skel->progs.lsm_run);
if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
return;
/* LSM prog will be triggered when attaching fentry */
fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
ASSERT_NULL(fentry_link, "fentry_attach_fail");
err = bpf_link__destroy(lsm_link);
ASSERT_OK(err, "destroy_lsm");
}
static void bpf_cgroup1_sleepable(struct test_cgroup1_hierarchy *skel)
{
struct bpf_link *lsm_link, *fentry_link;
int err;
/* Attach LSM prog first */
lsm_link = bpf_program__attach_lsm(skel->progs.lsm_s_run);
if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
return;
/* LSM prog will be triggered when attaching fentry */
fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
ASSERT_NULL(fentry_link, "fentry_attach_fail");
err = bpf_link__destroy(lsm_link);
ASSERT_OK(err, "destroy_lsm");
}
static void bpf_cgroup1_invalid_id(struct test_cgroup1_hierarchy *skel)
{
struct bpf_link *lsm_link, *fentry_link;
int err;
/* Attach LSM prog first */
lsm_link = bpf_program__attach_lsm(skel->progs.lsm_run);
if (!ASSERT_OK_PTR(lsm_link, "lsm_attach"))
return;
/* LSM prog will be triggered when attaching fentry */
fentry_link = bpf_program__attach_trace(skel->progs.fentry_run);
if (!ASSERT_OK_PTR(fentry_link, "fentry_attach_success"))
goto cleanup;
err = bpf_link__destroy(fentry_link);
ASSERT_OK(err, "destroy_lsm");
cleanup:
err = bpf_link__destroy(lsm_link);
ASSERT_OK(err, "destroy_fentry");
}
void test_cgroup1_hierarchy(void)
{
struct test_cgroup1_hierarchy *skel;
__u64 current_cgid;
int hid, err;
skel = test_cgroup1_hierarchy__open();
if (!ASSERT_OK_PTR(skel, "open"))
return;
skel->bss->target_pid = getpid();
err = bpf_program__set_attach_target(skel->progs.fentry_run, 0, "bpf_fentry_test1");
if (!ASSERT_OK(err, "fentry_set_target"))
goto destroy;
err = test_cgroup1_hierarchy__load(skel);
if (!ASSERT_OK(err, "load"))
goto destroy;
/* Setup cgroup1 hierarchy */
err = setup_classid_environment();
if (!ASSERT_OK(err, "setup_classid_environment"))
goto destroy;
err = join_classid();
if (!ASSERT_OK(err, "join_cgroup1"))
goto cleanup;
current_cgid = get_classid_cgroup_id();
if (!ASSERT_GE(current_cgid, 0, "cgroup1 id"))
goto cleanup;
hid = get_cgroup1_hierarchy_id("net_cls");
if (!ASSERT_GE(hid, 0, "cgroup1 id"))
goto cleanup;
skel->bss->target_hid = hid;
if (test__start_subtest("test_cgroup1_hierarchy")) {
skel->bss->target_ancestor_cgid = current_cgid;
bpf_cgroup1(skel);
}
if (test__start_subtest("test_root_cgid")) {
skel->bss->target_ancestor_cgid = 1;
skel->bss->target_ancestor_level = 0;
bpf_cgroup1(skel);
}
if (test__start_subtest("test_invalid_level")) {
skel->bss->target_ancestor_cgid = 1;
skel->bss->target_ancestor_level = 1;
bpf_cgroup1_invalid_id(skel);
}
if (test__start_subtest("test_invalid_cgid")) {
skel->bss->target_ancestor_cgid = 0;
bpf_cgroup1_invalid_id(skel);
}
if (test__start_subtest("test_invalid_hid")) {
skel->bss->target_ancestor_cgid = 1;
skel->bss->target_ancestor_level = 0;
skel->bss->target_hid = -1;
bpf_cgroup1_invalid_id(skel);
}
if (test__start_subtest("test_invalid_cgrp_name")) {
skel->bss->target_hid = get_cgroup1_hierarchy_id("net_cl");
skel->bss->target_ancestor_cgid = current_cgid;
bpf_cgroup1_invalid_id(skel);
}
if (test__start_subtest("test_invalid_cgrp_name2")) {
skel->bss->target_hid = get_cgroup1_hierarchy_id("net_cls,");
skel->bss->target_ancestor_cgid = current_cgid;
bpf_cgroup1_invalid_id(skel);
}
if (test__start_subtest("test_sleepable_prog")) {
skel->bss->target_hid = hid;
skel->bss->target_ancestor_cgid = current_cgid;
bpf_cgroup1_sleepable(skel);
}
cleanup:
cleanup_classid_environment();
destroy:
test_cgroup1_hierarchy__destroy(skel);
}
......@@ -71,7 +71,7 @@ void test_cgroup_v1v2(void)
}
ASSERT_OK(run_test(cgroup_fd, server_fd, false), "cgroup-v2-only");
setup_classid_environment();
set_classid(42);
set_classid();
ASSERT_OK(run_test(cgroup_fd, server_fd, true), "cgroup-v1v2");
cleanup_classid_environment();
close(server_fd);
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2023 Yafang Shao <laoar.shao@gmail.com> */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
__u32 target_ancestor_level;
__u64 target_ancestor_cgid;
int target_pid, target_hid;
struct cgroup *bpf_task_get_cgroup1(struct task_struct *task, int hierarchy_id) __ksym;
struct cgroup *bpf_cgroup_ancestor(struct cgroup *cgrp, int level) __ksym;
void bpf_cgroup_release(struct cgroup *cgrp) __ksym;
static int bpf_link_create_verify(int cmd)
{
struct cgroup *cgrp, *ancestor;
struct task_struct *task;
int ret = 0;
if (cmd != BPF_LINK_CREATE)
return 0;
task = bpf_get_current_task_btf();
/* Then it can run in parallel with others */
if (task->pid != target_pid)
return 0;
cgrp = bpf_task_get_cgroup1(task, target_hid);
if (!cgrp)
return 0;
/* Refuse it if its cgid or its ancestor's cgid is the target cgid */
if (cgrp->kn->id == target_ancestor_cgid)
ret = -1;
ancestor = bpf_cgroup_ancestor(cgrp, target_ancestor_level);
if (!ancestor)
goto out;
if (ancestor->kn->id == target_ancestor_cgid)
ret = -1;
bpf_cgroup_release(ancestor);
out:
bpf_cgroup_release(cgrp);
return ret;
}
SEC("lsm/bpf")
int BPF_PROG(lsm_run, int cmd, union bpf_attr *attr, unsigned int size)
{
return bpf_link_create_verify(cmd);
}
SEC("lsm.s/bpf")
int BPF_PROG(lsm_s_run, int cmd, union bpf_attr *attr, unsigned int size)
{
return bpf_link_create_verify(cmd);
}
SEC("fentry")
int BPF_PROG(fentry_run)
{
return 0;
}
char _license[] SEC("license") = "GPL";
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment