xref: /linux/tools/testing/selftests/x86/sysret_rip.c (revision 69050f8d6d075dc01af7a5f2f550a8067510366f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
4  * Copyright (c) 2014-2016 Andrew Lutomirski
5  */
6 
7 #define _GNU_SOURCE
8 
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <inttypes.h>
14 #include <sys/signal.h>
15 #include <sys/ucontext.h>
16 #include <sys/syscall.h>
17 #include <err.h>
18 #include <stddef.h>
19 #include <stdbool.h>
20 #include <setjmp.h>
21 #include <sys/user.h>
22 #include <sys/mman.h>
23 #include <assert.h>
24 
25 #include "helpers.h"
26 
27 /*
28  * These items are in clang_helpers_64.S, in order to avoid clang inline asm
29  * limitations:
30  */
31 void test_syscall_ins(void);
32 extern const char test_page[];
33 
34 static const void *current_test_page_addr = test_page;
35 
36 /* State used by our signal handlers. */
37 static gregset_t initial_regs;
38 
39 static volatile unsigned long rip;
40 
41 static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
42 {
43 	ucontext_t *ctx = (ucontext_t *)ctx_void;
44 
45 	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
46 		printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
47 		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
48 		fflush(stdout);
49 		_exit(1);
50 	}
51 
52 	memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
53 
54 	printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
55 }
56 
57 static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
58 {
59 	ucontext_t *ctx = (ucontext_t *)ctx_void;
60 
61 	memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
62 
63 	/* Set IP and CX to match so that SYSRET can happen. */
64 	ctx->uc_mcontext.gregs[REG_RIP] = rip;
65 	ctx->uc_mcontext.gregs[REG_RCX] = rip;
66 
67 	/* R11 and EFLAGS should already match. */
68 	assert(ctx->uc_mcontext.gregs[REG_EFL] ==
69 	       ctx->uc_mcontext.gregs[REG_R11]);
70 
71 	sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
72 }
73 
74 static void test_sigreturn_to(unsigned long ip)
75 {
76 	rip = ip;
77 	printf("[RUN]\tsigreturn to 0x%lx\n", ip);
78 	raise(SIGUSR1);
79 }
80 
81 static jmp_buf jmpbuf;
82 
83 static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
84 {
85 	ucontext_t *ctx = (ucontext_t *)ctx_void;
86 
87 	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
88 		printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
89 		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
90 		fflush(stdout);
91 		_exit(1);
92 	}
93 
94 	siglongjmp(jmpbuf, 1);
95 }
96 
97 static void test_syscall_fallthrough_to(unsigned long ip)
98 {
99 	void *new_address = (void *)(ip - 4096);
100 	void *ret;
101 
102 	printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
103 
104 	ret = mremap((void *)current_test_page_addr, 4096, 4096,
105 		     MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
106 	if (ret == MAP_FAILED) {
107 		if (ip <= (1UL << 47) - PAGE_SIZE) {
108 			err(1, "mremap to %p", new_address);
109 		} else {
110 			printf("[OK]\tmremap to %p failed\n", new_address);
111 			return;
112 		}
113 	}
114 
115 	if (ret != new_address)
116 		errx(1, "mremap malfunctioned: asked for %p but got %p\n",
117 		     new_address, ret);
118 
119 	current_test_page_addr = new_address;
120 	rip = ip;
121 
122 	if (sigsetjmp(jmpbuf, 1) == 0) {
123 		asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
124 			      [syscall_insn] "rm" (ip - 2));
125 		errx(1, "[FAIL]\tSyscall trampoline returned");
126 	}
127 
128 	printf("[OK]\tWe survived\n");
129 }
130 
131 int main(void)
132 {
133 	/*
134 	 * When the kernel returns from a slow-path syscall, it will
135 	 * detect whether SYSRET is appropriate.  If it incorrectly
136 	 * thinks that SYSRET is appropriate when RIP is noncanonical,
137 	 * it'll crash on Intel CPUs.
138 	 */
139 	sethandler(SIGUSR1, sigusr1, 0);
140 	for (int i = 47; i < 64; i++)
141 		test_sigreturn_to(1UL<<i);
142 
143 	clearhandler(SIGUSR1);
144 
145 	sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
146 
147 	/* One extra test to check that we didn't screw up the mremap logic. */
148 	test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
149 
150 	/* These are the interesting cases. */
151 	for (int i = 47; i < 64; i++) {
152 		test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
153 		test_syscall_fallthrough_to(1UL<<i);
154 	}
155 
156 	return 0;
157 }
158