Skip to content

Commit 55a8384

Browse files
anakryikoKernel Patches Daemon
authored andcommitted
selftests/bpf: Add tests for uprobe nop10 red zone clobbering
The uprobe nop5 optimization used to replace a 5-byte NOP with a 5-byte CALL to a trampoline. The CALL pushes a return address onto the stack at [rsp-8], clobbering whatever was stored there. On x86-64, the red zone is the 128 bytes below rsp that user code may use for temporary storage without adjusting rsp. Compilers can place USDT argument operands there, generating specs like "8@-8(%rbp)" when rbp == rsp. With the CALL-based optimization, the return address overwrites that argument before the BPF-side USDT argument fetch runs. Add two tests for this case. The uprobe_syscall subtest stores known values at -8(%rsp), -16(%rsp), and -24(%rsp), executes an optimized nop10 uprobe, and verifies the red-zone data is still intact. The USDT subtest triggers a probe in a function where the compiler places three USDT operands in the red zone and verifies that all 10 optimized invocations deliver the expected argument values to BPF. On an unfixed kernel, the first hit goes through the INT3 path and later hits use the optimized CALL path, so the red-zone checks fail after optimization. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> [ updates to use nop10 ] Signed-off-by: Jiri Olsa <jolsa@kernel.org> Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
1 parent c89d102 commit 55a8384

4 files changed

Lines changed: 162 additions & 0 deletions

File tree

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,48 @@ __nocf_check __weak void usdt_test(void)
357357
USDT(optimized_uprobe, usdt);
358358
}
359359

360+
/*
361+
* Assembly-level red zone clobbering test. Stores known values in the
362+
* red zone (below RSP), executes a nop10 (uprobe site), and checks that
363+
* the values survived. Returns 0 if intact, 1 if clobbered.
364+
*
365+
* The nop5 optimization used CALL (which pushes a return address to
366+
* [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization
367+
* should escape that by moving stackpointer below the redzone before
368+
* doing the CALL.
369+
*/
370+
__attribute__((aligned(16)))
371+
__nocf_check __weak __naked unsigned long uprobe_red_zone_test(void)
372+
{
373+
asm volatile (
374+
"movabs $0x1111111111111111, %%rax\n"
375+
"movq %%rax, -8(%%rsp)\n"
376+
"movabs $0x2222222222222222, %%rax\n"
377+
"movq %%rax, -16(%%rsp)\n"
378+
"movabs $0x3333333333333333, %%rax\n"
379+
"movq %%rax, -24(%%rsp)\n"
380+
381+
".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10: uprobe site */
382+
383+
"movabs $0x1111111111111111, %%rax\n"
384+
"cmpq %%rax, -8(%%rsp)\n"
385+
"jne 1f\n"
386+
"movabs $0x2222222222222222, %%rax\n"
387+
"cmpq %%rax, -16(%%rsp)\n"
388+
"jne 1f\n"
389+
"movabs $0x3333333333333333, %%rax\n"
390+
"cmpq %%rax, -24(%%rsp)\n"
391+
"jne 1f\n"
392+
393+
"xorl %%eax, %%eax\n"
394+
"retq\n"
395+
"1:\n"
396+
"movl $1, %%eax\n"
397+
"retq\n"
398+
::: "rax", "memory"
399+
);
400+
}
401+
360402
static int find_uprobes_trampoline(void *tramp_addr)
361403
{
362404
void *start, *end;
@@ -855,6 +897,37 @@ static void test_uprobe_race(void)
855897
#define __NR_uprobe 336
856898
#endif
857899

900+
static void test_uprobe_red_zone(void)
901+
{
902+
struct uprobe_syscall_executed *skel;
903+
struct bpf_link *link;
904+
void *nop10_addr;
905+
size_t offset;
906+
int i;
907+
908+
nop10_addr = find_nop10(uprobe_red_zone_test);
909+
if (!ASSERT_NEQ(nop10_addr, NULL, "find_nop10"))
910+
return;
911+
912+
skel = uprobe_syscall_executed__open_and_load();
913+
if (!ASSERT_OK_PTR(skel, "open_and_load"))
914+
return;
915+
916+
offset = get_uprobe_offset(nop10_addr);
917+
link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe,
918+
0, "/proc/self/exe", offset, NULL);
919+
if (!ASSERT_OK_PTR(link, "attach_uprobe"))
920+
goto cleanup;
921+
922+
for (i = 0; i < 10; i++)
923+
ASSERT_EQ(uprobe_red_zone_test(), 0, "red_zone_intact");
924+
925+
bpf_link__destroy(link);
926+
927+
cleanup:
928+
uprobe_syscall_executed__destroy(skel);
929+
}
930+
858931
static void test_uprobe_error(void)
859932
{
860933
long err = syscall(__NR_uprobe);
@@ -881,6 +954,8 @@ static void __test_uprobe_syscall(void)
881954
test_uprobe_usdt();
882955
if (test__start_subtest("uprobe_race"))
883956
test_uprobe_race();
957+
if (test__start_subtest("uprobe_red_zone"))
958+
test_uprobe_red_zone();
884959
if (test__start_subtest("uprobe_error"))
885960
test_uprobe_error();
886961
if (test__start_subtest("uprobe_regs_equal"))

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ static void subtest_basic_usdt(bool optimized)
250250
#ifdef __x86_64__
251251
extern void usdt_1(void);
252252
extern void usdt_2(void);
253+
extern void usdt_red_zone_trigger(void);
253254

254255
static unsigned char nop1[1] = { 0x90 };
255256
static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
@@ -340,6 +341,52 @@ static void subtest_optimized_attach(void)
340341
cleanup:
341342
test_usdt__destroy(skel);
342343
}
344+
345+
/*
346+
* Test that USDT arguments survive nop10 optimization in a function where
347+
* the compiler places operands in the red zone.
348+
*
349+
* Signal handlers are prone to having the compiler place USDT argument
350+
* operands in the red zone (below rsp).
351+
*
352+
* The nop5 optimization used CALL (which pushes a return address to
353+
* [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization
354+
* should escape that by moving stackpointer below the redzone before
355+
* doing the CALL.
356+
*/
357+
static void subtest_optimized_red_zone(void)
358+
{
359+
struct test_usdt *skel;
360+
int i;
361+
362+
skel = test_usdt__open_and_load();
363+
if (!ASSERT_OK_PTR(skel, "open_and_load"))
364+
return;
365+
366+
skel->bss->expected_arg[0] = 0xDEADBEEF;
367+
skel->bss->expected_arg[1] = 0xCAFEBABE;
368+
skel->bss->expected_arg[2] = 0xFEEDFACE;
369+
skel->bss->expected_pid = getpid();
370+
371+
skel->links.usdt_check_arg = bpf_program__attach_usdt(
372+
skel->progs.usdt_check_arg, 0, "/proc/self/exe",
373+
"optimized_attach", "usdt_red_zone", NULL);
374+
if (!ASSERT_OK_PTR(skel->links.usdt_check_arg, "attach_usdt_red_zone"))
375+
goto cleanup;
376+
377+
for (i = 0; i < 10; i++)
378+
usdt_red_zone_trigger();
379+
380+
ASSERT_EQ(skel->bss->arg_total, 10, "arg_total");
381+
ASSERT_EQ(skel->bss->arg_bad, 0, "arg_bad");
382+
ASSERT_EQ(skel->bss->arg_last[0], 0xDEADBEEF, "arg_last_1");
383+
ASSERT_EQ(skel->bss->arg_last[1], 0xCAFEBABE, "arg_last_2");
384+
ASSERT_EQ(skel->bss->arg_last[2], 0xFEEDFACE, "arg_last_3");
385+
386+
cleanup:
387+
test_usdt__destroy(skel);
388+
}
389+
343390
#endif
344391

345392
unsigned short test_usdt_100_semaphore SEC(".probes");
@@ -613,6 +660,8 @@ void test_usdt(void)
613660
subtest_basic_usdt(true);
614661
if (test__start_subtest("optimized_attach"))
615662
subtest_optimized_attach();
663+
if (test__start_subtest("optimized_red_zone"))
664+
subtest_optimized_red_zone();
616665
#endif
617666
if (test__start_subtest("multispec"))
618667
subtest_multispec_usdt();

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,30 @@ int usdt_executed(struct pt_regs *ctx)
149149
executed++;
150150
return 0;
151151
}
152+
153+
int arg_total;
154+
int arg_bad;
155+
long arg_last[3];
156+
long expected_arg[3];
157+
int expected_pid;
158+
159+
SEC("usdt")
160+
int BPF_USDT(usdt_check_arg, long arg1, long arg2, long arg3)
161+
{
162+
if (expected_pid != (bpf_get_current_pid_tgid() >> 32))
163+
return 0;
164+
165+
__sync_fetch_and_add(&arg_total, 1);
166+
arg_last[0] = arg1;
167+
arg_last[1] = arg2;
168+
arg_last[2] = arg3;
169+
170+
if (arg1 != expected_arg[0] ||
171+
arg2 != expected_arg[1] ||
172+
arg3 != expected_arg[2])
173+
__sync_fetch_and_add(&arg_bad, 1);
174+
175+
return 0;
176+
}
152177
#endif
153178
char _license[] SEC("license") = "GPL";

tools/testing/selftests/bpf/usdt_2.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,17 @@ void usdt_2(void)
1313
USDT(optimized_attach, usdt_2);
1414
}
1515

16+
static volatile unsigned long usdt_red_zone_arg1 = 0xDEADBEEF;
17+
static volatile unsigned long usdt_red_zone_arg2 = 0xCAFEBABE;
18+
static volatile unsigned long usdt_red_zone_arg3 = 0xFEEDFACE;
19+
20+
void __attribute__((noinline)) usdt_red_zone_trigger(void)
21+
{
22+
unsigned long a1 = usdt_red_zone_arg1;
23+
unsigned long a2 = usdt_red_zone_arg2;
24+
unsigned long a3 = usdt_red_zone_arg3;
25+
26+
USDT(optimized_attach, usdt_red_zone, a1, a2, a3);
27+
}
28+
1629
#endif

0 commit comments

Comments
 (0)