mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-31 22:34:33 +01:00
Replace ASSERT_OK_PTR by ASSERT_NEQ(res, NULL, ...) when checking the
result of malloc. It is more accurate since malloc returns NULL, not an
error code, on failure and it also prevents the following false GCC
warning when compiling BPF selftests with -O2:
In file included from test_loader.c:6:
test_loader.c: In function ‘verify_stderr’:
/bpf-next/tools/testing/selftests/bpf/test_progs.h:393:22: error: ‘buf’ may be used uninitialized [-Werror=maybe-uninitialized]
393 | int ___err = libbpf_get_error(___res); \
| ^~~~~~~~~~~~~~~~~~~~~~~~
test_loader.c:810:14: note: in expansion of macro ‘ASSERT_OK_PTR’
810 | if (!ASSERT_OK_PTR(buf, "malloc"))
| ^~~~~~~~~~~~~
In file included from /bpf-next/tools/testing/selftests/bpf/tools/include/bpf/bpf.h:32,
from /bpf-next/tools/testing/selftests/bpf/test_progs.h:37:
/bpf-next/tools/testing/selftests/bpf/tools/include/bpf/libbpf_legacy.h:113:17: note: by argument 1 of type ‘const void *’ to ‘libbpf_get_error’ declared here
113 | LIBBPF_API long libbpf_get_error(const void *ptr);
| ^~~~~~~~~~~~~~~~
Fixes: 554e4eb9e4 ("selftests/bpf: Reuse stderr parsing for libarena ASAN tests")
Signed-off-by: Viktor Malik <[email protected]>
Link: https://lore.kernel.org/bpf/e25d50805fbcb3632f24b488568ab5ba49b82094.1784112948.git.vmalik@redhat.com
Signed-off-by: Kumar Kartikeya Dwivedi <[email protected]>
1587 lines
41 KiB
C
1587 lines
41 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
|
|
#include <linux/capability.h>
|
|
#include <linux/err.h>
|
|
#include <stdlib.h>
|
|
#include <test_progs.h>
|
|
#include <bpf/btf.h>
|
|
|
|
#include "autoconf_helper.h"
|
|
#include "disasm_helpers.h"
|
|
#include "unpriv_helpers.h"
|
|
#include "cap_helpers.h"
|
|
#include "jit_disasm_helpers.h"
|
|
|
|
static inline const char *str_has_pfx(const char *str, const char *pfx)
|
|
{
|
|
size_t len = strlen(pfx);
|
|
|
|
return strncmp(str, pfx, len) == 0 ? str + len : NULL;
|
|
}
|
|
|
|
#define TEST_LOADER_LOG_BUF_SZ 2097152
|
|
|
|
|
|
/* Warning: duplicated in bpf_misc.h */
|
|
#define POINTER_VALUE 0xbadcafe
|
|
#define TEST_DATA_LEN 64
|
|
|
|
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
|
|
#define EFFICIENT_UNALIGNED_ACCESS 1
|
|
#else
|
|
#define EFFICIENT_UNALIGNED_ACCESS 0
|
|
#endif
|
|
|
|
static int sysctl_unpriv_disabled = -1;
|
|
|
|
enum mode {
|
|
PRIV = 1,
|
|
UNPRIV = 2
|
|
};
|
|
|
|
enum load_mode {
|
|
JITED = 1 << 0,
|
|
NO_JITED = 1 << 1,
|
|
};
|
|
|
|
struct test_subspec {
|
|
char *name;
|
|
char *description;
|
|
bool expect_failure;
|
|
struct expected_msgs expect_msgs;
|
|
struct expected_msgs expect_xlated;
|
|
struct expected_msgs jited;
|
|
struct expected_msgs stderr;
|
|
struct expected_msgs stdout;
|
|
int retval;
|
|
bool execute;
|
|
__u64 caps;
|
|
};
|
|
|
|
struct test_spec {
|
|
const char *prog_name;
|
|
struct test_subspec priv;
|
|
struct test_subspec unpriv;
|
|
const char *btf_custom_path;
|
|
const char *btf_custom_func_path;
|
|
int log_level;
|
|
int prog_flags;
|
|
int mode_mask;
|
|
int arch_mask;
|
|
int load_mask;
|
|
int linear_sz;
|
|
bool auxiliary;
|
|
bool valid;
|
|
};
|
|
|
|
static int tester_init(struct test_loader *tester)
|
|
{
|
|
if (!tester->log_buf) {
|
|
tester->log_buf_sz = TEST_LOADER_LOG_BUF_SZ;
|
|
tester->log_buf = calloc(tester->log_buf_sz, 1);
|
|
if (!ASSERT_OK_PTR(tester->log_buf, "tester_log_buf"))
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void test_loader_fini(struct test_loader *tester)
|
|
{
|
|
if (!tester)
|
|
return;
|
|
|
|
free(tester->log_buf);
|
|
}
|
|
|
|
void free_msgs(struct expected_msgs *msgs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < msgs->cnt; i++)
|
|
if (msgs->patterns[i].is_regex)
|
|
regfree(&msgs->patterns[i].regex);
|
|
free(msgs->patterns);
|
|
msgs->patterns = NULL;
|
|
msgs->cnt = 0;
|
|
}
|
|
|
|
static void free_test_spec(struct test_spec *spec)
|
|
{
|
|
/* Deallocate expect_msgs arrays. */
|
|
free_msgs(&spec->priv.expect_msgs);
|
|
free_msgs(&spec->unpriv.expect_msgs);
|
|
free_msgs(&spec->priv.expect_xlated);
|
|
free_msgs(&spec->unpriv.expect_xlated);
|
|
free_msgs(&spec->priv.jited);
|
|
free_msgs(&spec->unpriv.jited);
|
|
free_msgs(&spec->unpriv.stderr);
|
|
free_msgs(&spec->priv.stderr);
|
|
free_msgs(&spec->unpriv.stdout);
|
|
free_msgs(&spec->priv.stdout);
|
|
|
|
free(spec->priv.name);
|
|
free(spec->priv.description);
|
|
free(spec->unpriv.name);
|
|
free(spec->unpriv.description);
|
|
spec->priv.name = NULL;
|
|
spec->priv.description = NULL;
|
|
spec->unpriv.name = NULL;
|
|
spec->unpriv.description = NULL;
|
|
}
|
|
|
|
/* Compiles regular expression matching pattern.
|
|
* Pattern has a special syntax:
|
|
*
|
|
* pattern := (<verbatim text> | regex)*
|
|
* regex := "{{" <posix extended regular expression> "}}"
|
|
*
|
|
* In other words, pattern is a verbatim text with inclusion
|
|
* of regular expressions enclosed in "{{" "}}" pairs.
|
|
* For example, pattern "foo{{[0-9]+}}" matches strings like
|
|
* "foo0", "foo007", etc.
|
|
*/
|
|
static int compile_regex(const char *pattern, regex_t *regex)
|
|
{
|
|
char err_buf[256], buf[256] = {}, *ptr, *buf_end;
|
|
const char *original_pattern = pattern, *next;
|
|
bool in_regex = false;
|
|
int err;
|
|
|
|
buf_end = buf + sizeof(buf);
|
|
ptr = buf;
|
|
while (*pattern && ptr < buf_end - 2) {
|
|
if (!in_regex && (next = str_has_pfx(pattern, "{{"))) {
|
|
in_regex = true;
|
|
pattern = next;
|
|
continue;
|
|
}
|
|
if (in_regex && (next = str_has_pfx(pattern, "}}"))) {
|
|
in_regex = false;
|
|
pattern = next;
|
|
continue;
|
|
}
|
|
if (in_regex) {
|
|
*ptr++ = *pattern++;
|
|
continue;
|
|
}
|
|
/* list of characters that need escaping for extended posix regex */
|
|
if (strchr(".[]\\()*+?{}|^$", *pattern)) {
|
|
*ptr++ = '\\';
|
|
*ptr++ = *pattern++;
|
|
continue;
|
|
}
|
|
*ptr++ = *pattern++;
|
|
}
|
|
if (*pattern) {
|
|
PRINT_FAIL("Regexp too long: '%s'\n", original_pattern);
|
|
return -EINVAL;
|
|
}
|
|
if (in_regex) {
|
|
PRINT_FAIL("Regexp has open '{{' but no closing '}}': '%s'\n", original_pattern);
|
|
return -EINVAL;
|
|
}
|
|
err = regcomp(regex, buf, REG_EXTENDED | REG_NEWLINE);
|
|
if (err != 0) {
|
|
regerror(err, regex, err_buf, sizeof(err_buf));
|
|
PRINT_FAIL("Regexp compilation error in '%s': '%s'\n", buf, err_buf);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __push_msg(const char *pattern, bool on_next_line, bool negative,
|
|
struct expected_msgs *msgs)
|
|
{
|
|
struct expect_msg *msg;
|
|
void *tmp;
|
|
int err;
|
|
|
|
tmp = realloc(msgs->patterns,
|
|
(1 + msgs->cnt) * sizeof(struct expect_msg));
|
|
if (!tmp) {
|
|
ASSERT_FAIL("failed to realloc memory for messages\n");
|
|
return -ENOMEM;
|
|
}
|
|
msgs->patterns = tmp;
|
|
msg = &msgs->patterns[msgs->cnt];
|
|
msg->on_next_line = on_next_line;
|
|
msg->substr = pattern;
|
|
msg->negative = negative;
|
|
msg->is_regex = false;
|
|
if (strstr(pattern, "{{")) {
|
|
err = compile_regex(pattern, &msg->regex);
|
|
if (err)
|
|
return err;
|
|
msg->is_regex = true;
|
|
}
|
|
msgs->cnt += 1;
|
|
return 0;
|
|
}
|
|
|
|
static int clone_msgs(struct expected_msgs *from, struct expected_msgs *to)
|
|
{
|
|
struct expect_msg *msg;
|
|
int i, err;
|
|
|
|
for (i = 0; i < from->cnt; i++) {
|
|
msg = &from->patterns[i];
|
|
err = __push_msg(msg->substr, msg->on_next_line, msg->negative, to);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int push_msg(const char *substr, bool negative, struct expected_msgs *msgs)
|
|
{
|
|
return __push_msg(substr, false, negative, msgs);
|
|
}
|
|
|
|
static int push_disasm_msg(const char *regex_str, bool *on_next_line, struct expected_msgs *msgs)
|
|
{
|
|
int err;
|
|
|
|
if (strcmp(regex_str, "...") == 0) {
|
|
*on_next_line = false;
|
|
return 0;
|
|
}
|
|
err = __push_msg(regex_str, *on_next_line, false, msgs);
|
|
if (err)
|
|
return err;
|
|
*on_next_line = true;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_int(const char *str, int *val, const char *name)
|
|
{
|
|
char *end;
|
|
long tmp;
|
|
|
|
errno = 0;
|
|
if (str_has_pfx(str, "0x"))
|
|
tmp = strtol(str + 2, &end, 16);
|
|
else
|
|
tmp = strtol(str, &end, 10);
|
|
if (errno || end[0] != '\0') {
|
|
PRINT_FAIL("failed to parse %s from '%s'\n", name, str);
|
|
return -EINVAL;
|
|
}
|
|
*val = tmp;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_caps(const char *str, __u64 *val, const char *name)
|
|
{
|
|
int cap_flag = 0;
|
|
char *token = NULL, *saveptr = NULL;
|
|
|
|
char *str_cpy = strdup(str);
|
|
if (str_cpy == NULL) {
|
|
PRINT_FAIL("Memory allocation failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
token = strtok_r(str_cpy, "|", &saveptr);
|
|
while (token != NULL) {
|
|
errno = 0;
|
|
if (!strncmp("CAP_", token, sizeof("CAP_") - 1)) {
|
|
PRINT_FAIL("define %s constant in bpf_misc.h, failed to parse caps\n", token);
|
|
return -EINVAL;
|
|
}
|
|
cap_flag = strtol(token, NULL, 10);
|
|
if (!cap_flag || errno) {
|
|
PRINT_FAIL("failed to parse caps %s\n", name);
|
|
return -EINVAL;
|
|
}
|
|
*val |= (1ULL << cap_flag);
|
|
token = strtok_r(NULL, "|", &saveptr);
|
|
}
|
|
|
|
free(str_cpy);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_retval(const char *str, int *val, const char *name)
|
|
{
|
|
/*
|
|
* INT_MIN is defined as (-INT_MAX -1), i.e. it doesn't expand to a
|
|
* single int and cannot be parsed with strtol, so we handle it
|
|
* separately here. In addition, it expands to different expressions in
|
|
* different compilers so we use a prefixed _INT_MIN instead.
|
|
*/
|
|
if (strcmp(str, "_INT_MIN") == 0) {
|
|
*val = INT_MIN;
|
|
return 0;
|
|
}
|
|
|
|
return parse_int(str, val, name);
|
|
}
|
|
|
|
static void update_flags(int *flags, int flag, bool clear)
|
|
{
|
|
if (clear)
|
|
*flags &= ~flag;
|
|
else
|
|
*flags |= flag;
|
|
}
|
|
|
|
static const char *skip_decl_tag_pfx(const char *s)
|
|
{
|
|
int n = 0;
|
|
|
|
if (sscanf(s, "comment:%*d:%n", &n) < 0 || !n)
|
|
return NULL;
|
|
return s + n;
|
|
}
|
|
|
|
static int compare_decl_tags(const void *a, const void *b)
|
|
{
|
|
return strverscmp(*(const char **)a, *(const char **)b);
|
|
}
|
|
|
|
/*
|
|
* Compilers don't guarantee order in which BTF attributes would be generated,
|
|
* while order is important for test tags like __msg.
|
|
* Each test tag has the following prefix: "comment:" __COUNTER__,
|
|
* when sorted using strverscmp this gives same order as in the original C code.
|
|
*/
|
|
static const char **collect_decl_tags(struct btf *btf, int id, int *cnt)
|
|
{
|
|
const char **tmp, **tags = NULL;
|
|
const struct btf_type *t;
|
|
int i;
|
|
|
|
*cnt = 0;
|
|
for (i = 1; i < btf__type_cnt(btf); i++) {
|
|
t = btf__type_by_id(btf, i);
|
|
if (!btf_is_decl_tag(t) || t->type != id || btf_decl_tag(t)->component_idx != -1)
|
|
continue;
|
|
tmp = realloc(tags, (*cnt + 1) * sizeof(*tags));
|
|
if (!tmp) {
|
|
free(tags);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
tags = tmp;
|
|
tags[(*cnt)++] = btf__str_by_offset(btf, t->name_off);
|
|
}
|
|
|
|
if (*cnt)
|
|
qsort(tags, *cnt, sizeof(*tags), compare_decl_tags);
|
|
return tags;
|
|
}
|
|
|
|
enum arch {
|
|
ARCH_UNKNOWN = 0x1,
|
|
ARCH_X86_64 = 0x2,
|
|
ARCH_ARM64 = 0x4,
|
|
ARCH_RISCV64 = 0x8,
|
|
ARCH_S390X = 0x10,
|
|
ARCH_LOONGARCH = 0x20,
|
|
};
|
|
|
|
static int get_current_arch(void)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return ARCH_X86_64;
|
|
#elif defined(__aarch64__)
|
|
return ARCH_ARM64;
|
|
#elif defined(__riscv) && __riscv_xlen == 64
|
|
return ARCH_RISCV64;
|
|
#elif defined(__s390x__)
|
|
return ARCH_S390X;
|
|
#elif defined(__loongarch__)
|
|
return ARCH_LOONGARCH;
|
|
#endif
|
|
return ARCH_UNKNOWN;
|
|
}
|
|
|
|
/* Uses btf_decl_tag attributes to describe the expected test
|
|
* behavior, see bpf_misc.h for detailed description of each attribute
|
|
* and attribute combinations.
|
|
*/
|
|
static int parse_test_spec(struct test_loader *tester,
|
|
struct bpf_object *obj,
|
|
struct bpf_program *prog,
|
|
struct test_spec *spec)
|
|
{
|
|
const char *description = NULL;
|
|
bool has_unpriv_result = false;
|
|
bool has_unpriv_retval = false;
|
|
bool unpriv_xlated_on_next_line = true;
|
|
bool xlated_on_next_line = true;
|
|
bool unpriv_jit_on_next_line;
|
|
bool jit_on_next_line;
|
|
bool stderr_on_next_line = true;
|
|
bool unpriv_stderr_on_next_line = true;
|
|
bool stdout_on_next_line = true;
|
|
bool unpriv_stdout_on_next_line = true;
|
|
bool collect_jit = false;
|
|
const char **tags = NULL;
|
|
int func_id, i, nr_tags;
|
|
int err = 0;
|
|
u32 arch_mask = 0;
|
|
u32 load_mask = 0;
|
|
struct btf *btf;
|
|
enum arch arch;
|
|
|
|
memset(spec, 0, sizeof(*spec));
|
|
|
|
spec->prog_name = bpf_program__name(prog);
|
|
spec->prog_flags = testing_prog_flags();
|
|
|
|
btf = bpf_object__btf(obj);
|
|
if (!btf) {
|
|
ASSERT_FAIL("BPF object has no BTF");
|
|
return -EINVAL;
|
|
}
|
|
|
|
func_id = btf__find_by_name_kind(btf, spec->prog_name, BTF_KIND_FUNC);
|
|
if (func_id < 0) {
|
|
ASSERT_FAIL("failed to find FUNC BTF type for '%s'", spec->prog_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tags = collect_decl_tags(btf, func_id, &nr_tags);
|
|
if (IS_ERR(tags))
|
|
return PTR_ERR(tags);
|
|
|
|
for (i = 0; i < nr_tags; i++) {
|
|
const char *s, *val, *msg;
|
|
bool clear;
|
|
int flags;
|
|
|
|
s = skip_decl_tag_pfx(tags[i]);
|
|
if (!s)
|
|
continue;
|
|
if ((val = str_has_pfx(s, "test_description="))) {
|
|
description = val;
|
|
} else if (strcmp(s, "test_expect_failure") == 0) {
|
|
spec->priv.expect_failure = true;
|
|
spec->mode_mask |= PRIV;
|
|
} else if (strcmp(s, "test_expect_success") == 0) {
|
|
spec->priv.expect_failure = false;
|
|
spec->mode_mask |= PRIV;
|
|
} else if (strcmp(s, "test_expect_failure_unpriv") == 0) {
|
|
spec->unpriv.expect_failure = true;
|
|
spec->mode_mask |= UNPRIV;
|
|
has_unpriv_result = true;
|
|
} else if (strcmp(s, "test_expect_success_unpriv") == 0) {
|
|
spec->unpriv.expect_failure = false;
|
|
spec->mode_mask |= UNPRIV;
|
|
has_unpriv_result = true;
|
|
} else if (strcmp(s, "test_auxiliary") == 0) {
|
|
spec->auxiliary = true;
|
|
spec->mode_mask |= PRIV;
|
|
} else if (strcmp(s, "test_auxiliary_unpriv") == 0) {
|
|
spec->auxiliary = true;
|
|
spec->mode_mask |= UNPRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_msg="))) {
|
|
err = push_msg(msg, false, &spec->priv.expect_msgs);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= PRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_not_msg="))) {
|
|
err = push_msg(msg, true, &spec->priv.expect_msgs);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= PRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_msg_unpriv="))) {
|
|
err = push_msg(msg, false, &spec->unpriv.expect_msgs);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_not_msg_unpriv="))) {
|
|
err = push_msg(msg, true, &spec->unpriv.expect_msgs);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_jited="))) {
|
|
if (arch_mask == 0) {
|
|
PRINT_FAIL("__jited used before __arch_*");
|
|
goto cleanup;
|
|
}
|
|
if (collect_jit) {
|
|
err = push_disasm_msg(msg, &jit_on_next_line,
|
|
&spec->priv.jited);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= PRIV;
|
|
}
|
|
} else if ((msg = str_has_pfx(s, "test_jited_unpriv="))) {
|
|
if (arch_mask == 0) {
|
|
PRINT_FAIL("__unpriv_jited used before __arch_*");
|
|
goto cleanup;
|
|
}
|
|
if (collect_jit) {
|
|
err = push_disasm_msg(msg, &unpriv_jit_on_next_line,
|
|
&spec->unpriv.jited);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
}
|
|
} else if ((msg = str_has_pfx(s, "test_expect_xlated="))) {
|
|
err = push_disasm_msg(msg, &xlated_on_next_line,
|
|
&spec->priv.expect_xlated);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= PRIV;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_xlated_unpriv="))) {
|
|
err = push_disasm_msg(msg, &unpriv_xlated_on_next_line,
|
|
&spec->unpriv.expect_xlated);
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
} else if ((val = str_has_pfx(s, "test_retval="))) {
|
|
err = parse_retval(val, &spec->priv.retval, "__retval");
|
|
if (err)
|
|
goto cleanup;
|
|
spec->priv.execute = true;
|
|
spec->mode_mask |= PRIV;
|
|
} else if ((val = str_has_pfx(s, "test_retval_unpriv="))) {
|
|
err = parse_retval(val, &spec->unpriv.retval, "__retval_unpriv");
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
spec->unpriv.execute = true;
|
|
has_unpriv_retval = true;
|
|
} else if ((val = str_has_pfx(s, "test_log_level="))) {
|
|
err = parse_int(val, &spec->log_level, "test log level");
|
|
if (err)
|
|
goto cleanup;
|
|
} else if ((val = str_has_pfx(s, "test_prog_flags="))) {
|
|
clear = val[0] == '!';
|
|
if (clear)
|
|
val++;
|
|
|
|
if (strcmp(val, "BPF_F_STRICT_ALIGNMENT") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_STRICT_ALIGNMENT, clear);
|
|
} else if (strcmp(val, "BPF_F_ANY_ALIGNMENT") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_ANY_ALIGNMENT, clear);
|
|
} else if (strcmp(val, "BPF_F_TEST_RND_HI32") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_TEST_RND_HI32, clear);
|
|
} else if (strcmp(val, "BPF_F_TEST_STATE_FREQ") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_TEST_STATE_FREQ, clear);
|
|
} else if (strcmp(val, "BPF_F_SLEEPABLE") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_SLEEPABLE, clear);
|
|
} else if (strcmp(val, "BPF_F_XDP_HAS_FRAGS") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_XDP_HAS_FRAGS, clear);
|
|
} else if (strcmp(val, "BPF_F_TEST_REG_INVARIANTS") == 0) {
|
|
update_flags(&spec->prog_flags, BPF_F_TEST_REG_INVARIANTS, clear);
|
|
} else /* assume numeric value */ {
|
|
err = parse_int(val, &flags, "test prog flags");
|
|
if (err)
|
|
goto cleanup;
|
|
update_flags(&spec->prog_flags, flags, clear);
|
|
}
|
|
} else if ((val = str_has_pfx(s, "test_arch="))) {
|
|
if (strcmp(val, "X86_64") == 0) {
|
|
arch = ARCH_X86_64;
|
|
} else if (strcmp(val, "ARM64") == 0) {
|
|
arch = ARCH_ARM64;
|
|
} else if (strcmp(val, "RISCV64") == 0) {
|
|
arch = ARCH_RISCV64;
|
|
} else if (strcmp(val, "s390x") == 0) {
|
|
arch = ARCH_S390X;
|
|
} else if (strcmp(val, "LOONGARCH") == 0) {
|
|
arch = ARCH_LOONGARCH;
|
|
} else {
|
|
PRINT_FAIL("bad arch spec: '%s'\n", val);
|
|
err = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
arch_mask |= arch;
|
|
collect_jit = get_current_arch() == arch;
|
|
unpriv_jit_on_next_line = true;
|
|
jit_on_next_line = true;
|
|
} else if ((val = str_has_pfx(s, "test_btf_path="))) {
|
|
spec->btf_custom_path = val;
|
|
} else if ((val = str_has_pfx(s, "test_btf_func_path="))) {
|
|
spec->btf_custom_func_path = val;
|
|
} else if ((val = str_has_pfx(s, "test_caps_unpriv="))) {
|
|
err = parse_caps(val, &spec->unpriv.caps, "test caps");
|
|
if (err)
|
|
goto cleanup;
|
|
spec->mode_mask |= UNPRIV;
|
|
} else if ((val = str_has_pfx(s, "load_mode="))) {
|
|
if (strcmp(val, "jited") == 0) {
|
|
load_mask = JITED;
|
|
} else if (strcmp(val, "no_jited") == 0) {
|
|
load_mask = NO_JITED;
|
|
} else {
|
|
PRINT_FAIL("bad load spec: '%s'", val);
|
|
err = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
} else if ((msg = str_has_pfx(s, "test_expect_stderr="))) {
|
|
err = push_disasm_msg(msg, &stderr_on_next_line,
|
|
&spec->priv.stderr);
|
|
if (err)
|
|
goto cleanup;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_stderr_unpriv="))) {
|
|
err = push_disasm_msg(msg, &unpriv_stderr_on_next_line,
|
|
&spec->unpriv.stderr);
|
|
if (err)
|
|
goto cleanup;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_stdout="))) {
|
|
err = push_disasm_msg(msg, &stdout_on_next_line,
|
|
&spec->priv.stdout);
|
|
if (err)
|
|
goto cleanup;
|
|
} else if ((msg = str_has_pfx(s, "test_expect_stdout_unpriv="))) {
|
|
err = push_disasm_msg(msg, &unpriv_stdout_on_next_line,
|
|
&spec->unpriv.stdout);
|
|
if (err)
|
|
goto cleanup;
|
|
} else if ((val = str_has_pfx(s, "test_linear_size="))) {
|
|
switch (bpf_program__type(prog)) {
|
|
case BPF_PROG_TYPE_SCHED_ACT:
|
|
case BPF_PROG_TYPE_SCHED_CLS:
|
|
case BPF_PROG_TYPE_CGROUP_SKB:
|
|
err = parse_int(val, &spec->linear_sz, "test linear size");
|
|
if (err)
|
|
goto cleanup;
|
|
break;
|
|
default:
|
|
PRINT_FAIL("__linear_size for unsupported program type");
|
|
err = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
spec->arch_mask = arch_mask ?: -1;
|
|
spec->load_mask = load_mask ?: (JITED | NO_JITED);
|
|
|
|
if (spec->mode_mask == 0)
|
|
spec->mode_mask = PRIV;
|
|
|
|
if (spec->mode_mask & PRIV) {
|
|
spec->priv.name = strdup(spec->prog_name);
|
|
if (!spec->priv.name) {
|
|
PRINT_FAIL("failed to allocate memory for priv.name\n");
|
|
err = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (description) {
|
|
spec->priv.description = strdup(description);
|
|
if (!spec->priv.description) {
|
|
PRINT_FAIL("failed to allocate memory for priv.description\n");
|
|
err = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spec->mode_mask & UNPRIV) {
|
|
int name_len = strlen(spec->prog_name);
|
|
const char *suffix = " @unpriv";
|
|
int suffix_len = strlen(suffix);
|
|
char *name;
|
|
|
|
name = malloc(name_len + suffix_len + 1);
|
|
if (!name) {
|
|
PRINT_FAIL("failed to allocate memory for unpriv.name\n");
|
|
err = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
strcpy(name, spec->prog_name);
|
|
strcpy(&name[name_len], suffix);
|
|
spec->unpriv.name = name;
|
|
|
|
if (description) {
|
|
int descr_len = strlen(description);
|
|
char *descr;
|
|
|
|
descr = malloc(descr_len + suffix_len + 1);
|
|
if (!descr) {
|
|
PRINT_FAIL("failed to allocate memory for unpriv.description\n");
|
|
err = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
strcpy(descr, description);
|
|
strcpy(&descr[descr_len], suffix);
|
|
spec->unpriv.description = descr;
|
|
}
|
|
}
|
|
|
|
if (spec->mode_mask & (PRIV | UNPRIV)) {
|
|
if (!has_unpriv_result)
|
|
spec->unpriv.expect_failure = spec->priv.expect_failure;
|
|
|
|
if (!has_unpriv_retval) {
|
|
spec->unpriv.retval = spec->priv.retval;
|
|
spec->unpriv.execute = spec->priv.execute;
|
|
}
|
|
|
|
if (spec->unpriv.expect_msgs.cnt == 0)
|
|
clone_msgs(&spec->priv.expect_msgs, &spec->unpriv.expect_msgs);
|
|
if (spec->unpriv.expect_xlated.cnt == 0)
|
|
clone_msgs(&spec->priv.expect_xlated, &spec->unpriv.expect_xlated);
|
|
if (spec->unpriv.jited.cnt == 0)
|
|
clone_msgs(&spec->priv.jited, &spec->unpriv.jited);
|
|
if (spec->unpriv.stderr.cnt == 0)
|
|
clone_msgs(&spec->priv.stderr, &spec->unpriv.stderr);
|
|
if (spec->unpriv.stdout.cnt == 0)
|
|
clone_msgs(&spec->priv.stdout, &spec->unpriv.stdout);
|
|
}
|
|
|
|
spec->valid = true;
|
|
|
|
free(tags);
|
|
return 0;
|
|
|
|
cleanup:
|
|
free(tags);
|
|
free_test_spec(spec);
|
|
return err;
|
|
}
|
|
|
|
static void prepare_case(struct test_loader *tester,
|
|
struct test_spec *spec,
|
|
struct bpf_object *obj,
|
|
struct bpf_program *prog)
|
|
{
|
|
int min_log_level = 0, prog_flags;
|
|
|
|
if (env.verbosity > VERBOSE_NONE)
|
|
min_log_level = 1;
|
|
if (env.verbosity > VERBOSE_VERY)
|
|
min_log_level = 2;
|
|
|
|
bpf_program__set_log_buf(prog, tester->log_buf, tester->log_buf_sz);
|
|
|
|
/* Make sure we set at least minimal log level, unless test requires
|
|
* even higher level already. Make sure to preserve independent log
|
|
* level 4 (verifier stats), though.
|
|
*/
|
|
if ((spec->log_level & 3) < min_log_level)
|
|
bpf_program__set_log_level(prog, (spec->log_level & 4) | min_log_level);
|
|
else
|
|
bpf_program__set_log_level(prog, spec->log_level);
|
|
|
|
prog_flags = bpf_program__flags(prog);
|
|
bpf_program__set_flags(prog, prog_flags | spec->prog_flags);
|
|
|
|
tester->log_buf[0] = '\0';
|
|
}
|
|
|
|
static void emit_verifier_log(const char *log_buf, bool force)
|
|
{
|
|
if (!force && env.verbosity == VERBOSE_NONE)
|
|
return;
|
|
fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log_buf);
|
|
}
|
|
|
|
static void emit_xlated(const char *xlated, bool force)
|
|
{
|
|
if (!force && env.verbosity == VERBOSE_NONE)
|
|
return;
|
|
fprintf(stdout, "XLATED:\n=============\n%s=============\n", xlated);
|
|
}
|
|
|
|
static void emit_jited(const char *jited, bool force)
|
|
{
|
|
if (!force && env.verbosity == VERBOSE_NONE)
|
|
return;
|
|
fprintf(stdout, "JITED:\n=============\n%s=============\n", jited);
|
|
}
|
|
|
|
static void emit_stderr(const char *stderr, bool force)
|
|
{
|
|
if (!force && env.verbosity == VERBOSE_NONE)
|
|
return;
|
|
fprintf(stdout, "STDERR:\n=============\n%s=============\n", stderr);
|
|
}
|
|
|
|
static void verify_stderr(int prog_fd, struct expected_msgs *msgs)
|
|
{
|
|
LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts);
|
|
char *buf;
|
|
int ret;
|
|
|
|
if (!msgs->cnt)
|
|
return;
|
|
|
|
buf = malloc(TEST_LOADER_LOG_BUF_SZ);
|
|
if (!ASSERT_NEQ(buf, NULL, "malloc"))
|
|
return;
|
|
|
|
ret = bpf_prog_stream_read(prog_fd, 2, buf, TEST_LOADER_LOG_BUF_SZ - 1,
|
|
&ropts);
|
|
if (ret > 0) {
|
|
buf[ret] = '\0';
|
|
emit_stderr(buf, false);
|
|
validate_msgs(buf, msgs, emit_stderr);
|
|
} else {
|
|
ASSERT_GT(ret, 0, "stderr stream read");
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
void verify_test_stderr(struct bpf_object *obj, struct bpf_program *prog)
|
|
{
|
|
struct test_spec spec = {};
|
|
|
|
if (parse_test_spec(NULL, obj, prog, &spec))
|
|
return;
|
|
|
|
verify_stderr(bpf_program__fd(prog), &spec.priv.stderr);
|
|
free_test_spec(&spec);
|
|
}
|
|
|
|
static void emit_stdout(const char *bpf_stdout, bool force)
|
|
{
|
|
if (!force && env.verbosity == VERBOSE_NONE)
|
|
return;
|
|
fprintf(stdout, "STDOUT:\n=============\n%s=============\n", bpf_stdout);
|
|
}
|
|
|
|
static const char *match_msg(struct expect_msg *msg, const char **log)
|
|
{
|
|
const char *match = NULL;
|
|
regmatch_t reg_match[1];
|
|
int err;
|
|
|
|
if (!msg->is_regex) {
|
|
match = strstr(*log, msg->substr);
|
|
if (match)
|
|
*log = match + strlen(msg->substr);
|
|
} else {
|
|
err = regexec(&msg->regex, *log, 1, reg_match, 0);
|
|
if (err == 0) {
|
|
match = *log + reg_match[0].rm_so;
|
|
*log += reg_match[0].rm_eo;
|
|
}
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static int count_lines(const char *start, const char *end)
|
|
{
|
|
const char *tmp;
|
|
int n = 0;
|
|
|
|
for (tmp = start; tmp < end; ++tmp)
|
|
if (*tmp == '\n')
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
struct match {
|
|
const char *start;
|
|
const char *end;
|
|
int line;
|
|
};
|
|
|
|
/*
|
|
* Positive messages are matched sequentially, each next message
|
|
* is looked for starting from the end of a previous matched one.
|
|
*/
|
|
static void match_positive_msgs(const char *log, struct expected_msgs *msgs, struct match *matches)
|
|
{
|
|
const char *prev_match;
|
|
int i, line;
|
|
|
|
prev_match = log;
|
|
line = 0;
|
|
for (i = 0; i < msgs->cnt; i++) {
|
|
struct expect_msg *msg = &msgs->patterns[i];
|
|
const char *match = NULL;
|
|
|
|
if (msg->negative)
|
|
continue;
|
|
|
|
match = match_msg(msg, &log);
|
|
if (match) {
|
|
line += count_lines(prev_match, match);
|
|
matches[i].start = match;
|
|
matches[i].end = log;
|
|
matches[i].line = line;
|
|
prev_match = match;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Each negative messages N located between positive messages P1 and P2
|
|
* is matched in the span P1.end .. P2.start. Consequently, negative messages
|
|
* are unordered within the span.
|
|
*/
|
|
static void match_negative_msgs(const char *log, struct expected_msgs *msgs, struct match *matches)
|
|
{
|
|
const char *start = log, *end, *next, *match;
|
|
const char *log_end = log + strlen(log);
|
|
int i, j, next_positive;
|
|
|
|
for (i = 0; i < msgs->cnt; i++) {
|
|
struct expect_msg *msg = &msgs->patterns[i];
|
|
|
|
/* positive message bumps span start */
|
|
if (!msg->negative) {
|
|
start = matches[i].end ?: start;
|
|
continue;
|
|
}
|
|
|
|
/* count stride of negative patterns and adjust span end */
|
|
end = log_end;
|
|
for (next_positive = i + 1; next_positive < msgs->cnt; next_positive++) {
|
|
if (!msgs->patterns[next_positive].negative) {
|
|
end = matches[next_positive].start;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* try matching negative messages within identified span */
|
|
for (j = i; j < next_positive; j++) {
|
|
next = start;
|
|
match = match_msg(msg, &next);
|
|
if (match && next <= end) {
|
|
matches[j].start = match;
|
|
matches[j].end = next;
|
|
}
|
|
}
|
|
|
|
/* -1 to account for i++ */
|
|
i = next_positive - 1;
|
|
}
|
|
}
|
|
|
|
void validate_msgs(const char *log_buf, struct expected_msgs *msgs,
|
|
void (*emit_fn)(const char *buf, bool force))
|
|
{
|
|
struct match matches[msgs->cnt];
|
|
struct match *prev_match = NULL;
|
|
int i, j;
|
|
|
|
memset(matches, 0, sizeof(*matches) * msgs->cnt);
|
|
match_positive_msgs(log_buf, msgs, matches);
|
|
match_negative_msgs(log_buf, msgs, matches);
|
|
|
|
for (i = 0; i < msgs->cnt; i++) {
|
|
struct expect_msg *msg = &msgs->patterns[i];
|
|
struct match *match = &matches[i];
|
|
const char *pat_status;
|
|
bool unexpected;
|
|
bool wrong_line;
|
|
bool no_match;
|
|
|
|
no_match = !msg->negative && !match->start;
|
|
wrong_line = !msg->negative &&
|
|
msg->on_next_line &&
|
|
prev_match && prev_match->line + 1 != match->line;
|
|
unexpected = msg->negative && match->start;
|
|
if (no_match || wrong_line || unexpected) {
|
|
PRINT_FAIL("expect_msg\n");
|
|
if (env.verbosity == VERBOSE_NONE)
|
|
emit_fn(log_buf, true /*force*/);
|
|
for (j = 0; j <= i; j++) {
|
|
msg = &msgs->patterns[j];
|
|
if (j < i)
|
|
pat_status = "MATCHED ";
|
|
else if (wrong_line)
|
|
pat_status = "WRONG LINE";
|
|
else if (no_match)
|
|
pat_status = "EXPECTED ";
|
|
else
|
|
pat_status = "UNEXPECTED";
|
|
msg = &msgs->patterns[j];
|
|
fprintf(stderr, "%s %s: '%s'\n",
|
|
pat_status,
|
|
msg->is_regex ? " REGEX" : "SUBSTR",
|
|
msg->substr);
|
|
}
|
|
if (wrong_line) {
|
|
fprintf(stderr,
|
|
"expecting match at line %d, actual match is at line %d\n",
|
|
prev_match->line + 1, match->line);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!msg->negative)
|
|
prev_match = match;
|
|
}
|
|
}
|
|
|
|
struct cap_state {
|
|
__u64 old_caps;
|
|
bool initialized;
|
|
};
|
|
|
|
static int drop_capabilities(struct cap_state *caps)
|
|
{
|
|
const __u64 caps_to_drop = (1ULL << CAP_SYS_ADMIN | 1ULL << CAP_NET_ADMIN |
|
|
1ULL << CAP_PERFMON | 1ULL << CAP_BPF);
|
|
int err;
|
|
|
|
err = cap_disable_effective(caps_to_drop, &caps->old_caps);
|
|
if (err) {
|
|
PRINT_FAIL("failed to drop capabilities: %i, %s\n", err, strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
caps->initialized = true;
|
|
return 0;
|
|
}
|
|
|
|
static int restore_capabilities(struct cap_state *caps)
|
|
{
|
|
int err;
|
|
|
|
if (!caps->initialized)
|
|
return 0;
|
|
|
|
err = cap_enable_effective(caps->old_caps, NULL);
|
|
if (err)
|
|
PRINT_FAIL("failed to restore capabilities: %i, %s\n", err, strerror(-err));
|
|
caps->initialized = false;
|
|
return err;
|
|
}
|
|
|
|
static bool can_execute_unpriv(struct test_loader *tester, struct test_spec *spec)
|
|
{
|
|
if (sysctl_unpriv_disabled < 0)
|
|
sysctl_unpriv_disabled = get_unpriv_disabled() ? 1 : 0;
|
|
if (sysctl_unpriv_disabled)
|
|
return false;
|
|
if ((spec->prog_flags & BPF_F_ANY_ALIGNMENT) && !EFFICIENT_UNALIGNED_ACCESS)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool is_unpriv_capable_map(struct bpf_map *map)
|
|
{
|
|
enum bpf_map_type type;
|
|
__u32 flags;
|
|
|
|
type = bpf_map__type(map);
|
|
|
|
switch (type) {
|
|
case BPF_MAP_TYPE_HASH:
|
|
case BPF_MAP_TYPE_PERCPU_HASH:
|
|
case BPF_MAP_TYPE_HASH_OF_MAPS:
|
|
flags = bpf_map__map_flags(map);
|
|
return !(flags & BPF_F_ZERO_SEED);
|
|
case BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE:
|
|
case BPF_MAP_TYPE_ARRAY:
|
|
case BPF_MAP_TYPE_RINGBUF:
|
|
case BPF_MAP_TYPE_PROG_ARRAY:
|
|
case BPF_MAP_TYPE_CGROUP_ARRAY:
|
|
case BPF_MAP_TYPE_PERCPU_ARRAY:
|
|
case BPF_MAP_TYPE_USER_RINGBUF:
|
|
case BPF_MAP_TYPE_ARRAY_OF_MAPS:
|
|
case BPF_MAP_TYPE_CGROUP_STORAGE:
|
|
case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int do_prog_test_run(int fd_prog, int *retval, bool empty_opts, int linear_sz)
|
|
{
|
|
__u8 tmp_out[TEST_DATA_LEN << 2] = {};
|
|
__u8 tmp_in[TEST_DATA_LEN] = {};
|
|
struct __sk_buff ctx = {};
|
|
int err, saved_errno;
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = tmp_in,
|
|
.data_size_in = sizeof(tmp_in),
|
|
.data_out = tmp_out,
|
|
.data_size_out = sizeof(tmp_out),
|
|
.repeat = 1,
|
|
);
|
|
|
|
if (linear_sz) {
|
|
ctx.data_end = linear_sz;
|
|
topts.ctx_in = &ctx;
|
|
topts.ctx_size_in = sizeof(ctx);
|
|
}
|
|
|
|
if (empty_opts) {
|
|
memset(&topts, 0, sizeof(struct bpf_test_run_opts));
|
|
topts.sz = sizeof(struct bpf_test_run_opts);
|
|
}
|
|
err = bpf_prog_test_run_opts(fd_prog, &topts);
|
|
saved_errno = errno;
|
|
|
|
if (err) {
|
|
PRINT_FAIL("FAIL: Unexpected bpf_prog_test_run error: %d (%s) ",
|
|
saved_errno, strerror(saved_errno));
|
|
return err;
|
|
}
|
|
|
|
ASSERT_OK(0, "bpf_prog_test_run");
|
|
*retval = topts.retval;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool should_do_test_run(struct test_spec *spec, struct test_subspec *subspec)
|
|
{
|
|
if (!subspec->execute)
|
|
return false;
|
|
|
|
if (subspec->expect_failure)
|
|
return false;
|
|
|
|
if ((spec->prog_flags & BPF_F_ANY_ALIGNMENT) && !EFFICIENT_UNALIGNED_ACCESS) {
|
|
if (env.verbosity != VERBOSE_NONE)
|
|
printf("alignment prevents execution\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Get a disassembly of BPF program after verifier applies all rewrites */
|
|
static int get_xlated_program_text(int prog_fd, char *text, size_t text_sz)
|
|
{
|
|
struct bpf_insn *insn_start = NULL, *insn, *insn_end;
|
|
__u32 insns_cnt = 0, i;
|
|
char buf[64];
|
|
FILE *out = NULL;
|
|
int err;
|
|
|
|
err = get_xlated_program(prog_fd, &insn_start, &insns_cnt);
|
|
if (!ASSERT_OK(err, "get_xlated_program"))
|
|
goto out;
|
|
out = fmemopen(text, text_sz, "w");
|
|
if (!ASSERT_OK_PTR(out, "open_memstream"))
|
|
goto out;
|
|
insn_end = insn_start + insns_cnt;
|
|
insn = insn_start;
|
|
while (insn < insn_end) {
|
|
i = insn - insn_start;
|
|
insn = disasm_insn(insn, buf, sizeof(buf));
|
|
fprintf(out, "%d: %s\n", i, buf);
|
|
}
|
|
fflush(out);
|
|
|
|
out:
|
|
free(insn_start);
|
|
if (out)
|
|
fclose(out);
|
|
return err;
|
|
}
|
|
|
|
/* Read the bpf stream corresponding to the stream_id */
|
|
static int get_stream(int stream_id, int prog_fd, char *text, size_t text_sz)
|
|
{
|
|
LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts);
|
|
int ret;
|
|
|
|
ret = bpf_prog_stream_read(prog_fd, stream_id, text, text_sz, &ropts);
|
|
ASSERT_GT(ret, 0, "stream read");
|
|
text[ret] = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Fix up the program's BTF using BTF from a separate file.
|
|
*
|
|
* For __naked subprogs, clang drops parameter names from BTF. Find FUNC
|
|
* entries with anonymous parameters and replace their FUNC_PROTO with the
|
|
* properly-named version from the custom file.
|
|
*/
|
|
static int fixup_btf_from_path(struct bpf_object *obj, const char *path)
|
|
{
|
|
struct btf *prog_btf, *custom_btf;
|
|
__u32 i, j, cnt, custom_cnt;
|
|
int err = 0;
|
|
|
|
prog_btf = bpf_object__btf(obj);
|
|
if (!prog_btf)
|
|
return 0;
|
|
|
|
custom_btf = btf__parse(path, NULL);
|
|
if (!ASSERT_OK_PTR(custom_btf, "parse_custom_btf"))
|
|
return -EINVAL;
|
|
|
|
cnt = btf__type_cnt(prog_btf);
|
|
custom_cnt = btf__type_cnt(custom_btf);
|
|
|
|
/* Fix up FUNC entries with anonymous params.
|
|
* Save all data from prog_btf BEFORE calling btf__add_*,
|
|
* since those calls may reallocate the BTF data buffer
|
|
* and invalidate any pointers obtained from btf__type_by_id.
|
|
*/
|
|
for (i = 1; i < cnt; i++) {
|
|
const struct btf_type *t = btf__type_by_id(prog_btf, i);
|
|
const struct btf_type *fp, *custom_t, *custom_fp;
|
|
const struct btf_param *params, *custom_params;
|
|
__u32 ret_type_id, vlen;
|
|
__u32 *prog_param_types = NULL;
|
|
const char *name;
|
|
int new_proto_id;
|
|
|
|
if (!btf_is_func(t))
|
|
continue;
|
|
|
|
fp = btf__type_by_id(prog_btf, t->type);
|
|
if (!fp || !btf_is_func_proto(fp) || btf_vlen(fp) == 0)
|
|
continue;
|
|
|
|
/* Check if any param is anonymous */
|
|
params = btf_params(fp);
|
|
if (params[0].name_off != 0)
|
|
continue;
|
|
|
|
/* Find matching FUNC by name in custom BTF */
|
|
name = btf__name_by_offset(prog_btf, t->name_off);
|
|
if (!name)
|
|
continue;
|
|
|
|
for (j = 1; j < custom_cnt; j++) {
|
|
const char *cname;
|
|
|
|
custom_t = btf__type_by_id(custom_btf, j);
|
|
if (!btf_is_func(custom_t))
|
|
continue;
|
|
cname = btf__name_by_offset(custom_btf, custom_t->name_off);
|
|
if (cname && strcmp(name, cname) == 0)
|
|
break;
|
|
}
|
|
if (j >= custom_cnt)
|
|
continue;
|
|
|
|
custom_fp = btf__type_by_id(custom_btf, custom_t->type);
|
|
if (!custom_fp || !btf_is_func_proto(custom_fp))
|
|
continue;
|
|
|
|
vlen = btf_vlen(fp);
|
|
if (vlen != btf_vlen(custom_fp))
|
|
continue;
|
|
|
|
/* Save data before btf__add_* calls invalidate pointers */
|
|
ret_type_id = fp->type;
|
|
prog_param_types = malloc(vlen * sizeof(*prog_param_types));
|
|
if (!prog_param_types) {
|
|
err = -ENOMEM;
|
|
break;
|
|
}
|
|
for (j = 0; j < vlen; j++)
|
|
prog_param_types[j] = params[j].type;
|
|
|
|
/* Add a new FUNC_PROTO: param names from custom, types from prog */
|
|
new_proto_id = btf__add_func_proto(prog_btf, ret_type_id);
|
|
if (new_proto_id < 0) {
|
|
err = new_proto_id;
|
|
free(prog_param_types);
|
|
break;
|
|
}
|
|
|
|
custom_params = btf_params(custom_fp);
|
|
for (j = 0; j < vlen; j++) {
|
|
const char *pname;
|
|
|
|
pname = btf__name_by_offset(custom_btf, custom_params[j].name_off);
|
|
err = btf__add_func_param(prog_btf, pname ?: "", prog_param_types[j]);
|
|
if (err)
|
|
break;
|
|
}
|
|
free(prog_param_types);
|
|
if (err)
|
|
break;
|
|
|
|
/* Update the FUNC to point to the new FUNC_PROTO (re-fetch
|
|
* since btf__add_* may have reallocated the data buffer).
|
|
*/
|
|
((struct btf_type *)btf__type_by_id(prog_btf, i))->type = new_proto_id;
|
|
}
|
|
|
|
btf__free(custom_btf);
|
|
return err;
|
|
}
|
|
|
|
/* this function is forced noinline and has short generic name to look better
|
|
* in test_progs output (in case of a failure)
|
|
*/
|
|
static noinline
|
|
void run_subtest(struct test_loader *tester,
|
|
struct bpf_object_open_opts *open_opts,
|
|
const void *obj_bytes,
|
|
size_t obj_byte_cnt,
|
|
struct test_spec *specs,
|
|
struct test_spec *spec,
|
|
bool unpriv)
|
|
{
|
|
struct test_subspec *subspec = unpriv ? &spec->unpriv : &spec->priv;
|
|
int current_runtime = is_jit_enabled() ? JITED : NO_JITED;
|
|
struct bpf_program *tprog = NULL, *tprog_iter;
|
|
struct bpf_link *link, *links[32] = {};
|
|
struct test_spec *spec_iter;
|
|
struct cap_state caps = {};
|
|
struct bpf_object *tobj;
|
|
struct bpf_map *map;
|
|
int retval, err, i;
|
|
int links_cnt = 0;
|
|
bool should_load;
|
|
|
|
if (!test__start_subtest_with_desc(subspec->name, subspec->description))
|
|
return;
|
|
|
|
if ((get_current_arch() & spec->arch_mask) == 0) {
|
|
test__skip();
|
|
return;
|
|
}
|
|
|
|
if ((current_runtime & spec->load_mask) == 0) {
|
|
test__skip();
|
|
return;
|
|
}
|
|
|
|
if (unpriv) {
|
|
if (!can_execute_unpriv(tester, spec)) {
|
|
test__skip();
|
|
test__end_subtest();
|
|
return;
|
|
}
|
|
if (drop_capabilities(&caps)) {
|
|
test__end_subtest();
|
|
return;
|
|
}
|
|
if (subspec->caps) {
|
|
err = cap_enable_effective(subspec->caps, NULL);
|
|
if (err) {
|
|
PRINT_FAIL("failed to set capabilities: %i, %s\n", err, strerror(-err));
|
|
goto subtest_cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Implicitly reset to NULL if next test case doesn't specify.
|
|
* btf_custom_func_path also serves as btf_custom_path for kfunc resolution.
|
|
*/
|
|
open_opts->btf_custom_path = spec->btf_custom_path;
|
|
if (!open_opts->btf_custom_path)
|
|
open_opts->btf_custom_path = spec->btf_custom_func_path;
|
|
|
|
tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, open_opts);
|
|
if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
|
|
goto subtest_cleanup;
|
|
|
|
/* Fix up __naked subprog BTF using a separate file with named params */
|
|
if (spec->btf_custom_func_path) {
|
|
err = fixup_btf_from_path(tobj, spec->btf_custom_func_path);
|
|
if (err) {
|
|
PRINT_FAIL("failed to fixup BTF from %s: %d\n",
|
|
spec->btf_custom_func_path, err);
|
|
goto tobj_cleanup;
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
bpf_object__for_each_program(tprog_iter, tobj) {
|
|
spec_iter = &specs[i++];
|
|
should_load = false;
|
|
|
|
if (spec_iter->valid) {
|
|
if (strcmp(bpf_program__name(tprog_iter), spec->prog_name) == 0) {
|
|
tprog = tprog_iter;
|
|
should_load = true;
|
|
}
|
|
|
|
if (spec_iter->auxiliary &&
|
|
spec_iter->mode_mask & (unpriv ? UNPRIV : PRIV))
|
|
should_load = true;
|
|
}
|
|
|
|
bpf_program__set_autoload(tprog_iter, should_load);
|
|
}
|
|
|
|
prepare_case(tester, spec, tobj, tprog);
|
|
|
|
/* By default bpf_object__load() automatically creates all
|
|
* maps declared in the skeleton. Some map types are only
|
|
* allowed in priv mode. Disable autoload for such maps in
|
|
* unpriv mode.
|
|
*/
|
|
bpf_object__for_each_map(map, tobj)
|
|
bpf_map__set_autocreate(map, !unpriv || is_unpriv_capable_map(map));
|
|
|
|
err = bpf_object__load(tobj);
|
|
if (subspec->expect_failure) {
|
|
if (!ASSERT_ERR(err, "unexpected_load_success")) {
|
|
emit_verifier_log(tester->log_buf, false /*force*/);
|
|
goto tobj_cleanup;
|
|
}
|
|
} else {
|
|
if (!ASSERT_OK(err, "unexpected_load_failure")) {
|
|
emit_verifier_log(tester->log_buf, true /*force*/);
|
|
goto tobj_cleanup;
|
|
}
|
|
}
|
|
emit_verifier_log(tester->log_buf, false /*force*/);
|
|
validate_msgs(tester->log_buf, &subspec->expect_msgs, emit_verifier_log);
|
|
|
|
/* Restore capabilities because the kernel will silently ignore requests
|
|
* for program info (such as xlated program text) if we are not
|
|
* bpf-capable. Also, for some reason test_verifier executes programs
|
|
* with all capabilities restored. Do the same here.
|
|
*/
|
|
if (restore_capabilities(&caps))
|
|
goto tobj_cleanup;
|
|
|
|
if (subspec->expect_xlated.cnt) {
|
|
err = get_xlated_program_text(bpf_program__fd(tprog),
|
|
tester->log_buf, tester->log_buf_sz);
|
|
if (err)
|
|
goto tobj_cleanup;
|
|
emit_xlated(tester->log_buf, false /*force*/);
|
|
validate_msgs(tester->log_buf, &subspec->expect_xlated, emit_xlated);
|
|
}
|
|
|
|
if (subspec->jited.cnt) {
|
|
err = get_jited_program_text(bpf_program__fd(tprog),
|
|
tester->log_buf, tester->log_buf_sz);
|
|
if (err == -EOPNOTSUPP) {
|
|
printf("%s:SKIP: jited programs disassembly is not supported,\n", __func__);
|
|
printf("%s:SKIP: tests are built w/o LLVM development libs\n", __func__);
|
|
test__skip();
|
|
goto tobj_cleanup;
|
|
}
|
|
if (!ASSERT_EQ(err, 0, "get_jited_program_text"))
|
|
goto tobj_cleanup;
|
|
emit_jited(tester->log_buf, false /*force*/);
|
|
validate_msgs(tester->log_buf, &subspec->jited, emit_jited);
|
|
}
|
|
|
|
if (should_do_test_run(spec, subspec)) {
|
|
/* Do bpf_map__attach_struct_ops() for each struct_ops map.
|
|
* This should trigger bpf_struct_ops->reg callback on kernel side.
|
|
*/
|
|
bpf_object__for_each_map(map, tobj) {
|
|
if (!bpf_map__autocreate(map) ||
|
|
bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
|
|
continue;
|
|
if (links_cnt >= ARRAY_SIZE(links)) {
|
|
PRINT_FAIL("too many struct_ops maps");
|
|
goto tobj_cleanup;
|
|
}
|
|
link = bpf_map__attach_struct_ops(map);
|
|
if (!link) {
|
|
PRINT_FAIL("bpf_map__attach_struct_ops failed for map %s: err=%d\n",
|
|
bpf_map__name(map), -errno);
|
|
goto tobj_cleanup;
|
|
}
|
|
links[links_cnt++] = link;
|
|
}
|
|
|
|
if (tester->pre_execution_cb) {
|
|
err = tester->pre_execution_cb(tobj);
|
|
if (err) {
|
|
PRINT_FAIL("pre_execution_cb failed: %d\n", err);
|
|
goto tobj_cleanup;
|
|
}
|
|
}
|
|
|
|
err = do_prog_test_run(bpf_program__fd(tprog), &retval,
|
|
bpf_program__type(tprog) == BPF_PROG_TYPE_SYSCALL ? true : false,
|
|
spec->linear_sz);
|
|
if (!err && retval != subspec->retval && subspec->retval != POINTER_VALUE) {
|
|
PRINT_FAIL("Unexpected retval: %d != %d\n", retval, subspec->retval);
|
|
goto tobj_cleanup;
|
|
}
|
|
|
|
verify_stderr(bpf_program__fd(tprog), &subspec->stderr);
|
|
|
|
if (subspec->stdout.cnt) {
|
|
err = get_stream(1, bpf_program__fd(tprog),
|
|
tester->log_buf, tester->log_buf_sz);
|
|
if (err <= 0) {
|
|
PRINT_FAIL("Unexpected retval from get_stream(): %d, errno = %d\n",
|
|
err, errno);
|
|
goto tobj_cleanup;
|
|
}
|
|
emit_stdout(tester->log_buf, false /*force*/);
|
|
validate_msgs(tester->log_buf, &subspec->stdout, emit_stdout);
|
|
}
|
|
|
|
/* redo bpf_map__attach_struct_ops for each test */
|
|
while (links_cnt > 0)
|
|
bpf_link__destroy(links[--links_cnt]);
|
|
}
|
|
|
|
tobj_cleanup:
|
|
while (links_cnt > 0)
|
|
bpf_link__destroy(links[--links_cnt]);
|
|
bpf_object__close(tobj);
|
|
subtest_cleanup:
|
|
test__end_subtest();
|
|
restore_capabilities(&caps);
|
|
}
|
|
|
|
static void process_subtest(struct test_loader *tester,
|
|
const char *skel_name,
|
|
skel_elf_bytes_fn elf_bytes_factory)
|
|
{
|
|
LIBBPF_OPTS(bpf_object_open_opts, open_opts, .object_name = skel_name);
|
|
struct test_spec *specs = NULL;
|
|
struct bpf_object *obj = NULL;
|
|
struct bpf_program *prog;
|
|
const void *obj_bytes;
|
|
int err, i, nr_progs;
|
|
size_t obj_byte_cnt;
|
|
|
|
if (tester_init(tester) < 0)
|
|
return; /* failed to initialize tester */
|
|
|
|
obj_bytes = elf_bytes_factory(&obj_byte_cnt);
|
|
obj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, &open_opts);
|
|
if (!ASSERT_OK_PTR(obj, "obj_open_mem"))
|
|
return;
|
|
|
|
nr_progs = 0;
|
|
bpf_object__for_each_program(prog, obj)
|
|
++nr_progs;
|
|
|
|
specs = calloc(nr_progs, sizeof(struct test_spec));
|
|
if (!ASSERT_OK_PTR(specs, "specs_alloc"))
|
|
return;
|
|
|
|
i = 0;
|
|
bpf_object__for_each_program(prog, obj) {
|
|
/* ignore tests for which we can't derive test specification */
|
|
err = parse_test_spec(tester, obj, prog, &specs[i++]);
|
|
if (err)
|
|
PRINT_FAIL("Can't parse test spec for program '%s'\n",
|
|
bpf_program__name(prog));
|
|
}
|
|
|
|
i = 0;
|
|
bpf_object__for_each_program(prog, obj) {
|
|
struct test_spec *spec = &specs[i++];
|
|
|
|
if (!spec->valid || spec->auxiliary)
|
|
continue;
|
|
|
|
if (spec->mode_mask & PRIV)
|
|
run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
|
|
specs, spec, false);
|
|
if (spec->mode_mask & UNPRIV)
|
|
run_subtest(tester, &open_opts, obj_bytes, obj_byte_cnt,
|
|
specs, spec, true);
|
|
|
|
}
|
|
|
|
for (i = 0; i < nr_progs; ++i)
|
|
free_test_spec(&specs[i]);
|
|
free(specs);
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
void test_loader__run_subtests(struct test_loader *tester,
|
|
const char *skel_name,
|
|
skel_elf_bytes_fn elf_bytes_factory)
|
|
{
|
|
/* see comment in run_subtest() for why we do this function nesting */
|
|
process_subtest(tester, skel_name, elf_bytes_factory);
|
|
}
|