bpf: Take module reference on kprobe_multi link
authorJiri Olsa <jolsa@kernel.org>
Tue, 25 Oct 2022 13:41:44 +0000 (15:41 +0200)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 25 Oct 2022 17:14:51 +0000 (10:14 -0700)
Currently we allow to create kprobe multi link on function from kernel
module, but we don't take the module reference to ensure it's not
unloaded while we are tracing it.

The multi kprobe link is based on fprobe/ftrace layer which takes
different approach and releases ftrace hooks when module is unloaded
even if there's tracer registered on top of it.

Adding code that gathers all the related modules for the link and takes
their references before it's attached. All kernel module references are
released after link is unregistered.

Note that we do it the same way already for trampoline probes
(but for single address).

Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Link: https://lore.kernel.org/r/20221025134148.3300700-5-jolsa@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/trace/bpf_trace.c

index 2e1f03d..2460e38 100644 (file)
@@ -2450,6 +2450,8 @@ struct bpf_kprobe_multi_link {
        unsigned long *addrs;
        u64 *cookies;
        u32 cnt;
+       u32 mods_cnt;
+       struct module **mods;
 };
 
 struct bpf_kprobe_multi_run_ctx {
@@ -2505,6 +2507,14 @@ error:
        return err;
 }
 
+static void kprobe_multi_put_modules(struct module **mods, u32 cnt)
+{
+       u32 i;
+
+       for (i = 0; i < cnt; i++)
+               module_put(mods[i]);
+}
+
 static void free_user_syms(struct user_syms *us)
 {
        kvfree(us->syms);
@@ -2517,6 +2527,7 @@ static void bpf_kprobe_multi_link_release(struct bpf_link *link)
 
        kmulti_link = container_of(link, struct bpf_kprobe_multi_link, link);
        unregister_fprobe(&kmulti_link->fp);
+       kprobe_multi_put_modules(kmulti_link->mods, kmulti_link->mods_cnt);
 }
 
 static void bpf_kprobe_multi_link_dealloc(struct bpf_link *link)
@@ -2526,6 +2537,7 @@ static void bpf_kprobe_multi_link_dealloc(struct bpf_link *link)
        kmulti_link = container_of(link, struct bpf_kprobe_multi_link, link);
        kvfree(kmulti_link->addrs);
        kvfree(kmulti_link->cookies);
+       kfree(kmulti_link->mods);
        kfree(kmulti_link);
 }
 
@@ -2661,6 +2673,71 @@ static void symbols_swap_r(void *a, void *b, int size, const void *priv)
        }
 }
 
+struct module_addr_args {
+       unsigned long *addrs;
+       u32 addrs_cnt;
+       struct module **mods;
+       int mods_cnt;
+       int mods_cap;
+};
+
+static int module_callback(void *data, const char *name,
+                          struct module *mod, unsigned long addr)
+{
+       struct module_addr_args *args = data;
+       struct module **mods;
+
+       /* We iterate all modules symbols and for each we:
+        * - search for it in provided addresses array
+        * - if found we check if we already have the module pointer stored
+        *   (we iterate modules sequentially, so we can check just the last
+        *   module pointer)
+        * - take module reference and store it
+        */
+       if (!bsearch(&addr, args->addrs, args->addrs_cnt, sizeof(addr),
+                      bpf_kprobe_multi_addrs_cmp))
+               return 0;
+
+       if (args->mods && args->mods[args->mods_cnt - 1] == mod)
+               return 0;
+
+       if (args->mods_cnt == args->mods_cap) {
+               args->mods_cap = max(16, args->mods_cap * 3 / 2);
+               mods = krealloc_array(args->mods, args->mods_cap, sizeof(*mods), GFP_KERNEL);
+               if (!mods)
+                       return -ENOMEM;
+               args->mods = mods;
+       }
+
+       if (!try_module_get(mod))
+               return -EINVAL;
+
+       args->mods[args->mods_cnt] = mod;
+       args->mods_cnt++;
+       return 0;
+}
+
+static int get_modules_for_addrs(struct module ***mods, unsigned long *addrs, u32 addrs_cnt)
+{
+       struct module_addr_args args = {
+               .addrs     = addrs,
+               .addrs_cnt = addrs_cnt,
+       };
+       int err;
+
+       /* We return either err < 0 in case of error, ... */
+       err = module_kallsyms_on_each_symbol(module_callback, &args);
+       if (err) {
+               kprobe_multi_put_modules(args.mods, args.mods_cnt);
+               kfree(args.mods);
+               return err;
+       }
+
+       /* or number of modules found if everything is ok. */
+       *mods = args.mods;
+       return args.mods_cnt;
+}
+
 int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
 {
        struct bpf_kprobe_multi_link *link = NULL;
@@ -2771,10 +2848,25 @@ int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
                       bpf_kprobe_multi_cookie_cmp,
                       bpf_kprobe_multi_cookie_swap,
                       link);
+       } else {
+               /*
+                * We need to sort addrs array even if there are no cookies
+                * provided, to allow bsearch in get_modules_for_addrs.
+                */
+               sort(addrs, cnt, sizeof(*addrs),
+                      bpf_kprobe_multi_addrs_cmp, NULL);
+       }
+
+       err = get_modules_for_addrs(&link->mods, addrs, cnt);
+       if (err < 0) {
+               bpf_link_cleanup(&link_primer);
+               return err;
        }
+       link->mods_cnt = err;
 
        err = register_fprobe_ips(&link->fp, addrs, cnt);
        if (err) {
+               kprobe_multi_put_modules(link->mods, link->mods_cnt);
                bpf_link_cleanup(&link_primer);
                return err;
        }