2 * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
3 * Copyright (c) 2014-2016 Andrew Lutomirski
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
9 * This program is distributed in the hope it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
22 #include <sys/signal.h>
23 #include <sys/ucontext.h>
24 #include <sys/syscall.h>
35 ".pushsection \".text\", \"ax\"\n\t"
37 "test_page: .globl test_page\n\t"
38 ".fill 4094,1,0xcc\n\t"
39 "test_syscall_insn:\n\t"
41 ".ifne . - test_page - 4096\n\t"
42 ".error \"test page is not one page long\"\n\t"
47 extern const char test_page
[];
48 static void const *current_test_page_addr
= test_page
;
50 static void sethandler(int sig
, void (*handler
)(int, siginfo_t
*, void *),
54 memset(&sa
, 0, sizeof(sa
));
55 sa
.sa_sigaction
= handler
;
56 sa
.sa_flags
= SA_SIGINFO
| flags
;
57 sigemptyset(&sa
.sa_mask
);
58 if (sigaction(sig
, &sa
, 0))
62 static void clearhandler(int sig
)
65 memset(&sa
, 0, sizeof(sa
));
66 sa
.sa_handler
= SIG_DFL
;
67 sigemptyset(&sa
.sa_mask
);
68 if (sigaction(sig
, &sa
, 0))
72 /* State used by our signal handlers. */
73 static gregset_t initial_regs
;
75 static volatile unsigned long rip
;
77 static void sigsegv_for_sigreturn_test(int sig
, siginfo_t
*info
, void *ctx_void
)
79 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
81 if (rip
!= ctx
->uc_mcontext
.gregs
[REG_RIP
]) {
82 printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
83 rip
, (unsigned long)ctx
->uc_mcontext
.gregs
[REG_RIP
]);
88 memcpy(&ctx
->uc_mcontext
.gregs
, &initial_regs
, sizeof(gregset_t
));
90 printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip
);
93 static void sigusr1(int sig
, siginfo_t
*info
, void *ctx_void
)
95 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
97 memcpy(&initial_regs
, &ctx
->uc_mcontext
.gregs
, sizeof(gregset_t
));
99 /* Set IP and CX to match so that SYSRET can happen. */
100 ctx
->uc_mcontext
.gregs
[REG_RIP
] = rip
;
101 ctx
->uc_mcontext
.gregs
[REG_RCX
] = rip
;
103 /* R11 and EFLAGS should already match. */
104 assert(ctx
->uc_mcontext
.gregs
[REG_EFL
] ==
105 ctx
->uc_mcontext
.gregs
[REG_R11
]);
107 sethandler(SIGSEGV
, sigsegv_for_sigreturn_test
, SA_RESETHAND
);
112 static void test_sigreturn_to(unsigned long ip
)
115 printf("[RUN]\tsigreturn to 0x%lx\n", ip
);
119 static jmp_buf jmpbuf
;
121 static void sigsegv_for_fallthrough(int sig
, siginfo_t
*info
, void *ctx_void
)
123 ucontext_t
*ctx
= (ucontext_t
*)ctx_void
;
125 if (rip
!= ctx
->uc_mcontext
.gregs
[REG_RIP
]) {
126 printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
127 rip
, (unsigned long)ctx
->uc_mcontext
.gregs
[REG_RIP
]);
132 siglongjmp(jmpbuf
, 1);
135 static void test_syscall_fallthrough_to(unsigned long ip
)
137 void *new_address
= (void *)(ip
- 4096);
140 printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip
);
142 ret
= mremap((void *)current_test_page_addr
, 4096, 4096,
143 MREMAP_MAYMOVE
| MREMAP_FIXED
, new_address
);
144 if (ret
== MAP_FAILED
) {
145 if (ip
<= (1UL << 47) - PAGE_SIZE
) {
146 err(1, "mremap to %p", new_address
);
148 printf("[OK]\tmremap to %p failed\n", new_address
);
153 if (ret
!= new_address
)
154 errx(1, "mremap malfunctioned: asked for %p but got %p\n",
157 current_test_page_addr
= new_address
;
160 if (sigsetjmp(jmpbuf
, 1) == 0) {
161 asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid
),
162 [syscall_insn
] "rm" (ip
- 2));
163 errx(1, "[FAIL]\tSyscall trampoline returned");
166 printf("[OK]\tWe survived\n");
172 * When the kernel returns from a slow-path syscall, it will
173 * detect whether SYSRET is appropriate. If it incorrectly
174 * thinks that SYSRET is appropriate when RIP is noncanonical,
175 * it'll crash on Intel CPUs.
177 sethandler(SIGUSR1
, sigusr1
, 0);
178 for (int i
= 47; i
< 64; i
++)
179 test_sigreturn_to(1UL<<i
);
181 clearhandler(SIGUSR1
);
183 sethandler(SIGSEGV
, sigsegv_for_fallthrough
, 0);
185 /* One extra test to check that we didn't screw up the mremap logic. */
186 test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE
);
188 /* These are the interesting cases. */
189 for (int i
= 47; i
< 64; i
++) {
190 test_syscall_fallthrough_to((1UL<<i
) - PAGE_SIZE
);
191 test_syscall_fallthrough_to(1UL<<i
);