1 // SPDX-License-Identifier: GPL-2.0-only
3 * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
4 * Copyright (c) 2014-2016 Andrew Lutomirski
14 #include <sys/signal.h>
15 #include <sys/ucontext.h>
16 #include <sys/syscall.h>
27 ".pushsection \".text\", \"ax\"\n\t"
29 "test_page: .globl test_page\n\t"
30 ".fill 4094,1,0xcc\n\t"
31 "test_syscall_insn:\n\t"
33 ".ifne . - test_page - 4096\n\t"
34 ".error \"test page is not one page long\"\n\t"
39 extern const char test_page
[];
40 static void const *current_test_page_addr
= test_page
;
42 static void sethandler(int sig
, void (*handler
)(int, siginfo_t
*, void *),
46 memset(&sa
, 0, sizeof(sa
));
47 sa
.sa_sigaction
= handler
;
48 sa
.sa_flags
= SA_SIGINFO
| flags
;
49 sigemptyset(&sa
.sa_mask
);
50 if (sigaction(sig
, &sa
, 0))
54 static void clearhandler(int sig
)
57 memset(&sa
, 0, sizeof(sa
));
58 sa
.sa_handler
= SIG_DFL
;
59 sigemptyset(&sa
.sa_mask
);
60 if (sigaction(sig
, &sa
, 0))
64 /* State used by our signal handlers. */
65 static gregset_t initial_regs
;
67 static volatile unsigned long rip
;
69 static void sigsegv_for_sigreturn_test(int sig
, siginfo_t
*info
, void *ctx_void
)
71 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
73 if (rip
!= ctx
->uc_mcontext
.gregs
[REG_RIP
]) {
74 printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
75 rip
, (unsigned long)ctx
->uc_mcontext
.gregs
[REG_RIP
]);
80 memcpy(&ctx
->uc_mcontext
.gregs
, &initial_regs
, sizeof(gregset_t
));
82 printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip
);
85 static void sigusr1(int sig
, siginfo_t
*info
, void *ctx_void
)
87 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
89 memcpy(&initial_regs
, &ctx
->uc_mcontext
.gregs
, sizeof(gregset_t
));
91 /* Set IP and CX to match so that SYSRET can happen. */
92 ctx
->uc_mcontext
.gregs
[REG_RIP
] = rip
;
93 ctx
->uc_mcontext
.gregs
[REG_RCX
] = rip
;
95 /* R11 and EFLAGS should already match. */
96 assert(ctx
->uc_mcontext
.gregs
[REG_EFL
] ==
97 ctx
->uc_mcontext
.gregs
[REG_R11
]);
99 sethandler(SIGSEGV
, sigsegv_for_sigreturn_test
, SA_RESETHAND
);
104 static void test_sigreturn_to(unsigned long ip
)
107 printf("[RUN]\tsigreturn to 0x%lx\n", ip
);
111 static jmp_buf jmpbuf
;
113 static void sigsegv_for_fallthrough(int sig
, siginfo_t
*info
, void *ctx_void
)
115 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
117 if (rip
!= ctx
->uc_mcontext
.gregs
[REG_RIP
]) {
118 printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
119 rip
, (unsigned long)ctx
->uc_mcontext
.gregs
[REG_RIP
]);
124 siglongjmp(jmpbuf
, 1);
127 static void test_syscall_fallthrough_to(unsigned long ip
)
129 void *new_address
= (void *)(ip
- 4096);
132 printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip
);
134 ret
= mremap((void *)current_test_page_addr
, 4096, 4096,
135 MREMAP_MAYMOVE
| MREMAP_FIXED
, new_address
);
136 if (ret
== MAP_FAILED
) {
137 if (ip
<= (1UL << 47) - PAGE_SIZE
) {
138 err(1, "mremap to %p", new_address
);
140 printf("[OK]\tmremap to %p failed\n", new_address
);
145 if (ret
!= new_address
)
146 errx(1, "mremap malfunctioned: asked for %p but got %p\n",
149 current_test_page_addr
= new_address
;
152 if (sigsetjmp(jmpbuf
, 1) == 0) {
153 asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid
),
154 [syscall_insn
] "rm" (ip
- 2));
155 errx(1, "[FAIL]\tSyscall trampoline returned");
158 printf("[OK]\tWe survived\n");
164 * When the kernel returns from a slow-path syscall, it will
165 * detect whether SYSRET is appropriate. If it incorrectly
166 * thinks that SYSRET is appropriate when RIP is noncanonical,
167 * it'll crash on Intel CPUs.
169 sethandler(SIGUSR1
, sigusr1
, 0);
170 for (int i
= 47; i
< 64; i
++)
171 test_sigreturn_to(1UL<<i
);
173 clearhandler(SIGUSR1
);
175 sethandler(SIGSEGV
, sigsegv_for_fallthrough
, 0);
177 /* One extra test to check that we didn't screw up the mremap logic. */
178 test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE
);
180 /* These are the interesting cases. */
181 for (int i
= 47; i
< 64; i
++) {
182 test_syscall_fallthrough_to((1UL<<i
) - PAGE_SIZE
);
183 test_syscall_fallthrough_to(1UL<<i
);