Skip to content

Commit

Permalink
cgroups: add cgroup2 device controller support
Browse files Browse the repository at this point in the history
Add a bpf-based device controller implementation.

Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
  • Loading branch information
Christian Brauner committed Nov 29, 2019
1 parent f177506 commit bbf47ab
Show file tree
Hide file tree
Showing 11 changed files with 827 additions and 14 deletions.
4 changes: 4 additions & 0 deletions configure.ac
Expand Up @@ -368,6 +368,10 @@ AC_CHECK_TYPES([struct seccomp_notif_sizes], [], [], [[#include <seccomp.h>]])
AC_CHECK_DECLS([seccomp_syscall_resolve_name_arch], [], [], [[#include <seccomp.h>]])
CFLAGS="$OLD_CFLAGS"

AC_CHECK_HEADERS([linux/bpf.h], [
AC_CHECK_TYPES([struct bpf_cgroup_dev_ctx], [], [], [[#include <linux/bpf.h>]])
], [], [])

# Configuration examples
AC_ARG_ENABLE([examples],
[AS_HELP_STRING([--enable-examples], [install examples [default=yes]])],
Expand Down
2 changes: 2 additions & 0 deletions src/lxc/Makefile.am
Expand Up @@ -7,6 +7,7 @@ noinst_HEADERS = api_extensions.h \
caps.h \
cgroups/cgroup.h \
cgroups/cgroup_utils.h \
cgroups/cgroup2_devices.h \
compiler.h \
conf.h \
confile.h \
Expand Down Expand Up @@ -95,6 +96,7 @@ liblxc_la_SOURCES = af_unix.c af_unix.h \
caps.c caps.h \
cgroups/cgfsng.c \
cgroups/cgroup.c cgroups/cgroup.h \
cgroups/cgroup2_devices.c cgroups/cgroup2_devices.h \
cgroups/cgroup_utils.c cgroups/cgroup_utils.h \
compiler.h \
commands.c commands.h \
Expand Down
197 changes: 188 additions & 9 deletions src/lxc/cgroups/cgfsng.c
Expand Up @@ -54,6 +54,7 @@

#include "caps.h"
#include "cgroup.h"
#include "cgroup2_devices.h"
#include "cgroup_utils.h"
#include "commands.h"
#include "conf.h"
Expand Down Expand Up @@ -1105,6 +1106,12 @@ __cgfsng_ops static void cgfsng_payload_destroy(struct cgroup_ops *ops,
wrap.hierarchies = ops->hierarchies;
wrap.conf = handler->conf;

#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
ret = bpf_program_cgroup_detach(handler->conf->cgroup2_devices);
if (ret < 0)
WARN("Failed to detach bpf program from cgroup");
#endif

if (handler->conf && !lxc_list_empty(&handler->conf->id_map))
ret = userns_exec_1(handler->conf, cgroup_rmdir_wrapper, &wrap,
"cgroup_rmdir_wrapper");
Expand Down Expand Up @@ -2474,8 +2481,142 @@ static bool __cg_legacy_setup_limits(struct cgroup_ops *ops,
return ret;
}

static int bpf_device_cgroup_prepare(struct lxc_conf *conf, const char *key,
const char *val)
{
#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
struct device_item {
char type;
int major;
int minor;
char access[100];
int allow;
} device_item = {0};
int count, ret;
char temp[50];
struct bpf_program *device;

if (conf->cgroup2_devices) {
device = conf->cgroup2_devices;
} else {
device = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE);
if (device && bpf_program_init(device)) {
ERROR("Failed to initialize bpf program");
return -1;
}
}
if (!device) {
ERROR("Failed to create new ebpf device program");
return -1;
}

conf->cgroup2_devices = device;

if (strcmp("devices.allow", key) == 0)
device_item.allow = 1;

if (strcmp(val, "a") == 0) {
device->blacklist = (device_item.allow == 1);
return 0;
}

switch (*val) {
case 'a':
__fallthrough;
case 'b':
__fallthrough;
case 'c':
device_item.type = *val;
break;
default:
return -1;
}

val++;
if (!isspace(*val))
return -1;
val++;
if (*val == '*') {
device_item.major = ~0;
val++;
} else if (isdigit(*val)) {
memset(temp, 0, sizeof(temp));
for (count = 0; count < sizeof(temp) - 1; count++) {
temp[count] = *val;
val++;
if (!isdigit(*val))
break;
}
ret = lxc_safe_uint(temp, &device_item.major);
if (ret)
return -1;
} else {
return -1;
}
if (*val != ':')
return -1;
val++;

/* read minor */
if (*val == '*') {
device_item.minor = ~0;
val++;
} else if (isdigit(*val)) {
memset(temp, 0, sizeof(temp));
for (count = 0; count < sizeof(temp) - 1; count++) {
temp[count] = *val;
val++;
if (!isdigit(*val))
break;
}
ret = lxc_safe_uint(temp, &device_item.minor);
if (ret)
return -1;
} else {
return -1;
}
if (!isspace(*val))
return -1;
for (val++, count = 0; count < 3; count++, val++) {
switch (*val) {
case 'r':
device_item.access[count] = *val;
break;
case 'w':
device_item.access[count] = *val;
break;
case 'm':
device_item.access[count] = *val;
break;
case '\n':
case '\0':
count = 3;
break;
default:
return -1;
}
}

ret = bpf_program_append_device(device, device_item.type, device_item.major,
device_item.minor, device_item.access,
device_item.allow);
if (ret) {
ERROR("Failed to add new rule to bpf device program: type %c, major %d, minor %d, access %s, allow %d",
device_item.type, device_item.major, device_item.minor,
device_item.access, device_item.allow);
return -1;
} else {
TRACE("Added new rule to bpf device program: type %c, major %d, minor %d, access %s, allow %d",
device_item.type, device_item.major, device_item.minor,
device_item.access, device_item.allow);
}
#endif
return 0;
}

static bool __cg_unified_setup_limits(struct cgroup_ops *ops,
struct lxc_list *cgroup_settings)
struct lxc_list *cgroup_settings,
struct lxc_conf *conf)
{
struct lxc_list *iterator;
struct hierarchy *h = ops->unified;
Expand All @@ -2486,17 +2627,24 @@ static bool __cg_unified_setup_limits(struct cgroup_ops *ops,
if (!h)
return false;

lxc_list_for_each(iterator, cgroup_settings) {
lxc_list_for_each (iterator, cgroup_settings) {
__do_free char *fullpath = NULL;
int ret;
struct lxc_cgroup *cg = iterator->elem;

fullpath = must_make_path(h->container_full_path, cg->subsystem, NULL);
ret = lxc_write_to_file(fullpath, cg->value, strlen(cg->value), false, 0666);
if (ret < 0) {
SYSERROR("Failed to set \"%s\" to \"%s\"",
cg->subsystem, cg->value);
return false;
if (strncmp("devices", cg->subsystem, 7) == 0) {
ret = bpf_device_cgroup_prepare(conf, cg->subsystem,
cg->value)
} else {
fullpath = must_make_path(h->container_full_path,
cg->subsystem, NULL);
ret = lxc_write_to_file(fullpath, cg->value,
strlen(cg->value), false, 0666);
if (ret < 0) {
SYSERROR("Failed to set \"%s\" to \"%s\"",
cg->subsystem, cg->value);
return false;
}
}
TRACE("Set \"%s\" to \"%s\"", cg->subsystem, cg->value);
}
Expand All @@ -2505,14 +2653,44 @@ static bool __cg_unified_setup_limits(struct cgroup_ops *ops,
return true;
}

__cgfsng_ops bool cgfsng_devices_activate(struct cgroup_ops *ops,
struct lxc_handler *handler)
{
#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
int ret;
struct hierarchy *h = ops->unified;
struct bpf_program *device = handler->conf->cgroup2_devices;

if (!h)
return false;

if (!device)
return true;

ret = bpf_program_finalize(device);
if (ret)
return false;

return bpf_program_cgroup_attach(device, BPF_CGROUP_DEVICE,
h->container_full_path,
BPF_F_ALLOW_MULTI) == 0;
#else
return true;
#endif
}

__cgfsng_ops static bool cgfsng_setup_limits(struct cgroup_ops *ops,
struct lxc_conf *conf,
bool do_devices)
{
if (!__cg_legacy_setup_limits(ops, &conf->cgroup, do_devices))
return false;

return __cg_unified_setup_limits(ops, &conf->cgroup2);
/* for v2 we will have already set up devices */
if (do_devices)
return true;

return __cg_unified_setup_limits(ops, &conf->cgroup2, conf);
}

static bool cgroup_use_wants_controllers(const struct cgroup_ops *ops,
Expand Down Expand Up @@ -2893,6 +3071,7 @@ struct cgroup_ops *cgfsng_ops_init(struct lxc_conf *conf)
cgfsng_ops->chown = cgfsng_chown;
cgfsng_ops->mount = cgfsng_mount;
cgfsng_ops->nrtasks = cgfsng_nrtasks;
cgfsng_ops->devices_activate = cgfsng_devices_activate;

return move_ptr(cgfsng_ops);
}
2 changes: 2 additions & 0 deletions src/lxc/cgroups/cgroup.h
Expand Up @@ -164,6 +164,8 @@ struct cgroup_ops {
bool (*mount)(struct cgroup_ops *ops, struct lxc_handler *handler,
const char *root, int type);
int (*nrtasks)(struct cgroup_ops *ops);
bool (*devices_activate)(struct cgroup_ops *ops,
struct lxc_handler *handler);
};

extern struct cgroup_ops *cgroup_init(struct lxc_conf *conf);
Expand Down

0 comments on commit bbf47ab

Please sign in to comment.