linux-trace-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe
@ 2025-08-01 21:02 Jiri Olsa
  2025-08-01 21:02 ` [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed Jiri Olsa
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Jiri Olsa @ 2025-08-01 21:02 UTC (permalink / raw)
  To: Oleg Nesterov, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko
  Cc: bpf, linux-kernel, linux-trace-kernel, x86, Song Liu,
	Yonghong Song, John Fastabend, Hao Luo, Steven Rostedt,
	Ingo Molnar

hi,
we recently had several requests for tetragon to be able to change
user application function return value or divert its execution through
instruction pointer change.

This *RFC* patchset adds support for uprobe program to change app's
registers including instruction pointer.

There's a hiccup with instruction pointer change.. if uprobe handler
changes instruction pointer, the current code will still execute the
original instruction and increment the (new) ip with its length.

This makes the new instruction pointer bogus and application will
likely crash on illegal instruction execution.

I think if user decides to take execution elsewhere, we can skip
original instruction execution (patch#1), but I might be easily
wrong and overlooking something.. hence RFC ;-)

thoughts? thanks,
jirka


---
Jiri Olsa (4):
      uprobe: Do not emulate/sstep original instruction when ip is changed
      bpf: Allow uprobe program to change context registers
      selftests/bpf: Add uprobe context registers changes test
      selftests/bpf: Add uprobe context ip register change test

 include/linux/bpf.h                             |   1 +
 kernel/events/core.c                            |   4 +++
 kernel/events/uprobes.c                         |   3 ++
 kernel/trace/bpf_trace.c                        |   3 +-
 tools/testing/selftests/bpf/prog_tests/uprobe.c | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 tools/testing/selftests/bpf/progs/test_uprobe.c |  38 ++++++++++++++++++++++++
 6 files changed, 208 insertions(+), 3 deletions(-)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed
  2025-08-01 21:02 [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe Jiri Olsa
@ 2025-08-01 21:02 ` Jiri Olsa
  2025-08-02 10:34   ` Oleg Nesterov
  2025-08-01 21:02 ` [RFC 2/4] bpf: Allow uprobe program to change context registers Jiri Olsa
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Jiri Olsa @ 2025-08-01 21:02 UTC (permalink / raw)
  To: Oleg Nesterov, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko
  Cc: bpf, linux-kernel, linux-trace-kernel, x86, Song Liu,
	Yonghong Song, John Fastabend, Hao Luo, Steven Rostedt,
	Ingo Molnar

If uprobe handler changes instruction pointer we still execute single
step) or emulate the original instruction and increment the (new) ip
with its length.

This makes the new instruction pointer bogus and application will
likely crash on illegal instruction execution.

If user decided to take execution elsewhere, it makes little sense
to execute the original instruction, so let's skip it.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 kernel/events/uprobes.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 4c965ba77f9f..dff5509cde67 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -2742,6 +2742,9 @@ static void handle_swbp(struct pt_regs *regs)
 
 	handler_chain(uprobe, regs);
 
+	if (instruction_pointer(regs) != bp_vaddr)
+		goto out;
+
 	if (arch_uprobe_skip_sstep(&uprobe->arch, regs))
 		goto out;
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC 2/4] bpf: Allow uprobe program to change context registers
  2025-08-01 21:02 [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe Jiri Olsa
  2025-08-01 21:02 ` [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed Jiri Olsa
@ 2025-08-01 21:02 ` Jiri Olsa
  2025-08-01 21:02 ` [RFC 3/4] selftests/bpf: Add uprobe context registers changes test Jiri Olsa
  2025-08-01 21:02 ` [RFC 4/4] selftests/bpf: Add uprobe context ip register change test Jiri Olsa
  3 siblings, 0 replies; 8+ messages in thread
From: Jiri Olsa @ 2025-08-01 21:02 UTC (permalink / raw)
  To: Oleg Nesterov, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko
  Cc: bpf, linux-kernel, linux-trace-kernel, x86, Song Liu,
	Yonghong Song, John Fastabend, Hao Luo, Steven Rostedt,
	Ingo Molnar

Currently uprobe (BPF_PROG_TYPE_KPROBE) program can't write to the
context registers data. While this makes sense for kprobe attachments,
for uprobe attachment it might make sense to be able to change user
space registers to alter application execution.

Since uprobe and kprobe programs share the same type (BPF_PROG_TYPE_KPROBE),
we can't deny write access to context during the program load. We need
to check on it during program attachment to see if it's going to be
kprobe or uprobe.

Storing the program's write attempt to context and checking on it
during the attachment.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 include/linux/bpf.h      | 1 +
 kernel/events/core.c     | 4 ++++
 kernel/trace/bpf_trace.c | 3 +--
 3 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f9cd2164ed23..8c8bd00e0095 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1609,6 +1609,7 @@ struct bpf_prog_aux {
 	bool priv_stack_requested;
 	bool changes_pkt_data;
 	bool might_sleep;
+	bool kprobe_write_ctx;
 	u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
 	struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
 	struct bpf_arena *arena;
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 22fdf0c187cd..4eaeeb40aa08 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -11205,6 +11205,10 @@ static int __perf_event_set_bpf_prog(struct perf_event *event,
 	if (prog->kprobe_override && !is_kprobe)
 		return -EINVAL;
 
+	/* Writing to context allowed only for uprobes. */
+	if (prog->aux->kprobe_write_ctx && !is_uprobe)
+		return -EINVAL;
+
 	if (is_tracepoint || is_syscall_tp) {
 		int off = trace_event_get_offsets(event->tp_event);
 
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 3ae52978cae6..467fd5ab4b79 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -1521,8 +1521,6 @@ static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type
 {
 	if (off < 0 || off >= sizeof(struct pt_regs))
 		return false;
-	if (type != BPF_READ)
-		return false;
 	if (off % size != 0)
 		return false;
 	/*
@@ -1532,6 +1530,7 @@ static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type
 	if (off + size > sizeof(struct pt_regs))
 		return false;
 
+	prog->aux->kprobe_write_ctx |= type == BPF_WRITE;
 	return true;
 }
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC 3/4] selftests/bpf: Add uprobe context registers changes test
  2025-08-01 21:02 [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe Jiri Olsa
  2025-08-01 21:02 ` [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed Jiri Olsa
  2025-08-01 21:02 ` [RFC 2/4] bpf: Allow uprobe program to change context registers Jiri Olsa
@ 2025-08-01 21:02 ` Jiri Olsa
  2025-08-01 21:02 ` [RFC 4/4] selftests/bpf: Add uprobe context ip register change test Jiri Olsa
  3 siblings, 0 replies; 8+ messages in thread
From: Jiri Olsa @ 2025-08-01 21:02 UTC (permalink / raw)
  To: Oleg Nesterov, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko
  Cc: bpf, linux-kernel, linux-trace-kernel, x86, Song Liu,
	Yonghong Song, John Fastabend, Hao Luo, Steven Rostedt,
	Ingo Molnar

Adding test to check we can change common register values through
uprobe program.

It's x86_64 specific test.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 .../testing/selftests/bpf/prog_tests/uprobe.c | 114 +++++++++++++++++-
 .../testing/selftests/bpf/progs/test_uprobe.c |  24 ++++
 2 files changed, 137 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe.c b/tools/testing/selftests/bpf/prog_tests/uprobe.c
index cf3e0e7a64fa..7c7cb08d10b3 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe.c
@@ -2,6 +2,7 @@
 /* Copyright (c) 2023 Hengqi Chen */
 
 #include <test_progs.h>
+#include <asm/ptrace.h>
 #include "test_uprobe.skel.h"
 
 static FILE *urand_spawn(int *pid)
@@ -33,7 +34,7 @@ static int urand_trigger(FILE **urand_pipe)
 	return exit_code;
 }
 
-void test_uprobe(void)
+static void test_uprobe_attach(void)
 {
 	LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
 	struct test_uprobe *skel;
@@ -93,3 +94,114 @@ void test_uprobe(void)
 		pclose(urand_pipe);
 	test_uprobe__destroy(skel);
 }
+
+#ifdef __x86_64__
+__naked __maybe_unused unsigned long uprobe_regs_change_trigger(void)
+{
+	asm volatile (
+		"ret\n"
+	);
+}
+
+static __naked void uprobe_regs_change(struct pt_regs *before, struct pt_regs *after)
+{
+	asm volatile (
+		"movq %r11,  48(%rdi)\n"
+		"movq %r10,  56(%rdi)\n"
+		"movq  %r9,  64(%rdi)\n"
+		"movq  %r8,  72(%rdi)\n"
+		"movq %rax,  80(%rdi)\n"
+		"movq %rcx,  88(%rdi)\n"
+		"movq %rdx,  96(%rdi)\n"
+		"movq %rsi, 104(%rdi)\n"
+		"movq %rdi, 112(%rdi)\n"
+
+		/* save 2nd argument */
+		"pushq %rsi\n"
+		"call uprobe_regs_change_trigger\n"
+
+		/* save  return value and load 2nd argument pointer to rax */
+		"pushq %rax\n"
+		"movq 8(%rsp), %rax\n"
+
+		"movq %r11,  48(%rax)\n"
+		"movq %r10,  56(%rax)\n"
+		"movq  %r9,  64(%rax)\n"
+		"movq  %r8,  72(%rax)\n"
+		"movq %rcx,  88(%rax)\n"
+		"movq %rdx,  96(%rax)\n"
+		"movq %rsi, 104(%rax)\n"
+		"movq %rdi, 112(%rax)\n"
+
+		/* restore return value and 2nd argument */
+		"pop %rax\n"
+		"pop %rsi\n"
+
+		"movq %rax,  80(%rsi)\n"
+		"ret\n"
+	);
+}
+
+static void regs_common(void)
+{
+	struct pt_regs before = {}, after = {}, expected = {
+		.rax = 0xc0ffe,
+		.rcx = 0xbad,
+		.rdx = 0xdead,
+		.r8  = 0x8,
+		.r9  = 0x9,
+		.r10 = 0x10,
+		.r11 = 0x11,
+		.rdi = 0x12,
+		.rsi = 0x13,
+	};
+	LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
+	struct test_uprobe *skel;
+
+	skel = test_uprobe__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		return;
+
+	skel->bss->my_pid = getpid();
+	skel->bss->regs = expected;
+
+	uprobe_opts.func_name = "uprobe_regs_change_trigger";
+	skel->links.test_regs_change = bpf_program__attach_uprobe_opts(skel->progs.test_regs_change,
+							    -1,
+							    "/proc/self/exe",
+							    0 /* offset */,
+							    &uprobe_opts);
+	if (!ASSERT_OK_PTR(skel->links.test_regs_change, "bpf_program__attach_uprobe_opts"))
+		goto cleanup;
+
+	uprobe_regs_change(&before, &after);
+
+	ASSERT_EQ(after.rax, expected.rax, "ax");
+	ASSERT_EQ(after.rcx, expected.rcx, "cx");
+	ASSERT_EQ(after.rdx, expected.rdx, "dx");
+	ASSERT_EQ(after.r8, expected.r8, "r8");
+	ASSERT_EQ(after.r9, expected.r9, "r9");
+	ASSERT_EQ(after.r10, expected.r10, "r10");
+	ASSERT_EQ(after.r11, expected.r11, "r11");
+	ASSERT_EQ(after.rdi, expected.rdi, "rdi");
+	ASSERT_EQ(after.rsi, expected.rsi, "rsi");
+
+cleanup:
+	test_uprobe__destroy(skel);
+}
+
+static void test_uprobe_regs_change(void)
+{
+	if (test__start_subtest("regs_change_common"))
+		regs_common();
+}
+#else
+static void test_uprobe_regs_change(void) { }
+#endif
+
+void test_uprobe(void)
+{
+	if (test__start_subtest("attach"))
+		test_uprobe_attach();
+	test_uprobe_regs_change();
+}
diff --git a/tools/testing/selftests/bpf/progs/test_uprobe.c b/tools/testing/selftests/bpf/progs/test_uprobe.c
index 896c88a4960d..9437bd76a437 100644
--- a/tools/testing/selftests/bpf/progs/test_uprobe.c
+++ b/tools/testing/selftests/bpf/progs/test_uprobe.c
@@ -59,3 +59,27 @@ int BPF_UPROBE(test4)
 	test4_result = 1;
 	return 0;
 }
+
+#if defined(__TARGET_ARCH_x86)
+struct pt_regs regs;
+
+SEC("uprobe")
+int BPF_UPROBE(test_regs_change)
+{
+	pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+	if (pid != my_pid)
+		return 0;
+
+	ctx->ax  = regs.ax;
+	ctx->cx  = regs.cx;
+	ctx->dx  = regs.dx;
+	ctx->r8  = regs.r8;
+	ctx->r9  = regs.r9;
+	ctx->r10 = regs.r10;
+	ctx->r11 = regs.r11;
+	ctx->di  = regs.di;
+	ctx->si  = regs.si;
+	return 0;
+}
+#endif
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC 4/4] selftests/bpf: Add uprobe context ip register change test
  2025-08-01 21:02 [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe Jiri Olsa
                   ` (2 preceding siblings ...)
  2025-08-01 21:02 ` [RFC 3/4] selftests/bpf: Add uprobe context registers changes test Jiri Olsa
@ 2025-08-01 21:02 ` Jiri Olsa
  3 siblings, 0 replies; 8+ messages in thread
From: Jiri Olsa @ 2025-08-01 21:02 UTC (permalink / raw)
  To: Oleg Nesterov, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko
  Cc: bpf, linux-kernel, linux-trace-kernel, x86, Song Liu,
	Yonghong Song, John Fastabend, Hao Luo, Steven Rostedt,
	Ingo Molnar

Adding test to check we can change the application execution
through instruction pointer change through uprobe program.

It's x86_64 specific test.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
 .../testing/selftests/bpf/prog_tests/uprobe.c | 48 +++++++++++++++++++
 .../testing/selftests/bpf/progs/test_uprobe.c | 14 ++++++
 2 files changed, 62 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe.c b/tools/testing/selftests/bpf/prog_tests/uprobe.c
index 7c7cb08d10b3..05cd3b65260f 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe.c
@@ -190,10 +190,58 @@ static void regs_common(void)
 	test_uprobe__destroy(skel);
 }
 
+static __naked unsigned long uprobe_regs_change_ip_1(void)
+{
+	asm volatile (
+		"movq $0xc0ffee, %rax\n"
+		"ret\n"
+	);
+}
+
+static __naked unsigned long uprobe_regs_change_ip_2(void)
+{
+	asm volatile (
+		"movq $0xdeadbeef, %rax\n"
+		"ret\n"
+	);
+}
+
+static void regs_ip(void)
+{
+	LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
+	struct test_uprobe *skel;
+	unsigned long ret;
+
+	skel = test_uprobe__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		return;
+
+	skel->bss->my_pid = getpid();
+	skel->bss->ip = (unsigned long) uprobe_regs_change_ip_2;
+
+	uprobe_opts.func_name = "uprobe_regs_change_ip_1";
+	skel->links.test_regs_change_ip = bpf_program__attach_uprobe_opts(
+						skel->progs.test_regs_change_ip,
+						-1,
+						"/proc/self/exe",
+						0 /* offset */,
+						&uprobe_opts);
+	if (!ASSERT_OK_PTR(skel->links.test_regs_change_ip, "bpf_program__attach_uprobe_opts"))
+		goto cleanup;
+
+	ret = uprobe_regs_change_ip_1();
+	ASSERT_EQ(ret, 0xdeadbeef, "ret");
+
+cleanup:
+	test_uprobe__destroy(skel);
+}
+
 static void test_uprobe_regs_change(void)
 {
 	if (test__start_subtest("regs_change_common"))
 		regs_common();
+	if (test__start_subtest("regs_change_ip"))
+		regs_ip();
 }
 #else
 static void test_uprobe_regs_change(void) { }
diff --git a/tools/testing/selftests/bpf/progs/test_uprobe.c b/tools/testing/selftests/bpf/progs/test_uprobe.c
index 9437bd76a437..12f4065fca20 100644
--- a/tools/testing/selftests/bpf/progs/test_uprobe.c
+++ b/tools/testing/selftests/bpf/progs/test_uprobe.c
@@ -82,4 +82,18 @@ int BPF_UPROBE(test_regs_change)
 	ctx->si  = regs.si;
 	return 0;
 }
+
+unsigned long ip;
+
+SEC("uprobe")
+int BPF_UPROBE(test_regs_change_ip)
+{
+	pid_t pid = bpf_get_current_pid_tgid() >> 32;
+
+	if (pid != my_pid)
+		return 0;
+
+	ctx->ip = ip;
+	return 0;
+}
 #endif
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed
  2025-08-01 21:02 ` [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed Jiri Olsa
@ 2025-08-02 10:34   ` Oleg Nesterov
  2025-08-04  8:12     ` Jiri Olsa
  0 siblings, 1 reply; 8+ messages in thread
From: Oleg Nesterov @ 2025-08-02 10:34 UTC (permalink / raw)
  To: Jiri Olsa
  Cc: Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko, bpf,
	linux-kernel, linux-trace-kernel, x86, Song Liu, Yonghong Song,
	John Fastabend, Hao Luo, Steven Rostedt, Ingo Molnar

On 08/01, Jiri Olsa wrote:
>
> If uprobe handler changes instruction pointer we still execute single
> step) or emulate the original instruction and increment the (new) ip
> with its length.

Yes... but what if we there are multiple consumers? The 1st one changes
instruction_pointer, the next is unaware. Or it may change regs->ip too...

Oleg.

> This makes the new instruction pointer bogus and application will
> likely crash on illegal instruction execution.
> 
> If user decided to take execution elsewhere, it makes little sense
> to execute the original instruction, so let's skip it.
> 
> Signed-off-by: Jiri Olsa <jolsa@kernel.org>
> ---
>  kernel/events/uprobes.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
> index 4c965ba77f9f..dff5509cde67 100644
> --- a/kernel/events/uprobes.c
> +++ b/kernel/events/uprobes.c
> @@ -2742,6 +2742,9 @@ static void handle_swbp(struct pt_regs *regs)
>  
>  	handler_chain(uprobe, regs);
>  
> +	if (instruction_pointer(regs) != bp_vaddr)
> +		goto out;
> +
>  	if (arch_uprobe_skip_sstep(&uprobe->arch, regs))
>  		goto out;
>  
> -- 
> 2.50.1
> 


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed
  2025-08-02 10:34   ` Oleg Nesterov
@ 2025-08-04  8:12     ` Jiri Olsa
  2025-08-08 18:38       ` Jiri Olsa
  0 siblings, 1 reply; 8+ messages in thread
From: Jiri Olsa @ 2025-08-04  8:12 UTC (permalink / raw)
  To: Oleg Nesterov
  Cc: Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko, bpf,
	linux-kernel, linux-trace-kernel, x86, Song Liu, Yonghong Song,
	John Fastabend, Hao Luo, Steven Rostedt, Ingo Molnar

On Sat, Aug 02, 2025 at 12:34:27PM +0200, Oleg Nesterov wrote:
> On 08/01, Jiri Olsa wrote:
> >
> > If uprobe handler changes instruction pointer we still execute single
> > step) or emulate the original instruction and increment the (new) ip
> > with its length.
> 
> Yes... but what if we there are multiple consumers? The 1st one changes
> instruction_pointer, the next is unaware. Or it may change regs->ip too...

right, and I think that's already bad in current code

how about we dd flag to the consumer that ensures it's the only consumer
on the uprobe.. and we would skip original instruction execution for such
uprobe if its consumer changes the regs->ip.. I'll try to come up with the
patch

jirka

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed
  2025-08-04  8:12     ` Jiri Olsa
@ 2025-08-08 18:38       ` Jiri Olsa
  0 siblings, 0 replies; 8+ messages in thread
From: Jiri Olsa @ 2025-08-08 18:38 UTC (permalink / raw)
  To: Oleg Nesterov
  Cc: Jiri Olsa, Masami Hiramatsu, Peter Zijlstra, Andrii Nakryiko, bpf,
	linux-kernel, linux-trace-kernel, x86, Song Liu, Yonghong Song,
	John Fastabend, Hao Luo, Steven Rostedt, Ingo Molnar

On Mon, Aug 04, 2025 at 10:12:15AM +0200, Jiri Olsa wrote:
> On Sat, Aug 02, 2025 at 12:34:27PM +0200, Oleg Nesterov wrote:
> > On 08/01, Jiri Olsa wrote:
> > >
> > > If uprobe handler changes instruction pointer we still execute single
> > > step) or emulate the original instruction and increment the (new) ip
> > > with its length.
> > 
> > Yes... but what if we there are multiple consumers? The 1st one changes
> > instruction_pointer, the next is unaware. Or it may change regs->ip too...
> 
> right, and I think that's already bad in current code
> 
> how about we dd flag to the consumer that ensures it's the only consumer
> on the uprobe.. and we would skip original instruction execution for such
> uprobe if its consumer changes the regs->ip.. I'll try to come up with the
> patch

how about something like below?

jirka


---
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index 516217c39094..b2c49a2d5468 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -59,6 +59,7 @@ struct uprobe_consumer {
 	struct list_head cons_node;
 
 	__u64 id;	/* set when uprobe_consumer is registered */
+	bool is_unique; /* the only consumer on uprobe */
 };
 
 #ifdef CONFIG_UPROBES
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index f774367c8e71..b317f9fbbf5c 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1014,14 +1014,32 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset,
 	return uprobe;
 }
 
-static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc)
+static bool consumer_can_add(struct list_head *head, struct uprobe_consumer *uc)
+{
+	/* there's no consumer, free to add one */
+	if (list_empty(head))
+		return true;
+	/* uprobe has consumer(s), can't add unique one */
+	if (uc->is_unique)
+		return false;
+	/* uprobe has consumer(s), we can add one only if it's not unique consumer */
+	return !list_first_entry(head, struct uprobe_consumer, cons_node)->is_unique;
+}
+
+static int consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc)
 {
 	static atomic64_t id;
+	int ret = -EBUSY;
 
 	down_write(&uprobe->consumer_rwsem);
+	if (!consumer_can_add(&uprobe->consumers, uc))
+		goto unlock;
 	list_add_rcu(&uc->cons_node, &uprobe->consumers);
 	uc->id = (__u64) atomic64_inc_return(&id);
+	ret = 0;
+unlock:
 	up_write(&uprobe->consumer_rwsem);
+	return ret;
 }
 
 /*
@@ -1410,7 +1428,12 @@ struct uprobe *uprobe_register(struct inode *inode,
 		return uprobe;
 
 	down_write(&uprobe->register_rwsem);
-	consumer_add(uprobe, uc);
+	ret = consumer_add(uprobe, uc);
+	if (ret) {
+		put_uprobe(uprobe);
+		up_write(&uprobe->register_rwsem);
+		return ERR_PTR(ret);
+	}
 	ret = register_for_each_vma(uprobe, uc);
 	up_write(&uprobe->register_rwsem);
 
@@ -2522,7 +2545,7 @@ static bool ignore_ret_handler(int rc)
 	return rc == UPROBE_HANDLER_REMOVE || rc == UPROBE_HANDLER_IGNORE;
 }
 
-static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
+static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs, bool *is_unique)
 {
 	struct uprobe_consumer *uc;
 	bool has_consumers = false, remove = true;
@@ -2536,6 +2559,8 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
 		__u64 cookie = 0;
 		int rc = 0;
 
+		*is_unique |= uc->is_unique;
+
 		if (uc->handler) {
 			rc = uc->handler(uc, regs, &cookie);
 			WARN(rc < 0 || rc > 2,
@@ -2685,6 +2710,7 @@ static void handle_swbp(struct pt_regs *regs)
 {
 	struct uprobe *uprobe;
 	unsigned long bp_vaddr;
+	bool is_unique = false;
 	int is_swbp;
 
 	bp_vaddr = uprobe_get_swbp_addr(regs);
@@ -2739,7 +2765,10 @@ static void handle_swbp(struct pt_regs *regs)
 	if (arch_uprobe_ignore(&uprobe->arch, regs))
 		goto out;
 
-	handler_chain(uprobe, regs);
+	handler_chain(uprobe, regs, &is_unique);
+
+	if (is_unique && instruction_pointer(regs) != bp_vaddr)
+		goto out;
 
 	if (arch_uprobe_skip_sstep(&uprobe->arch, regs))
 		goto out;

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2025-08-08 18:39 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-01 21:02 [RFC 0/4] uprobe,bpf: Allow to change app registers from uprobe Jiri Olsa
2025-08-01 21:02 ` [RFC 1/4] uprobe: Do not emulate/sstep original instruction when ip is changed Jiri Olsa
2025-08-02 10:34   ` Oleg Nesterov
2025-08-04  8:12     ` Jiri Olsa
2025-08-08 18:38       ` Jiri Olsa
2025-08-01 21:02 ` [RFC 2/4] bpf: Allow uprobe program to change context registers Jiri Olsa
2025-08-01 21:02 ` [RFC 3/4] selftests/bpf: Add uprobe context registers changes test Jiri Olsa
2025-08-01 21:02 ` [RFC 4/4] selftests/bpf: Add uprobe context ip register change test Jiri Olsa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).