1*2025cf9eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
266060214SAndy Lutomirski /*
366060214SAndy Lutomirski * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
466060214SAndy Lutomirski * Copyright (c) 2014-2016 Andrew Lutomirski
566060214SAndy Lutomirski */
666060214SAndy Lutomirski
766060214SAndy Lutomirski #define _GNU_SOURCE
866060214SAndy Lutomirski
966060214SAndy Lutomirski #include <stdlib.h>
1066060214SAndy Lutomirski #include <unistd.h>
1166060214SAndy Lutomirski #include <stdio.h>
1266060214SAndy Lutomirski #include <string.h>
1366060214SAndy Lutomirski #include <inttypes.h>
1466060214SAndy Lutomirski #include <sys/signal.h>
1566060214SAndy Lutomirski #include <sys/ucontext.h>
1666060214SAndy Lutomirski #include <sys/syscall.h>
1766060214SAndy Lutomirski #include <err.h>
1866060214SAndy Lutomirski #include <stddef.h>
1966060214SAndy Lutomirski #include <stdbool.h>
2066060214SAndy Lutomirski #include <setjmp.h>
2166060214SAndy Lutomirski #include <sys/user.h>
2266060214SAndy Lutomirski #include <sys/mman.h>
2366060214SAndy Lutomirski #include <assert.h>
2466060214SAndy Lutomirski
2566060214SAndy Lutomirski
2666060214SAndy Lutomirski asm (
2766060214SAndy Lutomirski ".pushsection \".text\", \"ax\"\n\t"
2866060214SAndy Lutomirski ".balign 4096\n\t"
2966060214SAndy Lutomirski "test_page: .globl test_page\n\t"
3066060214SAndy Lutomirski ".fill 4094,1,0xcc\n\t"
3166060214SAndy Lutomirski "test_syscall_insn:\n\t"
3266060214SAndy Lutomirski "syscall\n\t"
3366060214SAndy Lutomirski ".ifne . - test_page - 4096\n\t"
3466060214SAndy Lutomirski ".error \"test page is not one page long\"\n\t"
3566060214SAndy Lutomirski ".endif\n\t"
3666060214SAndy Lutomirski ".popsection"
3766060214SAndy Lutomirski );
3866060214SAndy Lutomirski
3966060214SAndy Lutomirski extern const char test_page[];
4066060214SAndy Lutomirski static void const *current_test_page_addr = test_page;
4166060214SAndy Lutomirski
sethandler(int sig,void (* handler)(int,siginfo_t *,void *),int flags)4266060214SAndy Lutomirski static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
4366060214SAndy Lutomirski int flags)
4466060214SAndy Lutomirski {
4566060214SAndy Lutomirski struct sigaction sa;
4666060214SAndy Lutomirski memset(&sa, 0, sizeof(sa));
4766060214SAndy Lutomirski sa.sa_sigaction = handler;
4866060214SAndy Lutomirski sa.sa_flags = SA_SIGINFO | flags;
4966060214SAndy Lutomirski sigemptyset(&sa.sa_mask);
5066060214SAndy Lutomirski if (sigaction(sig, &sa, 0))
5166060214SAndy Lutomirski err(1, "sigaction");
5266060214SAndy Lutomirski }
5366060214SAndy Lutomirski
clearhandler(int sig)5466060214SAndy Lutomirski static void clearhandler(int sig)
5566060214SAndy Lutomirski {
5666060214SAndy Lutomirski struct sigaction sa;
5766060214SAndy Lutomirski memset(&sa, 0, sizeof(sa));
5866060214SAndy Lutomirski sa.sa_handler = SIG_DFL;
5966060214SAndy Lutomirski sigemptyset(&sa.sa_mask);
6066060214SAndy Lutomirski if (sigaction(sig, &sa, 0))
6166060214SAndy Lutomirski err(1, "sigaction");
6266060214SAndy Lutomirski }
6366060214SAndy Lutomirski
6466060214SAndy Lutomirski /* State used by our signal handlers. */
6566060214SAndy Lutomirski static gregset_t initial_regs;
6666060214SAndy Lutomirski
6766060214SAndy Lutomirski static volatile unsigned long rip;
6866060214SAndy Lutomirski
sigsegv_for_sigreturn_test(int sig,siginfo_t * info,void * ctx_void)6966060214SAndy Lutomirski static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
7066060214SAndy Lutomirski {
7166060214SAndy Lutomirski ucontext_t *ctx = (ucontext_t*)ctx_void;
7266060214SAndy Lutomirski
7366060214SAndy Lutomirski if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
7466060214SAndy Lutomirski printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
7566060214SAndy Lutomirski rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
7666060214SAndy Lutomirski fflush(stdout);
7766060214SAndy Lutomirski _exit(1);
7866060214SAndy Lutomirski }
7966060214SAndy Lutomirski
8066060214SAndy Lutomirski memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
8166060214SAndy Lutomirski
8266060214SAndy Lutomirski printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
8366060214SAndy Lutomirski }
8466060214SAndy Lutomirski
sigusr1(int sig,siginfo_t * info,void * ctx_void)8566060214SAndy Lutomirski static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
8666060214SAndy Lutomirski {
8766060214SAndy Lutomirski ucontext_t *ctx = (ucontext_t*)ctx_void;
8866060214SAndy Lutomirski
8966060214SAndy Lutomirski memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
9066060214SAndy Lutomirski
9166060214SAndy Lutomirski /* Set IP and CX to match so that SYSRET can happen. */
9266060214SAndy Lutomirski ctx->uc_mcontext.gregs[REG_RIP] = rip;
9366060214SAndy Lutomirski ctx->uc_mcontext.gregs[REG_RCX] = rip;
9466060214SAndy Lutomirski
9566060214SAndy Lutomirski /* R11 and EFLAGS should already match. */
9666060214SAndy Lutomirski assert(ctx->uc_mcontext.gregs[REG_EFL] ==
9766060214SAndy Lutomirski ctx->uc_mcontext.gregs[REG_R11]);
9866060214SAndy Lutomirski
9966060214SAndy Lutomirski sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
10066060214SAndy Lutomirski
10166060214SAndy Lutomirski return;
10266060214SAndy Lutomirski }
10366060214SAndy Lutomirski
test_sigreturn_to(unsigned long ip)10466060214SAndy Lutomirski static void test_sigreturn_to(unsigned long ip)
10566060214SAndy Lutomirski {
10666060214SAndy Lutomirski rip = ip;
10766060214SAndy Lutomirski printf("[RUN]\tsigreturn to 0x%lx\n", ip);
10866060214SAndy Lutomirski raise(SIGUSR1);
10966060214SAndy Lutomirski }
11066060214SAndy Lutomirski
11166060214SAndy Lutomirski static jmp_buf jmpbuf;
11266060214SAndy Lutomirski
sigsegv_for_fallthrough(int sig,siginfo_t * info,void * ctx_void)11366060214SAndy Lutomirski static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
11466060214SAndy Lutomirski {
11566060214SAndy Lutomirski ucontext_t *ctx = (ucontext_t*)ctx_void;
11666060214SAndy Lutomirski
11766060214SAndy Lutomirski if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
11866060214SAndy Lutomirski printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
11966060214SAndy Lutomirski rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
12066060214SAndy Lutomirski fflush(stdout);
12166060214SAndy Lutomirski _exit(1);
12266060214SAndy Lutomirski }
12366060214SAndy Lutomirski
12466060214SAndy Lutomirski siglongjmp(jmpbuf, 1);
12566060214SAndy Lutomirski }
12666060214SAndy Lutomirski
test_syscall_fallthrough_to(unsigned long ip)12766060214SAndy Lutomirski static void test_syscall_fallthrough_to(unsigned long ip)
12866060214SAndy Lutomirski {
12966060214SAndy Lutomirski void *new_address = (void *)(ip - 4096);
13066060214SAndy Lutomirski void *ret;
13166060214SAndy Lutomirski
13266060214SAndy Lutomirski printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
13366060214SAndy Lutomirski
13466060214SAndy Lutomirski ret = mremap((void *)current_test_page_addr, 4096, 4096,
13566060214SAndy Lutomirski MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
13666060214SAndy Lutomirski if (ret == MAP_FAILED) {
13766060214SAndy Lutomirski if (ip <= (1UL << 47) - PAGE_SIZE) {
13866060214SAndy Lutomirski err(1, "mremap to %p", new_address);
13966060214SAndy Lutomirski } else {
14066060214SAndy Lutomirski printf("[OK]\tmremap to %p failed\n", new_address);
14166060214SAndy Lutomirski return;
14266060214SAndy Lutomirski }
14366060214SAndy Lutomirski }
14466060214SAndy Lutomirski
14566060214SAndy Lutomirski if (ret != new_address)
14666060214SAndy Lutomirski errx(1, "mremap malfunctioned: asked for %p but got %p\n",
14766060214SAndy Lutomirski new_address, ret);
14866060214SAndy Lutomirski
14966060214SAndy Lutomirski current_test_page_addr = new_address;
15066060214SAndy Lutomirski rip = ip;
15166060214SAndy Lutomirski
15266060214SAndy Lutomirski if (sigsetjmp(jmpbuf, 1) == 0) {
15366060214SAndy Lutomirski asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
15466060214SAndy Lutomirski [syscall_insn] "rm" (ip - 2));
15566060214SAndy Lutomirski errx(1, "[FAIL]\tSyscall trampoline returned");
15666060214SAndy Lutomirski }
15766060214SAndy Lutomirski
15866060214SAndy Lutomirski printf("[OK]\tWe survived\n");
15966060214SAndy Lutomirski }
16066060214SAndy Lutomirski
main()16166060214SAndy Lutomirski int main()
16266060214SAndy Lutomirski {
16366060214SAndy Lutomirski /*
16466060214SAndy Lutomirski * When the kernel returns from a slow-path syscall, it will
16566060214SAndy Lutomirski * detect whether SYSRET is appropriate. If it incorrectly
16666060214SAndy Lutomirski * thinks that SYSRET is appropriate when RIP is noncanonical,
16766060214SAndy Lutomirski * it'll crash on Intel CPUs.
16866060214SAndy Lutomirski */
16966060214SAndy Lutomirski sethandler(SIGUSR1, sigusr1, 0);
17066060214SAndy Lutomirski for (int i = 47; i < 64; i++)
17166060214SAndy Lutomirski test_sigreturn_to(1UL<<i);
17266060214SAndy Lutomirski
17366060214SAndy Lutomirski clearhandler(SIGUSR1);
17466060214SAndy Lutomirski
17566060214SAndy Lutomirski sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
17666060214SAndy Lutomirski
17766060214SAndy Lutomirski /* One extra test to check that we didn't screw up the mremap logic. */
17866060214SAndy Lutomirski test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
17966060214SAndy Lutomirski
18066060214SAndy Lutomirski /* These are the interesting cases. */
18166060214SAndy Lutomirski for (int i = 47; i < 64; i++) {
18266060214SAndy Lutomirski test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
18366060214SAndy Lutomirski test_syscall_fallthrough_to(1UL<<i);
18466060214SAndy Lutomirski }
18566060214SAndy Lutomirski
18666060214SAndy Lutomirski return 0;
18766060214SAndy Lutomirski }
188