Files
Florent Revest (Anthropic) d5288c519b selftests/bpf: Detach a trampoline prog while a task sleeps before it
Add a test for the use-after-free fixed by the previous commit. A
sleepable prog on bpf_fentry_test1() blocks a task on a userfaultfd
page, like bpf_mod_race does, while the two progs that run after it in
the same trampoline are detached and freed one after the other, which
the test knows from their .bss maps going away. After the first detach
the task is in an image that isn't the trampoline's current one
anymore. The task is then released and must not call into the freed
progs. This is done with fentry progs, with fexit progs, where the task
is already past the original function, and with a task sleeping in a
fentry prog while fexit progs are detached, where the original function
must still be called. bpf_mod_race's userfaultfd helper moves to
testing_helpers.c so that both tests can use it.

Without the fix, on a kernel with KASAN:

  BUG: KASAN: vmalloc-out-of-bounds in __bpf_prog_enter_recur+0xed/0x1e0
  Read of size 8 at addr ffa0000000144040 by task test_progs/171
  CPU: 6 UID: 0 PID: 171 Comm: test_progs Tainted: G           OE       7.2.0+ #1 PREEMPT(full)
  Call Trace:
   <TASK>
   __bpf_prog_enter_recur+0xed/0x1e0
   bpf_trampoline_6442545468+0x72/0xe3
   bpf_fentry_test1+0x9/0x20
   bpf_prog_test_run_tracing+0x183/0x3e0
   __sys_bpf+0xd3f/0x38f0
   ...

Signed-off-by: Florent Revest (Anthropic) <[email protected]>
Signed-off-by: Alexei Starovoitov <[email protected]>
Link: https://patch.msgid.link/[email protected]
2026-09-29 05:54:16 +00:00

565 lines
11 KiB
C

// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (C) 2019 Netronome Systems, Inc. */
/* Copyright (C) 2020 Facebook, Inc. */
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <alloca.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "disasm.h"
#include "test_progs.h"
#include "testing_helpers.h"
#include <linux/membarrier.h>
#include <linux/userfaultfd.h>
int parse_num_list(const char *s, bool **num_set, int *num_set_len)
{
int i, set_len = 0, new_len, num, start = 0, end = -1;
bool *set = NULL, *tmp, parsing_end = false;
char *next;
while (s[0]) {
errno = 0;
num = strtol(s, &next, 10);
if (errno)
return -errno;
if (parsing_end)
end = num;
else
start = num;
if (!parsing_end && *next == '-') {
s = next + 1;
parsing_end = true;
continue;
} else if (*next == ',') {
parsing_end = false;
s = next + 1;
end = num;
} else if (*next == '\0') {
parsing_end = false;
s = next;
end = num;
} else {
return -EINVAL;
}
if (start > end)
return -EINVAL;
if (end + 1 > set_len) {
new_len = end + 1;
tmp = realloc(set, new_len);
if (!tmp) {
free(set);
return -ENOMEM;
}
for (i = set_len; i < start; i++)
tmp[i] = false;
set = tmp;
set_len = new_len;
}
for (i = start; i <= end; i++)
set[i] = true;
}
if (!set || parsing_end)
return -EINVAL;
*num_set = set;
*num_set_len = set_len;
return 0;
}
static int do_insert_test(struct test_filter_set *set,
char *test_str,
char *subtest_str)
{
struct test_filter *tmp, *test;
char **ctmp;
int i;
for (i = 0; i < set->cnt; i++) {
test = &set->tests[i];
if (strcmp(test_str, test->name) == 0) {
free(test_str);
goto subtest;
}
}
tmp = realloc(set->tests, sizeof(*test) * (set->cnt + 1));
if (!tmp)
return -ENOMEM;
set->tests = tmp;
test = &set->tests[set->cnt];
test->name = test_str;
test->subtests = NULL;
test->subtest_cnt = 0;
set->cnt++;
subtest:
if (!subtest_str)
return 0;
for (i = 0; i < test->subtest_cnt; i++) {
if (strcmp(subtest_str, test->subtests[i]) == 0) {
free(subtest_str);
return 0;
}
}
ctmp = realloc(test->subtests,
sizeof(*test->subtests) * (test->subtest_cnt + 1));
if (!ctmp)
return -ENOMEM;
test->subtests = ctmp;
test->subtests[test->subtest_cnt] = subtest_str;
test->subtest_cnt++;
return 0;
}
static int insert_test(struct test_filter_set *set,
char *test_spec,
bool is_glob_pattern)
{
char *pattern, *subtest_str, *ext_test_str, *ext_subtest_str = NULL;
int glob_chars = 0;
if (is_glob_pattern) {
pattern = "%s";
} else {
pattern = "*%s*";
glob_chars = 2;
}
subtest_str = strchr(test_spec, '/');
if (subtest_str) {
*subtest_str = '\0';
subtest_str += 1;
}
ext_test_str = malloc(strlen(test_spec) + glob_chars + 1);
if (!ext_test_str)
goto err;
sprintf(ext_test_str, pattern, test_spec);
if (subtest_str) {
ext_subtest_str = malloc(strlen(subtest_str) + glob_chars + 1);
if (!ext_subtest_str)
goto err;
sprintf(ext_subtest_str, pattern, subtest_str);
}
return do_insert_test(set, ext_test_str, ext_subtest_str);
err:
free(ext_test_str);
free(ext_subtest_str);
return -ENOMEM;
}
int parse_test_list_file(const char *path,
struct test_filter_set *set,
bool is_glob_pattern)
{
char *buf = NULL, *capture_start, *capture_end, *scan_end;
size_t buflen = 0;
int err = 0;
FILE *f;
f = fopen(path, "r");
if (!f) {
err = -errno;
fprintf(stderr, "Failed to open '%s': %d\n", path, err);
return err;
}
while (getline(&buf, &buflen, f) != -1) {
capture_start = buf;
while (isspace(*capture_start))
++capture_start;
capture_end = capture_start;
scan_end = capture_start;
while (*scan_end && *scan_end != '#') {
if (!isspace(*scan_end))
capture_end = scan_end;
++scan_end;
}
if (capture_end == capture_start)
continue;
*(++capture_end) = '\0';
err = insert_test(set, capture_start, is_glob_pattern);
if (err)
break;
}
free(buf);
fclose(f);
return err;
}
int parse_test_list(const char *s,
struct test_filter_set *set,
bool is_glob_pattern)
{
char *input, *state = NULL, *test_spec;
int err = 0, cnt = 0;
input = strdup(s);
if (!input)
return -ENOMEM;
while ((test_spec = strtok_r(cnt++ ? NULL : input, ",", &state))) {
err = insert_test(set, test_spec, is_glob_pattern);
if (err)
break;
}
free(input);
return err;
}
__u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info)
{
__u32 info_len = sizeof(*info);
int err;
memset(info, 0, sizeof(*info));
err = bpf_link_get_info_by_fd(bpf_link__fd(link), info, &info_len);
if (err) {
printf("failed to get link info: %d\n", -errno);
return 0;
}
return info->prog_id;
}
int extra_prog_load_log_flags = 0;
int testing_prog_flags(void)
{
static int cached_flags = -1;
static int prog_flags[] = { BPF_F_TEST_RND_HI32, BPF_F_TEST_REG_INVARIANTS };
static struct bpf_insn insns[] = {
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
int insn_cnt = ARRAY_SIZE(insns), i, fd, flags = 0;
LIBBPF_OPTS(bpf_prog_load_opts, opts);
if (cached_flags >= 0)
return cached_flags;
for (i = 0; i < ARRAY_SIZE(prog_flags); i++) {
opts.prog_flags = prog_flags[i];
fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "flag-test", "GPL",
insns, insn_cnt, &opts);
if (fd >= 0) {
flags |= prog_flags[i];
close(fd);
}
}
cached_flags = flags;
return cached_flags;
}
int bpf_prog_test_load(const char *file, enum bpf_prog_type type,
struct bpf_object **pobj, int *prog_fd)
{
LIBBPF_OPTS(bpf_object_open_opts, opts,
.kernel_log_level = extra_prog_load_log_flags,
);
struct bpf_object *obj;
struct bpf_program *prog;
__u32 flags;
int err;
obj = bpf_object__open_file(file, &opts);
if (!obj)
return -errno;
prog = bpf_object__next_program(obj, NULL);
if (!prog) {
err = -ENOENT;
goto err_out;
}
if (type != BPF_PROG_TYPE_UNSPEC && bpf_program__type(prog) != type)
bpf_program__set_type(prog, type);
flags = bpf_program__flags(prog) | testing_prog_flags();
bpf_program__set_flags(prog, flags);
err = bpf_object__load(obj);
if (err)
goto err_out;
*pobj = obj;
*prog_fd = bpf_program__fd(prog);
return 0;
err_out:
bpf_object__close(obj);
return err;
}
int bpf_test_load_program(enum bpf_prog_type type, const struct bpf_insn *insns,
size_t insns_cnt, const char *license,
__u32 kern_version, char *log_buf,
size_t log_buf_sz)
{
LIBBPF_OPTS(bpf_prog_load_opts, opts,
.kern_version = kern_version,
.prog_flags = testing_prog_flags(),
.log_level = extra_prog_load_log_flags,
.log_buf = log_buf,
.log_size = log_buf_sz,
);
return bpf_prog_load(type, NULL, license, insns, insns_cnt, &opts);
}
__u64 read_perf_max_sample_freq(void)
{
__u64 sample_freq = 5000; /* fallback to 5000 on error */
FILE *f;
f = fopen("/proc/sys/kernel/perf_event_max_sample_rate", "r");
if (f == NULL) {
printf("Failed to open /proc/sys/kernel/perf_event_max_sample_rate: err %d\n"
"return default value: 5000\n", -errno);
return sample_freq;
}
if (fscanf(f, "%llu", &sample_freq) != 1) {
printf("Failed to parse /proc/sys/kernel/perf_event_max_sample_rate: err %d\n"
"return default value: 5000\n", -errno);
}
fclose(f);
return sample_freq;
}
int finit_module(int fd, const char *param_values, int flags)
{
return syscall(__NR_finit_module, fd, param_values, flags);
}
int delete_module(const char *name, int flags)
{
return syscall(__NR_delete_module, name, flags);
}
int try_unload_module(const char *name, int retries, bool verbose)
{
int ret, cnt = 0;
if (kern_sync_rcu())
fprintf(stdout, "Failed to trigger kernel-side RCU sync!\n");
for (;;) {
ret = delete_module(name, 0);
if (!ret || errno != EAGAIN)
break;
if (++cnt > retries) {
fprintf(stdout, "Unload of %s timed out\n", name);
break;
}
usleep(100);
}
if (ret) {
if (errno == ENOENT) {
if (verbose)
fprintf(stdout, "%s.ko is already unloaded.\n", name);
return -1;
}
fprintf(stdout, "Failed to unload %s.ko from kernel: %d\n", name, -errno);
return -1;
}
if (verbose)
fprintf(stdout, "Successfully unloaded %s.ko.\n", name);
return 0;
}
int unload_module(const char *name, bool verbose)
{
return try_unload_module(name, 10000, verbose);
}
static int __load_module(const char *path, const char *param_values, bool verbose)
{
int fd;
if (verbose)
fprintf(stdout, "Loading %s...\n", path);
fd = open(path, O_RDONLY);
if (fd < 0) {
fprintf(stdout, "Can't find %s kernel module: %d\n", path, -errno);
return -ENOENT;
}
if (finit_module(fd, param_values, 0)) {
fprintf(stdout, "Failed to load %s into the kernel: %d\n", path, -errno);
close(fd);
return -EINVAL;
}
close(fd);
if (verbose)
fprintf(stdout, "Successfully loaded %s.\n", path);
return 0;
}
int load_module_params(const char *path, const char *param_values, bool verbose)
{
return __load_module(path, param_values, verbose);
}
int load_module(const char *path, bool verbose)
{
return __load_module(path, "", verbose);
}
int unload_bpf_testmod(bool verbose)
{
return unload_module("bpf_testmod", verbose);
}
int load_bpf_testmod(bool verbose)
{
return load_module("bpf_testmod.ko", verbose);
}
/*
* Trigger synchronize_rcu() in kernel.
*/
int kern_sync_rcu(void)
{
return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0);
}
int uffd_block_page(void *fault_addr)
{
struct uffdio_register uffd_register = {};
struct uffdio_api uffd_api = {};
int uffd;
uffd = syscall(__NR_userfaultfd, O_CLOEXEC);
if (uffd < 0)
return -errno;
uffd_api.api = UFFD_API;
uffd_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffd_api)) {
close(uffd);
return -1;
}
uffd_register.range.start = (unsigned long)fault_addr;
uffd_register.range.len = getpagesize();
uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) {
close(uffd);
return -1;
}
return uffd;
}
int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt)
{
__u32 buf_element_size = sizeof(struct bpf_insn);
struct bpf_prog_info info = {};
__u32 info_len = sizeof(info);
__u32 xlated_prog_len;
if (bpf_prog_get_info_by_fd(fd_prog, &info, &info_len)) {
perror("bpf_prog_get_info_by_fd failed");
return -1;
}
xlated_prog_len = info.xlated_prog_len;
if (xlated_prog_len % buf_element_size) {
printf("Program length %u is not multiple of %u\n",
xlated_prog_len, buf_element_size);
return -1;
}
*cnt = xlated_prog_len / buf_element_size;
*buf = calloc(*cnt, buf_element_size);
if (!*buf) {
perror("can't allocate xlated program buffer");
return -ENOMEM;
}
bzero(&info, sizeof(info));
info.xlated_prog_len = xlated_prog_len;
info.xlated_prog_insns = (__u64)(unsigned long)*buf;
if (bpf_prog_get_info_by_fd(fd_prog, &info, &info_len)) {
perror("second bpf_prog_get_info_by_fd failed");
goto out_free_buf;
}
return 0;
out_free_buf:
free(*buf);
*buf = NULL;
return -1;
}
bool is_jit_enabled(void)
{
const char *jit_sysctl = "/proc/sys/net/core/bpf_jit_enable";
bool enabled = false;
int sysctl_fd;
sysctl_fd = open(jit_sysctl, O_RDONLY);
if (sysctl_fd != -1) {
char tmpc;
if (read(sysctl_fd, &tmpc, sizeof(tmpc)) == 1)
enabled = (tmpc != '0');
close(sysctl_fd);
}
return enabled;
}
int stack_mprotect(void)
{
void *buf;
long sz;
int ret;
sz = sysconf(_SC_PAGESIZE);
if (sz < 0)
return sz;
buf = alloca(sz * 3);
ret = mprotect((void *)(((unsigned long)(buf + sz)) & ~(sz - 1)), sz,
PROT_READ | PROT_WRITE | PROT_EXEC);
return ret;
}