Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'uprobe-bpf-allow-to-change-app-registers-from-uprobe-registers'

Jiri Olsa says:

====================
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 patchset adds support for uprobe program to change app's registers
including instruction pointer.

v4 changes:
- rebased on bpf-next/master, we will handle the future simple conflict
with tip/perf/core
- changed condition in kprobe_prog_is_valid_access [Andrii]
- added acks
====================

Link: https://patch.msgid.link/20250916215301.664963-1-jolsa@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+289 -3
+1
include/linux/bpf.h
··· 1639 1639 bool priv_stack_requested; 1640 1640 bool changes_pkt_data; 1641 1641 bool might_sleep; 1642 + bool kprobe_write_ctx; 1642 1643 u64 prog_array_member_cnt; /* counts how many times as member of prog_array */ 1643 1644 struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */ 1644 1645 struct bpf_arena *arena;
+4
kernel/events/core.c
··· 11232 11232 if (prog->kprobe_override && !is_kprobe) 11233 11233 return -EINVAL; 11234 11234 11235 + /* Writing to context allowed only for uprobes. */ 11236 + if (prog->aux->kprobe_write_ctx && !is_uprobe) 11237 + return -EINVAL; 11238 + 11235 11239 if (is_tracepoint || is_syscall_tp) { 11236 11240 int off = trace_event_get_offsets(event->tp_event); 11237 11241
+7
kernel/events/uprobes.c
··· 2741 2741 2742 2742 handler_chain(uprobe, regs); 2743 2743 2744 + /* 2745 + * If user decided to take execution elsewhere, it makes little sense 2746 + * to execute the original instruction, so let's skip it. 2747 + */ 2748 + if (instruction_pointer(regs) != bp_vaddr) 2749 + goto out; 2750 + 2744 2751 if (arch_uprobe_skip_sstep(&uprobe->arch, regs)) 2745 2752 goto out; 2746 2753
+7 -2
kernel/trace/bpf_trace.c
··· 1338 1338 { 1339 1339 if (off < 0 || off >= sizeof(struct pt_regs)) 1340 1340 return false; 1341 - if (type != BPF_READ) 1342 - return false; 1343 1341 if (off % size != 0) 1344 1342 return false; 1345 1343 /* ··· 1346 1348 */ 1347 1349 if (off + size > sizeof(struct pt_regs)) 1348 1350 return false; 1351 + 1352 + if (type == BPF_WRITE) 1353 + prog->aux->kprobe_write_ctx = true; 1349 1354 1350 1355 return true; 1351 1356 } ··· 2734 2733 return -EINVAL; 2735 2734 2736 2735 if (!is_kprobe_multi(prog)) 2736 + return -EINVAL; 2737 + 2738 + /* Writing to context is not allowed for kprobes. */ 2739 + if (prog->aux->kprobe_write_ctx) 2737 2740 return -EINVAL; 2738 2741 2739 2742 flags = attr->link_create.kprobe_multi.flags;
+28
tools/testing/selftests/bpf/prog_tests/attach_probe.c
··· 3 3 #include "test_attach_kprobe_sleepable.skel.h" 4 4 #include "test_attach_probe_manual.skel.h" 5 5 #include "test_attach_probe.skel.h" 6 + #include "kprobe_write_ctx.skel.h" 6 7 7 8 /* this is how USDT semaphore is actually defined, except volatile modifier */ 8 9 volatile unsigned short uprobe_ref_ctr __attribute__((unused)) __attribute((section(".probes"))); ··· 201 200 cleanup: 202 201 test_attach_probe_manual__destroy(skel); 203 202 } 203 + 204 + #ifdef __x86_64__ 205 + /* attach kprobe/kretprobe long event name testings */ 206 + static void test_attach_kprobe_write_ctx(void) 207 + { 208 + struct kprobe_write_ctx *skel = NULL; 209 + struct bpf_link *link = NULL; 210 + 211 + skel = kprobe_write_ctx__open_and_load(); 212 + if (!ASSERT_OK_PTR(skel, "kprobe_write_ctx__open_and_load")) 213 + return; 214 + 215 + link = bpf_program__attach_kprobe_opts(skel->progs.kprobe_write_ctx, 216 + "bpf_fentry_test1", NULL); 217 + if (!ASSERT_ERR_PTR(link, "bpf_program__attach_kprobe_opts")) 218 + bpf_link__destroy(link); 219 + 220 + kprobe_write_ctx__destroy(skel); 221 + } 222 + #else 223 + static void test_attach_kprobe_write_ctx(void) 224 + { 225 + test__skip(); 226 + } 227 + #endif 204 228 205 229 static void test_attach_probe_auto(struct test_attach_probe *skel) 206 230 { ··· 432 406 test_attach_uprobe_long_event_name(); 433 407 if (test__start_subtest("kprobe-long_name")) 434 408 test_attach_kprobe_long_event_name(); 409 + if (test__start_subtest("kprobe-write-ctx")) 410 + test_attach_kprobe_write_ctx(); 435 411 436 412 cleanup: 437 413 test_attach_probe__destroy(skel);
+27
tools/testing/selftests/bpf/prog_tests/kprobe_multi_test.c
··· 7 7 #include "kprobe_multi_session.skel.h" 8 8 #include "kprobe_multi_session_cookie.skel.h" 9 9 #include "kprobe_multi_verifier.skel.h" 10 + #include "kprobe_write_ctx.skel.h" 10 11 #include "bpf/libbpf_internal.h" 11 12 #include "bpf/hashmap.h" 12 13 ··· 540 539 kprobe_multi_override__destroy(skel); 541 540 } 542 541 542 + #ifdef __x86_64__ 543 + static void test_attach_write_ctx(void) 544 + { 545 + struct kprobe_write_ctx *skel = NULL; 546 + struct bpf_link *link = NULL; 547 + 548 + skel = kprobe_write_ctx__open_and_load(); 549 + if (!ASSERT_OK_PTR(skel, "kprobe_write_ctx__open_and_load")) 550 + return; 551 + 552 + link = bpf_program__attach_kprobe_opts(skel->progs.kprobe_multi_write_ctx, 553 + "bpf_fentry_test1", NULL); 554 + if (!ASSERT_ERR_PTR(link, "bpf_program__attach_kprobe_opts")) 555 + bpf_link__destroy(link); 556 + 557 + kprobe_write_ctx__destroy(skel); 558 + } 559 + #else 560 + static void test_attach_write_ctx(void) 561 + { 562 + test__skip(); 563 + } 564 + #endif 565 + 543 566 void serial_test_kprobe_multi_bench_attach(void) 544 567 { 545 568 if (test__start_subtest("kernel")) ··· 603 578 test_session_cookie_skel_api(); 604 579 if (test__start_subtest("unique_match")) 605 580 test_unique_match(); 581 + if (test__start_subtest("attach_write_ctx")) 582 + test_attach_write_ctx(); 606 583 RUN_TESTS(kprobe_multi_verifier); 607 584 }
+155 -1
tools/testing/selftests/bpf/prog_tests/uprobe.c
··· 2 2 /* Copyright (c) 2023 Hengqi Chen */ 3 3 4 4 #include <test_progs.h> 5 + #include <asm/ptrace.h> 5 6 #include "test_uprobe.skel.h" 6 7 7 8 static FILE *urand_spawn(int *pid) ··· 34 33 return exit_code; 35 34 } 36 35 37 - void test_uprobe(void) 36 + static void test_uprobe_attach(void) 38 37 { 39 38 LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); 40 39 struct test_uprobe *skel; ··· 93 92 if (urand_pipe) 94 93 pclose(urand_pipe); 95 94 test_uprobe__destroy(skel); 95 + } 96 + 97 + #ifdef __x86_64__ 98 + __naked __maybe_unused unsigned long uprobe_regs_change_trigger(void) 99 + { 100 + asm volatile ( 101 + "ret\n" 102 + ); 103 + } 104 + 105 + static __naked void uprobe_regs_change(struct pt_regs *before, struct pt_regs *after) 106 + { 107 + asm volatile ( 108 + "movq %r11, 48(%rdi)\n" 109 + "movq %r10, 56(%rdi)\n" 110 + "movq %r9, 64(%rdi)\n" 111 + "movq %r8, 72(%rdi)\n" 112 + "movq %rax, 80(%rdi)\n" 113 + "movq %rcx, 88(%rdi)\n" 114 + "movq %rdx, 96(%rdi)\n" 115 + "movq %rsi, 104(%rdi)\n" 116 + "movq %rdi, 112(%rdi)\n" 117 + 118 + /* save 2nd argument */ 119 + "pushq %rsi\n" 120 + "call uprobe_regs_change_trigger\n" 121 + 122 + /* save return value and load 2nd argument pointer to rax */ 123 + "pushq %rax\n" 124 + "movq 8(%rsp), %rax\n" 125 + 126 + "movq %r11, 48(%rax)\n" 127 + "movq %r10, 56(%rax)\n" 128 + "movq %r9, 64(%rax)\n" 129 + "movq %r8, 72(%rax)\n" 130 + "movq %rcx, 88(%rax)\n" 131 + "movq %rdx, 96(%rax)\n" 132 + "movq %rsi, 104(%rax)\n" 133 + "movq %rdi, 112(%rax)\n" 134 + 135 + /* restore return value and 2nd argument */ 136 + "pop %rax\n" 137 + "pop %rsi\n" 138 + 139 + "movq %rax, 80(%rsi)\n" 140 + "ret\n" 141 + ); 142 + } 143 + 144 + static void regs_common(void) 145 + { 146 + struct pt_regs before = {}, after = {}, expected = { 147 + .rax = 0xc0ffe, 148 + .rcx = 0xbad, 149 + .rdx = 0xdead, 150 + .r8 = 0x8, 151 + .r9 = 0x9, 152 + .r10 = 0x10, 153 + .r11 = 0x11, 154 + .rdi = 0x12, 155 + .rsi = 0x13, 156 + }; 157 + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); 158 + struct test_uprobe *skel; 159 + 160 + skel = test_uprobe__open_and_load(); 161 + if (!ASSERT_OK_PTR(skel, "skel_open")) 162 + return; 163 + 164 + skel->bss->my_pid = getpid(); 165 + skel->bss->regs = expected; 166 + 167 + uprobe_opts.func_name = "uprobe_regs_change_trigger"; 168 + skel->links.test_regs_change = bpf_program__attach_uprobe_opts(skel->progs.test_regs_change, 169 + -1, 170 + "/proc/self/exe", 171 + 0 /* offset */, 172 + &uprobe_opts); 173 + if (!ASSERT_OK_PTR(skel->links.test_regs_change, "bpf_program__attach_uprobe_opts")) 174 + goto cleanup; 175 + 176 + uprobe_regs_change(&before, &after); 177 + 178 + ASSERT_EQ(after.rax, expected.rax, "ax"); 179 + ASSERT_EQ(after.rcx, expected.rcx, "cx"); 180 + ASSERT_EQ(after.rdx, expected.rdx, "dx"); 181 + ASSERT_EQ(after.r8, expected.r8, "r8"); 182 + ASSERT_EQ(after.r9, expected.r9, "r9"); 183 + ASSERT_EQ(after.r10, expected.r10, "r10"); 184 + ASSERT_EQ(after.r11, expected.r11, "r11"); 185 + ASSERT_EQ(after.rdi, expected.rdi, "rdi"); 186 + ASSERT_EQ(after.rsi, expected.rsi, "rsi"); 187 + 188 + cleanup: 189 + test_uprobe__destroy(skel); 190 + } 191 + 192 + static noinline unsigned long uprobe_regs_change_ip_1(void) 193 + { 194 + return 0xc0ffee; 195 + } 196 + 197 + static noinline unsigned long uprobe_regs_change_ip_2(void) 198 + { 199 + return 0xdeadbeef; 200 + } 201 + 202 + static void regs_ip(void) 203 + { 204 + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); 205 + struct test_uprobe *skel; 206 + unsigned long ret; 207 + 208 + skel = test_uprobe__open_and_load(); 209 + if (!ASSERT_OK_PTR(skel, "skel_open")) 210 + return; 211 + 212 + skel->bss->my_pid = getpid(); 213 + skel->bss->ip = (unsigned long) uprobe_regs_change_ip_2; 214 + 215 + uprobe_opts.func_name = "uprobe_regs_change_ip_1"; 216 + skel->links.test_regs_change_ip = bpf_program__attach_uprobe_opts( 217 + skel->progs.test_regs_change_ip, 218 + -1, 219 + "/proc/self/exe", 220 + 0 /* offset */, 221 + &uprobe_opts); 222 + if (!ASSERT_OK_PTR(skel->links.test_regs_change_ip, "bpf_program__attach_uprobe_opts")) 223 + goto cleanup; 224 + 225 + ret = uprobe_regs_change_ip_1(); 226 + ASSERT_EQ(ret, 0xdeadbeef, "ret"); 227 + 228 + cleanup: 229 + test_uprobe__destroy(skel); 230 + } 231 + 232 + static void test_uprobe_regs_change(void) 233 + { 234 + if (test__start_subtest("regs_change_common")) 235 + regs_common(); 236 + if (test__start_subtest("regs_change_ip")) 237 + regs_ip(); 238 + } 239 + #else 240 + static void test_uprobe_regs_change(void) { } 241 + #endif 242 + 243 + void test_uprobe(void) 244 + { 245 + if (test__start_subtest("attach")) 246 + test_uprobe_attach(); 247 + test_uprobe_regs_change(); 96 248 }
+22
tools/testing/selftests/bpf/progs/kprobe_write_ctx.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include "vmlinux.h" 3 + #include <bpf/bpf_helpers.h> 4 + #include <bpf/bpf_tracing.h> 5 + 6 + char _license[] SEC("license") = "GPL"; 7 + 8 + #if defined(__TARGET_ARCH_x86) 9 + SEC("kprobe") 10 + int kprobe_write_ctx(struct pt_regs *ctx) 11 + { 12 + ctx->ax = 0; 13 + return 0; 14 + } 15 + 16 + SEC("kprobe.multi") 17 + int kprobe_multi_write_ctx(struct pt_regs *ctx) 18 + { 19 + ctx->ax = 0; 20 + return 0; 21 + } 22 + #endif
+38
tools/testing/selftests/bpf/progs/test_uprobe.c
··· 59 59 test4_result = 1; 60 60 return 0; 61 61 } 62 + 63 + #if defined(__TARGET_ARCH_x86) 64 + struct pt_regs regs; 65 + 66 + SEC("uprobe") 67 + int BPF_UPROBE(test_regs_change) 68 + { 69 + pid_t pid = bpf_get_current_pid_tgid() >> 32; 70 + 71 + if (pid != my_pid) 72 + return 0; 73 + 74 + ctx->ax = regs.ax; 75 + ctx->cx = regs.cx; 76 + ctx->dx = regs.dx; 77 + ctx->r8 = regs.r8; 78 + ctx->r9 = regs.r9; 79 + ctx->r10 = regs.r10; 80 + ctx->r11 = regs.r11; 81 + ctx->di = regs.di; 82 + ctx->si = regs.si; 83 + return 0; 84 + } 85 + 86 + unsigned long ip; 87 + 88 + SEC("uprobe") 89 + int BPF_UPROBE(test_regs_change_ip) 90 + { 91 + pid_t pid = bpf_get_current_pid_tgid() >> 32; 92 + 93 + if (pid != my_pid) 94 + return 0; 95 + 96 + ctx->ip = ip; 97 + return 0; 98 + } 99 + #endif