]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fprobe: Fix unregister_fprobe() to wait for RCU grace period
authorMasami Hiramatsu (Google) <mhiramat@kernel.org>
Thu, 7 May 2026 07:46:29 +0000 (16:46 +0900)
committerMasami Hiramatsu (Google) <mhiramat@kernel.org>
Mon, 11 May 2026 10:04:46 +0000 (19:04 +0900)
Commit 4346ba1604093 ("fprobe: Rewrite fprobe on function-graph tracer")
changed fprobe to register struct fprobe to an rcu-hlist, but it forgot
to wait for RCU GP. Thus there can be use-after-free if the fprobe is
released right after unregistering. This can be happened on fprobe
event and sample module code.

To fix this issue, add synchronize_rcu() in unregister_fprobe().

Note that BPF is OK because fprobe is used as a part of
bpf_kprobe_multi_link. This unregisters its fprobe in
bpf_kprobe_multi_link_release() and it is deallocated via
bpf_kprobe_multi_link_dealloc(), which is invoked from
bpf_link_defer_dealloc_rcu_gp() RCU callback.

For BPF, this also introduced unregister_fprobe_async() which does
NOT wait for RCU grace priod.

Link: https://lore.kernel.org/all/177813998919.256460.2809243930741138224.stgit@mhiramat.tok.corp.google.com/
Fixes: 4346ba1604093 ("fprobe: Rewrite fprobe on function-graph tracer")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
include/linux/fprobe.h
kernel/trace/bpf_trace.c
kernel/trace/fprobe.c

index 0a3bcd1718f3796027c0df19c3a965ff9ec3d677..be1b38c981d4d9a79faae7f2af13c7a08d6cf638 100644 (file)
@@ -94,6 +94,7 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter
 int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num);
 int register_fprobe_syms(struct fprobe *fp, const char **syms, int num);
 int unregister_fprobe(struct fprobe *fp);
+int unregister_fprobe_async(struct fprobe *fp);
 bool fprobe_is_registered(struct fprobe *fp);
 int fprobe_count_ips_from_filter(const char *filter, const char *notfilter);
 #else
@@ -113,6 +114,10 @@ static inline int unregister_fprobe(struct fprobe *fp)
 {
        return -EOPNOTSUPP;
 }
+static inline int unregister_fprobe_async(struct fprobe *fp)
+{
+       return -EOPNOTSUPP;
+}
 static inline bool fprobe_is_registered(struct fprobe *fp)
 {
        return false;
index af7079aa0f36d9655818aa8a9eb98808cfb0052c..a02bd258677ee133a832941cfa4ed75530dfc64e 100644 (file)
@@ -2384,7 +2384,8 @@ static void bpf_kprobe_multi_link_release(struct bpf_link *link)
        struct bpf_kprobe_multi_link *kmulti_link;
 
        kmulti_link = container_of(link, struct bpf_kprobe_multi_link, link);
-       unregister_fprobe(&kmulti_link->fp);
+       /* Don't wait for RCU GP here. */
+       unregister_fprobe_async(&kmulti_link->fp);
        kprobe_multi_put_modules(kmulti_link->mods, kmulti_link->mods_cnt);
 }
 
index cc49ebd2a7737b63c21ca3d062a22cf1653cacf3..f378613ad1209cff44825ac570f29b053fb96285 100644 (file)
@@ -1093,14 +1093,15 @@ static int unregister_fprobe_nolock(struct fprobe *fp)
 }
 
 /**
- * unregister_fprobe() - Unregister fprobe.
+ * unregister_fprobe_async() - Unregister fprobe without RCU GP wait
  * @fp: A fprobe data structure to be unregistered.
  *
  * Unregister fprobe (and remove ftrace hooks from the function entries).
+ * This function will NOT wait until the fprobe is no longer used.
  *
  * Return 0 if @fp is unregistered successfully, -errno if not.
  */
-int unregister_fprobe(struct fprobe *fp)
+int unregister_fprobe_async(struct fprobe *fp)
 {
        guard(mutex)(&fprobe_mutex);
        if (!fp || !fprobe_registered(fp))
@@ -1108,6 +1109,24 @@ int unregister_fprobe(struct fprobe *fp)
 
        return unregister_fprobe_nolock(fp);
 }
+
+/**
+ * unregister_fprobe() - Unregister fprobe with RCU GP wait
+ * @fp: A fprobe data structure to be unregistered.
+ *
+ * Unregister fprobe (and remove ftrace hooks from the function entries).
+ * This function will block until the fprobe is no longer used.
+ *
+ * Return 0 if @fp is unregistered successfully, -errno if not.
+ */
+int unregister_fprobe(struct fprobe *fp)
+{
+       int ret = unregister_fprobe_async(fp);
+
+       if (!ret)
+               synchronize_rcu();
+       return ret;
+}
 EXPORT_SYMBOL_GPL(unregister_fprobe);
 
 static int __init fprobe_initcall(void)