BPF List
 help / color / mirror / Atom feed
From: Jiri Olsa <olsajiri@gmail.com>
To: Andrii Nakryiko <andrii@kernel.org>
Cc: bpf@vger.kernel.org, ast@kernel.org, daniel@iogearbox.net,
	martin.lau@kernel.org, kernel-team@meta.com
Subject: Re: [PATCH v2 bpf 4/5] selftests/bpf: extend multi-uprobe tests with child thread case
Date: Wed, 22 May 2024 09:46:36 +0200	[thread overview]
Message-ID: <Zk2i3Ohmnv52Zn08@krava> (raw)
In-Reply-To: <20240521163401.3005045-5-andrii@kernel.org>

On Tue, May 21, 2024 at 09:34:00AM -0700, Andrii Nakryiko wrote:
> Extend existing multi-uprobe tests to test that PID filtering works
> correctly. We already have child *process* tests, but we need also child
> *thread* tests. This patch adds spawn_thread() helper to start child
> thread, wait for it to be ready, and then instruct it to trigger desired
> uprobes.
> 
> Additionally, we extend BPF-side code to track thread ID, not just
> process ID. Also we detect whether extraneous triggerings with
> unexpected process IDs happened, and validate that none of that happened
> in practice.
> 
> These changes prove that fixed PID filtering logic for multi-uprobe
> works as expected. These tests fail on old kernels.
> 
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>

Acked-by: Jiri Olsa <jolsa@kernel.org>

jirka

> ---
>  .../bpf/prog_tests/uprobe_multi_test.c        | 107 ++++++++++++++++--
>  .../selftests/bpf/progs/uprobe_multi.c        |  17 ++-
>  2 files changed, 115 insertions(+), 9 deletions(-)
> 
> 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 38fda42fd70f..677232d31432 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0
>  
>  #include <unistd.h>
> +#include <pthread.h>
>  #include <test_progs.h>
>  #include "uprobe_multi.skel.h"
>  #include "uprobe_multi_bench.skel.h"
> @@ -27,7 +28,10 @@ noinline void uprobe_multi_func_3(void)
>  
>  struct child {
>  	int go[2];
> +	int c2p[2]; /* child -> parent channel */
>  	int pid;
> +	int tid;
> +	pthread_t thread;
>  };
>  
>  static void release_child(struct child *child)
> @@ -38,6 +42,10 @@ static void release_child(struct child *child)
>  		return;
>  	close(child->go[1]);
>  	close(child->go[0]);
> +	if (child->thread)
> +		pthread_join(child->thread, NULL);
> +	close(child->c2p[0]);
> +	close(child->c2p[1]);
>  	if (child->pid > 0)
>  		waitpid(child->pid, &child_status, 0);
>  }
> @@ -63,7 +71,7 @@ static struct child *spawn_child(void)
>  	if (pipe(child.go))
>  		return NULL;
>  
> -	child.pid = fork();
> +	child.pid = child.tid = fork();
>  	if (child.pid < 0) {
>  		release_child(&child);
>  		errno = EINVAL;
> @@ -89,6 +97,66 @@ static struct child *spawn_child(void)
>  	return &child;
>  }
>  
> +static void *child_thread(void *ctx)
> +{
> +	struct child *child = ctx;
> +	int c = 0, err;
> +
> +	child->tid = syscall(SYS_gettid);
> +
> +	/* let parent know we are ready */
> +	err = write(child->c2p[1], &c, 1);
> +	if (err != 1)
> +		pthread_exit(&err);
> +
> +	/* wait for parent's kick */
> +	err = read(child->go[0], &c, 1);
> +	if (err != 1)
> +		pthread_exit(&err);
> +
> +	uprobe_multi_func_1();
> +	uprobe_multi_func_2();
> +	uprobe_multi_func_3();
> +
> +	err = 0;
> +	pthread_exit(&err);
> +}
> +
> +static struct child *spawn_thread(void)
> +{
> +	static struct child child;
> +	int c, err;
> +
> +	/* pipe to notify child to execute the trigger functions */
> +	if (pipe(child.go))
> +		return NULL;
> +	/* pipe to notify parent that child thread is ready */
> +	if (pipe(child.c2p)) {
> +		close(child.go[0]);
> +		close(child.go[1]);
> +		return NULL;
> +	}
> +
> +	child.pid = getpid();
> +
> +	err = pthread_create(&child.thread, NULL, child_thread, &child);
> +	if (err) {
> +		err = -errno;
> +		close(child.go[0]);
> +		close(child.go[1]);
> +		close(child.c2p[0]);
> +		close(child.c2p[1]);
> +		errno = -err;
> +		return NULL;
> +	}
> +
> +	err = read(child.c2p[0], &c, 1);
> +	if (!ASSERT_EQ(err, 1, "child_thread_ready"))
> +		return NULL;
> +
> +	return &child;
> +}
> +
>  static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child)
>  {
>  	skel->bss->uprobe_multi_func_1_addr = (__u64) uprobe_multi_func_1;
> @@ -103,15 +171,22 @@ static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child
>  	 * passed at the probe attach.
>  	 */
>  	skel->bss->pid = child ? 0 : getpid();
> +	skel->bss->expect_pid = child ? child->pid : 0;
> +
> +	/* trigger all probes, if we are testing child *process*, just to make
> +	 * sure that PID filtering doesn't let through activations from wrong
> +	 * PIDs; when we test child *thread*, we don't want to do this to
> +	 * avoid double counting number of triggering events
> +	 */
> +	if (!child || !child->thread) {
> +		uprobe_multi_func_1();
> +		uprobe_multi_func_2();
> +		uprobe_multi_func_3();
> +	}
>  
>  	if (child)
>  		kick_child(child);
>  
> -	/* trigger all probes */
> -	uprobe_multi_func_1();
> -	uprobe_multi_func_2();
> -	uprobe_multi_func_3();
> -
>  	/*
>  	 * There are 2 entry and 2 exit probe called for each uprobe_multi_func_[123]
>  	 * function and each slepable probe (6) increments uprobe_multi_sleep_result.
> @@ -126,8 +201,12 @@ static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child
>  
>  	ASSERT_EQ(skel->bss->uprobe_multi_sleep_result, 6, "uprobe_multi_sleep_result");
>  
> -	if (child)
> +	ASSERT_FALSE(skel->bss->bad_pid_seen, "bad_pid_seen");
> +
> +	if (child) {
>  		ASSERT_EQ(skel->bss->child_pid, child->pid, "uprobe_multi_child_pid");
> +		ASSERT_EQ(skel->bss->child_tid, child->tid, "uprobe_multi_child_tid");
> +	}
>  }
>  
>  static void test_skel_api(void)
> @@ -210,6 +289,13 @@ test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_multi
>  		return;
>  
>  	__test_attach_api(binary, pattern, opts, child);
> +
> +	/* pid filter (thread) */
> +	child = spawn_thread();
> +	if (!ASSERT_OK_PTR(child, "spawn_thread"))
> +		return;
> +
> +	__test_attach_api(binary, pattern, opts, child);
>  }
>  
>  static void test_attach_api_pattern(void)
> @@ -495,6 +581,13 @@ static void test_link_api(void)
>  		return;
>  
>  	__test_link_api(child);
> +
> +	/* pid filter (thread) */
> +	child = spawn_thread();
> +	if (!ASSERT_OK_PTR(child, "spawn_thread"))
> +		return;
> +
> +	__test_link_api(child);
>  }
>  
>  static void test_bench_attach_uprobe(void)
> diff --git a/tools/testing/selftests/bpf/progs/uprobe_multi.c b/tools/testing/selftests/bpf/progs/uprobe_multi.c
> index 419d9aa28fce..86a7ff5d3726 100644
> --- a/tools/testing/selftests/bpf/progs/uprobe_multi.c
> +++ b/tools/testing/selftests/bpf/progs/uprobe_multi.c
> @@ -22,6 +22,10 @@ __u64 uprobe_multi_sleep_result = 0;
>  
>  int pid = 0;
>  int child_pid = 0;
> +int child_tid = 0;
> +
> +int expect_pid = 0;
> +bool bad_pid_seen = false;
>  
>  bool test_cookie = false;
>  void *user_ptr = 0;
> @@ -36,11 +40,19 @@ static __always_inline bool verify_sleepable_user_copy(void)
>  
>  static void uprobe_multi_check(void *ctx, bool is_return, bool is_sleep)
>  {
> -	child_pid = bpf_get_current_pid_tgid() >> 32;
> +	__u64 cur_pid_tgid = bpf_get_current_pid_tgid();
> +	__u32 cur_pid;
>  
> -	if (pid && child_pid != pid)
> +	cur_pid = cur_pid_tgid >> 32;
> +	if (pid && cur_pid != pid)
>  		return;
>  
> +	if (expect_pid && cur_pid != expect_pid)
> +		bad_pid_seen = true;
> +
> +	child_pid = cur_pid_tgid >> 32;
> +	child_tid = (__u32)cur_pid_tgid;
> +
>  	__u64 cookie = test_cookie ? bpf_get_attach_cookie(ctx) : 0;
>  	__u64 addr = bpf_get_func_ip(ctx);
>  
> @@ -97,5 +109,6 @@ int uretprobe_sleep(struct pt_regs *ctx)
>  SEC("uprobe.multi//proc/self/exe:uprobe_multi_func_*")
>  int uprobe_extra(struct pt_regs *ctx)
>  {
> +	/* we need this one just to mix PID-filtered and global uprobes */
>  	return 0;
>  }
> -- 
> 2.43.0
> 
> 

  reply	other threads:[~2024-05-22  7:46 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-05-21 16:33 [PATCH v2 bpf 0/5] Fix BPF multi-uprobe PID filtering logic Andrii Nakryiko
2024-05-21 16:33 ` [PATCH v2 bpf 1/5] bpf: fix " Andrii Nakryiko
2024-05-21 16:33 ` [PATCH v2 bpf 2/5] bpf: remove unnecessary rcu_read_{lock,unlock}() in multi-uprobe attach logic Andrii Nakryiko
2024-05-21 16:33 ` [PATCH v2 bpf 3/5] libbpf: detect broken PID filtering logic for multi-uprobe Andrii Nakryiko
2024-05-21 16:34 ` [PATCH v2 bpf 4/5] selftests/bpf: extend multi-uprobe tests with child thread case Andrii Nakryiko
2024-05-22  7:46   ` Jiri Olsa [this message]
2024-05-21 16:34 ` [PATCH v2 bpf 5/5] selftests/bpf: extend multi-uprobe tests with USDTs Andrii Nakryiko
2024-05-25 17:50 ` [PATCH v2 bpf 0/5] Fix BPF multi-uprobe PID filtering logic patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Zk2i3Ohmnv52Zn08@krava \
    --to=olsajiri@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=kernel-team@meta.com \
    --cc=martin.lau@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox