summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-06-15 03:24:26 +0300
committerAlexei Starovoitov <ast@kernel.org>2026-06-15 03:24:33 +0300
commita6f615db083d0993bad9ca8fe226d1c996c4bf5b (patch)
tree4e5dc3ee1bfff46cd4c1dfb39ee490144e1f18c3
parent16deef8de06ed69aa79d037a168a70407a84a5ca (diff)
parentdf29003c55115737a8fb4f8a60c6c2bba4c4a484 (diff)
downloadlinux-a6f615db083d0993bad9ca8fe226d1c996c4bf5b.tar.xz
Merge branch 'bpf-allow-uprobe_multi-binary-specified-by-file-descriptor'
Jiri Olsa says: ==================== bpf: Allow uprobe_multi binary specified by file descriptor Add ability to open uprobe_multi link on top of binary identified by file descriptor. This allows us to avoid the race where the binary is replaced between path resolution and attachment, ensuring we monitor the intended binary. v1: https://lore.kernel.org/bpf/20260609104244.588321-1-jolsa@kernel.org/T/#m0275d5f39805c57dc8fd3308c640237dc7aec4db v2: https://lore.kernel.org/bpf/20260610143627.804790-1-jolsa@kernel.org/T/#m153e18fa426140fdcc773cc97b10e006531656c0 v3 changes: - guard t_user acesss with access_ok [sashiko] v2 changes: - move path retrieval in separate function so CLASS(..) is not used in function with goto-based cleanup [sashiko] - force zero path_fd in case BPF_F_UPROBE_MULTI_PATH_FD is not set [sashiko] - add space around | in bpf_uprobe_multi_link_attach [Alexei] ==================== Link: https://patch.msgid.link/20260611114230.950379-1-jolsa@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
-rw-r--r--include/uapi/linux/bpf.h7
-rw-r--r--kernel/bpf/syscall.c4
-rw-r--r--kernel/trace/bpf_trace.c60
-rw-r--r--tools/include/uapi/linux/bpf.h7
-rw-r--r--tools/lib/bpf/bpf.c1
-rw-r--r--tools/lib/bpf/bpf.h3
-rw-r--r--tools/testing/selftests/bpf/prog_tests/fill_link_info.c2
-rw-r--r--tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c94
8 files changed, 158 insertions, 20 deletions
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 11dd610fa5fa..89b36de5fdbb 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1327,7 +1327,11 @@ enum {
* BPF_TRACE_UPROBE_MULTI attach type to create return probe.
*/
enum {
- BPF_F_UPROBE_MULTI_RETURN = (1U << 0)
+ /* Get return uprobe. */
+ BPF_F_UPROBE_MULTI_RETURN = (1U << 0),
+
+ /* Get path from provided path_fd. */
+ BPF_F_UPROBE_MULTI_PATH_FD = (1U << 1),
};
/* link_create.netfilter.flags used in LINK_CREATE command for
@@ -1864,6 +1868,7 @@ union bpf_attr {
__u32 cnt;
__u32 flags;
__u32 pid;
+ __u32 path_fd;
} uprobe_multi;
struct {
union {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 7ed949f70f82..b44106c8ea75 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3480,7 +3480,7 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
seq_printf(m, "link_type:\t%s\n", link->flags == BPF_F_KPROBE_MULTI_RETURN ?
"kretprobe_multi" : "kprobe_multi");
else if (link->type == BPF_LINK_TYPE_UPROBE_MULTI)
- seq_printf(m, "link_type:\t%s\n", link->flags == BPF_F_UPROBE_MULTI_RETURN ?
+ seq_printf(m, "link_type:\t%s\n", link->flags & BPF_F_UPROBE_MULTI_RETURN ?
"uretprobe_multi" : "uprobe_multi");
else
seq_printf(m, "link_type:\t%s\n", bpf_link_type_strs[type]);
@@ -5840,7 +5840,7 @@ err_put:
return err;
}
-#define BPF_LINK_CREATE_LAST_FIELD link_create.uprobe_multi.pid
+#define BPF_LINK_CREATE_LAST_FIELD link_create.uprobe_multi.path_fd
static int link_create(union bpf_attr *attr, bpfptr_t uattr)
{
struct bpf_prog *prog;
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 90432f0fc2a8..82f8feea6931 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -23,6 +23,7 @@
#include <linux/sort.h>
#include <linux/key.h>
#include <linux/namei.h>
+#include <linux/file.h>
#include <net/bpf_sk_storage.h>
@@ -3214,6 +3215,38 @@ static u64 bpf_uprobe_multi_cookie(struct bpf_run_ctx *ctx)
return run_ctx->uprobe->cookie;
}
+static int bpf_uprobe_multi_get_path(const union bpf_attr *attr, struct path *path)
+{
+ void __user *upath = u64_to_user_ptr(attr->link_create.uprobe_multi.path);
+ u32 path_fd = attr->link_create.uprobe_multi.path_fd;
+ u32 flags = attr->link_create.uprobe_multi.flags;
+
+ if (flags & BPF_F_UPROBE_MULTI_PATH_FD) {
+ /*
+ * When BPF_F_UPROBE_MULTI_PATH_FD is set, the executable is
+ * identified by path_fd, upath must be NULL.
+ */
+ if (upath)
+ return -EINVAL;
+
+ CLASS(fd, f)(path_fd);
+ if (fd_empty(f))
+ return -EBADF;
+ *path = fd_file(f)->f_path;
+ path_get(path);
+ return 0;
+ }
+
+ /*
+ * When BPF_F_UPROBE_MULTI_PATH_FD is not set, the path is resolved
+ * relative to the cwd (AT_FDCWD) or absolute using the upath string.
+ */
+ if (!upath || path_fd)
+ return -EINVAL;
+
+ return user_path_at(AT_FDCWD, upath, LOOKUP_FOLLOW, path);
+}
+
int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
struct bpf_uprobe_multi_link *link = NULL;
@@ -3223,10 +3256,9 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
struct task_struct *task = NULL;
unsigned long __user *uoffsets;
u64 __user *ucookies;
- void __user *upath;
+ unsigned long size;
u32 flags, cnt, i;
struct path path;
- char *name;
pid_t pid;
int err;
@@ -3241,19 +3273,18 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
return -EINVAL;
flags = attr->link_create.uprobe_multi.flags;
- if (flags & ~BPF_F_UPROBE_MULTI_RETURN)
+ if (flags & ~(BPF_F_UPROBE_MULTI_RETURN | BPF_F_UPROBE_MULTI_PATH_FD))
return -EINVAL;
/*
- * path, offsets and cnt are mandatory,
+ * offsets and cnt are mandatory,
* ref_ctr_offsets and cookies are optional
*/
- upath = u64_to_user_ptr(attr->link_create.uprobe_multi.path);
uoffsets = u64_to_user_ptr(attr->link_create.uprobe_multi.offsets);
cnt = attr->link_create.uprobe_multi.cnt;
pid = attr->link_create.uprobe_multi.pid;
- if (!upath || !uoffsets || !cnt || pid < 0)
+ if (!uoffsets || !cnt || pid < 0)
return -EINVAL;
if (cnt > MAX_UPROBE_MULTI_CNT)
return -E2BIG;
@@ -3261,14 +3292,17 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
uref_ctr_offsets = u64_to_user_ptr(attr->link_create.uprobe_multi.ref_ctr_offsets);
ucookies = u64_to_user_ptr(attr->link_create.uprobe_multi.cookies);
- name = strndup_user(upath, PATH_MAX);
- if (IS_ERR(name)) {
- err = PTR_ERR(name);
- return err;
- }
+ /*
+ * All uoffsets/uref_ctr_offsets/ucookies arrays have the same value
+ * size, we need to check their address range is safe for __get_user
+ * calls.
+ */
+ size = sizeof(*uoffsets) * cnt;
+ if (!access_ok(uoffsets, size) || !access_ok(uref_ctr_offsets, size) ||
+ !access_ok(ucookies, size))
+ return -EFAULT;
- err = kern_path(name, LOOKUP_FOLLOW, &path);
- kfree(name);
+ err = bpf_uprobe_multi_get_path(attr, &path);
if (err)
return err;
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 11dd610fa5fa..89b36de5fdbb 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1327,7 +1327,11 @@ enum {
* BPF_TRACE_UPROBE_MULTI attach type to create return probe.
*/
enum {
- BPF_F_UPROBE_MULTI_RETURN = (1U << 0)
+ /* Get return uprobe. */
+ BPF_F_UPROBE_MULTI_RETURN = (1U << 0),
+
+ /* Get path from provided path_fd. */
+ BPF_F_UPROBE_MULTI_PATH_FD = (1U << 1),
};
/* link_create.netfilter.flags used in LINK_CREATE command for
@@ -1864,6 +1868,7 @@ union bpf_attr {
__u32 cnt;
__u32 flags;
__u32 pid;
+ __u32 path_fd;
} uprobe_multi;
struct {
union {
diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index f37e3416f61a..96819c082c77 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -842,6 +842,7 @@ int bpf_link_create(int prog_fd, int target_fd,
attr.link_create.uprobe_multi.ref_ctr_offsets = ptr_to_u64(OPTS_GET(opts, uprobe_multi.ref_ctr_offsets, 0));
attr.link_create.uprobe_multi.cookies = ptr_to_u64(OPTS_GET(opts, uprobe_multi.cookies, 0));
attr.link_create.uprobe_multi.pid = OPTS_GET(opts, uprobe_multi.pid, 0);
+ attr.link_create.uprobe_multi.path_fd = OPTS_GET(opts, uprobe_multi.path_fd, 0);
if (!OPTS_ZEROED(opts, uprobe_multi))
return libbpf_err(-EINVAL);
break;
diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
index 012354131cf6..7534a593edae 100644
--- a/tools/lib/bpf/bpf.h
+++ b/tools/lib/bpf/bpf.h
@@ -444,6 +444,7 @@ struct bpf_link_create_opts {
const unsigned long *ref_ctr_offsets;
const __u64 *cookies;
__u32 pid;
+ __u32 path_fd;
} uprobe_multi;
struct {
__u64 cookie;
@@ -477,7 +478,7 @@ struct bpf_link_create_opts {
};
size_t :0;
};
-#define bpf_link_create_opts__last_field uprobe_multi.pid
+#define bpf_link_create_opts__last_field uprobe_multi.path_fd
LIBBPF_API int bpf_link_create(int prog_fd, int target_fd,
enum bpf_attach_type attach_type,
diff --git a/tools/testing/selftests/bpf/prog_tests/fill_link_info.c b/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
index e40114620751..f589eefbf9fb 100644
--- a/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
+++ b/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
@@ -469,7 +469,7 @@ verify_umulti_link_info(int fd, bool retprobe, __u64 *offsets,
ASSERT_EQ(info.uprobe_multi.pid, getpid(), "info.uprobe_multi.pid");
ASSERT_EQ(info.uprobe_multi.count, 3, "info.uprobe_multi.count");
- ASSERT_EQ(info.uprobe_multi.flags & BPF_F_KPROBE_MULTI_RETURN,
+ ASSERT_EQ(info.uprobe_multi.flags & BPF_F_UPROBE_MULTI_RETURN,
retprobe, "info.uprobe_multi.flags.retprobe");
ASSERT_EQ(info.uprobe_multi.path_size, strlen(path) + 1, "info.uprobe_multi.path_size");
ASSERT_STREQ(path_buf, path, "info.uprobe_multi.path");
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
index 56cbea280fbd..f0baf5738b75 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
@@ -2,6 +2,7 @@
#include <unistd.h>
#include <pthread.h>
+#include <fcntl.h>
#include <test_progs.h>
#include "uprobe_multi.skel.h"
#include "uprobe_multi_bench.skel.h"
@@ -536,7 +537,37 @@ static void test_attach_api_fails(void)
link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_ERR(link_fd, "link_fd"))
goto cleanup;
- ASSERT_EQ(link_fd, -EINVAL, "pid_is_wrong");
+ if (!ASSERT_EQ(link_fd, -EINVAL, "pid_is_wrong"))
+ goto cleanup;
+
+ /* wrong path_fd */
+ LIBBPF_OPTS_RESET(opts,
+ .uprobe_multi.path = NULL,
+ .uprobe_multi.path_fd = -1,
+ .uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD,
+ .uprobe_multi.offsets = (unsigned long *)&offset,
+ .uprobe_multi.cnt = 1,
+ );
+
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_ERR(link_fd, "link_fd"))
+ goto cleanup;
+ if (!ASSERT_EQ(link_fd, -EBADF, "path_fd_is_wrong"))
+ goto cleanup;
+
+ /* path and path_fd both set with BPF_F_UPROBE_MULTI_PATH_FD flag */
+ LIBBPF_OPTS_RESET(opts,
+ .uprobe_multi.path = path,
+ .uprobe_multi.path_fd = 1,
+ .uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD,
+ .uprobe_multi.offsets = (unsigned long *)&offset,
+ .uprobe_multi.cnt = 1,
+ );
+
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_ERR(link_fd, "link_fd"))
+ goto cleanup;
+ ASSERT_EQ(link_fd, -EINVAL, "path_and_path_fd_together");
cleanup:
if (link_fd >= 0)
@@ -757,6 +788,65 @@ static void test_link_api(void)
__test_link_api(&child);
}
+static void test_link_api_path_fd(void)
+{
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+ const char *resolve_path = "/proc/self/exe";
+ int prog_fd, link_fd = -1, path_fd = -1;
+ struct uprobe_multi *skel = NULL;
+ unsigned long *offsets = NULL;
+ const char *syms[3] = {
+ "uprobe_multi_func_1",
+ "uprobe_multi_func_2",
+ "uprobe_multi_func_3",
+ };
+ int err;
+
+ err = elf_resolve_syms_offsets(resolve_path, ARRAY_SIZE(syms), syms,
+ &offsets, STT_FUNC);
+ if (!ASSERT_OK(err, "elf_resolve_syms_offsets"))
+ return;
+
+ path_fd = open(resolve_path, O_RDONLY);
+ if (!ASSERT_GE(path_fd, 0, "path_fd"))
+ goto cleanup;
+
+ opts.uprobe_multi.path_fd = path_fd;
+ opts.uprobe_multi.offsets = offsets;
+ opts.uprobe_multi.cnt = ARRAY_SIZE(syms);
+ opts.uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD;
+
+ skel = uprobe_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.uprobe);
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_GE(link_fd, 0, "bpf_link_create"))
+ goto cleanup;
+
+ skel->bss->uprobe_multi_func_1_addr = (__u64)uprobe_multi_func_1;
+ skel->bss->uprobe_multi_func_2_addr = (__u64)uprobe_multi_func_2;
+ skel->bss->uprobe_multi_func_3_addr = (__u64)uprobe_multi_func_3;
+ skel->bss->pid = getpid();
+
+ uprobe_multi_func_1();
+ uprobe_multi_func_2();
+ uprobe_multi_func_3();
+
+ ASSERT_EQ(skel->bss->uprobe_multi_func_1_result, 1, "uprobe_multi_func_1_result");
+ ASSERT_EQ(skel->bss->uprobe_multi_func_2_result, 1, "uprobe_multi_func_2_result");
+ ASSERT_EQ(skel->bss->uprobe_multi_func_3_result, 1, "uprobe_multi_func_3_result");
+
+cleanup:
+ if (link_fd >= 0)
+ close(link_fd);
+ if (path_fd >= 0)
+ close(path_fd);
+ uprobe_multi__destroy(skel);
+ free(offsets);
+}
+
static struct bpf_program *
get_program(struct uprobe_multi_consumers *skel, int prog)
{
@@ -1354,6 +1444,8 @@ void test_uprobe_multi_test(void)
test_attach_api_syms();
if (test__start_subtest("link_api"))
test_link_api();
+ if (test__start_subtest("link_api_path_fd"))
+ test_link_api_path_fd();
if (test__start_subtest("bench_uprobe"))
test_bench_attach_uprobe();
if (test__start_subtest("bench_usdt"))