Skip to content

Commit 590016a

Browse files
author
Alexei Starovoitov
committed
Merge branch 'fix-bpf-multi-uprobe-pid-filtering-logic'
Andrii Nakryiko says: ==================== Fix BPF multi-uprobe PID filtering logic It turns out that current implementation of multi-uprobe PID filtering logic is broken. It filters by thread, while the promise is filtering by process. Patch SamirMulani#1 fixes the logic trivially. The rest is testing and mitigations that are necessary for libbpf to not break users of USDT programs. v1->v2: - fix selftest in last patch (CI); - use semicolon in patch SamirMulani#3 (Jiri). ==================== Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents 44382b3 + 198034a commit 590016a

File tree

4 files changed

+206
-19
lines changed

4 files changed

+206
-19
lines changed

kernel/trace/bpf_trace.c

+4-6
Original file line numberDiff line numberDiff line change
@@ -3295,7 +3295,7 @@ static int uprobe_prog_run(struct bpf_uprobe *uprobe,
32953295
struct bpf_run_ctx *old_run_ctx;
32963296
int err = 0;
32973297

3298-
if (link->task && current != link->task)
3298+
if (link->task && current->mm != link->task->mm)
32993299
return 0;
33003300

33013301
if (sleepable)
@@ -3396,8 +3396,9 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
33963396
upath = u64_to_user_ptr(attr->link_create.uprobe_multi.path);
33973397
uoffsets = u64_to_user_ptr(attr->link_create.uprobe_multi.offsets);
33983398
cnt = attr->link_create.uprobe_multi.cnt;
3399+
pid = attr->link_create.uprobe_multi.pid;
33993400

3400-
if (!upath || !uoffsets || !cnt)
3401+
if (!upath || !uoffsets || !cnt || pid < 0)
34013402
return -EINVAL;
34023403
if (cnt > MAX_UPROBE_MULTI_CNT)
34033404
return -E2BIG;
@@ -3421,11 +3422,8 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
34213422
goto error_path_put;
34223423
}
34233424

3424-
pid = attr->link_create.uprobe_multi.pid;
34253425
if (pid) {
3426-
rcu_read_lock();
3427-
task = get_pid_task(find_vpid(pid), PIDTYPE_PID);
3428-
rcu_read_unlock();
3426+
task = get_pid_task(find_vpid(pid), PIDTYPE_TGID);
34293427
if (!task) {
34303428
err = -ESRCH;
34313429
goto error_path_put;

tools/lib/bpf/features.c

+30-1
Original file line numberDiff line numberDiff line change
@@ -392,11 +392,40 @@ static int probe_uprobe_multi_link(int token_fd)
392392
link_fd = bpf_link_create(prog_fd, -1, BPF_TRACE_UPROBE_MULTI, &link_opts);
393393
err = -errno; /* close() can clobber errno */
394394

395+
if (link_fd >= 0 || err != -EBADF) {
396+
close(link_fd);
397+
close(prog_fd);
398+
return 0;
399+
}
400+
401+
/* Initial multi-uprobe support in kernel didn't handle PID filtering
402+
* correctly (it was doing thread filtering, not process filtering).
403+
* So now we'll detect if PID filtering logic was fixed, and, if not,
404+
* we'll pretend multi-uprobes are not supported, if not.
405+
* Multi-uprobes are used in USDT attachment logic, and we need to be
406+
* conservative here, because multi-uprobe selection happens early at
407+
* load time, while the use of PID filtering is known late at
408+
* attachment time, at which point it's too late to undo multi-uprobe
409+
* selection.
410+
*
411+
* Creating uprobe with pid == -1 for (invalid) '/' binary will fail
412+
* early with -EINVAL on kernels with fixed PID filtering logic;
413+
* otherwise -ESRCH would be returned if passed correct binary path
414+
* (but we'll just get -BADF, of course).
415+
*/
416+
link_opts.uprobe_multi.pid = -1; /* invalid PID */
417+
link_opts.uprobe_multi.path = "/"; /* invalid path */
418+
link_opts.uprobe_multi.offsets = &offset;
419+
link_opts.uprobe_multi.cnt = 1;
420+
421+
link_fd = bpf_link_create(prog_fd, -1, BPF_TRACE_UPROBE_MULTI, &link_opts);
422+
err = -errno; /* close() can clobber errno */
423+
395424
if (link_fd >= 0)
396425
close(link_fd);
397426
close(prog_fd);
398427

399-
return link_fd < 0 && err == -EBADF;
428+
return link_fd < 0 && err == -EINVAL;
400429
}
401430

402431
static int probe_kern_bpf_cookie(int token_fd)

tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c

+126-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// SPDX-License-Identifier: GPL-2.0
22

33
#include <unistd.h>
4+
#include <pthread.h>
45
#include <test_progs.h>
56
#include "uprobe_multi.skel.h"
67
#include "uprobe_multi_bench.skel.h"
78
#include "uprobe_multi_usdt.skel.h"
89
#include "bpf/libbpf_internal.h"
910
#include "testing_helpers.h"
11+
#include "../sdt.h"
1012

1113
static char test_data[] = "test_data";
1214

@@ -25,9 +27,17 @@ noinline void uprobe_multi_func_3(void)
2527
asm volatile ("");
2628
}
2729

30+
noinline void usdt_trigger(void)
31+
{
32+
STAP_PROBE(test, pid_filter_usdt);
33+
}
34+
2835
struct child {
2936
int go[2];
37+
int c2p[2]; /* child -> parent channel */
3038
int pid;
39+
int tid;
40+
pthread_t thread;
3141
};
3242

3343
static void release_child(struct child *child)
@@ -38,6 +48,10 @@ static void release_child(struct child *child)
3848
return;
3949
close(child->go[1]);
4050
close(child->go[0]);
51+
if (child->thread)
52+
pthread_join(child->thread, NULL);
53+
close(child->c2p[0]);
54+
close(child->c2p[1]);
4155
if (child->pid > 0)
4256
waitpid(child->pid, &child_status, 0);
4357
}
@@ -63,7 +77,7 @@ static struct child *spawn_child(void)
6377
if (pipe(child.go))
6478
return NULL;
6579

66-
child.pid = fork();
80+
child.pid = child.tid = fork();
6781
if (child.pid < 0) {
6882
release_child(&child);
6983
errno = EINVAL;
@@ -82,13 +96,75 @@ static struct child *spawn_child(void)
8296
uprobe_multi_func_1();
8397
uprobe_multi_func_2();
8498
uprobe_multi_func_3();
99+
usdt_trigger();
85100

86101
exit(errno);
87102
}
88103

89104
return &child;
90105
}
91106

107+
static void *child_thread(void *ctx)
108+
{
109+
struct child *child = ctx;
110+
int c = 0, err;
111+
112+
child->tid = syscall(SYS_gettid);
113+
114+
/* let parent know we are ready */
115+
err = write(child->c2p[1], &c, 1);
116+
if (err != 1)
117+
pthread_exit(&err);
118+
119+
/* wait for parent's kick */
120+
err = read(child->go[0], &c, 1);
121+
if (err != 1)
122+
pthread_exit(&err);
123+
124+
uprobe_multi_func_1();
125+
uprobe_multi_func_2();
126+
uprobe_multi_func_3();
127+
usdt_trigger();
128+
129+
err = 0;
130+
pthread_exit(&err);
131+
}
132+
133+
static struct child *spawn_thread(void)
134+
{
135+
static struct child child;
136+
int c, err;
137+
138+
/* pipe to notify child to execute the trigger functions */
139+
if (pipe(child.go))
140+
return NULL;
141+
/* pipe to notify parent that child thread is ready */
142+
if (pipe(child.c2p)) {
143+
close(child.go[0]);
144+
close(child.go[1]);
145+
return NULL;
146+
}
147+
148+
child.pid = getpid();
149+
150+
err = pthread_create(&child.thread, NULL, child_thread, &child);
151+
if (err) {
152+
err = -errno;
153+
close(child.go[0]);
154+
close(child.go[1]);
155+
close(child.c2p[0]);
156+
close(child.c2p[1]);
157+
errno = -err;
158+
return NULL;
159+
}
160+
161+
err = read(child.c2p[0], &c, 1);
162+
if (!ASSERT_EQ(err, 1, "child_thread_ready"))
163+
return NULL;
164+
165+
return &child;
166+
}
167+
92168
static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child)
93169
{
94170
skel->bss->uprobe_multi_func_1_addr = (__u64) uprobe_multi_func_1;
@@ -103,15 +179,23 @@ static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child
103179
* passed at the probe attach.
104180
*/
105181
skel->bss->pid = child ? 0 : getpid();
182+
skel->bss->expect_pid = child ? child->pid : 0;
183+
184+
/* trigger all probes, if we are testing child *process*, just to make
185+
* sure that PID filtering doesn't let through activations from wrong
186+
* PIDs; when we test child *thread*, we don't want to do this to
187+
* avoid double counting number of triggering events
188+
*/
189+
if (!child || !child->thread) {
190+
uprobe_multi_func_1();
191+
uprobe_multi_func_2();
192+
uprobe_multi_func_3();
193+
usdt_trigger();
194+
}
106195

107196
if (child)
108197
kick_child(child);
109198

110-
/* trigger all probes */
111-
uprobe_multi_func_1();
112-
uprobe_multi_func_2();
113-
uprobe_multi_func_3();
114-
115199
/*
116200
* There are 2 entry and 2 exit probe called for each uprobe_multi_func_[123]
117201
* function and each slepable probe (6) increments uprobe_multi_sleep_result.
@@ -126,8 +210,12 @@ static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child
126210

127211
ASSERT_EQ(skel->bss->uprobe_multi_sleep_result, 6, "uprobe_multi_sleep_result");
128212

129-
if (child)
213+
ASSERT_FALSE(skel->bss->bad_pid_seen, "bad_pid_seen");
214+
215+
if (child) {
130216
ASSERT_EQ(skel->bss->child_pid, child->pid, "uprobe_multi_child_pid");
217+
ASSERT_EQ(skel->bss->child_tid, child->tid, "uprobe_multi_child_tid");
218+
}
131219
}
132220

133221
static void test_skel_api(void)
@@ -190,8 +278,24 @@ __test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_mul
190278
if (!ASSERT_OK_PTR(skel->links.uprobe_extra, "bpf_program__attach_uprobe_multi"))
191279
goto cleanup;
192280

281+
/* Attach (uprobe-backed) USDTs */
282+
skel->links.usdt_pid = bpf_program__attach_usdt(skel->progs.usdt_pid, pid, binary,
283+
"test", "pid_filter_usdt", NULL);
284+
if (!ASSERT_OK_PTR(skel->links.usdt_pid, "attach_usdt_pid"))
285+
goto cleanup;
286+
287+
skel->links.usdt_extra = bpf_program__attach_usdt(skel->progs.usdt_extra, -1, binary,
288+
"test", "pid_filter_usdt", NULL);
289+
if (!ASSERT_OK_PTR(skel->links.usdt_extra, "attach_usdt_extra"))
290+
goto cleanup;
291+
193292
uprobe_multi_test_run(skel, child);
194293

294+
ASSERT_FALSE(skel->bss->bad_pid_seen_usdt, "bad_pid_seen_usdt");
295+
if (child) {
296+
ASSERT_EQ(skel->bss->child_pid_usdt, child->pid, "usdt_multi_child_pid");
297+
ASSERT_EQ(skel->bss->child_tid_usdt, child->tid, "usdt_multi_child_tid");
298+
}
195299
cleanup:
196300
uprobe_multi__destroy(skel);
197301
}
@@ -210,6 +314,13 @@ test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_multi
210314
return;
211315

212316
__test_attach_api(binary, pattern, opts, child);
317+
318+
/* pid filter (thread) */
319+
child = spawn_thread();
320+
if (!ASSERT_OK_PTR(child, "spawn_thread"))
321+
return;
322+
323+
__test_attach_api(binary, pattern, opts, child);
213324
}
214325

215326
static void test_attach_api_pattern(void)
@@ -397,7 +508,7 @@ static void test_attach_api_fails(void)
397508
link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
398509
if (!ASSERT_ERR(link_fd, "link_fd"))
399510
goto cleanup;
400-
ASSERT_EQ(link_fd, -ESRCH, "pid_is_wrong");
511+
ASSERT_EQ(link_fd, -EINVAL, "pid_is_wrong");
401512

402513
cleanup:
403514
if (link_fd >= 0)
@@ -495,6 +606,13 @@ static void test_link_api(void)
495606
return;
496607

497608
__test_link_api(child);
609+
610+
/* pid filter (thread) */
611+
child = spawn_thread();
612+
if (!ASSERT_OK_PTR(child, "spawn_thread"))
613+
return;
614+
615+
__test_link_api(child);
498616
}
499617

500618
static void test_bench_attach_uprobe(void)

tools/testing/selftests/bpf/progs/uprobe_multi.c

+46-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// SPDX-License-Identifier: GPL-2.0
2-
#include <linux/bpf.h>
2+
#include "vmlinux.h"
33
#include <bpf/bpf_helpers.h>
44
#include <bpf/bpf_tracing.h>
5-
#include <stdbool.h>
5+
#include <bpf/usdt.bpf.h>
66

77
char _license[] SEC("license") = "GPL";
88

@@ -22,6 +22,13 @@ __u64 uprobe_multi_sleep_result = 0;
2222

2323
int pid = 0;
2424
int child_pid = 0;
25+
int child_tid = 0;
26+
int child_pid_usdt = 0;
27+
int child_tid_usdt = 0;
28+
29+
int expect_pid = 0;
30+
bool bad_pid_seen = false;
31+
bool bad_pid_seen_usdt = false;
2532

2633
bool test_cookie = false;
2734
void *user_ptr = 0;
@@ -36,11 +43,19 @@ static __always_inline bool verify_sleepable_user_copy(void)
3643

3744
static void uprobe_multi_check(void *ctx, bool is_return, bool is_sleep)
3845
{
39-
child_pid = bpf_get_current_pid_tgid() >> 32;
46+
__u64 cur_pid_tgid = bpf_get_current_pid_tgid();
47+
__u32 cur_pid;
4048

41-
if (pid && child_pid != pid)
49+
cur_pid = cur_pid_tgid >> 32;
50+
if (pid && cur_pid != pid)
4251
return;
4352

53+
if (expect_pid && cur_pid != expect_pid)
54+
bad_pid_seen = true;
55+
56+
child_pid = cur_pid_tgid >> 32;
57+
child_tid = (__u32)cur_pid_tgid;
58+
4459
__u64 cookie = test_cookie ? bpf_get_attach_cookie(ctx) : 0;
4560
__u64 addr = bpf_get_func_ip(ctx);
4661

@@ -97,5 +112,32 @@ int uretprobe_sleep(struct pt_regs *ctx)
97112
SEC("uprobe.multi//proc/self/exe:uprobe_multi_func_*")
98113
int uprobe_extra(struct pt_regs *ctx)
99114
{
115+
/* we need this one just to mix PID-filtered and global uprobes */
116+
return 0;
117+
}
118+
119+
SEC("usdt")
120+
int usdt_pid(struct pt_regs *ctx)
121+
{
122+
__u64 cur_pid_tgid = bpf_get_current_pid_tgid();
123+
__u32 cur_pid;
124+
125+
cur_pid = cur_pid_tgid >> 32;
126+
if (pid && cur_pid != pid)
127+
return 0;
128+
129+
if (expect_pid && cur_pid != expect_pid)
130+
bad_pid_seen_usdt = true;
131+
132+
child_pid_usdt = cur_pid_tgid >> 32;
133+
child_tid_usdt = (__u32)cur_pid_tgid;
134+
135+
return 0;
136+
}
137+
138+
SEC("usdt")
139+
int usdt_extra(struct pt_regs *ctx)
140+
{
141+
/* we need this one just to mix PID-filtered and global USDT probes */
100142
return 0;
101143
}

0 commit comments

Comments
 (0)