diff --git a/src/libcrun/ebpf.c b/src/libcrun/ebpf.c index 8097746c59..b7069748a0 100644 --- a/src/libcrun/ebpf.c +++ b/src/libcrun/ebpf.c @@ -299,9 +299,14 @@ libcrun_ebpf_load (struct bpf_program *program, int dirfd, const char *pin, libc #ifndef HAVE_EBPF return crun_make_error (err, 0, "eBPF not supported"); #else - int fd, ret; + cleanup_close int replacefd = -1; + cleanup_close int fd = -1; + union bpf_attr attr_open; + int attempts_left = 20; union bpf_attr attr; struct rlimit limit; + uint32_t progs[2]; + int ret; limit.rlim_cur = RLIM_INFINITY; limit.rlim_max = RLIM_INFINITY; @@ -329,28 +334,84 @@ libcrun_ebpf_load (struct bpf_program *program, int dirfd, const char *pin, libc fd = bpf (BPF_PROG_LOAD, &attr, sizeof (attr)); if (fd < 0) - return crun_make_error (err, errno, "bpf create %s", log); + return crun_make_error (err, errno, "bpf create `%s`", log); + } + +repeat: + memset (&attr_open, 0, sizeof (attr_open)); + attr_open.query.target_fd = dirfd; + attr_open.query.attach_type = BPF_CGROUP_DEVICE; + attr_open.query.prog_cnt = sizeof (progs) / sizeof (progs[0]); + attr_open.query.prog_ids = (uint64_t) &progs; + + ret = bpf (BPF_PROG_QUERY, &attr_open, sizeof (attr_open)); + if (UNLIKELY (ret < 0)) + return crun_make_error (err, errno, "bpf query"); + + if (attr_open.query.prog_cnt > 1) + return crun_make_error (err, 0, "invalid device cgroup state, more than one program installed"); + + if (attr_open.query.prog_cnt == 1) + { +#ifdef BPF_F_REPLACE + memset (&attr_open, 0, sizeof (attr_open)); + attr_open.prog_id = progs[0]; + replacefd = bpf (BPF_PROG_GET_FD_BY_ID, &attr_open, sizeof (attr_open)); + if (UNLIKELY (replacefd < 0)) + { + if (errno == ENOENT && attempts_left > 0) + { + /* Another update might have raced and updated, try again. */ + close_and_reset (&replacefd); + + attempts_left--; + goto repeat; + } + return crun_make_error (err, errno, "cannot open existing eBPF program"); + } +#else + return crun_make_error (err, 0, "eBPF program already configured"); +#endif } memset (&attr, 0, sizeof (attr)); attr.attach_type = BPF_CGROUP_DEVICE; attr.target_fd = dirfd; attr.attach_bpf_fd = fd; +#ifdef BPF_F_REPLACE + attr.attach_flags = BPF_F_ALLOW_MULTI | ((replacefd >= 0) ? BPF_F_REPLACE : 0); + attr.replace_bpf_fd = replacefd; +#else attr.attach_flags = BPF_F_ALLOW_MULTI; +#endif ret = bpf (BPF_PROG_ATTACH, &attr, sizeof (attr)); - if (ret < 0) - return crun_make_error (err, errno, "bpf attach"); + if (UNLIKELY (ret < 0)) + { + if (errno == ENOENT && replacefd >= 0 && attempts_left > 0) + { + /* Another update might have already updated the cgroup, try again. */ + close_and_reset (&replacefd); + + attempts_left--; + goto repeat; + } + if (errno == EINVAL && replacefd >= 0) + return crun_make_error (err, errno, "bpf attach. The kernel might not support BPF_F_REPLACE"); + return crun_make_error (err, errno, "bpf attach"); + } /* Optionally pin the program to the specified path. */ if (pin) { + unlink (pin); + memset (&attr, 0, sizeof (attr)); attr.pathname = (uint64_t) pin; attr.bpf_fd = fd; ret = bpf (BPF_OBJ_PIN, &attr, sizeof (attr)); if (ret < 0) - return crun_make_error (err, errno, "bpf pin to %s", pin); + return crun_make_error (err, errno, "bpf pin to `%s`", pin); } return fd;