Browse Source

jail: add support for cgroup devices as in OCI run-time spec

Implement eBPF generator to emulate cgroup-v1 devices.{allow,deny}
as we got only cgroup-v2 available while the spec was written having
cgroups-v1 in mind.
Instead of literally emulating the legacy behavior, do like other
runtimes do as well when running on cgroup-v2: simply translate each
device rule into a bunch of eBPF instructions and then execute them
in reverse order, prepended by some default rules covering /dev/null,
/dev/random, /dev/tty, ...

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Daniel Golle 2 years ago
parent
commit
2dcefbd609
5 changed files with 504 additions and 14 deletions
  1. 1 1
      CMakeLists.txt
  2. 451 0
      jail/cgroups-bpf.c
  3. 20 0
      jail/cgroups-bpf.h
  4. 26 8
      jail/cgroups.c
  5. 6 5
      jail/jail.c

+ 1 - 1
CMakeLists.txt

@@ -112,7 +112,7 @@ SET(SOURCES_OCI_SECCOMP jail/seccomp-oci.c)
 ENDIF()
 
 IF(JAIL_SUPPORT)
-ADD_EXECUTABLE(ujail jail/jail.c jail/cgroups.c jail/elf.c jail/fs.c jail/capabilities.c ${SOURCES_OCI_SECCOMP})
+ADD_EXECUTABLE(ujail jail/jail.c jail/cgroups.c jail/cgroups-bpf.c jail/elf.c jail/fs.c jail/capabilities.c ${SOURCES_OCI_SECCOMP})
 TARGET_LINK_LIBRARIES(ujail ${ubox} ${ubus} ${blobmsg_json})
 INSTALL(TARGETS ujail
 	RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}

+ 451 - 0
jail/cgroups-bpf.c

@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * somehow emulate devices.allow/devices.deny using eBPF
+ *
+ * OCI run-time spec defines the syntax for allowing/denying access
+ * to devices according to the definition of cgroup-v1 in the Kernel
+ * as described in Documentation/admin-guide/cgroup-v1.
+ */
+
+#include <assert.h>
+#include <linux/bpf.h>
+#include <sys/reg.h>
+#include <sys/syscall.h>
+
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/list.h>
+
+#include "cgroups.h"
+#include "cgroups-bpf.h"
+#include "log.h"
+
+static struct bpf_insn *program = NULL;
+static int bpf_total_insn = 0;
+static const char *license = "GPL";
+
+static int
+syscall_bpf (int cmd, union bpf_attr *attr, unsigned int size)
+{
+	return (int) syscall (__NR_bpf, cmd, attr, size);
+}
+
+/* from crun/src/libcrun/ebpf.c */
+#define BPF_ALU32_IMM(OP, DST, IMM) \
+	((struct bpf_insn){ .code = BPF_ALU | BPF_OP (OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })
+
+#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \
+	((struct bpf_insn){                    \
+		.code = BPF_LDX | BPF_SIZE (SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
+
+#define BPF_MOV64_REG(DST, SRC) \
+	((struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })
+
+#define BPF_JMP_A(OFF) \
+	((struct bpf_insn){ .code = BPF_JMP | BPF_JA, .dst_reg = 0, .src_reg = 0, .off = OFF, .imm = 0 })
+
+#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
+	((struct bpf_insn){ .code = BPF_JMP | BPF_OP (OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = OFF, .imm = IMM })
+
+#define BPF_JMP_REG(OP, DST, SRC, OFF) \
+	((struct bpf_insn){ .code = BPF_JMP | BPF_OP (OP) | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
+
+#define BPF_MOV64_IMM(DST, IMM) \
+	((struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })
+
+#define BPF_MOV32_REG(DST, SRC) \
+	((struct bpf_insn){ .code = BPF_ALU | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })
+
+#define BPF_EXIT_INSN() \
+	((struct bpf_insn){ .code = BPF_JMP | BPF_EXIT, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 })
+
+/* taken from systemd.  */
+static const struct bpf_insn pre_insn[] = {
+	/* type -> R2.  */
+	BPF_LDX_MEM (BPF_W, BPF_REG_2, BPF_REG_1, 0),
+	BPF_ALU32_IMM (BPF_AND, BPF_REG_2, 0xFFFF),
+	/* access -> R3.  */
+	BPF_LDX_MEM (BPF_W, BPF_REG_3, BPF_REG_1, 0),
+	BPF_ALU32_IMM (BPF_RSH, BPF_REG_3, 16),
+	/* major -> R4.  */
+	BPF_LDX_MEM (BPF_W, BPF_REG_4, BPF_REG_1, 4),
+	/* minor -> R5.  */
+	BPF_LDX_MEM (BPF_W, BPF_REG_5, BPF_REG_1, 8),
+};
+
+enum {
+	OCI_LINUX_CGROUPS_DEVICES_ALLOW,
+	OCI_LINUX_CGROUPS_DEVICES_TYPE,
+	OCI_LINUX_CGROUPS_DEVICES_MAJOR,
+	OCI_LINUX_CGROUPS_DEVICES_MINOR,
+	OCI_LINUX_CGROUPS_DEVICES_ACCESS,
+	__OCI_LINUX_CGROUPS_DEVICES_MAX,
+};
+
+static const struct blobmsg_policy oci_linux_cgroups_devices_policy[] = {
+	[OCI_LINUX_CGROUPS_DEVICES_ALLOW] = { "allow", BLOBMSG_TYPE_BOOL },
+	[OCI_LINUX_CGROUPS_DEVICES_TYPE] = { "type", BLOBMSG_TYPE_STRING },
+	[OCI_LINUX_CGROUPS_DEVICES_MAJOR] = { "major", BLOBMSG_CAST_INT64 },
+	[OCI_LINUX_CGROUPS_DEVICES_MINOR] = { "minor", BLOBMSG_CAST_INT64 },
+	[OCI_LINUX_CGROUPS_DEVICES_ACCESS] = { "access", BLOBMSG_TYPE_STRING },
+};
+
+/*
+ * cgroup-v1 devices got a (default) behaviour and a list of exceptions.
+ * define datatypes similar to the legacy kernel code.
+ */
+#define DEVCG_DEV_ALL (BPF_DEVCG_DEV_BLOCK | BPF_DEVCG_DEV_CHAR)
+#define DEVCG_ACC_ALL (BPF_DEVCG_ACC_READ | BPF_DEVCG_ACC_WRITE | BPF_DEVCG_ACC_MKNOD)
+
+enum devcg_behavior {
+	DEVCG_DEFAULT_NONE,
+	DEVCG_DEFAULT_ALLOW,
+	DEVCG_DEFAULT_DENY,
+};
+
+struct dev_exception_item {
+	uint32_t		major, minor;
+	short			type;
+	short			access;
+	struct list_head	list;
+	bool			allow;
+};
+
+/*
+ * add a bunch of default rules
+ */
+static int add_default_exceptions(struct list_head *exceptions)
+{
+	int i, ret = 0;
+	struct dev_exception_item *cur;
+	/* from crun/src/libcrun/cgroup.c */
+	const struct dev_exception_item defrules[] = {
+		/* always allow mknod */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = ~0,  .minor = ~0,  .access = BPF_DEVCG_ACC_MKNOD },
+		{ .allow = true, .type = BPF_DEVCG_DEV_BLOCK, .major = ~0,  .minor = ~0,  .access = BPF_DEVCG_ACC_MKNOD },
+		/* /dev/null */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 1,   .minor = 3,   .access = DEVCG_ACC_ALL },
+		/* /dev/random */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 1,   .minor = 8,   .access = DEVCG_ACC_ALL },
+		/* /dev/full */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 1,   .minor = 7,   .access = DEVCG_ACC_ALL },
+		/* /dev/tty */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 5,   .minor = 0,   .access = DEVCG_ACC_ALL },
+		/* /dev/zero */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 1,   .minor = 5,   .access = DEVCG_ACC_ALL },
+		/* /dev/urandom */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 1,   .minor = 9,   .access = DEVCG_ACC_ALL },
+		/* /dev/console */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 5,   .minor = 1,   .access = DEVCG_ACC_ALL },
+		/* /dev/pts/[0-255] */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 136, .minor = ~0,  .access = DEVCG_ACC_ALL },
+		/* /dev/ptmx */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 5,   .minor = 2,   .access = DEVCG_ACC_ALL },
+		/* /dev/net/tun */
+		{ .allow = true, .type = BPF_DEVCG_DEV_CHAR,  .major = 10,  .minor = 200, .access = DEVCG_ACC_ALL },
+	};
+
+	for (i = 0; i < (sizeof(defrules) / sizeof(struct dev_exception_item)); ++i) {
+		cur = malloc(sizeof(struct dev_exception_item));
+		if (!cur) {
+			ret = ENOMEM;
+			break;
+		}
+		/* add defaults to list in reverse order (last item will be first in list) */
+		memcpy(cur, &defrules[i], sizeof(struct dev_exception_item));
+		list_add(&cur->list, exceptions);
+	}
+
+	return ret;
+}
+
+/*
+ * free all exceptions in the list
+ */
+static void flush_exceptions(struct list_head *freelist)
+{
+	struct dev_exception_item *dl, *dln;
+
+	if (!list_empty(freelist))
+		list_for_each_entry_safe(dl, dln, freelist, list) {
+			list_del(&dl->list);
+			free(dl);
+		}
+}
+
+/*
+ * parse OCI cgroups devices and translate into cgroups-v2 eBPF program
+ */
+int parseOCIlinuxcgroups_devices(struct blob_attr *msg)
+{
+	struct blob_attr *tb[__OCI_LINUX_CGROUPS_DEVICES_MAX];
+	struct blob_attr *cur;
+	int rem, ret = 0;
+	int bpf_type, bpf_access;
+	unsigned char acidx;
+	bool allow = false,
+	     has_access = false,
+	     has_type = false,
+	     has_major = false,
+	     has_minor = false;
+	int total_ins = 0,
+	    cur_ins = 0,
+	    pre_insn_len = sizeof(pre_insn) / sizeof(struct bpf_insn),
+	    next_ins;
+	char *access, *devtype;
+	uint32_t devmajor, devminor;
+	struct dev_exception_item *dl;
+	struct list_head exceptions;
+	enum devcg_behavior behavior = DEVCG_DEFAULT_ALLOW;
+	INIT_LIST_HEAD(&exceptions);
+
+	/* parse according to OCI spec */
+	blobmsg_for_each_attr(cur, msg, rem) {
+		blobmsg_parse(oci_linux_cgroups_devices_policy, __OCI_LINUX_CGROUPS_DEVICES_MAX,
+			      tb, blobmsg_data(cur), blobmsg_len(cur));
+
+		if (!tb[OCI_LINUX_CGROUPS_DEVICES_ALLOW]) {
+			ret = EINVAL;
+			goto out;
+		}
+
+		allow = blobmsg_get_bool(tb[OCI_LINUX_CGROUPS_DEVICES_ALLOW]);
+
+		bpf_access = 0;
+		if (tb[OCI_LINUX_CGROUPS_DEVICES_ACCESS]) {
+			access = blobmsg_get_string(tb[OCI_LINUX_CGROUPS_DEVICES_ACCESS]);
+			if ((strlen(access) > 3) || (strlen(access) == 0)) {
+				ret = EINVAL;
+				goto out;
+			}
+
+			for (acidx = 0; acidx < strlen(access); ++acidx) {
+				switch (access[acidx]) {
+					case 'r':
+						bpf_access |= BPF_DEVCG_ACC_READ;
+						break;
+					case 'w':
+						bpf_access |= BPF_DEVCG_ACC_WRITE;
+						break;
+					case 'm':
+						bpf_access |= BPF_DEVCG_ACC_MKNOD;
+						break;
+					default:
+						ret = EINVAL;
+						goto out;
+				}
+			}
+		}
+
+		if (!bpf_access)
+			bpf_access = DEVCG_ACC_ALL;
+
+		bpf_type = 0;
+		if (tb[OCI_LINUX_CGROUPS_DEVICES_TYPE]) {
+			devtype = blobmsg_get_string(tb[OCI_LINUX_CGROUPS_DEVICES_TYPE]);
+
+			switch (devtype[0]) {
+				case 'c':
+					bpf_type = BPF_DEVCG_DEV_CHAR;
+					break;
+				case 'b':
+					bpf_type = BPF_DEVCG_DEV_BLOCK;
+					break;
+				case 'a':
+					bpf_type = DEVCG_DEV_ALL;
+					break;
+				default:
+					ret = EINVAL;
+					goto out;
+			}
+		}
+
+		if (!bpf_type)
+			bpf_type = DEVCG_DEV_ALL;
+
+		if (tb[OCI_LINUX_CGROUPS_DEVICES_MAJOR])
+			devmajor = blobmsg_cast_u64(tb[OCI_LINUX_CGROUPS_DEVICES_MAJOR]);
+		else
+			devmajor = ~0;
+
+		if (tb[OCI_LINUX_CGROUPS_DEVICES_MINOR])
+			devminor = blobmsg_cast_u64(tb[OCI_LINUX_CGROUPS_DEVICES_MINOR]);
+		else
+			devminor = ~0;
+
+		if (bpf_type == DEVCG_DEV_ALL) {
+			/* wildcard => change default policy and flush all existing rules */
+			flush_exceptions(&exceptions);
+			behavior = allow?DEVCG_DEFAULT_ALLOW:DEVCG_DEFAULT_DENY;
+		} else {
+			/* allocate and populate record for exception */
+			dl = malloc(sizeof(struct dev_exception_item));
+			if (!dl) {
+				ret = ENOSPC;
+				break;
+			}
+			dl->allow = allow;
+			dl->type = bpf_type;
+			dl->access = bpf_access;
+			dl->major = devmajor;
+			dl->minor = devminor;
+
+			/* push to exceptions list, last goes first */
+			list_add(&dl->list, &exceptions);
+		}
+	}
+	if (ret)
+		goto out;
+
+	/* add default rules */
+	ret = add_default_exceptions(&exceptions);
+	if (ret)
+		goto out;
+
+	/* calculate number of instructions to allocate */
+	list_for_each_entry(dl, &exceptions, list) {
+		has_access = dl->access != DEVCG_ACC_ALL;
+		has_type = dl->type != DEVCG_DEV_ALL;
+		has_major = dl->major != ~0;
+		has_minor = dl->minor != ~0;
+
+		total_ins += (has_type ? 1 : 0) + (has_access ? 3 : 0) + (has_major ? 1 : 0) + (has_minor ? 1 : 0) + 2;
+	}
+
+	/* acccount for loader instructions */
+	total_ins += pre_insn_len;
+
+	/* final accept/deny block */
+	total_ins += 2;
+
+	/* allocate memory for eBPF program */
+	program = calloc(total_ins, sizeof(struct bpf_insn));
+	if (!program) {
+		ret = ENOMEM;
+		goto out;
+	}
+
+	/* copy program loader instructions */
+	memcpy(program, &pre_insn, sizeof(pre_insn));
+	cur_ins = pre_insn_len;
+
+	/* generate eBPF program */
+	list_for_each_entry(dl, &exceptions, list) {
+		has_access = dl->access != DEVCG_ACC_ALL;
+		has_type = dl->type != DEVCG_DEV_ALL;
+		has_major = dl->major != ~0;
+		has_minor = dl->minor != ~0;
+
+		next_ins = (has_type ? 1 : 0) + (has_access ? 3 : 0) + (has_major ? 1 : 0) + (has_minor ? 1 : 0) + 1;
+
+		if (has_type) {
+			program[cur_ins++] = BPF_JMP_IMM(BPF_JNE, BPF_REG_2, dl->type, next_ins);
+			--next_ins;
+		}
+
+		if (has_access) {
+			program[cur_ins++] = BPF_MOV32_REG(BPF_REG_1, BPF_REG_3);
+			program[cur_ins++] = BPF_ALU32_IMM(BPF_AND, BPF_REG_1, dl->access);
+			program[cur_ins++] = BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, next_ins - 2);
+			next_ins -= 3;
+		}
+
+		if (has_major) {
+			program[cur_ins++] = BPF_JMP_IMM(BPF_JNE, BPF_REG_4, dl->major, next_ins);
+			--next_ins;
+		}
+
+		if (has_minor) {
+			program[cur_ins++] = BPF_JMP_IMM(BPF_JNE, BPF_REG_5, dl->minor, next_ins);
+			--next_ins;
+		}
+
+		program[cur_ins++] = BPF_MOV64_IMM(BPF_REG_0, dl->allow ? 1 : 0);
+		program[cur_ins++] = BPF_EXIT_INSN();
+	}
+
+	/* default behavior */
+	program[cur_ins++] = BPF_MOV64_IMM(BPF_REG_0, (behavior == DEVCG_DEFAULT_ALLOW)?1:0);
+	program[cur_ins++] = BPF_EXIT_INSN();
+
+	if (debug) {
+		fprintf(stderr, "cgroup devices:\na > devices.%s\n",
+			(behavior == DEVCG_DEFAULT_ALLOW)?"allow":"deny");
+
+		list_for_each_entry(dl, &exceptions, list)
+			fprintf(stderr, "%c %d:%d %s%s%s > devices.%s\n",
+				(dl->type == DEVCG_DEV_ALL)?'a':
+					(dl->type == BPF_DEVCG_DEV_CHAR)?'c':'b',
+				(dl->major == ~0)?-1:dl->major,
+				(dl->minor == ~0)?-1:dl->minor,
+				(dl->access & BPF_DEVCG_ACC_READ)?"r":"",
+				(dl->access & BPF_DEVCG_ACC_WRITE)?"w":"",
+				(dl->access & BPF_DEVCG_ACC_MKNOD)?"m":"",
+				(dl->allow)?"allow":"deny");
+
+		fprintf(stderr, "generated cgroup-devices eBPF program:\n");
+		fprintf(stderr, " [idx]\tcode\t dest\t src\t off\t imm\n");
+		for (cur_ins=0; cur_ins<total_ins; cur_ins++)
+			fprintf(stderr, " [%03d]\t%02hhx\t%3hhu\t%3hhu\t%04hx\t%d\n", cur_ins,
+				program[cur_ins].code,
+				program[cur_ins].dst_reg,
+				program[cur_ins].src_reg,
+				program[cur_ins].off,
+				program[cur_ins].imm);
+	}
+
+	assert(cur_ins == total_ins);
+	bpf_total_insn = total_ins;
+	ret = 0;
+
+out:
+	flush_exceptions(&exceptions);
+	return ret;
+}
+
+/*
+ * attach eBPF program to cgroup
+ */
+int attach_cgroups_ebpf(int cgroup_dirfd) {
+	int prog_fd;
+#if ( __WORDSIZE == 64 )
+	uint64_t program_ptr = (uint64_t)program;
+	uint64_t license_ptr = (uint64_t)license;
+#elif ( __WORDSIZE == 32 )
+	uint32_t program_ptr = (uint32_t)program;
+	uint32_t license_ptr = (uint32_t)license;
+#else
+#error
+#endif
+	union bpf_attr load_attr = {
+		.prog_type = BPF_PROG_TYPE_CGROUP_DEVICE,
+		.license   = license_ptr,
+		.insns     = program_ptr,
+		.insn_cnt  = bpf_total_insn,
+	};
+
+	if (!program)
+		return 0;
+
+	prog_fd = syscall_bpf(BPF_PROG_LOAD, &load_attr, sizeof(load_attr));
+	if (prog_fd < 0)
+		return EIO;
+
+	union bpf_attr attach_attr = {
+		.attach_type = BPF_CGROUP_DEVICE,
+		.target_fd = cgroup_dirfd,
+		.attach_bpf_fd = prog_fd,
+	};
+
+	return syscall_bpf(BPF_PROG_ATTACH, &attach_attr, sizeof (attach_attr));
+}

+ 20 - 0
jail/cgroups-bpf.h

@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _JAIL_CGROUPS_BPF_H
+#define _JAIL_CGROUPS_BPF_H
+
+int parseOCIlinuxcgroups_devices(struct blob_attr *msg);
+int attach_cgroups_ebpf(int cgroup_dirfd);
+
+#endif

+ 26 - 8
jail/cgroups.c

@@ -16,7 +16,6 @@
  * https://github.com/containers/crun/blob/0.14.1/crun.1.md#cgroup-v2
  *
  * ToDo:
- *  - convert cgroup1 devices to eBPF program
  *  - convert cgroup1 net_prio and net_cls to eBPF program
  *  - rdma (anyone?) intelrdt (anyone?)
  */
@@ -43,6 +42,7 @@
 
 #include "log.h"
 #include "cgroups.h"
+#include "cgroups-bpf.h"
 
 #define CGROUP_ROOT "/sys/fs/cgroup/"
 #define CGROUP_IO_WEIGHT_MAX 10000
@@ -197,11 +197,22 @@ void cgroups_apply(pid_t pid)
 		close(fd);
 	}
 
+	int dirfd = open(cgroup_path, O_DIRECTORY);
+	if (dirfd < 0) {
+		ERROR("can't open %s: %m\n", cgroup_path);
+	} else {
+		attach_cgroups_ebpf(dirfd);
+		close(dirfd);
+	}
+
 	snprintf(ent, maxlen, "%s/%s", cgroup_path, "cgroup.procs");
 	fd = open(ent, O_WRONLY);
-	assert(fd != -1);
-	dprintf(fd, "%d", pid);
-	close(fd);
+	if (fd < 0) {
+		ERROR("can't open %s: %m\n", cgroup_path);
+	} else {
+		dprintf(fd, "%d", pid);
+		close(fd);
+	}
 
 	free(ent);
 }
@@ -349,7 +360,8 @@ static int parseOCIlinuxcgroups_legacy_blockio(struct blob_attr *msg)
 	numweightstrs = 0;
 
 	if (weight > -1)
-		asprintf(&weightstrs[numweightstrs++], "default %d", weight);
+		if (asprintf(&weightstrs[numweightstrs++], "default %d", weight) < 0)
+			return ENOMEM;
 
 	blobmsg_for_each_attr(cur, tb[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE], rem) {
 		uint64_t major, minor;
@@ -382,7 +394,8 @@ static int parseOCIlinuxcgroups_legacy_blockio(struct blob_attr *msg)
 		major = blobmsg_cast_u64(tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MAJOR]);
 		minor = blobmsg_cast_u64(tbwd[OCI_LINUX_CGROUPS_BLOCKIO_WEIGHTDEVICE_MINOR]);
 
-		asprintf(&weightstrs[numweightstrs++], "%" PRIu64 ":%" PRIu64 " %u", major, minor, devweight);
+		if (asprintf(&weightstrs[numweightstrs++], "%" PRIu64 ":%" PRIu64 " %u", major, minor, devweight) < 0)
+			return ENOMEM;
 	}
 
 	if (numweightstrs) {
@@ -785,8 +798,7 @@ int parseOCIlinuxcgroups(struct blob_attr *msg)
 
 	blobmsg_parse(oci_linux_cgroups_policy, __OCI_LINUX_CGROUPS_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
 
-	if (tb[OCI_LINUX_CGROUPS_DEVICES] ||
-	    tb[OCI_LINUX_CGROUPS_HUGEPAGELIMITS] ||
+	if (tb[OCI_LINUX_CGROUPS_HUGEPAGELIMITS] ||
 	    tb[OCI_LINUX_CGROUPS_INTELRDT] ||
 	    tb[OCI_LINUX_CGROUPS_NETWORK] ||
 	    tb[OCI_LINUX_CGROUPS_RDMA])
@@ -804,6 +816,12 @@ int parseOCIlinuxcgroups(struct blob_attr *msg)
 			return ret;
 	}
 
+	if (tb[OCI_LINUX_CGROUPS_DEVICES]) {
+		ret = parseOCIlinuxcgroups_devices(tb[OCI_LINUX_CGROUPS_DEVICES]);
+		if (ret)
+			return ret;
+	}
+
 	if (tb[OCI_LINUX_CGROUPS_MEMORY]) {
 		ret = parseOCIlinuxcgroups_legacy_memory(tb[OCI_LINUX_CGROUPS_MEMORY]);
 		if (ret)

+ 6 - 5
jail/jail.c

@@ -514,8 +514,7 @@ static int apply_sysctl(const char *jail_root)
 	if (!opts.sysctl)
 		return 0;
 
-	asprintf(&procdir, "%s/proc", jail_root);
-	if (!procdir)
+	if (asprintf(&procdir, "%s/proc", jail_root) < 0)
 		return ENOMEM;
 
 	mkdir(procdir, 0700);
@@ -525,8 +524,7 @@ static int apply_sysctl(const char *jail_root)
 	cur = opts.sysctl;
 
 	while (*cur) {
-		asprintf(&fname, "%s/sys/%s", procdir, (*cur)->entry);
-		if (!fname)
+		if (asprintf(&fname, "%s/sys/%s", procdir, (*cur)->entry) < 0)
 			return ENOMEM;
 
 		DEBUG("sysctl: writing '%s' to %s\n", (*cur)->value, fname);
@@ -2581,7 +2579,10 @@ int main(int argc, char **argv)
 			ret=-1;
 			goto errout;
 		}
-		asprintf(&jsonfile, "%s/config.json", opts.ocibundle);
+		if (asprintf(&jsonfile, "%s/config.json", opts.ocibundle) < 0) {
+			ret=-ENOMEM;
+			goto errout;
+		}
 		ocires = parseOCI(jsonfile);
 		free(jsonfile);
 		if (ocires) {