Skip to content

Commit 5166973

Browse files
dwindsorPeter Zijlstra
authored andcommitted
selftests/x86: Add shadow stack uprobe CALL test
Add coverage for entry uprobes installed on CALL instructions while user shadow stack is enabled. The test puts an entry uprobe on a helper whose first instruction is a relative CALL, then verifies that the call/return sequence completes without SIGSEGV. This catches regressions where x86 uprobe CALL emulation updates the regular user stack but leaves the CET shadow stack stale. Signed-off-by: David Windsor <dwindsor@gmail.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://patch.msgid.link/b957039191118c5eba97d01d80c494b859f115a6.1782777969.git.dwindsor@gmail.com
1 parent abf0885 commit 5166973

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

tools/testing/selftests/x86/test_shadow_stack.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,86 @@ static int test_uretprobe(void)
873873
return err;
874874
}
875875

876+
/* Keep the CALL first so the function address is exactly the probed CALL. */
877+
extern void uprobe_call_trigger(void);
878+
asm (".pushsection .text\n"
879+
".global uprobe_call_target\n"
880+
".type uprobe_call_target, @function\n"
881+
"uprobe_call_target:\n"
882+
" ret\n"
883+
".size uprobe_call_target, .-uprobe_call_target\n"
884+
885+
".global uprobe_call_trigger\n"
886+
".type uprobe_call_trigger, @function\n"
887+
"uprobe_call_trigger:\n"
888+
" call uprobe_call_target\n"
889+
" ret\n"
890+
".size uprobe_call_trigger, .-uprobe_call_trigger\n"
891+
".popsection\n"
892+
);
893+
894+
/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */
895+
static int test_uprobe_call(void)
896+
{
897+
const size_t attr_sz = sizeof(struct perf_event_attr);
898+
const char *file = "/proc/self/exe";
899+
int fd = -1, type, err = 1;
900+
struct perf_event_attr attr;
901+
struct sigaction sa = {};
902+
ssize_t offset;
903+
904+
type = determine_uprobe_perf_type();
905+
if (type < 0) {
906+
if (type == -ENOENT)
907+
printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n");
908+
return 0;
909+
}
910+
911+
offset = get_uprobe_offset(uprobe_call_trigger);
912+
if (offset < 0)
913+
return 1;
914+
915+
sa.sa_sigaction = segv_gp_handler;
916+
sa.sa_flags = SA_SIGINFO;
917+
if (sigaction(SIGSEGV, &sa, NULL))
918+
return 1;
919+
920+
/* Setup entry uprobe through perf event interface. */
921+
memset(&attr, 0, attr_sz);
922+
attr.size = attr_sz;
923+
attr.type = type;
924+
attr.config = 0;
925+
attr.config1 = (__u64)(unsigned long)file;
926+
attr.config2 = offset;
927+
928+
fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
929+
-1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
930+
if (fd < 0)
931+
goto out;
932+
933+
if (sigsetjmp(jmp_buffer, 1))
934+
goto out;
935+
936+
if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK))
937+
goto out;
938+
939+
/*
940+
* This either segfaults and goes through sigsetjmp above
941+
* or succeeds and we're good.
942+
*/
943+
uprobe_call_trigger();
944+
945+
printf("[OK]\tUprobe on CALL test\n");
946+
err = 0;
947+
948+
out:
949+
ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
950+
signal(SIGSEGV, SIG_DFL);
951+
if (fd >= 0)
952+
close(fd);
953+
return err;
954+
}
955+
876956
void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
877957
{
878958
/* The SSP adjustment caused a segfault. */
@@ -1071,6 +1151,12 @@ int main(int argc, char *argv[])
10711151
goto out;
10721152
}
10731153

1154+
if (test_uprobe_call()) {
1155+
ret = 1;
1156+
printf("[FAIL]\tuprobe on CALL test\n");
1157+
goto out;
1158+
}
1159+
10741160
return ret;
10751161

10761162
out:

0 commit comments

Comments
 (0)