diff --git a/src/mass_attacher.c b/src/mass_attacher.c index 98a3438..5fcb779 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]; @@ -137,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, @@ -178,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); @@ -190,6 +188,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; @@ -201,6 +207,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); @@ -216,12 +225,11 @@ 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++) { - 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); } @@ -236,104 +244,25 @@ 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); -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_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 */ @@ -354,7 +283,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(); @@ -389,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); @@ -417,8 +348,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; @@ -453,33 +384,115 @@ 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; - int kprobe_idx; + t = btf__type_by_id(att->vmlinux_btf, i); if (!btf_is_func(t)) continue; + /* 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 already processed a function with such name */ - kprobe_idx = find_kprobe(att, func_name, NULL); - if (kprobe_idx >= 0 && att->kprobes[kprobe_idx].used) + /* check if we have a matching attachable kprobe */ + kp = find_kprobe(att, func_name, NULL); + if (!kp) { + if (att->verbose) + printf("Function '%s' is not an attachable kprobe, skipping.\n", func_name); continue; + } - err = prepare_func(att, func_name, NULL, t, i); + /* we might have already processed it, skip if so */ + if (kp->used) + continue; + + err = prepare_func(att, kp, att->vmlinux_btf, i); 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; + + /* 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->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; + } + } 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, 0); + err = prepare_func(att, kp, NULL, 0); if (err) return err; } @@ -530,13 +543,17 @@ 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' matched %d functions.\n", - att->deny_globs[i].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); + for (i = 0; i < att->globs.glob_cnt; i++) { + struct glob_spec *g = &att->globs.globs[i]; + + 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->flags & GLOB_INTERNAL) ? " (internal)" : "", + g->matches); } } } @@ -588,16 +605,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; @@ -621,111 +628,59 @@ 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_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); - fn_desc = fn_desc_buf; - } + kp->used = true; /* mark it as processed, no matter the outcome */ - 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); + 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; } 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); + if (kp->cnt != ksym_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 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; + if (att->use_fentries && !is_func_type_ok(btf, btf_id)) { + if (att->debug) { + printf("Function '%s%s%s%s' has prototype incompatible with fentry/fexit, skipping.\n", + NAME_MOD(kp->name, kp->mod)); } - } - - 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) { - printf("Function '%s' has mismatched %d ksyms vs %d attachable kprobe entries, skipping.\n", - fn_desc, ksym_cnt, kprobe_cnt); - att->func_skip_cnt += ksym_cnt; - return 0; - } - - if (att->use_fentries && !is_func_type_ok(att->vmlinux_btf, t)) { - if (att->debug) - printf("Function '%s' has prototype incompatible with fentry/fexit, skipping.\n", fn_desc); 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; } @@ -737,7 +692,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 +702,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) { @@ -757,8 +713,10 @@ static int prepare_func(struct mass_attacher *att, 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; @@ -824,12 +782,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; @@ -873,19 +832,53 @@ 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; + struct glob_spec *g; + 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)); + } + } + + 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)); + } + } + + /* 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; } @@ -897,17 +890,18 @@ 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; } -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 +951,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 +974,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; @@ -1021,17 +1018,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; } @@ -1067,15 +1058,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; @@ -1085,8 +1070,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; @@ -1094,8 +1079,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; @@ -1114,8 +1099,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; } @@ -1126,21 +1111,25 @@ 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", + 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, - func_desc, func_addr, finfo->btf_id, - att->skel->data_func_infos->func_infos[i].flags); + NAME_MOD(finfo->name, finfo->module), func_addr, finfo->btf_id, + flags_buf); } 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)); } } @@ -1229,11 +1218,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; @@ -1246,18 +1230,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 +1297,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; 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..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[] = { @@ -1933,7 +1968,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 +2244,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 +2251,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]; @@ -2227,8 +2260,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; } @@ -2255,13 +2290,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.c b/src/utils.c index f2b95ed..e4c8b20 100644 --- a/src/utils.c +++ b/src/utils.c @@ -283,3 +283,134 @@ 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; +} + +/* 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; + + 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 3f0f461..e7ba8df 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 */ @@ -42,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 */ @@ -66,4 +76,32 @@ 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); +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 */