// 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) {Lines 1–500 of 1586. Load more
// 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) {