12025cf9eSThomas 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