When a kernel module exports a symbol with the same name as an existing vmlinux symbol, kprobe attachment via bpf_program__attach_kprobe_opts() fails with EADDRNOTAVAIL (perf_event_open path) or EINVAL (legacy tracefs path). Implement a fallback mechanism that, on such failures, resolves the vmlinux address through /proc/kallsyms - filtering out module symbols - and retries probe attachment using the absolute address, bypassing kernel symbol name validation. Changes: - Restructure libbpf_kallsyms_parse() to accept a bool skip_if_module parameter that filters module symbols before invoking the callback. - Add find_kaddr_cb() callback and find_kaddr_ctx struct to resolve vmlinux text symbol addresses from /proc/kallsyms. - Detect duplicate symbol errors (-EADDRNOTAVAIL for perf_event_open, -EINVAL for legacy tracefs) and retry with resolved KADDR. - Fix NULL pointer in gen_probe_legacy_event_name() when using absolute address by passing func_name instead of optional_func_name. - Fix NULL pointer in pr_warn() calls within perf_event_kprobe_open_legacy() by using ternary fallback. - Fix memory leak of legacy_probe buffer on retry path. Signed-off-by: Andrey Grodzovsky --- tools/lib/bpf/libbpf.c | 155 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 18 deletions(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 0be7017800fe..64738d4bf3e0 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8460,9 +8460,11 @@ static int bpf_object__sanitize_maps(struct bpf_object *obj) typedef int (*kallsyms_cb_t)(unsigned long long sym_addr, char sym_type, const char *sym_name, void *ctx); -static int libbpf_kallsyms_parse(kallsyms_cb_t cb, void *ctx) +static int libbpf_kallsyms_parse(kallsyms_cb_t cb, void *ctx, + bool skip_if_module) { char sym_type, sym_name[500]; + char module_name[128]; unsigned long long sym_addr; int ret, err = 0; FILE *f; @@ -8475,21 +8477,39 @@ static int libbpf_kallsyms_parse(kallsyms_cb_t cb, void *ctx) } while (true) { - ret = fscanf(f, "%llx %c %499s%*[^\n]\n", - &sym_addr, &sym_type, sym_name); - if (ret == EOF && feof(f)) + int n = 0; + + /* + * Format tries to match both vmlinux and module kallsyms + * entries in one call. Module entries have the form: + * addr type name [module] + * %n fires only if the closing ']' literal is matched. + */ + ret = fscanf(f, "%llx %c %499s [%127[^] \t\n\r\v\f]] %n", + &sym_addr, &sym_type, sym_name, module_name, &n); + + /* EOF or malformed line - handle after the loop */ + if (ret != 3 && ret != 4) break; - if (ret != 3) { - pr_warn("failed to read kallsyms entry: %d\n", ret); - err = -EINVAL; + + /* ret == 4 && n == 0: scanset matched but ']' didn't */ + if (ret == 4 && n == 0) break; - } + + if (ret == 4 && skip_if_module) + continue; err = cb(sym_addr, sym_type, sym_name, ctx); if (err) break; } + /* !err: don't overwrite callback errors with -EINVAL */ + if (!feof(f) && !err) { + pr_warn("failed to read kallsyms entry: ret=%d\n", ret); + err = -EINVAL; + } + fclose(f); return err; } @@ -8529,7 +8549,7 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type, static int bpf_object__read_kallsyms_file(struct bpf_object *obj) { - return libbpf_kallsyms_parse(kallsyms_cb, obj); + return libbpf_kallsyms_parse(kallsyms_cb, obj, false); } static int find_ksym_btf_id(struct bpf_object *obj, const char *ksym_name, @@ -11527,10 +11547,20 @@ static void gen_probe_legacy_event_name(char *buf, size_t buf_sz, static int add_kprobe_event_legacy(const char *probe_name, bool retprobe, const char *kfunc_name, size_t offset) { - return append_to_file(tracefs_kprobe_events(), "%c:%s/%s %s+0x%zx", - retprobe ? 'r' : 'p', - retprobe ? "kretprobes" : "kprobes", - probe_name, kfunc_name, offset); + /* When kfunc_name is NULL, use absolute address format (0xADDR), + * otherwise use symbol+offset format (SYMBOL+0xOFF) + */ + if (kfunc_name) { + return append_to_file(tracefs_kprobe_events(), "%c:%s/%s %s+0x%zx", + retprobe ? 'r' : 'p', + retprobe ? "kretprobes" : "kprobes", + probe_name, kfunc_name, offset); + } else { + return append_to_file(tracefs_kprobe_events(), "%c:%s/%s 0x%zx", + retprobe ? 'r' : 'p', + retprobe ? "kretprobes" : "kprobes", + probe_name, offset); + } } static int remove_kprobe_event_legacy(const char *probe_name, bool retprobe) @@ -11559,7 +11589,7 @@ static int perf_event_kprobe_open_legacy(const char *probe_name, bool retprobe, err = add_kprobe_event_legacy(probe_name, retprobe, kfunc_name, offset); if (err < 0) { pr_warn("failed to add legacy kprobe event for '%s+0x%zx': %s\n", - kfunc_name, offset, + kfunc_name ? kfunc_name : "(abs_addr)", offset, errstr(err)); return err; } @@ -11567,7 +11597,7 @@ static int perf_event_kprobe_open_legacy(const char *probe_name, bool retprobe, if (type < 0) { err = type; pr_warn("failed to determine legacy kprobe event id for '%s+0x%zx': %s\n", - kfunc_name, offset, + kfunc_name ? kfunc_name : "(abs_addr)", offset, errstr(err)); goto err_clean_legacy; } @@ -11651,6 +11681,44 @@ int probe_kern_syscall_wrapper(int token_fd) } } +/* Context structure for finding vmlinux kernel symbol address */ +struct find_kaddr_ctx { + const char *func_name; + unsigned long long kaddr; +}; + +/* Callback to find vmlinux kernel text symbol address */ +static int find_kaddr_cb(unsigned long long sym_addr, char sym_type, + const char *sym_name, void *ctx) +{ + struct find_kaddr_ctx *data = ctx; + + /* Only match text section symbols ('T' or 't') */ + if (toupper(sym_type) != 'T') + return 0; + + /* Check if this is the symbol we're looking for */ + if (strcmp(sym_name, data->func_name) == 0) { + + /* + * If we already have an address, we've encountered a + * duplicate symbol name. Reject such symbols to avoid + * ambiguous resolution. + */ + if (data->kaddr && data->kaddr != sym_addr) { + pr_warn("kernel symbol '%s': resolution is ambiguous: 0x%llx or 0x%llx\n", + sym_name, data->kaddr, sym_addr); + return -EINVAL; + } + + data->kaddr = sym_addr; + pr_debug("found kernel symbol %s at address 0x%llx\n", + sym_name, data->kaddr); + } + + return 0; +} + struct bpf_link * bpf_program__attach_kprobe_opts(const struct bpf_program *prog, const char *func_name, @@ -11663,6 +11731,8 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog, size_t offset; bool retprobe, legacy; int pfd, err; + const char *optional_func_name = func_name; + struct find_kaddr_ctx kaddr_ctx = { .kaddr = 0 }; if (!OPTS_VALID(opts, bpf_kprobe_opts)) return libbpf_err_ptr(-EINVAL); @@ -11693,9 +11763,10 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog, return libbpf_err_ptr(-EINVAL); } +retry_create: if (!legacy) { pfd = perf_event_open_probe(false /* uprobe */, retprobe, - func_name, offset, + optional_func_name, offset, -1 /* pid */, 0 /* ref_ctr_off */); } else { char probe_name[MAX_EVENT_NAME_LEN]; @@ -11707,7 +11778,7 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog, if (!legacy_probe) return libbpf_err_ptr(-ENOMEM); - pfd = perf_event_kprobe_open_legacy(legacy_probe, retprobe, func_name, + pfd = perf_event_kprobe_open_legacy(legacy_probe, retprobe, optional_func_name, offset, -1 /* pid */); } if (pfd < 0) { @@ -11716,6 +11787,54 @@ bpf_program__attach_kprobe_opts(const struct bpf_program *prog, prog->name, retprobe ? "kretprobe" : "kprobe", func_name, offset, errstr(err)); + + /* + * If attachment fails with EADDRNOTAVAIL (or EINVAL in + * legacy mode) and we used non-NULL func_name, try to + * find function address in /proc/kallsyms and retry + * using KADDR instead of ambiguous symbol. Legacy + * tracefs API converts EADDRNOTAVAIL to EINVAL, so + * we need to check for both. + */ + if ((err == -EADDRNOTAVAIL || (legacy && err == -EINVAL)) && optional_func_name) { + pr_debug("kallsyms lookup for %s\n", + func_name); + + kaddr_ctx.func_name = func_name; + kaddr_ctx.kaddr = 0; + + /* + * Drop the function symbol and add the resolved + * KADDR to the offset. + * This avoids kallsyms validation for duplicate + * symbols in the kernel. See attr.config2 in + * perf_event_open_probe and kernel code in + * perf_kprobe_init/create_local_trace_kprobe. + */ + optional_func_name = NULL; + + err = libbpf_kallsyms_parse(find_kaddr_cb, &kaddr_ctx, true); + if (err) { + pr_warn("failed to get addr for %s\n", + func_name); + goto err_out; + } + + if (kaddr_ctx.kaddr) { + pr_debug("retrying %s using kaddr 0x%llx\n", + func_name, kaddr_ctx.kaddr); + + offset += kaddr_ctx.kaddr; + free(legacy_probe); + legacy_probe = NULL; + goto retry_create; + } + + pr_warn("symbol '%s' not found in vmlinux\n", + func_name); + err = -ENOENT; + } + goto err_out; } link = bpf_program__attach_perf_event_opts(prog, pfd, &pe_opts); @@ -11931,7 +12050,7 @@ static int libbpf_available_kallsyms_parse(struct kprobe_multi_resolve *res) data.syms = syms; data.res = res; data.cnt = cnt; - libbpf_kallsyms_parse(avail_kallsyms_cb, &data); + libbpf_kallsyms_parse(avail_kallsyms_cb, &data, false); if (res->cnt == 0) err = -ENOENT; -- 2.34.1