]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
bpf: rework live device cgroup update
authorChristian Brauner <christian.brauner@ubuntu.com>
Thu, 18 Feb 2021 13:50:03 +0000 (14:50 +0100)
committerChristian Brauner <christian.brauner@ubuntu.com>
Thu, 18 Feb 2021 13:56:01 +0000 (14:56 +0100)
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
src/lxc/cgroups/cgroup2_devices.c
src/lxc/cgroups/cgroup2_devices.h
src/lxc/commands.c

index 40ade62e469e04a1d795af229abb6b223d70ef1b..c22f880496b1d788376f7687c757d05b74c6afdc 100644 (file)
@@ -626,3 +626,87 @@ bool bpf_cgroup_devices_attach(struct cgroup_ops *ops, struct lxc_list *devices)
        swap(prog, ops->cgroup2_devices);
        return log_trace(true, "Attached bpf program");
 }
+
+bool bpf_cgroup_devices_update(struct cgroup_ops *ops,
+                              struct device_item *new,
+                              struct lxc_list *devices)
+{
+       __do_bpf_program_free struct bpf_program *prog = NULL;
+       static int can_use_bpf_replace = -1;
+       struct bpf_program *prog_old;
+       union bpf_attr *attr;
+       int ret;
+
+       if (!ops)
+               return ret_set_errno(false, EINVAL);
+
+       if (!pure_unified_layout(ops))
+               return ret_set_errno(false, EINVAL);
+
+       if (ops->unified->cgfd_limit < 0)
+               return ret_set_errno(false, EBADF);
+
+       ret = bpf_list_add_device(devices, new);
+       if (ret < 0)
+               return false;
+
+       /* No previous device program attached. */
+       prog_old = ops->cgroup2_devices;
+       if (!prog_old)
+               return bpf_cgroup_devices_attach(ops, devices);
+
+       prog = __bpf_cgroup_devices(devices);
+       if (!prog)
+               return syserrno(false, "Failed to create bpf program");
+
+       ret = bpf_program_load_kernel(prog);
+       if (ret < 0)
+               return syserrno(false, "Failed to load bpf program");
+
+       attr = &(union bpf_attr){
+               .attach_type    = prog_old->attached_type,
+               .target_fd      = prog_old->fd_cgroup,
+               .attach_bpf_fd  = prog->kernel_fd,
+       };
+
+       switch (can_use_bpf_replace) {
+       case 1:
+               attr->replace_bpf_fd = prog_old->kernel_fd;
+               attr->attach_flags = BPF_F_REPLACE | BPF_F_ALLOW_MULTI;
+
+               ret = bpf(BPF_PROG_ATTACH, attr, sizeof(*attr));
+               break;
+       case -1:
+               attr->replace_bpf_fd = prog_old->kernel_fd;
+               attr->attach_flags = BPF_F_REPLACE | BPF_F_ALLOW_MULTI;
+
+               can_use_bpf_replace = !bpf(BPF_PROG_ATTACH, attr, sizeof(*attr));
+               if (can_use_bpf_replace > 0)
+                       break;
+
+               __fallthrough;
+       case 0:
+               attr->attach_flags = BPF_F_ALLOW_MULTI;
+               attr->replace_bpf_fd = 0;
+
+               ret = bpf(BPF_PROG_ATTACH, attr, sizeof(*attr));
+               break;
+       }
+       if (ret < 0)
+               return syserrno(false, "Failed to update bpf program");
+
+       if (can_use_bpf_replace > 0) {
+               /* The old program was automatically detached by the kernel. */
+               close_prot_errno_disarm(prog_old->kernel_fd);
+               /* The new bpf program now owns the cgroup fd. */
+               prog->fd_cgroup = move_fd(prog_old->fd_cgroup);
+               TRACE("Replaced existing bpf program");
+       } else {
+               TRACE("Appended bpf program");
+       }
+       prog->attached_type  = prog_old->attached_type;
+       prog->attached_flags = attr->attach_flags;
+       swap(prog, ops->cgroup2_devices);
+
+       return true;
+}
index 3def6359756f55352da8e02bf45da48a821bdc29..8c7231a72f55103619f7fad8cc08e0e81965181a 100644 (file)
@@ -100,6 +100,9 @@ __hidden extern int bpf_list_add_device(struct lxc_list *devices,
                                        struct device_item *device);
 __hidden extern bool bpf_cgroup_devices_attach(struct cgroup_ops *ops,
                                               struct lxc_list *devices);
+__hidden extern bool bpf_cgroup_devices_update(struct cgroup_ops *ops,
+                                              struct device_item *new,
+                                              struct lxc_list *devices);
 
 define_cleanup_function(struct bpf_program *, bpf_program_free);
 #define __do_bpf_program_free call_cleaner(bpf_program_free)
index 32144b51191b34627ad0d23dcfff0b004c540863..b0049991654cc646998a2dbae1782ad9c392c079 100644 (file)
@@ -1193,17 +1193,10 @@ static int lxc_cmd_add_bpf_device_cgroup_callback(int fd, struct lxc_cmd_req *re
                                                  struct lxc_handler *handler,
                                                  struct lxc_epoll_descr *descr)
 {
-       __do_bpf_program_free struct bpf_program *devices = NULL;
-       struct lxc_cmd_rsp rsp = {0};
-       struct lxc_conf *conf = handler->conf;
-       struct cgroup_ops *cgroup_ops = handler->cgroup_ops;
-       struct hierarchy *unified = cgroup_ops->unified;
-       int fd_replace = -EBADF;
-       __u32 flags = 0;
        int ret;
-       struct lxc_list *it;
+       struct lxc_cmd_rsp rsp = {};
        struct device_item *device;
-       struct bpf_program *devices_old;
+       struct lxc_conf *conf;
 
        if (req->datalen <= 0)
                return LXC_CMD_REAP_CLIENT_FD;
@@ -1213,81 +1206,14 @@ static int lxc_cmd_add_bpf_device_cgroup_callback(int fd, struct lxc_cmd_req *re
 
        if (!req->data)
                return LXC_CMD_REAP_CLIENT_FD;
-       device = (struct device_item *)req->data;
-
-       rsp.ret = -1;
-       if (!unified)
-               goto respond;
-
-       if (unified->cgfd_mon < 0)
-               goto respond;
-
-       ret = bpf_list_add_device(&conf->devices, 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;
-
-       bpf_device_set_type(devices, &conf->devices);
-       TRACE("Device bpf %s all devices by default",
-             bpf_device_block_all(devices) ? "blocks" : "allows");
-
-       lxc_list_for_each(it, &conf->devices) {
-               struct device_item *cur = it->elem;
-
-               if (!bpf_device_add(devices, cur)) {
-                       TRACE("Skipping type %c, major %d, minor %d, access %s, allow %d",
-                             cur->type, cur->major, cur->minor, cur->access,
-                             cur->allow);
-                       continue;
-               }
-
-               ret = bpf_program_append_device(devices, cur);
-               if (ret)
-                       goto respond;
-       }
-
-       ret = bpf_program_finalize(devices);
-       if (ret)
-               goto respond;
-
-       flags |= BPF_F_ALLOW_MULTI;
-
-       devices_old = cgroup_ops->cgroup2_devices;
-       if (devices_old && devices_old->kernel_fd >= 0) {
-               flags |= BPF_F_REPLACE;
-               fd_replace = devices_old->kernel_fd;
-       }
-
-       ret = bpf_program_cgroup_attach(devices, BPF_CGROUP_DEVICE,
-                                       unified->cgfd_limit, fd_replace, flags);
-       if (ret)
-               goto respond;
-
-       /*
-        * In case we replaced the current bpf program then we don't
-        * need to detach anything. We simply need to close the old fd.
-        */
-       if (devices_old && (flags & BPF_F_REPLACE)) {
-               close_prot_errno_disarm(devices_old->kernel_fd);
-               /* Technically not needed but better safe than segfaulted. */
-               fd_replace = -EBADF;
-       }
 
-       /* Replace old bpf program. */
-       devices_old = move_ptr(cgroup_ops->cgroup2_devices);
-       cgroup_ops->cgroup2_devices = move_ptr(devices);
-       devices = move_ptr(devices_old);
-
-       rsp.ret = 0;
+       device = (struct device_item *)req->data;
+       conf = handler->conf;
+       if (!bpf_cgroup_devices_update(handler->cgroup_ops, device, &conf->devices))
+               rsp.ret = -1;
+       else
+               rsp.ret = 0;
 
-respond:
        ret = lxc_cmd_rsp_send(fd, &rsp);
        if (ret < 0)
                return LXC_CMD_REAP_CLIENT_FD;