The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxc/pull/3195

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
From e4dffa2f972db6f92aa894bc0b103f8cd95c74dd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brau...@ubuntu.com>
Date: Sun, 1 Dec 2019 11:55:19 +0100
Subject: [PATCH 1/5] api_extension: add cgroup2_devices api extension

This will only be defined if liblxc was even compiled with bpf supported.
Support itself will be determined at runtime by liblxc itself.

Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
 src/lxc/api_extensions.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/lxc/api_extensions.h b/src/lxc/api_extensions.h
index a063c0a75c..ba9ff7ff67 100644
--- a/src/lxc/api_extensions.h
+++ b/src/lxc/api_extensions.h
@@ -24,6 +24,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "config.h"
+
 /*
  * api_extensions is the list of all API extensions in the order they were
  * added.
@@ -50,6 +52,9 @@ static char *api_extensions[] = {
        "network_gateway_device_route",
        "network_phys_macvlan_mtu",
        "network_veth_router",
+#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
+       "cgroup2_devices",
+#endif
 };
 
 static size_t nr_api_extensions = sizeof(api_extensions) / 
sizeof(*api_extensions);

From 46383a85a90c15da798c2859b5e2d5b979e9e46b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brau...@ubuntu.com>
Date: Sat, 30 Nov 2019 15:00:29 +0100
Subject: [PATCH 2/5] macro: remove unused macros

Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
 src/lxc/cgroups/cgroup2_devices.c |  2 +-
 src/lxc/macro.h                   | 79 ++++---------------------------
 2 files changed, 9 insertions(+), 72 deletions(-)

diff --git a/src/lxc/cgroups/cgroup2_devices.c 
b/src/lxc/cgroups/cgroup2_devices.c
index 6616a180ed..d44ab32824 100644
--- a/src/lxc/cgroups/cgroup2_devices.c
+++ b/src/lxc/cgroups/cgroup2_devices.c
@@ -359,7 +359,7 @@ int bpf_program_cgroup_attach(struct bpf_program *prog, int 
type,
        if (ret < 0)
                return error_log_errno(errno, "Failed to attach bpf program");
 
-       free_and_replace(prog->attached_path, copy);
+       free_replace_move_ptr(prog->attached_path, copy);
        prog->attached_type = type;
        prog->attached_flags = flags;
 
diff --git a/src/lxc/macro.h b/src/lxc/macro.h
index 6f3379b3c4..d9d91cd527 100644
--- a/src/lxc/macro.h
+++ b/src/lxc/macro.h
@@ -460,6 +460,14 @@ enum {
                -1;                    \
        })
 
+#define free_replace_move_ptr(a, b) \
+       ({                          \
+               free(a);            \
+               (a) = (b);          \
+               (b) = NULL;         \
+               0;                  \
+       })
+
 /* Container's specific file/directory names */
 #define LXC_CONFIG_FNAME      "config"
 #define LXC_PARTIAL_FNAME     "partial"
@@ -467,77 +475,6 @@ enum {
 #define LXC_TIMESTAMP_FNAME   "ts"
 #define LXC_COMMENT_FNAME     "comment"
 
-/* Taken from systemd. */
-#define free_and_replace(a, b) \
-       ({                     \
-               free(a);       \
-               (a) = (b);     \
-               (b) = NULL;    \
-               0;             \
-       })
-
-#define XCONCATENATE(x, y) x##y
-#define CONCATENATE(x, y) XCONCATENATE(x, y)
-#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
-#define UNIQ __COUNTER__
-#undef MIN
-#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
-#define __MIN(aq, a, bq, b)                                                    
\
-       ({                                                                     \
-               const typeof(a) UNIQ_T(A, aq) = (a);                           \
-               const typeof(b) UNIQ_T(B, bq) = (b);                           \
-               UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
-       })
-
-/* Taken from the kernel. */
-
-/*
- * min()/max()/clamp() macros must accomplish three things:
- *
- * - avoid multiple evaluations of the arguments (so side-effects like
- *   "x++" happen only once) when non-constant.
- * - perform strict type-checking (to generate warnings instead of
- *   nasty runtime surprises). See the "unnecessary" pointer comparison
- *   in __typecheck().
- * - retain result as a constant expressions when called with only
- *   constant expressions (to avoid tripping VLA warnings in stack
- *   allocation usage).
- */
-#define __typecheck(x, y) (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
-
-/*
- * This returns a constant expression while determining if an argument is
- * a constant expression, most importantly without evaluating the argument.
- * Glory to Martin Uecker <martin.uec...@med.uni-goettingen.de>
- */
-#define __is_constexpr(x) \
-       (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x)*0l)) : (int *)8)))
-
-#define __no_side_effects(x, y) (__is_constexpr(x) && __is_constexpr(y))
-
-#define __safe_cmp(x, y) (__typecheck(x, y) && __no_side_effects(x, y))
-
-#define __cmp(x, y, op) ((x)op(y) ? (x) : (y))
-
-#define __cmp_once(x, y, unique_x, unique_y, op) \
-       ({                                       \
-               typeof(x) unique_x = (x);        \
-               typeof(y) unique_y = (y);        \
-               __cmp(unique_x, unique_y, op);   \
-       })
-
-#define __careful_cmp(x, y, op)                                  \
-       __builtin_choose_expr(__safe_cmp(x, y), __cmp(x, y, op), \
-                             __cmp_once(x, y, __UNIQUE_ID(__x), \
-                                        __UNIQUE_ID(__y), op))
-
-/**
- * min - return minimum of two values of the same or compatible types
- * @x: first value
- * @y: second value
- */
-#define min(x, y) __careful_cmp(x, y, <)
-
 #define ARRAY_SIZE(x)                                                        \
        (__builtin_choose_expr(!__builtin_types_compatible_p(typeof(x),      \
                                                             typeof(&*(x))), \

From cce5a3d71696b402ae7316f569085c759435690b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brau...@ubuntu.com>
Date: Sat, 30 Nov 2019 15:21:00 +0100
Subject: [PATCH 3/5] cgroups/cgfsng: "atomically" replace bpf device programs

Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
 src/lxc/cgroups/cgfsng.c | 38 +++++++++++++++++++++++++-------------
 src/lxc/cgroups/cgroup.c |  4 ++++
 src/lxc/cgroups/cgroup.h |  7 +++++++
 3 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/src/lxc/cgroups/cgfsng.c b/src/lxc/cgroups/cgfsng.c
index e6f6c0c74f..e2332abd10 100644
--- a/src/lxc/cgroups/cgfsng.c
+++ b/src/lxc/cgroups/cgfsng.c
@@ -2485,7 +2485,7 @@ static bool __cg_legacy_setup_limits(struct cgroup_ops 
*ops,
  * Some of the parsing logic comes from the original cgroup device v1
  * implementation in the kernel.
  */
-static int bpf_device_cgroup_prepare(struct lxc_conf *conf, const char *key,
+static int bpf_device_cgroup_prepare(struct cgroup_ops *ops, const char *key,
                                     const char *val)
 {
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
@@ -2500,8 +2500,8 @@ static int bpf_device_cgroup_prepare(struct lxc_conf 
*conf, const char *key,
        char temp[50];
        struct bpf_program *device;
 
-       if (conf->cgroup2_devices) {
-               device = conf->cgroup2_devices;
+       if (ops->cgroup2_devices) {
+               device = ops->cgroup2_devices;
        } else {
                device = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE);
                if (device && bpf_program_init(device)) {
@@ -2514,7 +2514,7 @@ static int bpf_device_cgroup_prepare(struct lxc_conf 
*conf, const char *key,
                return -1;
        }
 
-       conf->cgroup2_devices = device;
+       ops->cgroup2_devices = device;
 
        if (strcmp("devices.allow", key) == 0)
                device_item.allow = 1;
@@ -2637,7 +2637,7 @@ static bool __cg_unified_setup_limits(struct cgroup_ops 
*ops,
                struct lxc_cgroup *cg = iterator->elem;
 
                if (strncmp("devices", cg->subsystem, 7) == 0) {
-                       ret = bpf_device_cgroup_prepare(conf, cg->subsystem,
+                       ret = bpf_device_cgroup_prepare(ops, cg->subsystem,
                                                        cg->value);
                } else {
                        fullpath = must_make_path(h->container_full_path,
@@ -2662,25 +2662,37 @@ __cgfsng_ops bool cgfsng_devices_activate(struct 
cgroup_ops *ops,
 {
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
        int ret;
+       struct lxc_conf *conf;
        struct hierarchy *h = ops->unified;
-       struct bpf_program *device = handler->conf->cgroup2_devices;
+       struct bpf_program *devices_new = ops->cgroup2_devices;
 
        if (!h)
                return false;
 
-       if (!device)
+       if (!devices_new)
                return true;
 
-       ret = bpf_program_finalize(device);
+       ret = bpf_program_finalize(devices_new);
        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;
+       ret = bpf_program_cgroup_attach(devices_new, BPF_CGROUP_DEVICE,
+                                       h->container_full_path,
+                                       BPF_F_ALLOW_MULTI);
+       if (ret)
+               return false;
+
+       /* Replace old bpf program. */
+       conf = handler->conf;
+       if (conf->cgroup2_devices) {
+               struct bpf_program *old_devices;
+
+               old_devices = move_ptr(conf->cgroup2_devices);
+               conf->cgroup2_devices = move_ptr(ops->cgroup2_devices);
+               bpf_program_free(old_devices);
+       }
 #endif
+       return true;
 }
 
 __cgfsng_ops static bool cgfsng_setup_limits(struct cgroup_ops *ops,
diff --git a/src/lxc/cgroups/cgroup.c b/src/lxc/cgroups/cgroup.c
index 5443bd9611..b6244241fe 100644
--- a/src/lxc/cgroups/cgroup.c
+++ b/src/lxc/cgroups/cgroup.c
@@ -30,6 +30,7 @@
 #include <unistd.h>
 
 #include "cgroup.h"
+#include "cgroup2_devices.h"
 #include "conf.h"
 #include "config.h"
 #include "initutils.h"
@@ -86,6 +87,9 @@ void cgroup_exit(struct cgroup_ops *ops)
        free(ops->cgroup_pattern);
        free(ops->container_cgroup);
 
+       if (ops->cgroup2_devices)
+               bpf_program_free(ops->cgroup2_devices);
+
        for (it = ops->hierarchies; it && *it; it++) {
                char **p;
 
diff --git a/src/lxc/cgroups/cgroup.h b/src/lxc/cgroups/cgroup.h
index bb6c91cce8..47a1550079 100644
--- a/src/lxc/cgroups/cgroup.h
+++ b/src/lxc/cgroups/cgroup.h
@@ -118,6 +118,13 @@ struct cgroup_ops {
        /* Pointer to the unified hierarchy. Do not free! */
        struct hierarchy *unified;
 
+       /*
+        * @cgroup2_devices
+        * bpf program to limit device access; only applicable to privileged
+        * containers.
+        */
+       struct bpf_program *cgroup2_devices;
+
        /*
         * @cgroup_layout
         * - What cgroup layout the container is running with.

From 4bfb655ea8d9f824571b970927c4eefb9c97f902 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brau...@ubuntu.com>
Date: Sat, 30 Nov 2019 16:05:44 +0100
Subject: [PATCH 4/5] conf: record cgroup2 devices in parsed format

Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
 src/lxc/cgroups/cgfsng.c          | 62 ++++++++++++++++++++++++++-----
 src/lxc/cgroups/cgroup2_devices.c |  2 +-
 src/lxc/conf.c                    | 13 +++++++
 src/lxc/conf.h                    | 10 +++++
 src/lxc/log.h                     |  6 +++
 src/lxc/memory_utils.h            |  8 ++++
 6 files changed, 91 insertions(+), 10 deletions(-)

diff --git a/src/lxc/cgroups/cgfsng.c b/src/lxc/cgroups/cgfsng.c
index e2332abd10..4b4a6a92a1 100644
--- a/src/lxc/cgroups/cgfsng.c
+++ b/src/lxc/cgroups/cgfsng.c
@@ -2481,21 +2481,60 @@ static bool __cg_legacy_setup_limits(struct cgroup_ops 
*ops,
        return ret;
 }
 
+static int bpf_list_add_device(struct lxc_conf *conf, struct device_item 
*device)
+{
+       __do_free struct lxc_list *list_elem = NULL;
+       __do_free struct device_item *new_device = NULL;
+       struct lxc_list *it;
+
+       lxc_list_for_each(it, &conf->devices) {
+               struct device_item *cur = it->elem;
+
+               if (cur->type != device->type)
+                       continue;
+               if (cur->major != device->major)
+                       continue;
+               if (cur->minor != device->minor)
+                       continue;
+               if (strcmp(cur->access, device->access))
+                       continue;
+
+               /*
+                * The rule is switched from allow to deny or vica versa so
+                * don't bother allocating just flip the existing one.
+                */
+               if (cur->allow != device->allow) {
+                       cur->allow = device->allow;
+                       return log_trace(0, "Reusing existing rule of bpf 
device program: type %c, major %d, minor %d, access %s, allow %d",
+                                        cur->type, cur->major, cur->minor,
+                                        cur->access, cur->allow);
+               }
+       }
+
+       list_elem = malloc(sizeof(*list_elem));
+       if (!list_elem)
+               return error_log_errno(ENOMEM, "Failed to allocate new device 
list");
+
+       new_device = memdup(device, sizeof(struct device_item));
+       if (!new_device)
+               return error_log_errno(ENOMEM, "Failed to allocate new device 
item");
+
+       lxc_list_add_elem(list_elem, move_ptr(new_device));
+       lxc_list_add_tail(&conf->devices, move_ptr(list_elem));
+
+       return 0;
+}
+
 /*
  * Some of the parsing logic comes from the original cgroup device v1
  * implementation in the kernel.
  */
-static int bpf_device_cgroup_prepare(struct cgroup_ops *ops, const char *key,
+static int bpf_device_cgroup_prepare(struct cgroup_ops *ops,
+                                    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};
+       struct device_item device_item = {0};
        int count, ret;
        char temp[50];
        struct bpf_program *device;
@@ -2614,6 +2653,11 @@ static int bpf_device_cgroup_prepare(struct cgroup_ops 
*ops, const char *key,
                      device_item.type, device_item.major, device_item.minor,
                      device_item.access, device_item.allow);
        }
+
+       ret = bpf_list_add_device(conf, &device_item);
+       if (ret)
+               return -1;
+
 #endif
        return 0;
 }
@@ -2637,7 +2681,7 @@ static bool __cg_unified_setup_limits(struct cgroup_ops 
*ops,
                struct lxc_cgroup *cg = iterator->elem;
 
                if (strncmp("devices", cg->subsystem, 7) == 0) {
-                       ret = bpf_device_cgroup_prepare(ops, cg->subsystem,
+                       ret = bpf_device_cgroup_prepare(ops, conf, 
cg->subsystem,
                                                        cg->value);
                } else {
                        fullpath = must_make_path(h->container_full_path,
diff --git a/src/lxc/cgroups/cgroup2_devices.c 
b/src/lxc/cgroups/cgroup2_devices.c
index d44ab32824..762fd14f6d 100644
--- a/src/lxc/cgroups/cgroup2_devices.c
+++ b/src/lxc/cgroups/cgroup2_devices.c
@@ -7,7 +7,6 @@
 #endif
 #include <errno.h>
 #include <fcntl.h>
-#include <linux/filter.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -24,6 +23,7 @@
 
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
 #include <linux/bpf.h>
+#include <linux/filter.h>
 
 lxc_log_define(cgroup2_devices, cgroup);
 
diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index c03b663835..1cb074b513 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -2736,6 +2736,7 @@ struct lxc_conf *lxc_conf_init(void)
        new->logfd = -1;
        lxc_list_init(&new->cgroup);
        lxc_list_init(&new->cgroup2);
+       lxc_list_init(&new->devices);
        lxc_list_init(&new->network);
        lxc_list_init(&new->mount_list);
        lxc_list_init(&new->caps);
@@ -3883,6 +3884,17 @@ int lxc_clear_cgroups(struct lxc_conf *c, const char 
*key, int version)
        return 0;
 }
 
+static void lxc_clear_devices(struct lxc_conf *conf)
+{
+       struct lxc_list *list = &conf->devices;
+       struct lxc_list *it, *next;
+
+       lxc_list_for_each_safe(it, list, next) {
+               lxc_list_del(it);
+               free(it);
+       }
+}
+
 int lxc_clear_limits(struct lxc_conf *c, const char *key)
 {
        struct lxc_list *it, *next;
@@ -4119,6 +4131,7 @@ void lxc_conf_free(struct lxc_conf *conf)
        lxc_clear_config_keepcaps(conf);
        lxc_clear_cgroups(conf, "lxc.cgroup", CGROUP_SUPER_MAGIC);
        lxc_clear_cgroups(conf, "lxc.cgroup2", CGROUP2_SUPER_MAGIC);
+       lxc_clear_devices(conf);
        lxc_clear_cgroup2_devices(conf);
        lxc_clear_hooks(conf, "lxc.hook");
        lxc_clear_mount_entries(conf);
diff --git a/src/lxc/conf.h b/src/lxc/conf.h
index 741ac4f096..44d7934fe4 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -230,6 +230,14 @@ struct lxc_state_client {
        lxc_state_t states[MAX_STATE];
 };
 
+struct device_item {
+       char type;
+       int major;
+       int minor;
+       char access[4];
+       int allow;
+};
+
 struct lxc_conf {
        /* Pointer to the name of the container. Do not free! */
        const char *name;
@@ -242,6 +250,8 @@ struct lxc_conf {
                struct lxc_list cgroup;
                struct lxc_list cgroup2;
                struct bpf_program *cgroup2_devices;
+               /* This should be reimplemented as a hashmap. */
+               struct lxc_list devices;
        };
 
        struct {
diff --git a/src/lxc/log.h b/src/lxc/log.h
index 3c5be95c37..8e45919643 100644
--- a/src/lxc/log.h
+++ b/src/lxc/log.h
@@ -512,6 +512,12 @@ ATTR_UNUSED static inline void LXC_##LEVEL(struct 
lxc_log_locinfo* locinfo,        \
                -1;                              \
        })
 
+#define log_trace(__ret__, format, ...)              \
+       ({                                    \
+               TRACE(format, ##__VA_ARGS__); \
+               __ret__;                      \
+       })
+
 extern int lxc_log_fd;
 
 extern int lxc_log_syslog(int facility);
diff --git a/src/lxc/memory_utils.h b/src/lxc/memory_utils.h
index b5220c1899..660e147b7d 100644
--- a/src/lxc/memory_utils.h
+++ b/src/lxc/memory_utils.h
@@ -71,4 +71,12 @@ static inline void __auto_close__(int *fd)
 #define __do_fclose __attribute__((__cleanup__(__auto_fclose__)))
 #define __do_closedir __attribute__((__cleanup__(__auto_closedir__)))
 
+static inline void *memdup(const void *data, size_t len)
+{
+       void *copy = NULL;
+
+       copy = len ? malloc(len) : NULL;
+       return copy ? memcpy(copy, data, len) : NULL;
+}
+
 #endif /* __LXC_MEMORY_UTILS_H */

From 9846f55fd5ae37c2d941b85a1a4a875d05e9e05e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brau...@ubuntu.com>
Date: Sat, 30 Nov 2019 19:33:19 +0100
Subject: [PATCH 5/5] cgroups/cgfsng: implement cgroup2 device controller live
 update

Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
 src/lxc/cgroups/cgfsng.c          | 368 +++++++++++++++---------------
 src/lxc/cgroups/cgroup.h          |   1 +
 src/lxc/cgroups/cgroup2_devices.c | 119 +++++++++-
 src/lxc/cgroups/cgroup2_devices.h |  41 +++-
 src/lxc/commands.c                | 171 +++++++++++---
 src/lxc/commands.h                |   5 +
 src/lxc/conf.h                    |   5 +
 src/lxc/log.h                     |   7 +
 8 files changed, 482 insertions(+), 235 deletions(-)

diff --git a/src/lxc/cgroups/cgfsng.c b/src/lxc/cgroups/cgfsng.c
index 4b4a6a92a1..bfe701c568 100644
--- a/src/lxc/cgroups/cgfsng.c
+++ b/src/lxc/cgroups/cgfsng.c
@@ -176,6 +176,11 @@ static void must_append_controller(char **klist, char 
**nlist, char ***clist,
        (*clist)[newentry] = copy;
 }
 
+static inline bool pure_unified_layout(const struct cgroup_ops *ops)
+{
+       return ops->cgroup_layout == CGROUP_LAYOUT_UNIFIED;
+}
+
 /* Given a handler's cgroup data, return the struct hierarchy for the 
controller
  * @c, or NULL if there is none.
  */
@@ -196,8 +201,12 @@ struct hierarchy *get_hierarchy(struct cgroup_ops *ops, 
const char *controller)
                        if (ops->hierarchies[i]->controllers &&
                            !ops->hierarchies[i]->controllers[0])
                                return ops->hierarchies[i];
-
                        continue;
+               } else if (pure_unified_layout(ops) &&
+                          strcmp(controller, "devices") == 0) {
+                       if (ops->unified->bpf_device_controller)
+                               return ops->unified;
+                       break;
                }
 
                if (string_in_list(ops->hierarchies[i]->controllers, 
controller))
@@ -778,9 +787,9 @@ static char **cg_unified_make_empty_controller(void)
 static char **cg_unified_get_controllers(const char *file)
 {
        __do_free char *buf = NULL;
-       char *tok;
        char *sep = " \t\n";
        char **aret = NULL;
+       char *tok;
 
        buf = read_file(file);
        if (!buf)
@@ -2278,12 +2287,116 @@ __cgfsng_ops static int cgfsng_get(struct cgroup_ops 
*ops, const char *filename,
        return ret;
 }
 
+static int device_cgroup_rule_parse(struct device_item *device, const char 
*key,
+                                   const char *val)
+{
+       int blacklist_or_err = 0;
+       int count, ret;
+       char temp[50];
+
+       if (strcmp("devices.allow", key) == 0)
+               device->allow = 1;
+       else
+               device->allow = 0;
+
+       if (strcmp(val, "a") == 0) {
+               /* global rule */
+               device->type = 'a';
+               device->major = -1;
+               device->minor = -1;
+               device->global_rule = device->allow;
+               device->allow = -1;
+               return 0;
+       } else {
+               device->global_rule = -1;
+       }
+
+       switch (*val) {
+       case 'a':
+               __fallthrough;
+       case 'b':
+               __fallthrough;
+       case 'c':
+               device->type = *val;
+               break;
+       default:
+               return -1;
+       }
+
+       val++;
+       if (!isspace(*val))
+               return -1;
+       val++;
+       if (*val == '*') {
+               device->major = -1;
+               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->major);
+               if (ret)
+                       return -1;
+       } else {
+               return -1;
+       }
+       if (*val != ':')
+               return -1;
+       val++;
+
+       /* read minor */
+       if (*val == '*') {
+               device->minor = -1;
+               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->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->access[count] = *val;
+                       break;
+               case 'w':
+                       device->access[count] = *val;
+                       break;
+               case 'm':
+                       device->access[count] = *val;
+                       break;
+               case '\n':
+               case '\0':
+                       count = 3;
+                       break;
+               default:
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
 /* Called externally (i.e. from 'lxc-cgroup') to set new cgroup limits.  Here 
we
  * don't have a cgroup_data set up, so we ask the running container through the
  * commands API for the cgroup path.
  */
 __cgfsng_ops static int cgfsng_set(struct cgroup_ops *ops,
-                                    const char *filename, const char *value,
+                                    const char *key, const char *value,
                                     const char *name, const char *lxcpath)
 {
        __do_free char *path = NULL;
@@ -2292,11 +2405,26 @@ __cgfsng_ops static int cgfsng_set(struct cgroup_ops 
*ops,
        struct hierarchy *h;
        int ret = -1;
 
-       controller = must_copy_string(filename);
+       controller = must_copy_string(key);
        p = strchr(controller, '.');
        if (p)
                *p = '\0';
 
+       if (pure_unified_layout(ops) && strcmp(controller, "devices") == 0) {
+               struct device_item device = {0};
+
+               ret = device_cgroup_rule_parse(&device, key, value);
+               if (ret < 0)
+                       return error_log_errno(EINVAL, "Failed to parse device 
string %s=%s",
+                                              key, value);
+
+               ret = lxc_cmd_add_bpf_device_cgroup(name, lxcpath, &device);
+               if (ret < 0)
+                       return -1;
+
+               return 0;
+       }
+
        path = lxc_cmd_get_cgroup_path(name, lxcpath, controller);
        /* not running */
        if (!path)
@@ -2306,7 +2434,7 @@ __cgfsng_ops static int cgfsng_set(struct cgroup_ops *ops,
        if (h) {
                __do_free char *fullpath = NULL;
 
-               fullpath = build_full_cgpath_from_monitorpath(h, path, 
filename);
+               fullpath = build_full_cgpath_from_monitorpath(h, path, key);
                ret = lxc_write_to_file(fullpath, value, strlen(value), false, 
0666);
        }
 
@@ -2481,50 +2609,6 @@ static bool __cg_legacy_setup_limits(struct cgroup_ops 
*ops,
        return ret;
 }
 
-static int bpf_list_add_device(struct lxc_conf *conf, struct device_item 
*device)
-{
-       __do_free struct lxc_list *list_elem = NULL;
-       __do_free struct device_item *new_device = NULL;
-       struct lxc_list *it;
-
-       lxc_list_for_each(it, &conf->devices) {
-               struct device_item *cur = it->elem;
-
-               if (cur->type != device->type)
-                       continue;
-               if (cur->major != device->major)
-                       continue;
-               if (cur->minor != device->minor)
-                       continue;
-               if (strcmp(cur->access, device->access))
-                       continue;
-
-               /*
-                * The rule is switched from allow to deny or vica versa so
-                * don't bother allocating just flip the existing one.
-                */
-               if (cur->allow != device->allow) {
-                       cur->allow = device->allow;
-                       return log_trace(0, "Reusing existing rule of bpf 
device program: type %c, major %d, minor %d, access %s, allow %d",
-                                        cur->type, cur->major, cur->minor,
-                                        cur->access, cur->allow);
-               }
-       }
-
-       list_elem = malloc(sizeof(*list_elem));
-       if (!list_elem)
-               return error_log_errno(ENOMEM, "Failed to allocate new device 
list");
-
-       new_device = memdup(device, sizeof(struct device_item));
-       if (!new_device)
-               return error_log_errno(ENOMEM, "Failed to allocate new device 
item");
-
-       lxc_list_add_elem(list_elem, move_ptr(new_device));
-       lxc_list_add_tail(&conf->devices, move_ptr(list_elem));
-
-       return 0;
-}
-
 /*
  * Some of the parsing logic comes from the original cgroup device v1
  * implementation in the kernel.
@@ -2535,129 +2619,17 @@ static int bpf_device_cgroup_prepare(struct cgroup_ops 
*ops,
 {
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
        struct device_item device_item = {0};
-       int count, ret;
-       char temp[50];
-       struct bpf_program *device;
-
-       if (ops->cgroup2_devices) {
-               device = ops->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;
-       }
-
-       ops->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;
-               }
-       }
+       int ret;
 
-       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);
-       }
+       ret = device_cgroup_rule_parse(&device_item, key, val);
+       if (ret < 0)
+               return error_log_errno(EINVAL,
+                                      "Failed to parse device string %s=%s",
+                                      key, val);
 
        ret = bpf_list_add_device(conf, &device_item);
-       if (ret)
+       if (ret < 0)
                return -1;
-
 #endif
        return 0;
 }
@@ -2705,36 +2677,57 @@ __cgfsng_ops bool cgfsng_devices_activate(struct 
cgroup_ops *ops,
                                          struct lxc_handler *handler)
 {
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
+       __do_bpf_program_free struct bpf_program *devices = NULL;
+       struct lxc_conf *conf = handler->conf;
+       struct hierarchy *unified = ops->unified;
        int ret;
-       struct lxc_conf *conf;
-       struct hierarchy *h = ops->unified;
-       struct bpf_program *devices_new = ops->cgroup2_devices;
+       struct lxc_list *it;
+       struct bpf_program *devices_old;
 
-       if (!h)
+       if (!unified)
                return false;
 
-       if (!devices_new)
+       if (lxc_list_empty(&conf->devices))
                return true;
 
-       ret = bpf_program_finalize(devices_new);
+       devices = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE);
+       if (!devices)
+               return log_error(false, ENOMEM,
+                                "Failed to create new bpf program");
+
+       ret = bpf_program_init(devices);
        if (ret)
-               return false;
+               return log_error(false, ENOMEM,
+                                "Failed to initialize bpf program");
+
+       lxc_list_for_each(it, &conf->devices) {
+               struct device_item *cur = it->elem;
+
+               ret = bpf_program_append_device(devices, cur);
+               if (ret)
+                       return log_error(false,
+                                        ENOMEM, "Failed to add new rule to bpf 
device program: type %c, major %d, minor %d, access %s, allow %d, global_rule 
%d",
+                                        cur->type, cur->major, cur->minor,
+                                        cur->access, cur->allow, 
cur->global_rule);
+               TRACE("Added rule to bpf device program: type %c, major %d, 
minor %d, access %s, allow %d, global_rule %d",
+                     cur->type, cur->major, cur->minor, cur->access,
+                     cur->allow, cur->global_rule);
+       }
+
+       ret = bpf_program_finalize(devices);
+       if (ret)
+               return log_error(false, ENOMEM, "Failed to finalize bpf 
program");
 
-       ret = bpf_program_cgroup_attach(devices_new, BPF_CGROUP_DEVICE,
-                                       h->container_full_path,
+       ret = bpf_program_cgroup_attach(devices, BPF_CGROUP_DEVICE,
+                                       unified->container_full_path,
                                        BPF_F_ALLOW_MULTI);
        if (ret)
-               return false;
+               return log_error(false, ENOMEM, "Failed to attach bpf program");
 
        /* Replace old bpf program. */
-       conf = handler->conf;
-       if (conf->cgroup2_devices) {
-               struct bpf_program *old_devices;
-
-               old_devices = move_ptr(conf->cgroup2_devices);
-               conf->cgroup2_devices = move_ptr(ops->cgroup2_devices);
-               bpf_program_free(old_devices);
-       }
+       devices_old = move_ptr(conf->cgroup2_devices);
+       conf->cgroup2_devices = move_ptr(devices);
+       devices = move_ptr(devices_old);
 #endif
        return true;
 }
@@ -3045,6 +3038,9 @@ static int cg_unified_init(struct cgroup_ops *ops, bool 
relative,
        if (!unprivileged)
                cg_unified_delegate(&new->cgroup2_chown);
 
+       if (bpf_devices_cgroup_supported())
+               new->bpf_device_controller = 1;
+
        ops->cgroup_layout = CGROUP_LAYOUT_UNIFIED;
        ops->unified = new;
        return CGROUP2_SUPER_MAGIC;
diff --git a/src/lxc/cgroups/cgroup.h b/src/lxc/cgroups/cgroup.h
index 47a1550079..a3eb46b9d6 100644
--- a/src/lxc/cgroups/cgroup.h
+++ b/src/lxc/cgroups/cgroup.h
@@ -92,6 +92,7 @@ struct hierarchy {
        char *container_full_path;
        char *monitor_full_path;
        int version;
+       int bpf_device_controller:1;
 };
 
 struct cgroup_ops {
diff --git a/src/lxc/cgroups/cgroup2_devices.c 
b/src/lxc/cgroups/cgroup2_devices.c
index 762fd14f6d..52c1860f51 100644
--- a/src/lxc/cgroups/cgroup2_devices.c
+++ b/src/lxc/cgroups/cgroup2_devices.c
@@ -195,33 +195,38 @@ int bpf_program_init(struct bpf_program *prog)
        return bpf_program_add_instructions(prog, pre_insn, 
ARRAY_SIZE(pre_insn));
 }
 
-int bpf_program_append_device(struct bpf_program *prog, char type, int major,
-                             int minor, const char *access, int allow)
+int bpf_program_append_device(struct bpf_program *prog, struct device_item 
*device)
 {
        int ret;
        int jump_nr = 1;
        struct bpf_insn bpf_access_decision[] = {
-           BPF_MOV64_IMM(BPF_REG_0, allow),
+           BPF_MOV64_IMM(BPF_REG_0, device->allow),
            BPF_EXIT_INSN(),
        };
        int access_mask;
        int device_type;
 
-       device_type = bpf_device_type(type);
+       /* This is a global rule so no need to append anything. */
+       if (device->global_rule >= 0) {
+               prog->blacklist = device->global_rule;
+               return 0;
+       }
+
+       device_type = bpf_device_type(device->type);
        if (device_type < 0)
-               return error_log_errno(EINVAL, "Invalid bpf cgroup device type 
%c", type);
+               return error_log_errno(EINVAL, "Invalid bpf cgroup device type 
%c", device->type);
 
        if (device_type > 0)
                jump_nr++;
 
-       access_mask = bpf_access_mask(access);
+       access_mask = bpf_access_mask(device->access);
        if (!bpf_device_all_access(access_mask))
                jump_nr += 3;
 
-       if (major >= 0)
+       if (device->major != -1)
                jump_nr++;
 
-       if (minor >= 0)
+       if (device->minor != -1)
                jump_nr++;
 
        if (device_type > 0) {
@@ -247,9 +252,9 @@ int bpf_program_append_device(struct bpf_program *prog, 
char type, int major,
                        return error_log_errno(errno, "Failed to add 
instructions to bpf cgroup program");
        }
 
-       if (major >= 0) {
+       if (device->major >= 0) {
                struct bpf_insn ins[] = {
-                   BPF_JMP_IMM(BPF_JNE, BPF_REG_4, major, jump_nr--),
+                   BPF_JMP_IMM(BPF_JNE, BPF_REG_4, device->major, jump_nr--),
                };
 
                ret = bpf_program_add_instructions(prog, ins, ARRAY_SIZE(ins));
@@ -257,9 +262,9 @@ int bpf_program_append_device(struct bpf_program *prog, 
char type, int major,
                        return error_log_errno(errno, "Failed to add 
instructions to bpf cgroup program");
        }
 
-       if (minor >= 0) {
+       if (device->minor >= 0) {
                struct bpf_insn ins[] = {
-                   BPF_JMP_IMM(BPF_JNE, BPF_REG_5, minor, jump_nr--),
+                   BPF_JMP_IMM(BPF_JNE, BPF_REG_5, device->minor, jump_nr--),
                };
 
                ret = bpf_program_add_instructions(prog, ins, ARRAY_SIZE(ins));
@@ -411,4 +416,94 @@ void lxc_clear_cgroup2_devices(struct lxc_conf *conf)
                (void)bpf_program_free(conf->cgroup2_devices);
        }
 }
+
+int bpf_list_add_device(struct lxc_conf *conf, struct device_item *device)
+{
+       __do_free struct lxc_list *list_elem = NULL;
+       __do_free struct device_item *new_device = NULL;
+       struct lxc_list *it;
+
+       lxc_list_for_each(it, &conf->devices) {
+               struct device_item *cur = it->elem;
+
+               if (cur->global_rule != -1 && device->global_rule != -1) {
+                       TRACE("Switched from %s to %s",
+                             cur->global_rule == 0 ? "whitelist" : "blacklist",
+                             device->global_rule == 0 ? "whitelist"
+                                                      : "blacklist");
+                       cur->global_rule = device->global_rule;
+                       return 1;
+               }
+
+               if (cur->type != device->type)
+                       continue;
+               if (cur->major != device->major)
+                       continue;
+               if (cur->minor != device->minor)
+                       continue;
+               if (strcmp(cur->access, device->access))
+                       continue;
+
+               /*
+                * The rule is switched from allow to deny or vica versa so
+                * don't bother allocating just flip the existing one.
+                */
+               if (cur->allow != device->allow) {
+                       cur->allow = device->allow;
+                       return log_trace(0, "Switched existing rule of bpf 
device program: type %c, major %d, minor %d, access %s, allow %d, global_rule 
%d",
+                                        cur->type, cur->major, cur->minor,
+                                        cur->access, cur->allow,
+                                        cur->global_rule);
+               }
+
+               return log_trace(1, "Reusing existing rule of bpf device 
program: type %c, major %d, minor %d, access %s, allow %d, global_rule %d",
+                                cur->type, cur->major, cur->minor, cur->access,
+                                cur->allow, cur->global_rule);
+       }
+
+       list_elem = malloc(sizeof(*list_elem));
+       if (!list_elem)
+               return error_log_errno(ENOMEM, "Failed to allocate new device 
list");
+
+       new_device = memdup(device, sizeof(struct device_item));
+       if (!new_device)
+               return error_log_errno(ENOMEM, "Failed to allocate new device 
item");
+
+       lxc_list_add_elem(list_elem, move_ptr(new_device));
+       lxc_list_add_tail(&conf->devices, move_ptr(list_elem));
+
+       return 0;
+}
+
+bool bpf_devices_cgroup_supported(void)
+{
+       const struct bpf_insn dummy[] = {
+           BPF_MOV64_IMM(BPF_REG_0, 1),
+           BPF_EXIT_INSN(),
+       };
+
+       __do_bpf_program_free struct bpf_program *prog = NULL;
+       int ret;
+
+       if (geteuid() != 0)
+               return log_error(false, EINVAL,
+                                "The bpf device cgroup requires real root");
+
+       prog = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE);
+       if (prog < 0)
+               return log_error(false,
+                                errno, "Failed to allocate new bpf device 
cgroup program");
+
+       ret = bpf_program_add_instructions(prog, dummy, ARRAY_SIZE(dummy));
+       if (ret < 0)
+               return log_error(false,
+                                errno, "Failed to add new instructions to bpf 
device cgroup program");
+
+       ret = bpf_program_load_kernel(prog, NULL, 0);
+       if (ret < 0)
+               return log_error(false,
+                                errno, "Failed to load new bpf device cgroup 
program");
+
+       return log_trace(true, "The bpf device cgroup is supported");
+}
 #endif
diff --git a/src/lxc/cgroups/cgroup2_devices.h 
b/src/lxc/cgroups/cgroup2_devices.h
index afcc6b9376..09596fb356 100644
--- a/src/lxc/cgroups/cgroup2_devices.h
+++ b/src/lxc/cgroups/cgroup2_devices.h
@@ -5,6 +5,7 @@
 #ifndef __LXC_CGROUP2_DEVICES_H
 #define __LXC_CGROUP2_DEVICES_H
 
+#include <errno.h>
 #include <fcntl.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -79,53 +80,61 @@ struct bpf_program {
 #ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
 struct bpf_program *bpf_program_new(uint32_t prog_type);
 int bpf_program_init(struct bpf_program *prog);
-int bpf_program_append_device(struct bpf_program *prog, char type, int major,
-                             int minor, const char *access, int allow);
+int bpf_program_append_device(struct bpf_program *prog,
+                             struct device_item *device);
 int bpf_program_finalize(struct bpf_program *prog);
 int bpf_program_cgroup_attach(struct bpf_program *prog, int type,
                              const char *path, uint32_t flags);
 int bpf_program_cgroup_detach(struct bpf_program *prog);
 void bpf_program_free(struct bpf_program *prog);
 void lxc_clear_cgroup2_devices(struct lxc_conf *conf);
-static inline void __do_bpf_program_free(struct bpf_program **prog)
+bool bpf_devices_cgroup_supported(void);
+static inline void __auto_bpf_program_free__(struct bpf_program **prog)
 {
        if (*prog) {
                bpf_program_free(*prog);
                *prog = NULL;
        }
 }
+int bpf_list_add_device(struct lxc_conf *conf, struct device_item *device);
 #else
 static inline struct bpf_program *bpf_program_new(uint32_t prog_type)
 {
+       errno = ENOSYS;
        return NULL;
 }
 
 static inline int bpf_program_init(struct bpf_program *prog)
 {
-       return -ENOSYS;
+       errno = ENOSYS;
+       return -1;
 }
 
 static inline int bpf_program_append_device(struct bpf_program *prog, char 
type,
                                            int major, int minor,
                                            const char *access, int allow)
 {
-       return -ENOSYS;
+       errno = ENOSYS;
+       return -1;
 }
 
 static inline int bpf_program_finalize(struct bpf_program *prog)
 {
-       return -ENOSYS;
+       errno = ENOSYS;
+       return -1;
 }
 
 static inline int bpf_program_cgroup_attach(struct bpf_program *prog, int type,
                                            const char *path, uint32_t flags)
 {
-       return -ENOSYS;
+       errno = ENOSYS;
+       return -1;
 }
 
 static inline int bpf_program_cgroup_detach(struct bpf_program *prog)
 {
-       return -ENOSYS;
+       errno = ENOSYS;
+       return -1;
 }
 
 static inline void bpf_program_free(struct bpf_program *prog)
@@ -136,9 +145,23 @@ static inline void lxc_clear_cgroup2_devices(struct 
lxc_conf *conf)
 {
 }
 
-static inline void __do_bpf_program_free(struct bpf_program **prog)
+static inline bool bpf_devices_cgroup_supported(void)
+{
+       return false;
+}
+
+static inline void __auto_bpf_program_free__(struct bpf_program **prog)
+{
+}
+
+int bpf_list_add_device(struct lxc_conf *conf, struct device_item *device)
 {
+       errno = ENOSYS;
+       return -1;
 }
 #endif
 
+#define __do_bpf_program_free \
+       __attribute__((__cleanup__(__auto_bpf_program_free__)))
+
 #endif /* __LXC_CGROUP2_DEVICES_H */
diff --git a/src/lxc/commands.c b/src/lxc/commands.c
index 90e3c5863d..f4920c7846 100644
--- a/src/lxc/commands.c
+++ b/src/lxc/commands.c
@@ -39,6 +39,7 @@
 
 #include "af_unix.h"
 #include "cgroup.h"
+#include "cgroups/cgroup2_devices.h"
 #include "commands.h"
 #include "commands_utils.h"
 #include "conf.h"
@@ -85,20 +86,21 @@ lxc_log_define(commands, lxc);
 static const char *lxc_cmd_str(lxc_cmd_t cmd)
 {
        static const char *const cmdname[LXC_CMD_MAX] = {
-               [LXC_CMD_CONSOLE]             = "console",
-               [LXC_CMD_TERMINAL_WINCH]      = "terminal_winch",
-               [LXC_CMD_STOP]                = "stop",
-               [LXC_CMD_GET_STATE]           = "get_state",
-               [LXC_CMD_GET_INIT_PID]        = "get_init_pid",
-               [LXC_CMD_GET_CLONE_FLAGS]     = "get_clone_flags",
-               [LXC_CMD_GET_CGROUP]          = "get_cgroup",
-               [LXC_CMD_GET_CONFIG_ITEM]     = "get_config_item",
-               [LXC_CMD_GET_NAME]            = "get_name",
-               [LXC_CMD_GET_LXCPATH]         = "get_lxcpath",
-               [LXC_CMD_ADD_STATE_CLIENT]    = "add_state_client",
-               [LXC_CMD_CONSOLE_LOG]         = "console_log",
-               [LXC_CMD_SERVE_STATE_CLIENTS] = "serve_state_clients",
-               [LXC_CMD_SECCOMP_NOTIFY_ADD_LISTENER] = 
"seccomp_notify_add_listener",
+               [LXC_CMD_CONSOLE]                       = "console",
+               [LXC_CMD_TERMINAL_WINCH]                = "terminal_winch",
+               [LXC_CMD_STOP]                          = "stop",
+               [LXC_CMD_GET_STATE]                     = "get_state",
+               [LXC_CMD_GET_INIT_PID]                  = "get_init_pid",
+               [LXC_CMD_GET_CLONE_FLAGS]               = "get_clone_flags",
+               [LXC_CMD_GET_CGROUP]                    = "get_cgroup",
+               [LXC_CMD_GET_CONFIG_ITEM]               = "get_config_item",
+               [LXC_CMD_GET_NAME]                      = "get_name",
+               [LXC_CMD_GET_LXCPATH]                   = "get_lxcpath",
+               [LXC_CMD_ADD_STATE_CLIENT]              = "add_state_client",
+               [LXC_CMD_CONSOLE_LOG]                   = "console_log",
+               [LXC_CMD_SERVE_STATE_CLIENTS]           = "serve_state_clients",
+               [LXC_CMD_SECCOMP_NOTIFY_ADD_LISTENER]   = 
"seccomp_notify_add_listener",
+               [LXC_CMD_ADD_BPF_DEVICE_CGROUP]         = 
"add_bpf_device_cgroup",
        };
 
        if (cmd >= LXC_CMD_MAX)
@@ -925,6 +927,118 @@ static int lxc_cmd_add_state_client_callback(int fd, 
struct lxc_cmd_req *req,
        return 1;
 }
 
+int lxc_cmd_add_bpf_device_cgroup(const char *name, const char *lxcpath,
+                                 struct device_item *device)
+{
+#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
+       int stopped = 0;
+       struct lxc_cmd_rr cmd = {
+           .req = {
+               .cmd     = LXC_CMD_ADD_BPF_DEVICE_CGROUP,
+               .data    = device,
+               .datalen = sizeof(struct device_item),
+           },
+       };
+       int ret;
+
+       if (strlen(device->access) > STRLITERALLEN("rwm"))
+               return error_log_errno(EINVAL, "Invalid access mode specified 
%s",
+                                      device->access);
+
+       ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
+       if (ret < 0 || cmd.rsp.ret < 0)
+               return error_log_errno(errno, "Failed to add new bpf device 
cgroup rule");
+
+       return 0;
+#else
+       return minus_one_set_errno(ENOSYS);
+#endif
+}
+
+static int lxc_cmd_add_bpf_device_cgroup_callback(int fd, struct lxc_cmd_req 
*req,
+                                                 struct lxc_handler *handler,
+                                                 struct lxc_epoll_descr *descr)
+{
+#ifdef HAVE_STRUCT_BPF_CGROUP_DEV_CTX
+       __do_bpf_program_free struct bpf_program *devices = NULL;
+       struct lxc_cmd_rsp rsp = {0};
+       struct lxc_conf *conf = handler->conf;
+       struct hierarchy *unified = handler->cgroup_ops->unified;
+       struct lxc_list *list_elem = NULL;
+       struct device_item *new_device = NULL;
+       int ret;
+       struct lxc_list *it;
+       struct device_item *device;
+       struct bpf_program *devices_old;
+
+       if (req->datalen <= 0)
+               goto reap_client_fd;
+
+       if (req->datalen != sizeof(struct device_item))
+               goto reap_client_fd;
+
+       if (!req->data)
+               goto reap_client_fd;
+       device = (struct device_item *)req->data;
+
+       rsp.ret = -1;
+       if (!unified)
+               goto respond;
+
+       ret = bpf_list_add_device(conf, device);
+       if (ret < 0)
+               goto respond;
+
+       devices = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE);
+       if (!devices)
+               goto respond;
+
+       ret = bpf_program_init(devices);
+       if (ret)
+               goto respond;
+
+       lxc_list_for_each(it, &conf->devices) {
+               struct device_item *cur = it->elem;
+
+               ret = bpf_program_append_device(devices, cur);
+               if (ret)
+                       goto respond;
+       }
+
+       ret = bpf_program_finalize(devices);
+       if (ret)
+               goto respond;
+
+       ret = bpf_program_cgroup_attach(devices, BPF_CGROUP_DEVICE,
+                                       unified->container_full_path,
+                                       BPF_F_ALLOW_MULTI);
+       if (ret)
+               goto respond;
+
+       /* Replace old bpf program. */
+       devices_old = move_ptr(conf->cgroup2_devices);
+       conf->cgroup2_devices = move_ptr(devices);
+       devices = move_ptr(devices_old);
+
+       rsp.ret = 0;
+
+respond:
+       ret = lxc_cmd_rsp_send(fd, &rsp);
+       if (ret < 0)
+               goto reap_client_fd;
+
+       return 0;
+
+reap_client_fd:
+       /* Special indicator to lxc_cmd_handler() to close the fd and do related
+        * cleanup.
+        */
+       return 1;
+#else
+       return minus_one_set_errno(ENOSYS);
+#endif
+}
+
 int lxc_cmd_console_log(const char *name, const char *lxcpath,
                        struct lxc_console_log *log)
 {
@@ -1123,20 +1237,21 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req 
*req,
                                struct lxc_epoll_descr *);
 
        callback cb[LXC_CMD_MAX] = {
-               [LXC_CMD_CONSOLE]                     = 
lxc_cmd_console_callback,
-               [LXC_CMD_TERMINAL_WINCH]              = 
lxc_cmd_terminal_winch_callback,
-               [LXC_CMD_STOP]                        = lxc_cmd_stop_callback,
-               [LXC_CMD_GET_STATE]                   = 
lxc_cmd_get_state_callback,
-               [LXC_CMD_GET_INIT_PID]                = 
lxc_cmd_get_init_pid_callback,
-               [LXC_CMD_GET_CLONE_FLAGS]             = 
lxc_cmd_get_clone_flags_callback,
-               [LXC_CMD_GET_CGROUP]                  = 
lxc_cmd_get_cgroup_callback,
-               [LXC_CMD_GET_CONFIG_ITEM]             = 
lxc_cmd_get_config_item_callback,
-               [LXC_CMD_GET_NAME]                    = 
lxc_cmd_get_name_callback,
-               [LXC_CMD_GET_LXCPATH]                 = 
lxc_cmd_get_lxcpath_callback,
-               [LXC_CMD_ADD_STATE_CLIENT]            = 
lxc_cmd_add_state_client_callback,
-               [LXC_CMD_CONSOLE_LOG]                 = 
lxc_cmd_console_log_callback,
-               [LXC_CMD_SERVE_STATE_CLIENTS]         = 
lxc_cmd_serve_state_clients_callback,
-               [LXC_CMD_SECCOMP_NOTIFY_ADD_LISTENER] = 
lxc_cmd_seccomp_notify_add_listener_callback,
+               [LXC_CMD_CONSOLE]                       = 
lxc_cmd_console_callback,
+               [LXC_CMD_TERMINAL_WINCH]                = 
lxc_cmd_terminal_winch_callback,
+               [LXC_CMD_STOP]                          = lxc_cmd_stop_callback,
+               [LXC_CMD_GET_STATE]                     = 
lxc_cmd_get_state_callback,
+               [LXC_CMD_GET_INIT_PID]                  = 
lxc_cmd_get_init_pid_callback,
+               [LXC_CMD_GET_CLONE_FLAGS]               = 
lxc_cmd_get_clone_flags_callback,
+               [LXC_CMD_GET_CGROUP]                    = 
lxc_cmd_get_cgroup_callback,
+               [LXC_CMD_GET_CONFIG_ITEM]               = 
lxc_cmd_get_config_item_callback,
+               [LXC_CMD_GET_NAME]                      = 
lxc_cmd_get_name_callback,
+               [LXC_CMD_GET_LXCPATH]                   = 
lxc_cmd_get_lxcpath_callback,
+               [LXC_CMD_ADD_STATE_CLIENT]              = 
lxc_cmd_add_state_client_callback,
+               [LXC_CMD_CONSOLE_LOG]                   = 
lxc_cmd_console_log_callback,
+               [LXC_CMD_SERVE_STATE_CLIENTS]           = 
lxc_cmd_serve_state_clients_callback,
+               [LXC_CMD_SECCOMP_NOTIFY_ADD_LISTENER]   = 
lxc_cmd_seccomp_notify_add_listener_callback,
+               [LXC_CMD_ADD_BPF_DEVICE_CGROUP]         = 
lxc_cmd_add_bpf_device_cgroup_callback,
        };
 
        if (req->cmd >= LXC_CMD_MAX) {
diff --git a/src/lxc/commands.h b/src/lxc/commands.h
index d7d0c6096a..008b7c30e2 100644
--- a/src/lxc/commands.h
+++ b/src/lxc/commands.h
@@ -47,6 +47,7 @@ typedef enum {
        LXC_CMD_CONSOLE_LOG,
        LXC_CMD_SERVE_STATE_CLIENTS,
        LXC_CMD_SECCOMP_NOTIFY_ADD_LISTENER,
+       LXC_CMD_ADD_BPF_DEVICE_CGROUP,
        LXC_CMD_MAX,
 } lxc_cmd_t;
 
@@ -131,4 +132,8 @@ extern int lxc_cmd_seccomp_notify_add_listener(const char 
*name,
                                               /* unused */ unsigned int 
command,
                                               /* unused */ unsigned int flags);
 
+struct device_item;
+extern int lxc_cmd_add_bpf_device_cgroup(const char *name, const char *lxcpath,
+                                        struct device_item *device);
+
 #endif /* __commands_h */
diff --git a/src/lxc/conf.h b/src/lxc/conf.h
index 44d7934fe4..9142d31710 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -236,6 +236,11 @@ struct device_item {
        int minor;
        char access[4];
        int allow;
+       /* -1 -> no global rule
+        *  0 -> whitelist (deny all)
+        *  1 -> blacklist (allow all)
+        */
+       int global_rule;
 };
 
 struct lxc_conf {
diff --git a/src/lxc/log.h b/src/lxc/log.h
index 8e45919643..c6b2be2d6e 100644
--- a/src/lxc/log.h
+++ b/src/lxc/log.h
@@ -518,6 +518,13 @@ ATTR_UNUSED static inline void LXC_##LEVEL(struct 
lxc_log_locinfo* locinfo,        \
                __ret__;                      \
        })
 
+#define log_error(__ret__, __errno__, format, ...) \
+       ({                                         \
+               errno = __errno__;                 \
+               SYSERROR(format, ##__VA_ARGS__);   \
+               __ret__;                           \
+       })
+
 extern int lxc_log_fd;
 
 extern int lxc_log_syslog(int facility);
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to