From a781889d9e326adc7b93e03a99187dc6ba49174b Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Thu, 8 Feb 2024 15:25:07 -0800 Subject: [PATCH 01/10] mass_attach: eliminate vmlinux BTF assumption in func info Store `struct btf *`for each function explicitly. This opens up ability to have module BTF as a source of BTF information for some functions. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 34 +++++++++++++++++----------------- src/mass_attacher.h | 2 ++ src/retsnoop.c | 4 +--- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 98a3438..0c3d159 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -333,7 +333,7 @@ static int find_kprobe(const struct mass_attacher *att, const char *name, const static bool is_func_type_ok(const struct btf *btf, const struct btf_type *t); static int prepare_func(struct mass_attacher *att, const char *func_name, const char *module, - const struct btf_type *t, int btf_id); + const struct btf *btf, const struct btf_type *t, int btf_id); static int calibrate_features(struct mass_attacher *att); /* BPF map is assumed to have been sized to a single element */ @@ -470,7 +470,7 @@ int mass_attacher__prepare(struct mass_attacher *att) if (kprobe_idx >= 0 && att->kprobes[kprobe_idx].used) continue; - err = prepare_func(att, func_name, NULL, t, i); + err = prepare_func(att, func_name, NULL, att->vmlinux_btf, t, i); if (err) return err; } @@ -479,7 +479,8 @@ int mass_attacher__prepare(struct mass_attacher *att) if (att->kprobes[i].used) continue; - err = prepare_func(att, att->kprobes[i].name, att->kprobes[i].mod, NULL, 0); + err = prepare_func(att, att->kprobes[i].name, att->kprobes[i].mod, + NULL, NULL, 0); if (err) return err; } @@ -623,7 +624,7 @@ static bool ksym_eq(const struct ksym *ksym, const char *name, static int prepare_func(struct mass_attacher *att, const char *func_name, const char *module, - const struct btf_type *t, int btf_id) + const struct btf *btf, const struct btf_type *t, int btf_id) { const struct ksym * const *ksym_it, * const *tmp_it; char fn_desc_buf[512]; @@ -708,7 +709,7 @@ static int prepare_func(struct mass_attacher *att, return 0; } - if (att->use_fentries && !is_func_type_ok(att->vmlinux_btf, t)) { + if (att->use_fentries && !is_func_type_ok(btf, t)) { if (att->debug) printf("Function '%s' has prototype incompatible with fentry/fexit, skipping.\n", fn_desc); att->func_skip_cnt += ksym_cnt; @@ -737,7 +738,7 @@ static int prepare_func(struct mass_attacher *att, finfo = &att->func_infos[att->func_cnt]; memset(finfo, 0, sizeof(*finfo) * ksym_cnt); - arg_cnt = func_arg_cnt(att->vmlinux_btf, btf_id); + arg_cnt = func_arg_cnt(btf, btf_id); for (i = 0; i < ksym_cnt; i++, finfo++, ksym_it++) { const struct ksym *ksym = *ksym_it; @@ -747,6 +748,7 @@ static int prepare_func(struct mass_attacher *att, finfo->name = ksym->name; finfo->module = ksym->module; finfo->arg_cnt = arg_cnt; + finfo->btf = btf; finfo->btf_id = btf_id; if (att->use_fentries) { @@ -907,7 +909,7 @@ static int load_available_kprobes(struct mass_attacher *att) return 0; } -static int clone_prog(const struct bpf_program *prog, int attach_btf_id); +static int clone_prog(const struct bpf_program *prog, int btf_fd, int attach_btf_id); static bool is_ret_void(const struct btf *btf, int btf_id); int mass_attacher__load(struct mass_attacher *att) @@ -957,17 +959,19 @@ int mass_attacher__load(struct mass_attacher *att) } if (att->use_fentries) { - err = clone_prog(att->fentries[finfo->arg_cnt], finfo->btf_id); + int btf_fd = finfo->btf == att->vmlinux_btf ? 0 : btf__fd(finfo->btf); + + err = clone_prog(att->fentries[finfo->arg_cnt], btf_fd, finfo->btf_id); if (err < 0) { fprintf(stderr, "Failed to clone FENTRY BPF program for function '%s': %d\n", func_name, err); return err; } finfo->fentry_prog_fd = err; - if (is_ret_void(att->vmlinux_btf, finfo->btf_id)) - err = clone_prog(att->fexit_voids[finfo->arg_cnt], finfo->btf_id); + if (is_ret_void(finfo->btf, finfo->btf_id)) + err = clone_prog(att->fexit_voids[finfo->arg_cnt], btf_fd, finfo->btf_id); else - err = clone_prog(att->fexits[finfo->arg_cnt], finfo->btf_id); + err = clone_prog(att->fexits[finfo->arg_cnt], btf_fd, finfo->btf_id); if (err < 0) { fprintf(stderr, "Failed to clone FEXIT BPF program for function '%s': %d\n", func_name, err); return err; @@ -978,10 +982,11 @@ int mass_attacher__load(struct mass_attacher *att) return 0; } -static int clone_prog(const struct bpf_program *prog, int attach_btf_id) +static int clone_prog(const struct bpf_program *prog, int btf_fd, int attach_btf_id) { LIBBPF_OPTS(bpf_prog_load_opts, opts, .expected_attach_type = bpf_program__get_expected_attach_type(prog), + .attach_btf_obj_fd = btf_fd, .attach_btf_id = attach_btf_id, ); int fd; @@ -1229,11 +1234,6 @@ struct SKEL_NAME *mass_attacher__skeleton(const struct mass_attacher *att) return att->skel; } -const struct btf *mass_attacher__btf(const struct mass_attacher *att) -{ - return att->vmlinux_btf; -} - size_t mass_attacher__func_cnt(const struct mass_attacher *att) { return att->func_cnt; diff --git a/src/mass_attacher.h b/src/mass_attacher.h index 4ea8885..c7226ae 100644 --- a/src/mass_attacher.h +++ b/src/mass_attacher.h @@ -18,7 +18,9 @@ struct mass_attacher_func_info { long addr; long size; int arg_cnt; + int btf_id; + const struct btf *btf; int fentry_prog_fd; int fexit_prog_fd; diff --git a/src/retsnoop.c b/src/retsnoop.c index 6c80948..a63f048 100644 --- a/src/retsnoop.c +++ b/src/retsnoop.c @@ -1933,7 +1933,6 @@ int main(int argc, char **argv, char **envp) { long page_size = sysconf(_SC_PAGESIZE); struct mass_attacher_opts att_opts = {}; - const struct btf *vmlinux_btf = NULL; struct ksyms *ksyms = NULL; struct mass_attacher *att = NULL; struct retsnoop_bpf *skel = NULL; @@ -2210,7 +2209,6 @@ int main(int argc, char **argv, char **envp) } skel->data_func_infos = bpf_map__initial_value(skel->maps.data_func_infos, &tmp_n); - vmlinux_btf = mass_attacher__btf(att); for (i = 0; i < n; i++) { const struct mass_attacher_func_info *finfo; const struct glob *glob; @@ -2218,7 +2216,7 @@ int main(int argc, char **argv, char **envp) __u32 flags; finfo = mass_attacher__func(att, i); - flags = func_flags(finfo->name, vmlinux_btf, finfo->btf_id); + flags = func_flags(finfo->name, finfo->btf, finfo->btf_id); for (j = 0; j < env.entry_glob_cnt; j++) { glob = &env.entry_globs[j]; From 4926e48bdb43ba02d16f2d36c8e41344d1a7f305 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Thu, 8 Feb 2024 17:01:33 -0800 Subject: [PATCH 02/10] mass_attacher: filter out kprobes according to globs early Refactor how retsnoop collects kprobe information. Apply glob rules early on so that resulting set of attachable kprobes are small. We'll use this to figure out all kernel modules that are relevant when loading module BTFs. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 237 ++++++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 107 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 0c3d159..c332188 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -190,6 +190,14 @@ struct mass_attacher *mass_attacher__new(struct SKEL_NAME *skel, struct ksyms *k return att; } +static void cleanup_kprobe_info(struct kprobe_info *info) +{ + free(info->name); + free(info->mod); + info->name = NULL; + info->mod = NULL; +} + void mass_attacher__free(struct mass_attacher *att) { int i; @@ -218,10 +226,8 @@ void mass_attacher__free(struct mass_attacher *att) free(att->func_infos); if (att->kprobes) { - for (i = 0; i < att->kprobe_cnt; i++) { - free(att->kprobes[i].name); - free(att->kprobes[i].mod); - } + for (i = 0; i < att->kprobe_cnt; i++) + cleanup_kprobe_info(&att->kprobes[i]); free(att->kprobes); } @@ -326,14 +332,14 @@ int mass_attacher__deny_glob(struct mass_attacher *att, const char *glob, const } static int bump_rlimit(int resource, rlim_t max); -static int load_available_kprobes(struct mass_attacher *attacher); +static int load_matching_kprobes(struct mass_attacher *attacher); static int func_arg_cnt(const struct btf *btf, int id); -static int find_kprobe(const struct mass_attacher *att, const char *name, const char *module); -static bool is_func_type_ok(const struct btf *btf, const struct btf_type *t); -static int prepare_func(struct mass_attacher *att, - const char *func_name, const char *module, - const struct btf *btf, const struct btf_type *t, int btf_id); +static struct kprobe_info *find_kprobe(const struct mass_attacher *att, + const char *name, const char *module); +static bool is_func_type_ok(const struct btf *btf, int btf_id); +static int prepare_func(struct mass_attacher *att, struct kprobe_info *kp, + const struct btf *btf, int btf_id); static int calibrate_features(struct mass_attacher *att); /* BPF map is assumed to have been sized to a single element */ @@ -417,8 +423,8 @@ int mass_attacher__prepare(struct mass_attacher *att) !resize_map(att->skel->maps.data_running, cpu_cnt)) return -EINVAL; - /* Load names of possible kprobes */ - err = load_available_kprobes(att); + /* Load names of attachable kprobes matching glob specs */ + err = load_matching_kprobes(att); if (err) { fprintf(stderr, "Failed to read the list of available kprobes: %d\n", err); return err; @@ -457,8 +463,8 @@ int mass_attacher__prepare(struct mass_attacher *att) n = btf__type_cnt(att->vmlinux_btf); for (i = 1; i < n; i++) { const struct btf_type *t = btf__type_by_id(att->vmlinux_btf, i); + struct kprobe_info *kp; const char *func_name; - int kprobe_idx; if (!btf_is_func(t)) continue; @@ -466,21 +472,28 @@ int mass_attacher__prepare(struct mass_attacher *att) func_name = btf__str_by_offset(att->vmlinux_btf, t->name_off); /* check if we already processed a function with such name */ - kprobe_idx = find_kprobe(att, func_name, NULL); - if (kprobe_idx >= 0 && att->kprobes[kprobe_idx].used) + kp = find_kprobe(att, func_name, NULL); + if (!kp) { + if (att->debug_extra) + printf("Function '%s' is not attachable kprobe, skipping.\n", func_name); + continue; + } + if (kp->used) continue; - err = prepare_func(att, func_name, NULL, att->vmlinux_btf, t, i); + err = prepare_func(att, kp, att->vmlinux_btf, i); if (err) return err; } if (!att->use_fentries) { + struct kprobe_info *kp; + for (i = 0; i < att->kprobe_cnt; i++) { - if (att->kprobes[i].used) + kp = &att->kprobes[i]; + if (kp->used) continue; - err = prepare_func(att, att->kprobes[i].name, att->kprobes[i].mod, - NULL, NULL, 0); + err = prepare_func(att, kp, NULL, 0); if (err) return err; } @@ -622,24 +635,25 @@ static bool ksym_eq(const struct ksym *ksym, const char *name, return strcmp(ksym->name, name) == 0; } -static int prepare_func(struct mass_attacher *att, - const char *func_name, const char *module, - const struct btf *btf, const struct btf_type *t, int btf_id) +static int prepare_func(struct mass_attacher *att, struct kprobe_info *kp, + const struct btf *btf, int btf_id) { const struct ksym * const *ksym_it, * const *tmp_it; char fn_desc_buf[512]; const char *fn_desc; struct mass_attacher_func_info *finfo; - int i, arg_cnt, kprobe_idx, ksym_cnt, kprobe_cnt; + int i, arg_cnt, ksym_cnt; void *tmp; - fn_desc = func_name; - if (module) { - snprintf(fn_desc_buf, sizeof(fn_desc_buf), "%s [%s]", func_name, module); + kp->used = true; /* mark it as processed, no matter the outcome */ + + fn_desc = kp->name; + if (kp->mod) { + snprintf(fn_desc_buf, sizeof(fn_desc_buf), "%s [%s]", kp->name, kp->mod); fn_desc = fn_desc_buf; } - ksym_it = ksyms__get_symbol_iter(att->ksyms, func_name, module, KSYM_FUNC); + ksym_it = ksyms__get_symbol_iter(att->ksyms, kp->name, kp->mod, KSYM_FUNC); if (!ksym_it) { if (att->debug_extra) printf("Function '%s' not found in /proc/kallsyms! Skipping.\n", fn_desc); @@ -648,68 +662,18 @@ static int prepare_func(struct mass_attacher *att, } ksym_cnt = 0; - for (tmp_it = ksym_it; ksym_eq(*tmp_it, func_name, module, KSYM_FUNC); tmp_it++) { + for (tmp_it = ksym_it; ksym_eq(*tmp_it, kp->name, kp->mod, KSYM_FUNC); tmp_it++) { ksym_cnt++; } - /* any deny glob forces skipping a function */ - for (i = 0; i < att->deny_glob_cnt; i++) { - if (!full_glob_matches(att->deny_globs[i].glob, att->deny_globs[i].mod_glob, - func_name, module)) - continue; - - att->deny_globs[i].matches++; - - if (att->debug_extra) - printf("Function '%s' is denied by '%s' glob.\n", - fn_desc, att->deny_globs[i].glob); - att->func_skip_cnt += ksym_cnt; - return 0; - } - - /* if any allow glob is specified, function has to match one of them */ - if (att->allow_glob_cnt) { - bool found = false; - - for (i = 0; i < att->allow_glob_cnt; i++) { - if (!full_glob_matches(att->allow_globs[i].glob, att->allow_globs[i].mod_glob, - func_name, module)) - continue; - - att->allow_globs[i].matches++; - if (att->debug_extra) { - printf("Function '%s' is allowed by '%s' glob.\n", - fn_desc, att->allow_globs[i].glob); - } - - found = true; - break; - } - - if (!found) { - att->func_skip_cnt += ksym_cnt; - return 0; - } - } - - kprobe_idx = find_kprobe(att, func_name, module); - if (kprobe_idx < 0) { - if (att->debug_extra) - printf("Function '%s' is not attachable kprobe, skipping.\n", fn_desc); - att->func_skip_cnt += ksym_cnt; - return 0; - } - att->kprobes[kprobe_idx].used = true; - - kprobe_cnt = att->kprobes[kprobe_idx].cnt; - if (kprobe_cnt != ksym_cnt) { + if (kp->cnt != ksym_cnt) { printf("Function '%s' has mismatched %d ksyms vs %d attachable kprobe entries, skipping.\n", - fn_desc, ksym_cnt, kprobe_cnt); + fn_desc, ksym_cnt, kp->cnt); att->func_skip_cnt += ksym_cnt; return 0; } - if (att->use_fentries && !is_func_type_ok(btf, t)) { + if (att->use_fentries && !is_func_type_ok(btf, btf_id)) { if (att->debug) printf("Function '%s' has prototype incompatible with fentry/fexit, skipping.\n", fn_desc); att->func_skip_cnt += ksym_cnt; @@ -826,12 +790,13 @@ static bool kprobe_eq(const struct kprobe_info *k1, const struct kprobe_info *k2 return strcmp(k1->name, k2->name) == 0; } -static int load_available_kprobes(struct mass_attacher *att) +static int load_matching_kprobes(struct mass_attacher *att) { static char sym_buf[256], mod_buf[128], *mod; const char *fname = tracefs_available_filter_functions(); struct kprobe_info *k; - int i, j, cnt, err, orig_cnt; + int i, j, cnt, err; + int orig_cnt, filter_cnt; void *tmp; FILE *f; @@ -875,19 +840,82 @@ static int load_available_kprobes(struct mass_attacher *att) return -ENOMEM; } } + orig_cnt = att->kprobe_cnt; + + /* filter kprobes according to specified globs */ + for (i = 0; i < att->kprobe_cnt;) { + const char *name = att->kprobes[i].name; + const char *mod = att->kprobes[i].mod; + const char *name_glob, *mod_glob; + bool keep = false;; + + for (j = 0; j < att->deny_glob_cnt; j++) { + name_glob = att->deny_globs[j].glob; + mod_glob = att->deny_globs[j].mod_glob; + if (!full_glob_matches(name_glob, mod_glob, name, mod)) + continue; + + att->deny_globs[j].matches++; + + if (att->debug_extra) { + printf("Function '%s%s%s%s' is denied by '%s%s%s%s' glob.\n", + name, + mod ? " [" : "", mod ?: "", mod ? "]" : "", + name_glob, + mod_glob ? " [" : "", mod_glob ?: "", mod_glob ? "]" : ""); + } + + keep = false; + goto kprobe_verdict; + } + + /* if any allow glob is specified, function has to match one of them */ + if (att->allow_glob_cnt == 0) { + keep = true; + goto kprobe_verdict; + } + + for (j = 0; j < att->allow_glob_cnt; j++) { + name_glob = att->allow_globs[j].glob; + mod_glob = att->allow_globs[j].mod_glob; + if (!full_glob_matches(name_glob, mod_glob, name, mod)) + continue; + + att->allow_globs[j].matches++; + if (att->debug_extra) { + printf("Function '%s%s%s%s' is allowed by '%s%s%s%s' glob.\n", + name, + mod ? " [" : "", mod ?: "", mod ? "]" : "", + name_glob, + mod_glob ? " [" : "", mod_glob ?: "", mod_glob ? "]" : ""); + } + + keep = true; + goto kprobe_verdict; + } + +kprobe_verdict: + if (keep) { + i++; + continue; + } + + /* clean up memory and swap in last element */ + cleanup_kprobe_info(&att->kprobes[i]); + att->kprobes[i] = att->kprobes[att->kprobe_cnt - 1]; + att->kprobe_cnt--; + + att->func_skip_cnt += 1; + } + filter_cnt = att->kprobe_cnt; qsort(att->kprobes, att->kprobe_cnt, sizeof(*att->kprobes), kprobe_by_mod_name_cmp); - orig_cnt = att->kprobe_cnt; for (i = 0, j = 0; j < att->kprobe_cnt; j++) { if (kprobe_eq(&att->kprobes[i], &att->kprobes[j])) { att->kprobes[i].cnt++; - if (i != j) { - free(att->kprobes[j].name); - free(att->kprobes[j].mod); - att->kprobes[j].name = NULL; - att->kprobes[j].mod = NULL; - } + if (i != j) + cleanup_kprobe_info(&att->kprobes[j]); continue; } @@ -899,11 +927,12 @@ static int load_available_kprobes(struct mass_attacher *att) } att->kprobes[i].cnt++; } - att->kprobe_cnt = i + 1; + /* it could be that globs filtered out all discovered kprobes */ + att->kprobe_cnt = att->kprobe_cnt ? i + 1 : 0; if (att->verbose) { - printf("Discovered %d available kprobes, compacted to %d unique names!\n", - orig_cnt, att->kprobe_cnt); + printf("Discovered %d available kprobes, filtered down to %d, and compacted to %d unique names!\n", + orig_cnt, filter_cnt, att->kprobe_cnt); } return 0; @@ -1246,18 +1275,16 @@ const struct mass_attacher_func_info *mass_attacher__func(const struct mass_atta return &att->func_infos[id]; } -static int find_kprobe(const struct mass_attacher *att, const char *name, const char *module) +static struct kprobe_info *find_kprobe(const struct mass_attacher *att, + const char *name, const char *module) { /* we reuse kprobe_info as lookup key, so need to force non-const * strings; but that's ok, we are never freeing key's name/mod */ struct kprobe_info key = { .name = (char *)name, .mod = (char *)module }; - struct kprobe_info *k; - - k = bsearch(&key, att->kprobes, att->kprobe_cnt, sizeof(*att->kprobes), - kprobe_by_mod_name_cmp); - return k == NULL ? -1 : k - att->kprobes; + return bsearch(&key, att->kprobes, att->kprobe_cnt, sizeof(*att->kprobes), + kprobe_by_mod_name_cmp); } static int func_arg_cnt(const struct btf *btf, int id) @@ -1315,21 +1342,17 @@ static bool is_ret_void(const struct btf *btf, int btf_id) return t->type == 0; } -static bool is_func_type_ok(const struct btf *btf, const struct btf_type *t) +static bool is_func_type_ok(const struct btf *btf, int btf_id) { const struct btf_param *p; + const struct btf_type *t; int i; - t = btf__type_by_id(btf, t->type); + t = btf__type_by_id(btf, btf_id); /* FUNC */ + t = btf__type_by_id(btf, t->type); /* FUNC_PROTO */ if (btf_vlen(t) > MAX_FUNC_ARG_CNT) return false; - /* IGNORE VOID FUNCTIONS, THIS SHOULDN'T BE DONE IN GENERAL!!! */ - /* - if (!t->type) - return false; - */ - if (t->type && !is_ret_type_ok(btf, btf__type_by_id(btf, t->type))) return false; From d514b8ab1d66e69ea3061535976c1c4fb92bf451 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 10:31:33 -0800 Subject: [PATCH 03/10] mass_attacher: fetch module BTFs and use them for function flags Now that we know minimal set of module BTFs we need, try to fetch them, and if successful, use BTF information to augment kprobe information. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 77 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index c332188..d0805f0 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -100,6 +100,9 @@ struct mass_attacher { struct bpf_link *kentry_multi_link; struct bpf_link *kexit_multi_link; + struct btf **mod_btfs; + int mod_btf_cnt; + struct bpf_program *fentries[MAX_FUNC_ARG_CNT + 1]; struct bpf_program *fexits[MAX_FUNC_ARG_CNT + 1]; struct bpf_program *fexit_voids[MAX_FUNC_ARG_CNT + 1]; @@ -209,6 +212,9 @@ void mass_attacher__free(struct mass_attacher *att) att->skel->bss->ready = false; btf__free(att->vmlinux_btf); + for (i = 0; i < att->mod_btf_cnt; i++) + btf__free(att->mod_btfs[i]); + free(att->mod_btfs); bpf_link__destroy(att->kentry_multi_link); bpf_link__destroy(att->kexit_multi_link); @@ -360,7 +366,8 @@ static void *resize_map(struct bpf_map *map, size_t elem_cnt) int mass_attacher__prepare(struct mass_attacher *att) { - int err, i, n, cpu_cnt, tmp_cnt; + int err, i, j, n, cpu_cnt, tmp_cnt; + const char *mod; /* Load and cache /proc/kallsyms for IP <-> kfunc mapping */ att->ksyms = ksyms__load(); @@ -459,23 +466,25 @@ int mass_attacher__prepare(struct mass_attacher *att) fprintf(stderr, "Failed to load vmlinux BTF: %d\n", err); return -EINVAL; } + if (att->verbose) + printf("Loaded BTF for [vmlinux].\n"); n = btf__type_cnt(att->vmlinux_btf); for (i = 1; i < n; i++) { - const struct btf_type *t = btf__type_by_id(att->vmlinux_btf, i); + const struct btf_type *t; struct kprobe_info *kp; const char *func_name; + t = btf__type_by_id(att->vmlinux_btf, i); if (!btf_is_func(t)) continue; - func_name = btf__str_by_offset(att->vmlinux_btf, t->name_off); - /* check if we already processed a function with such name */ + func_name = btf__str_by_offset(att->vmlinux_btf, t->name_off); kp = find_kprobe(att, func_name, NULL); if (!kp) { if (att->debug_extra) - printf("Function '%s' is not attachable kprobe, skipping.\n", func_name); + printf("Function '%s [vmlinux]' is not attachable kprobe, skipping.\n", func_name); continue; } if (kp->used) @@ -485,6 +494,64 @@ int mass_attacher__prepare(struct mass_attacher *att) if (err) return err; } + /* now we go over all kprobes to figure out necessary kernel modules; + * all kprobes are sorted by module first, so it's easy to find all + * unique module names by keeping track of change in kprobe's module + */ + mod = NULL; + for (i = 0; i < att->kprobe_cnt; i++) { + struct kprobe_info *kp = &att->kprobes[i]; + struct btf *btf; + void *tmp; + + /* vmlinux, not a module */ + if (!kp->mod) + continue; + + /* same module */ + if (kp->mod && mod && strcmp(mod, kp->mod) == 0) + continue; + mod = kp->mod; + + btf = btf__load_module_btf(mod, att->vmlinux_btf); + if (!btf) { + err = -errno; + if (att->verbose) + printf("Failed to load BTF for module [%s]: %d\n", mod, err); + continue; + } + if (att->verbose) + printf("Loaded BTF for module [%s].\n", mod); + tmp = realloc(att->mod_btfs, (att->mod_btf_cnt + 1) * sizeof(*att->mod_btfs)); + if (!tmp) + return -ENOMEM; + att->mod_btfs = tmp; + att->mod_btfs[att->mod_btf_cnt] = btf; + att->mod_btf_cnt++; + + n = btf__type_cnt(btf); + for (j = btf__type_cnt(att->vmlinux_btf); j < n; j++) { + const struct btf_type *t; + const char *func_name; + + t = btf__type_by_id(btf, j); + if (!btf_is_func(t)) + continue; + + /* check if we already processed a function with such name */ + func_name = btf__str_by_offset(btf, t->name_off); + kp = find_kprobe(att, func_name, mod); + if (!kp) { + if (att->debug_extra) + printf("Function '%s [%s]' is not attachable kprobe, skipping.\n", func_name, mod); + continue; + } + + err = prepare_func(att, kp, btf, j); + if (err) + return err; + } + } if (!att->use_fentries) { struct kprobe_info *kp; From 229118eafcafeddbf042906ae2ac632ab54b9996 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 10:36:12 -0800 Subject: [PATCH 04/10] mass_attacher: remove duplicate feature calibration output We already emit this information from retsnoop.c, no need to duplicate code and pollute output. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index d0805f0..ab7213e 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -669,16 +669,6 @@ static int calibrate_features(struct mass_attacher *att) att->has_bpf_cookie = skel->bss->has_bpf_cookie; att->has_kprobe_multi = skel->bss->has_kprobe_multi; - if (att->debug) { - printf("Feature calibration results:\n" - "\tkretprobe IP offset: %d\n" - "\tfexit sleep fix: %s\n" - "\tfentry re-entry protection: %s\n", - att->kret_ip_off, - att->has_fexit_sleep_fix ? "yes" : "no", - att->has_fentry_protection ? "yes" : "no"); - } - calib_feat_bpf__destroy(skel); return 0; From 9a0d42f45ad9af2754935980b7a61570354f608f Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 11:00:10 -0800 Subject: [PATCH 05/10] retsnoop: normalize func/glob display name output Add NAME_MOD() macro that formats glob or function spec as either '' if it's not module-specific or ' []' if it is module-specific. Normalize all the output code to use this approach and not have to do ad-hoc work arounds to minimize conditional logic just to output this information consistently. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 101 ++++++++++++++++++-------------------------- src/retsnoop.c | 15 +++---- src/utils.h | 8 ++++ 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index ab7213e..3b9dab4 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -484,7 +484,7 @@ int mass_attacher__prepare(struct mass_attacher *att) kp = find_kprobe(att, func_name, NULL); if (!kp) { if (att->debug_extra) - printf("Function '%s [vmlinux]' is not attachable kprobe, skipping.\n", func_name); + printf("Function '%s' is not attachable kprobe, skipping.\n", func_name); continue; } if (kp->used) @@ -612,12 +612,14 @@ int mass_attacher__prepare(struct mass_attacher *att) if (att->debug) { for (i = 0; i < att->deny_glob_cnt; i++) { - printf("Deny glob '%s' matched %d functions.\n", - att->deny_globs[i].glob, att->deny_globs[i].matches); + printf("Deny glob '%s%s%s%s' matched %d functions.\n", + NAME_MOD(att->deny_globs[i].glob, att->deny_globs[i].mod_glob), + att->deny_globs[i].matches); } for (i = 0; i < att->allow_glob_cnt; i++) { - printf("Allow glob '%s' matched %d functions.\n", - att->allow_globs[i].glob, att->allow_globs[i].matches); + printf("Allow glob '%s%s%s%s' matched %d functions.\n", + NAME_MOD(att->allow_globs[i].glob, att->allow_globs[i].mod_glob), + att->allow_globs[i].matches); } } } @@ -696,24 +698,17 @@ static int prepare_func(struct mass_attacher *att, struct kprobe_info *kp, const struct btf *btf, int btf_id) { const struct ksym * const *ksym_it, * const *tmp_it; - char fn_desc_buf[512]; - const char *fn_desc; struct mass_attacher_func_info *finfo; int i, arg_cnt, ksym_cnt; void *tmp; kp->used = true; /* mark it as processed, no matter the outcome */ - fn_desc = kp->name; - if (kp->mod) { - snprintf(fn_desc_buf, sizeof(fn_desc_buf), "%s [%s]", kp->name, kp->mod); - fn_desc = fn_desc_buf; - } - ksym_it = ksyms__get_symbol_iter(att->ksyms, kp->name, kp->mod, KSYM_FUNC); if (!ksym_it) { if (att->debug_extra) - printf("Function '%s' not found in /proc/kallsyms! Skipping.\n", fn_desc); + printf("Function '%s%s%s%s' not found in /proc/kallsyms! Skipping.\n", + NAME_MOD(kp->name, kp->mod)); att->func_skip_cnt++; return 0; } @@ -724,30 +719,34 @@ static int prepare_func(struct mass_attacher *att, struct kprobe_info *kp, } if (kp->cnt != ksym_cnt) { - printf("Function '%s' has mismatched %d ksyms vs %d attachable kprobe entries, skipping.\n", - fn_desc, ksym_cnt, kp->cnt); + printf("Function '%s%s%s%s' has mismatched %d ksyms vs %d attachable kprobe entries, skipping.\n", + NAME_MOD(kp->name, kp->mod), ksym_cnt, kp->cnt); att->func_skip_cnt += ksym_cnt; return 0; } if (att->use_fentries && !is_func_type_ok(btf, btf_id)) { - if (att->debug) - printf("Function '%s' has prototype incompatible with fentry/fexit, skipping.\n", fn_desc); + if (att->debug) { + printf("Function '%s%s%s%s' has prototype incompatible with fentry/fexit, skipping.\n", + NAME_MOD(kp->name, kp->mod)); + } att->func_skip_cnt += ksym_cnt; return 0; } if (att->use_fentries && ksym_cnt > 1) { - if (att->verbose) - printf("Function '%s' has multiple (%d) ambiguous instances and is incompatible with fentry/fexit, skipping.\n", - fn_desc, ksym_cnt); + if (att->verbose) { + printf("Function '%s%s%s%s' has multiple (%d) ambiguous instances and is incompatible with fentry/fexit, skipping.\n", + NAME_MOD(kp->name, kp->mod), ksym_cnt); + } att->func_skip_cnt += ksym_cnt; return 0; } if (att->max_func_cnt && att->func_cnt + ksym_cnt > att->max_func_cnt) { - if (att->verbose) + if (att->verbose) { fprintf(stderr, "Maximum allowed number of functions (%d) reached, skipping the rest.\n", att->max_func_cnt); + } return -E2BIG; } @@ -780,8 +779,10 @@ static int prepare_func(struct mass_attacher *att, struct kprobe_info *kp, att->func_cnt++; - if (att->debug_extra) - printf("Found function '%s' at address 0x%lx...\n", fn_desc, ksym->addr); + if (att->debug_extra) { + printf("Found function '%s%s%s%s' at address 0x%lx...\n", + NAME_MOD(kp->name, kp->mod), ksym->addr); + } } return 0; @@ -916,10 +917,7 @@ static int load_matching_kprobes(struct mass_attacher *att) if (att->debug_extra) { printf("Function '%s%s%s%s' is denied by '%s%s%s%s' glob.\n", - name, - mod ? " [" : "", mod ?: "", mod ? "]" : "", - name_glob, - mod_glob ? " [" : "", mod_glob ?: "", mod_glob ? "]" : ""); + NAME_MOD(name, mod), NAME_MOD(name_glob, mod_glob)); } keep = false; @@ -941,10 +939,7 @@ static int load_matching_kprobes(struct mass_attacher *att) att->allow_globs[j].matches++; if (att->debug_extra) { printf("Function '%s%s%s%s' is allowed by '%s%s%s%s' glob.\n", - name, - mod ? " [" : "", mod ?: "", mod ? "]" : "", - name_glob, - mod_glob ? " [" : "", mod_glob ?: "", mod_glob ? "]" : ""); + NAME_MOD(name, mod), NAME_MOD(name_glob, mod_glob)); } keep = true; @@ -1112,17 +1107,11 @@ static void debug_multi_kprobe(struct mass_attacher *att, unsigned long *addrs, if (l == r) { /* we narrowed it down to single function, report it */ struct mass_attacher_func_info *finfo = &att->func_infos[l]; - const char *func_desc = finfo->name; - char buf[256]; int err; err = -errno; - if (finfo->module) { - snprintf(buf, sizeof(buf), "%s [%s]", finfo->name, finfo->module); - func_desc = buf; - } - printf("DEBUG: KPROBE.MULTI can't attach to func #%d (%s) at addr %lx using %s: %d\n", - l + 1, func_desc, finfo->addr, + printf("DEBUG: KPROBE.MULTI can't attach to func #%d (%s%s%s%s) at addr %lx using %s: %d\n", + l + 1, NAME_MOD(finfo->name, finfo->module), finfo->addr, addrs ? "addrs" : "syms", err); return; } @@ -1158,15 +1147,9 @@ int mass_attacher__attach(struct mass_attacher *att) for (i = 0; i < att->func_cnt; i++) { struct mass_attacher_func_info *finfo = &att->func_infos[i]; - const char *func_name = finfo->name, *func_desc = finfo->name; - char buf[256]; + const char *func_name = finfo->name; long func_addr = finfo->addr; - if (finfo->module) { - snprintf(buf, sizeof(buf), "%s [%s]", finfo->name, finfo->module); - func_desc = buf; - } - if (att->dry_run) goto skip_attach; @@ -1176,8 +1159,8 @@ int mass_attacher__attach(struct mass_attacher *att) prog_fd = att->func_infos[i].fentry_prog_fd; err = bpf_raw_tracepoint_open(NULL, prog_fd); if (err < 0) { - fprintf(stderr, "Failed to attach FENTRY prog (fd %d) for func #%d (%s) at addr %lx: %d\n", - prog_fd, i + 1, func_desc, func_addr, -errno); + fprintf(stderr, "Failed to attach FENTRY prog (fd %d) for func #%d (%s%s%s%s) at addr %lx: %d\n", + prog_fd, i + 1, NAME_MOD(finfo->name, finfo->module), func_addr, -errno); goto err_out; } att->func_infos[i].fentry_link_fd = err; @@ -1185,8 +1168,8 @@ int mass_attacher__attach(struct mass_attacher *att) prog_fd = att->func_infos[i].fexit_prog_fd; err = bpf_raw_tracepoint_open(NULL, prog_fd); if (err < 0) { - fprintf(stderr, "Failed to attach FEXIT prog (fd %d) for func #%d (%s) at addr %lx: %d\n", - prog_fd, i + 1, func_desc, func_addr, -errno); + fprintf(stderr, "Failed to attach FEXIT prog (fd %d) for func #%d (%s%s%s%s) at addr %lx: %d\n", + prog_fd, i + 1, NAME_MOD(finfo->name, finfo->module), func_addr, -errno); goto err_out; } att->func_infos[i].fexit_link_fd = err; @@ -1205,8 +1188,8 @@ int mass_attacher__attach(struct mass_attacher *att) func_name, &kprobe_opts); err = libbpf_get_error(finfo->kentry_link); if (err) { - fprintf(stderr, "Failed to attach KPROBE prog for func #%d (%s) at addr %lx: %d\n", - i + 1, func_desc, func_addr, err); + fprintf(stderr, "Failed to attach KPROBE prog for func #%d (%s%s%s%s) at addr %lx: %d\n", + i + 1, NAME_MOD(finfo->name, finfo->module), func_addr, err); goto err_out; } @@ -1217,21 +1200,21 @@ int mass_attacher__attach(struct mass_attacher *att) func_name, &kprobe_opts); err = libbpf_get_error(finfo->kexit_link); if (err) { - fprintf(stderr, "Failed to attach KRETPROBE prog for func #%d (%s) at addr %lx: %d\n", - i + 1, func_desc, func_addr, err); + fprintf(stderr, "Failed to attach KRETPROBE prog for func #%d (%s%s%s%s) at addr %lx: %d\n", + i + 1, NAME_MOD(finfo->name, finfo->module), func_addr, err); goto err_out; } } skip_attach: if (att->debug) { - printf("Attached%s to function #%d '%s' (addr %lx, btf id %d, flags 0x%x).\n", + printf("Attached%s to function #%d '%s%s%s%s' (addr %lx, btf id %d, flags 0x%x).\n", att->dry_run ? " (dry run)" : "", i + 1, - func_desc, func_addr, finfo->btf_id, + NAME_MOD(finfo->name, finfo->module), func_addr, finfo->btf_id, att->skel->data_func_infos->func_infos[i].flags); } else if (att->verbose) { - printf("Attached%s to function #%d '%s'.\n", - att->dry_run ? " (dry run)" : "", i + 1, func_desc); + printf("Attached%s to function #%d '%s%s%s%s'.\n", + att->dry_run ? " (dry run)" : "", i + 1, NAME_MOD(finfo->name, finfo->module)); } } diff --git a/src/retsnoop.c b/src/retsnoop.c index a63f048..96c4845 100644 --- a/src/retsnoop.c +++ b/src/retsnoop.c @@ -2225,8 +2225,10 @@ int main(int argc, char **argv, char **envp) flags |= FUNC_IS_ENTRY; - if (env.verbose) - printf("Function '%s' is marked as an entry point.\n", finfo->name); + if (env.verbose) { + printf("Function '%s%s%s%s' is marked as an entry point.\n", + NAME_MOD(finfo->name, finfo->module)); + } break; } @@ -2253,13 +2255,8 @@ int main(int argc, char **argv, char **envp) if (!matched && glob->mandatory) { err = -ENOENT; - if (glob->mod) { - fprintf(stderr, "Entry glob '%s[%s]' doesn't match any kernel function!\n", - glob->name, glob->mod); - } else { - fprintf(stderr, "Entry glob '%s' doesn't match any kernel function!\n", - glob->name); - } + fprintf(stderr, "Entry glob '%s%s%s%s' doesn't match any kernel function!\n", + NAME_MOD(glob->name, glob->mod)); goto cleanup_silent; } } diff --git a/src/utils.h b/src/utils.h index 3f0f461..dffe025 100644 --- a/src/utils.h +++ b/src/utils.h @@ -13,6 +13,14 @@ #define min(x, y) ((x) < (y) ? (x): (y)) #define max(x, y) ((x) < (y) ? (y): (x)) +/* Macro to output glob or kprobe full display name in the form of either: + * - 'name', if mod is NULL; + * - 'name [mod]', if mod is not NULL; + * printf() format string should have %s%s%s%s arguments + * corresponding to NAME_MOD() "invocation" + */ +#define NAME_MOD(name, mod) name, mod ? " [" : "", mod ?: "", mod ? "]" : "" + /* * Errno helpers */ From 6a7f4c8e05339d883e545c4a7c9043719d746280 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 11:28:32 -0800 Subject: [PATCH 06/10] mass_attacher: encapsulate globs management into glob_set struct Extract internal API for working with a set of allow/deny globs. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 144 ++++++++++---------------------------------- src/utils.c | 79 ++++++++++++++++++++++++ src/utils.h | 27 +++++++++ 3 files changed, 138 insertions(+), 112 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 3b9dab4..2b02133 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -140,13 +140,7 @@ struct mass_attacher { int func_skip_cnt; - int allow_glob_cnt; - int deny_glob_cnt; - struct { - char *glob; - char *mod_glob; - int matches; - } *allow_globs, *deny_globs; + struct glob_set globs; }; struct mass_attacher *mass_attacher__new(struct SKEL_NAME *skel, struct ksyms *ksyms, @@ -230,6 +224,7 @@ void mass_attacher__free(struct mass_attacher *att) } free(att->func_infos); + glob_set__clear(&att->globs); if (att->kprobes) { for (i = 0; i < att->kprobe_cnt; i++) @@ -248,93 +243,14 @@ void mass_attacher__free(struct mass_attacher *att) free(att); } -static bool is_valid_glob(const char *glob) -{ - int n; - - if (!glob) { - fprintf(stderr, "NULL glob provided.\n"); - return false; - } - - n = strlen(glob); - if (n == 0) { - fprintf(stderr, "Empty glob provided.\n"); - return false; - } - - if (strcmp(glob, "**") == 0) { - fprintf(stderr, "Unsupported glob '%s'.\n", glob); - return false; - } - - return true; -} - int mass_attacher__allow_glob(struct mass_attacher *att, const char *glob, const char *mod_glob) { - void *tmp, *s1, *s2 = NULL; - - if (!is_valid_glob(glob)) - return -EINVAL; - if (mod_glob && !is_valid_glob(mod_glob)) - return -EINVAL; - - tmp = realloc(att->allow_globs, (att->allow_glob_cnt + 1) * sizeof(*att->allow_globs)); - if (!tmp) - return -ENOMEM; - att->allow_globs = tmp; - - s1 = strdup(glob); - if (!s1) - return -ENOMEM; - if (mod_glob) { - s2 = strdup(mod_glob); - if (!s2) { - free(s1); - return -ENOMEM; - } - } - - att->allow_globs[att->allow_glob_cnt].glob = s1; - att->allow_globs[att->allow_glob_cnt].mod_glob = s2; - att->allow_globs[att->allow_glob_cnt].matches = 0; - att->allow_glob_cnt++; - - return 0; + return glob_set__add_glob(&att->globs, glob, mod_glob, GLOB_ALLOW); } int mass_attacher__deny_glob(struct mass_attacher *att, const char *glob, const char *mod_glob) { - void *tmp, *s1, *s2 = NULL; - - if (!is_valid_glob(glob)) - return -EINVAL; - if (mod_glob && !is_valid_glob(mod_glob)) - return -EINVAL; - - tmp = realloc(att->deny_globs, (att->deny_glob_cnt + 1) * sizeof(*att->deny_globs)); - if (!tmp) - return -ENOMEM; - att->deny_globs = tmp; - - s1 = strdup(glob); - if (!s1) - return -ENOMEM; - if (mod_glob) { - s2 = strdup(mod_glob); - if (!s2) { - free(s1); - return -ENOMEM; - } - } - - att->deny_globs[att->deny_glob_cnt].glob = s1; - att->deny_globs[att->deny_glob_cnt].mod_glob = s2; - att->deny_globs[att->deny_glob_cnt].matches = 0; - att->deny_glob_cnt++; - - return 0; + return glob_set__add_glob(&att->globs, glob, mod_glob, GLOB_DENY); } static int bump_rlimit(int resource, rlim_t max); @@ -611,15 +527,12 @@ int mass_attacher__prepare(struct mass_attacher *att) printf("Skipped %d functions in total.\n", att->func_skip_cnt); if (att->debug) { - for (i = 0; i < att->deny_glob_cnt; i++) { - printf("Deny glob '%s%s%s%s' matched %d functions.\n", - NAME_MOD(att->deny_globs[i].glob, att->deny_globs[i].mod_glob), - att->deny_globs[i].matches); - } - for (i = 0; i < att->allow_glob_cnt; i++) { - printf("Allow glob '%s%s%s%s' matched %d functions.\n", - NAME_MOD(att->allow_globs[i].glob, att->allow_globs[i].mod_glob), - att->allow_globs[i].matches); + for (i = 0; i < att->globs.glob_cnt; i++) { + struct glob_spec *g = &att->globs.globs[i]; + + printf("%s glob '%s%s%s%s' matched %d functions.\n", + (g->flags & GLOB_ALLOW) ? "Allow" : "Deny", + NAME_MOD(g->glob, g->mod_glob), g->matches); } } } @@ -904,20 +817,25 @@ static int load_matching_kprobes(struct mass_attacher *att) for (i = 0; i < att->kprobe_cnt;) { const char *name = att->kprobes[i].name; const char *mod = att->kprobes[i].mod; - const char *name_glob, *mod_glob; - bool keep = false;; + struct glob_spec *g; + bool keep = false; + int allow_glob_cnt = 0; + + for (j = 0; j < att->globs.glob_cnt; j++) { + g = &att->globs.globs[j]; + if (g->flags & GLOB_ALLOW) { + allow_glob_cnt++; + continue; + } - for (j = 0; j < att->deny_glob_cnt; j++) { - name_glob = att->deny_globs[j].glob; - mod_glob = att->deny_globs[j].mod_glob; - if (!full_glob_matches(name_glob, mod_glob, name, mod)) + if (!full_glob_matches(g->glob, g->mod_glob, name, mod)) continue; - att->deny_globs[j].matches++; + g->matches++; if (att->debug_extra) { printf("Function '%s%s%s%s' is denied by '%s%s%s%s' glob.\n", - NAME_MOD(name, mod), NAME_MOD(name_glob, mod_glob)); + NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); } keep = false; @@ -925,21 +843,23 @@ static int load_matching_kprobes(struct mass_attacher *att) } /* if any allow glob is specified, function has to match one of them */ - if (att->allow_glob_cnt == 0) { + if (allow_glob_cnt == 0) { keep = true; goto kprobe_verdict; } - for (j = 0; j < att->allow_glob_cnt; j++) { - name_glob = att->allow_globs[j].glob; - mod_glob = att->allow_globs[j].mod_glob; - if (!full_glob_matches(name_glob, mod_glob, name, mod)) + for (j = 0; j < att->globs.glob_cnt; j++) { + g = &att->globs.globs[j]; + if (g->flags & GLOB_DENY) + continue; + + if (!full_glob_matches(g->glob, g->mod_glob, name, mod)) continue; - att->allow_globs[j].matches++; + g->matches++; if (att->debug_extra) { printf("Function '%s%s%s%s' is allowed by '%s%s%s%s' glob.\n", - NAME_MOD(name, mod), NAME_MOD(name_glob, mod_glob)); + NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); } keep = true; diff --git a/src/utils.c b/src/utils.c index f2b95ed..79d5379 100644 --- a/src/utils.c +++ b/src/utils.c @@ -283,3 +283,82 @@ int append_pid(int **pids, int *cnt, const char *arg) return 0; } + +static bool is_valid_glob(const char *glob) +{ + int n; + + if (!glob) { + fprintf(stderr, "NULL glob provided.\n"); + return false; + } + + n = strlen(glob); + if (n == 0) { + fprintf(stderr, "Empty glob provided.\n"); + return false; + } + + if (strcmp(glob, "**") == 0) { + fprintf(stderr, "Unsupported glob '%s'.\n", glob); + return false; + } + + return true; +} + +int glob_set__add_glob(struct glob_set *gs, const char *glob, const char *mod_glob, enum glob_flags flags) +{ + void *tmp, *s1, *s2 = NULL; + struct glob_spec *g; + + /* exactly one of GLOB_ALLOW or GLOB_DENY should be set */ + if (!(flags & (GLOB_ALLOW | GLOB_DENY))) + return -EINVAL; + if ((flags & (GLOB_ALLOW | GLOB_DENY)) == (GLOB_ALLOW | GLOB_DENY)) + return -EINVAL; + if (!is_valid_glob(glob)) + return -EINVAL; + if (mod_glob && !is_valid_glob(mod_glob)) + return -EINVAL; + + tmp = realloc(gs->globs, (gs->glob_cnt + 1) * sizeof(*gs->globs)); + if (!tmp) + return -ENOMEM; + gs->globs = tmp; + + g = &gs->globs[gs->glob_cnt]; + memset(g, 0, sizeof(*g)); + + s1 = strdup(glob); + if (!s1) + return -ENOMEM; + if (mod_glob) { + s2 = strdup(mod_glob); + if (!s2) { + free(s1); + return -ENOMEM; + } + } + + g->glob = s1; + g->mod_glob = s2; + g->flags = flags; + + gs->glob_cnt += 1; + + return 0; +} + +void glob_set__clear(struct glob_set *gs) +{ + int i; + + for (i = 0; i < gs->glob_cnt; i++) { + free(gs->globs[i].glob); + free(gs->globs[i].mod_glob); + } + free(gs->globs); + gs->globs = NULL; + gs->glob_cnt = 0; +} diff --git a/src/utils.h b/src/utils.h index dffe025..bddd0da 100644 --- a/src/utils.h +++ b/src/utils.h @@ -74,4 +74,31 @@ int append_compile_unit(struct addr2line *a2l, struct glob **globs, int *cnt, co int append_pid(int **pids, int *cnt, const char *arg); +enum glob_flags { + GLOB_ALLOW = 0x1, + GLOB_DENY = 0x2, + /* implicitly added glob, this affects match logging verboseness + * (internal globs are not emitted in logs unless debug verboseness is + * requested) + */ + GLOB_INTERNAL = 0x4, +}; + +struct glob_spec { + char *glob; + char *mod_glob; + int matches; + enum glob_flags flags; +}; + +struct glob_set { + struct glob_spec *globs; + int glob_cnt; +}; + +int glob_set__add_glob(struct glob_set *gs, + const char *glob, const char *mod_glob, + enum glob_flags flags); +void glob_set__clear(struct glob_set *gs); + #endif /* __UTILS_H */ From 68cc4dead6ae0dfb7c0d1e6f5186805bf5fe73f6 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 12:09:59 -0800 Subject: [PATCH 07/10] globs: abstract away glob set matching logic This allows to keep reporting and stats counting separate from the (now reusable) logic of matching a given set of allow/deny globs. There are 4 possible outcomes for any given glob set. It could allow (match) or deny (mismatch), and do it either explicitly (there is a matching allow/deny glob) or implicitly (no glob matched, but match or mismatch is implicit, depending on whether there is an explicit GLOB_ALLOW glob). Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 78 ++++++++++++++------------------------------- src/utils.c | 52 ++++++++++++++++++++++++++++++ src/utils.h | 1 + 3 files changed, 77 insertions(+), 54 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 2b02133..3528dbf 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -818,66 +818,36 @@ static int load_matching_kprobes(struct mass_attacher *att) const char *name = att->kprobes[i].name; const char *mod = att->kprobes[i].mod; struct glob_spec *g; - bool keep = false; - int allow_glob_cnt = 0; - - for (j = 0; j < att->globs.glob_cnt; j++) { - g = &att->globs.globs[j]; - if (g->flags & GLOB_ALLOW) { - allow_glob_cnt++; - continue; - } - - if (!full_glob_matches(g->glob, g->mod_glob, name, mod)) - continue; - - g->matches++; - - if (att->debug_extra) { - printf("Function '%s%s%s%s' is denied by '%s%s%s%s' glob.\n", - NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); + int glob_idx; + + if (glob_set__match(&att->globs, name, mod, &glob_idx)) { + if (glob_idx >= 0) { + g = &att->globs.globs[glob_idx]; + g->matches++; + if (att->debug_extra) { + printf("Function '%s%s%s%s' is allowed by '%s%s%s%s' glob.\n", + NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); + } } - keep = false; - goto kprobe_verdict; - } - - /* if any allow glob is specified, function has to match one of them */ - if (allow_glob_cnt == 0) { - keep = true; - goto kprobe_verdict; - } - - for (j = 0; j < att->globs.glob_cnt; j++) { - g = &att->globs.globs[j]; - if (g->flags & GLOB_DENY) - continue; - - if (!full_glob_matches(g->glob, g->mod_glob, name, mod)) - continue; - - g->matches++; - if (att->debug_extra) { - printf("Function '%s%s%s%s' is allowed by '%s%s%s%s' glob.\n", - NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); + i++; /* keep kprobe */ + } else { + if (glob_idx >= 0) { + g = &att->globs.globs[glob_idx]; + g->matches++; + if (att->debug_extra) { + printf("Function '%s%s%s%s' is denied by '%s%s%s%s' glob.\n", + NAME_MOD(name, mod), NAME_MOD(g->glob, g->mod_glob)); + } } - keep = true; - goto kprobe_verdict; - } + /* clean up memory and swap in last element */ + cleanup_kprobe_info(&att->kprobes[i]); + att->kprobes[i] = att->kprobes[att->kprobe_cnt - 1]; + att->kprobe_cnt--; -kprobe_verdict: - if (keep) { - i++; - continue; + att->func_skip_cnt += 1; } - - /* clean up memory and swap in last element */ - cleanup_kprobe_info(&att->kprobes[i]); - att->kprobes[i] = att->kprobes[att->kprobe_cnt - 1]; - att->kprobe_cnt--; - - att->func_skip_cnt += 1; } filter_cnt = att->kprobe_cnt; diff --git a/src/utils.c b/src/utils.c index 79d5379..e4c8b20 100644 --- a/src/utils.c +++ b/src/utils.c @@ -350,6 +350,58 @@ int glob_set__add_glob(struct glob_set *gs, const char *glob, const char *mod_gl return 0; } +/* Find matching glob and return its index. GLOB_DENY globs are checked and + * matched first. If no GLOB_DENY matches, then GLOB_ALLOW globs are checked. + * Return true, if glob set allows given name/mod pair. If there was explicit + * GLOB_ALLOW glob that matched, its index is returned in glob_idx, otherwise + * glob_idx is set to -ENOENT; + * Return false, if glob set disallows given name/mod pair. If there was + * explicit GLOB_DENY glob that matched, its index is returned in glob_idx, + * otherwise glob_idx is set to -ENOENT. + * glob_idx pointer is optional and can be NULL. + */ +bool glob_set__match(const struct glob_set *gs, const char *name, const char *mod, int *glob_idx) +{ + struct glob_spec *g; + int i, deny_glob_cnt = 0; + + if (glob_idx) + *glob_idx = -ENOENT; + + for (i = 0; i < gs->glob_cnt; i++) { + g = &gs->globs[i]; + if (!(g->flags & GLOB_DENY)) + continue; + + deny_glob_cnt++; + + if (full_glob_matches(g->glob, g->mod_glob, name, mod)) { + if (glob_idx) + *glob_idx = i; + return false; /* explicit mismatch */ + } + } + + /* if no explicit GLOB_ALLOW globs are specified, we are OK */ + if (deny_glob_cnt == gs->glob_cnt) + return true; /* implicit match */ + + /* if any allow glob is specified, function has to match one of them */ + for (i = 0; i < gs->glob_cnt; i++) { + g = &gs->globs[i]; + if (!(g->flags & GLOB_ALLOW)) + continue; + + if (full_glob_matches(g->glob, g->mod_glob, name, mod)) { + if (glob_idx) + *glob_idx = i; + return true; /* explicit match */ + } + } + + return false; /* implicit mismatch */ +} + void glob_set__clear(struct glob_set *gs) { int i; diff --git a/src/utils.h b/src/utils.h index bddd0da..9f4cf9e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -99,6 +99,7 @@ struct glob_set { int glob_set__add_glob(struct glob_set *gs, const char *glob, const char *mod_glob, enum glob_flags flags); +bool glob_set__match(const struct glob_set *gs, const char *name, const char *mod, int *glob_idx); void glob_set__clear(struct glob_set *gs); #endif /* __UTILS_H */ From ebdd1bb7f9e3096b405372ba4c30a7241051a993 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 13:39:45 -0800 Subject: [PATCH 08/10] mass_attacher: don't report internally added globs unless debug_extra is set Mass attacher adds a few extra globs to prevent attaching to dangerous functions. Don't report their stats unless debug-extra verboseness is set. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 3528dbf..feed2b6 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -175,7 +175,8 @@ struct mass_attacher *mass_attacher__new(struct SKEL_NAME *skel, struct ksyms *k att->use_fentries = opts->attach_mode == MASS_ATTACH_FENTRY; for (i = 0; i < ARRAY_SIZE(enforced_deny_globs); i++) { - err = mass_attacher__deny_glob(att, enforced_deny_globs[i], NULL); + err = glob_set__add_glob(&att->globs, enforced_deny_globs[i], NULL, + GLOB_DENY | GLOB_INTERNAL); if (err) { fprintf(stderr, "Failed to add enforced deny glob '%s': %d\n", enforced_deny_globs[i], err); @@ -318,7 +319,8 @@ int mass_attacher__prepare(struct mass_attacher *att) if (att->use_fentries && !att->has_fexit_sleep_fix) { for (i = 0; i < ARRAY_SIZE(sleepable_deny_globs); i++) { - err = mass_attacher__deny_glob(att, sleepable_deny_globs[i], NULL); + err = glob_set__add_glob(&att->globs, sleepable_deny_globs[i], NULL, + GLOB_DENY | GLOB_INTERNAL); if (err) { fprintf(stderr, "Failed to add enforced deny glob '%s': %d\n", sleepable_deny_globs[i], err); @@ -530,9 +532,14 @@ int mass_attacher__prepare(struct mass_attacher *att) for (i = 0; i < att->globs.glob_cnt; i++) { struct glob_spec *g = &att->globs.globs[i]; - printf("%s glob '%s%s%s%s' matched %d functions.\n", + if ((g->flags & GLOB_INTERNAL) && !att->debug_extra) + continue; + + printf("%s glob '%s%s%s%s'%s matched %d functions.\n", (g->flags & GLOB_ALLOW) ? "Allow" : "Deny", - NAME_MOD(g->glob, g->mod_glob), g->matches); + NAME_MOD(g->glob, g->mod_glob), + (g->flags & GLOB_INTERNAL) ? " (internal)" : "", + g->matches); } } } From c8a516f1bf46fa3578f4be8390f9991dc5d6e384 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 14:18:33 -0800 Subject: [PATCH 09/10] mass_attacher: format function flags as human-readable symbolic values Emit a set of function flags as symbolic names to ease validation and debugging. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 8 ++++++-- src/retsnoop.c | 35 +++++++++++++++++++++++++++++++++++ src/utils.h | 2 ++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index feed2b6..60363b1 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -1105,10 +1105,14 @@ int mass_attacher__attach(struct mass_attacher *att) skip_attach: if (att->debug) { - printf("Attached%s to function #%d '%s%s%s%s' (addr %lx, btf id %d, flags 0x%x).\n", + char flags_buf[256]; + + format_func_flags(flags_buf, sizeof(flags_buf), + att->skel->data_func_infos->func_infos[i].flags); + printf("Attached%s to function #%d '%s%s%s%s' (addr 0x%lx, btf id %d, flags %s).\n", att->dry_run ? " (dry run)" : "", i + 1, NAME_MOD(finfo->name, finfo->module), func_addr, finfo->btf_id, - att->skel->data_func_infos->func_infos[i].flags); + flags_buf); } else if (att->verbose) { printf("Attached%s to function #%d '%s%s%s%s'.\n", att->dry_run ? " (dry run)" : "", i + 1, NAME_MOD(finfo->name, finfo->module)); diff --git a/src/retsnoop.c b/src/retsnoop.c index 96c4845..f644b0d 100644 --- a/src/retsnoop.c +++ b/src/retsnoop.c @@ -1770,6 +1770,41 @@ static int func_flags(const char *func_name, const struct btf *btf, int btf_id) return 0; } +void format_func_flags(char *buf, size_t buf_sz, int flags) +{ + char s[256]; + size_t s_len = 0; + + if (flags & FUNC_IS_ENTRY) { + snappendf(s, "%sENTRY", s_len ? "|" : ""); + flags &= ~FUNC_IS_ENTRY; + } + if (flags & FUNC_CANT_FAIL) { + snappendf(s, "%sNOFAIL", s_len ? "|" : ""); + flags &= ~FUNC_CANT_FAIL; + } + if (flags & FUNC_NEEDS_SIGN_EXT) { + snappendf(s, "%sSIGNEXT", s_len ? "|" : ""); + flags &= ~FUNC_NEEDS_SIGN_EXT; + } + if (flags & FUNC_RET_PTR) { + snappendf(s, "%sPTR", s_len ? "|" : ""); + flags &= ~FUNC_RET_PTR; + } + if (flags & FUNC_RET_BOOL) { + snappendf(s, "%sBOOL", s_len ? "|" : ""); + flags &= ~FUNC_RET_BOOL; + } + if (flags & FUNC_RET_VOID) { + snappendf(s, "%sVOID", s_len ? "|" : ""); + flags &= ~FUNC_RET_VOID; + } + if (flags) + snappendf(s, "%s0x%x", s_len ? "|" : "", flags); + + snprintf(buf, buf_sz, "%s", s); +} + static int find_vmlinux(char *path, size_t max_len, bool soft) { const char *locations[] = { diff --git a/src/utils.h b/src/utils.h index 9f4cf9e..e7ba8df 100644 --- a/src/utils.h +++ b/src/utils.h @@ -50,6 +50,8 @@ static inline uint64_t now_ns(void) void ts_to_str(uint64_t ts, char buf[], size_t buf_sz); +void format_func_flags(char *buf, size_t buf_sz, int flags); + /* * Glob helpers */ From 9bf1985ff010100bd74e7afe1ab13f93033413cd Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Feb 2024 14:40:07 -0800 Subject: [PATCH 10/10] mass_attacher: only log non-attachable kprobes if they pass globs Instead of logging every BTF FUNC record that doesn't match globs *or* doesn't have a corresponding attachable kprobes, filter out all the records that don't satisfy globs first, so that we can log high signal message about functions that are not attachable, but were requested by user through globs. Signed-off-by: Andrii Nakryiko --- src/mass_attacher.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 60363b1..5fcb779 100644 --- a/src/mass_attacher.c +++ b/src/mass_attacher.c @@ -397,14 +397,20 @@ int mass_attacher__prepare(struct mass_attacher *att) if (!btf_is_func(t)) continue; - /* check if we already processed a function with such name */ + /* skip BTF function if it doesn't match globs */ func_name = btf__str_by_offset(att->vmlinux_btf, t->name_off); + if (!glob_set__match(&att->globs, func_name, NULL, NULL)) + continue; + + /* check if we have a matching attachable kprobe */ kp = find_kprobe(att, func_name, NULL); if (!kp) { - if (att->debug_extra) - printf("Function '%s' is not attachable kprobe, skipping.\n", func_name); + if (att->verbose) + printf("Function '%s' is not an attachable kprobe, skipping.\n", func_name); continue; } + + /* we might have already processed it, skip if so */ if (kp->used) continue; @@ -456,15 +462,23 @@ int mass_attacher__prepare(struct mass_attacher *att) if (!btf_is_func(t)) continue; - /* check if we already processed a function with such name */ + /* skip BTF function if it doesn't match globs */ func_name = btf__str_by_offset(btf, t->name_off); + if (!glob_set__match(&att->globs, func_name, mod, NULL)) + continue; + + /* check if we have a matching attachable kprobe */ kp = find_kprobe(att, func_name, mod); if (!kp) { - if (att->debug_extra) - printf("Function '%s [%s]' is not attachable kprobe, skipping.\n", func_name, mod); + if (att->verbose) + printf("Function '%s [%s]' is not an attachable kprobe, skipping.\n", func_name, mod); continue; } + /* we might have already processed it, skip if so */ + if (kp->used) + continue; + err = prepare_func(att, kp, btf, j); if (err) return err;