Skip to content

Commit 87b33ab

Browse files
committed
ebpf: replace the existing programs on update
on a container update, make sure the existing eBPF programs are completely replaced. Signed-off-by: Giuseppe Scrivano <[email protected]>
1 parent 981f3ba commit 87b33ab

File tree

1 file changed

+163
-13
lines changed

1 file changed

+163
-13
lines changed

src/libcrun/ebpf.c

+163-13
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,169 @@ bpf_program_complete_dev (struct bpf_program *program, libcrun_error_t *err arg_
293293
return program;
294294
}
295295

296+
static int
297+
read_all_progs (int dirfd, uint32_t **progs_out, size_t *n_progs_out, libcrun_error_t *err)
298+
{
299+
cleanup_free uint32_t *progs = NULL;
300+
union bpf_attr attr;
301+
int ret;
302+
size_t cur_size;
303+
304+
/* The kernel 5.7 has a hard limit of 64, let's be safe if the limit
305+
will be increased in future and attempt up to 4096. */
306+
for (cur_size = 64; cur_size <= 4096; cur_size *= 2)
307+
{
308+
progs = xrealloc (progs, sizeof (uint32_t) * cur_size);
309+
310+
memset (&attr, 0, sizeof (attr));
311+
attr.query.target_fd = dirfd;
312+
attr.query.attach_type = BPF_CGROUP_DEVICE;
313+
attr.query.prog_cnt = cur_size;
314+
attr.query.prog_ids = (uint64_t) progs;
315+
316+
ret = bpf (BPF_PROG_QUERY, &attr, sizeof (attr));
317+
}
318+
while (ret < 0 && errno == ENOSPC);
319+
320+
if (UNLIKELY (ret < 0))
321+
return crun_make_error (err, errno, "bpf query");
322+
323+
*progs_out = progs;
324+
progs = NULL;
325+
*n_progs_out = attr.query.prog_cnt;
326+
return 0;
327+
}
328+
329+
static int
330+
remove_all_progs (int dirfd, uint32_t *progs, size_t n_progs, libcrun_error_t *err)
331+
{
332+
union bpf_attr attr;
333+
size_t i;
334+
335+
for (i = 0; i < n_progs; i++)
336+
{
337+
cleanup_close int fd = -1;
338+
int ret;
339+
340+
memset (&attr, 0, sizeof (attr));
341+
attr.prog_id = progs[i];
342+
fd = bpf (BPF_PROG_GET_FD_BY_ID, &attr, sizeof (attr));
343+
if (UNLIKELY (fd < 0))
344+
{
345+
/* Already removed. Nothing to do. */
346+
if (errno == ENOENT)
347+
continue;
348+
349+
return crun_make_error (err, errno, "cannot open existing eBPF program");
350+
}
351+
352+
memset (&attr, 0, sizeof (attr));
353+
attr.attach_type = BPF_CGROUP_DEVICE;
354+
attr.target_fd = dirfd;
355+
attr.attach_bpf_fd = fd;
356+
357+
ret = bpf (BPF_PROG_DETACH, &attr, sizeof (attr));
358+
if (UNLIKELY (ret < 0))
359+
{
360+
/* Already removed. Nothing to do. */
361+
if (errno == ENOENT)
362+
continue;
363+
364+
return crun_make_error (err, errno, "cannot detach eBPF program");
365+
}
366+
}
367+
return 0;
368+
}
369+
370+
static int
371+
ebpf_attach_program (int fd, int dirfd, libcrun_error_t *err)
372+
{
373+
#ifdef BPF_F_REPLACE
374+
bool skip_replace = false;
375+
#endif
376+
const int MAX_ATTEMPTS = 20;
377+
int attempt;
378+
379+
for (attempt = 0;; attempt++)
380+
{
381+
cleanup_free uint32_t *progs = NULL;
382+
cleanup_close int replacefd = -1;
383+
union bpf_attr attr;
384+
size_t n_progs = 0;
385+
int ret;
386+
387+
ret = read_all_progs (dirfd, &progs, &n_progs, err);
388+
if (UNLIKELY (ret < 0))
389+
return ret;
390+
391+
#ifdef BPF_F_REPLACE
392+
/* There is just one program installed, let's attempt an atomic replace if supported. */
393+
if (!skip_replace && n_progs == 1)
394+
{
395+
memset (&attr, 0, sizeof (attr));
396+
attr.prog_id = progs[0];
397+
replacefd = bpf (BPF_PROG_GET_FD_BY_ID, &attr, sizeof (attr));
398+
if (UNLIKELY (replacefd < 0))
399+
{
400+
if (errno == ENOENT && attempt < MAX_ATTEMPTS)
401+
{
402+
/* Another update might have raced and updated, try again. */
403+
continue;
404+
}
405+
return crun_make_error (err, errno, "cannot open existing eBPF program");
406+
}
407+
}
408+
#endif
409+
410+
memset (&attr, 0, sizeof (attr));
411+
attr.attach_type = BPF_CGROUP_DEVICE;
412+
attr.target_fd = dirfd;
413+
attr.attach_bpf_fd = fd;
414+
attr.attach_flags = BPF_F_ALLOW_MULTI;
415+
#ifdef BPF_F_REPLACE
416+
if (replacefd >= 0)
417+
{
418+
attr.attach_flags = BPF_F_ALLOW_MULTI | BPF_F_REPLACE;
419+
attr.replace_bpf_fd = replacefd;
420+
}
421+
#endif
422+
423+
ret = bpf (BPF_PROG_ATTACH, &attr, sizeof (attr));
424+
if (UNLIKELY (ret < 0))
425+
{
426+
if (errno == ENOENT && replacefd >= 0 && attempt < MAX_ATTEMPTS)
427+
{
428+
/* Another update might have already updated the cgroup, try again. */
429+
continue;
430+
}
431+
#ifdef BPF_F_REPLACE
432+
if (errno == EINVAL && replacefd >= 0)
433+
{
434+
skip_replace = true;
435+
continue;
436+
}
437+
#endif
438+
return crun_make_error (err, errno, "bpf attach");
439+
}
440+
441+
/* Now that the new program is installed, remove all the programs that were previously installed. */
442+
if (replacefd < 0 && n_progs)
443+
return remove_all_progs (dirfd, progs, n_progs, err);
444+
445+
return 0;
446+
}
447+
}
448+
296449
int
297450
libcrun_ebpf_load (struct bpf_program *program, int dirfd, const char *pin, libcrun_error_t *err)
298451
{
299452
#ifndef HAVE_EBPF
300453
return crun_make_error (err, 0, "eBPF not supported");
301454
#else
302-
int fd, ret;
455+
cleanup_close int fd = -1;
303456
union bpf_attr attr;
304457
struct rlimit limit;
458+
int ret;
305459

306460
limit.rlim_cur = RLIM_INFINITY;
307461
limit.rlim_max = RLIM_INFINITY;
@@ -329,30 +483,26 @@ libcrun_ebpf_load (struct bpf_program *program, int dirfd, const char *pin, libc
329483

330484
fd = bpf (BPF_PROG_LOAD, &attr, sizeof (attr));
331485
if (fd < 0)
332-
return crun_make_error (err, errno, "bpf create %s", log);
486+
return crun_make_error (err, errno, "bpf create `%s`", log);
333487
}
334488

335-
memset (&attr, 0, sizeof (attr));
336-
attr.attach_type = BPF_CGROUP_DEVICE;
337-
attr.target_fd = dirfd;
338-
attr.attach_bpf_fd = fd;
339-
attr.attach_flags = BPF_F_ALLOW_MULTI;
340-
341-
ret = bpf (BPF_PROG_ATTACH, &attr, sizeof (attr));
342-
if (ret < 0)
343-
return crun_make_error (err, errno, "bpf attach");
489+
ret = ebpf_attach_program (fd, dirfd, err);
490+
if (UNLIKELY (ret < 0))
491+
return ret;
344492

345493
/* Optionally pin the program to the specified path. */
346494
if (pin)
347495
{
496+
unlink (pin);
497+
348498
memset (&attr, 0, sizeof (attr));
349499
attr.pathname = (uint64_t) pin;
350500
attr.bpf_fd = fd;
351501
ret = bpf (BPF_OBJ_PIN, &attr, sizeof (attr));
352502
if (ret < 0)
353-
return crun_make_error (err, errno, "bpf pin to %s", pin);
503+
return crun_make_error (err, errno, "bpf pin to `%s`", pin);
354504
}
355505

356-
return fd;
506+
return 0;
357507
#endif
358508
}

0 commit comments

Comments
 (0)