162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
462306a36Sopenharmony_ci * Copyright (c) 2014-2016 Andrew Lutomirski
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define _GNU_SOURCE
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <stdlib.h>
1062306a36Sopenharmony_ci#include <unistd.h>
1162306a36Sopenharmony_ci#include <stdio.h>
1262306a36Sopenharmony_ci#include <string.h>
1362306a36Sopenharmony_ci#include <inttypes.h>
1462306a36Sopenharmony_ci#include <sys/signal.h>
1562306a36Sopenharmony_ci#include <sys/ucontext.h>
1662306a36Sopenharmony_ci#include <sys/syscall.h>
1762306a36Sopenharmony_ci#include <err.h>
1862306a36Sopenharmony_ci#include <stddef.h>
1962306a36Sopenharmony_ci#include <stdbool.h>
2062306a36Sopenharmony_ci#include <setjmp.h>
2162306a36Sopenharmony_ci#include <sys/user.h>
2262306a36Sopenharmony_ci#include <sys/mman.h>
2362306a36Sopenharmony_ci#include <assert.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ciasm (
2762306a36Sopenharmony_ci	".pushsection \".text\", \"ax\"\n\t"
2862306a36Sopenharmony_ci	".balign 4096\n\t"
2962306a36Sopenharmony_ci	"test_page: .globl test_page\n\t"
3062306a36Sopenharmony_ci	".fill 4094,1,0xcc\n\t"
3162306a36Sopenharmony_ci	"test_syscall_insn:\n\t"
3262306a36Sopenharmony_ci	"syscall\n\t"
3362306a36Sopenharmony_ci	".ifne . - test_page - 4096\n\t"
3462306a36Sopenharmony_ci	".error \"test page is not one page long\"\n\t"
3562306a36Sopenharmony_ci	".endif\n\t"
3662306a36Sopenharmony_ci	".popsection"
3762306a36Sopenharmony_ci    );
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ciextern const char test_page[];
4062306a36Sopenharmony_cistatic void const *current_test_page_addr = test_page;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
4362306a36Sopenharmony_ci		       int flags)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct sigaction sa;
4662306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
4762306a36Sopenharmony_ci	sa.sa_sigaction = handler;
4862306a36Sopenharmony_ci	sa.sa_flags = SA_SIGINFO | flags;
4962306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
5062306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
5162306a36Sopenharmony_ci		err(1, "sigaction");
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic void clearhandler(int sig)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct sigaction sa;
5762306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
5862306a36Sopenharmony_ci	sa.sa_handler = SIG_DFL;
5962306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
6062306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
6162306a36Sopenharmony_ci		err(1, "sigaction");
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/* State used by our signal handlers. */
6562306a36Sopenharmony_cistatic gregset_t initial_regs;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic volatile unsigned long rip;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
7462306a36Sopenharmony_ci		printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
7562306a36Sopenharmony_ci		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
7662306a36Sopenharmony_ci		fflush(stdout);
7762306a36Sopenharmony_ci		_exit(1);
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic void sigusr1(int sig, siginfo_t *info, void *ctx_void)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* Set IP and CX to match so that SYSRET can happen. */
9262306a36Sopenharmony_ci	ctx->uc_mcontext.gregs[REG_RIP] = rip;
9362306a36Sopenharmony_ci	ctx->uc_mcontext.gregs[REG_RCX] = rip;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* R11 and EFLAGS should already match. */
9662306a36Sopenharmony_ci	assert(ctx->uc_mcontext.gregs[REG_EFL] ==
9762306a36Sopenharmony_ci	       ctx->uc_mcontext.gregs[REG_R11]);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic void test_sigreturn_to(unsigned long ip)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	rip = ip;
10762306a36Sopenharmony_ci	printf("[RUN]\tsigreturn to 0x%lx\n", ip);
10862306a36Sopenharmony_ci	raise(SIGUSR1);
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic jmp_buf jmpbuf;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
11862306a36Sopenharmony_ci		printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
11962306a36Sopenharmony_ci		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
12062306a36Sopenharmony_ci		fflush(stdout);
12162306a36Sopenharmony_ci		_exit(1);
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	siglongjmp(jmpbuf, 1);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic void test_syscall_fallthrough_to(unsigned long ip)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	void *new_address = (void *)(ip - 4096);
13062306a36Sopenharmony_ci	void *ret;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	ret = mremap((void *)current_test_page_addr, 4096, 4096,
13562306a36Sopenharmony_ci		     MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
13662306a36Sopenharmony_ci	if (ret == MAP_FAILED) {
13762306a36Sopenharmony_ci		if (ip <= (1UL << 47) - PAGE_SIZE) {
13862306a36Sopenharmony_ci			err(1, "mremap to %p", new_address);
13962306a36Sopenharmony_ci		} else {
14062306a36Sopenharmony_ci			printf("[OK]\tmremap to %p failed\n", new_address);
14162306a36Sopenharmony_ci			return;
14262306a36Sopenharmony_ci		}
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (ret != new_address)
14662306a36Sopenharmony_ci		errx(1, "mremap malfunctioned: asked for %p but got %p\n",
14762306a36Sopenharmony_ci		     new_address, ret);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	current_test_page_addr = new_address;
15062306a36Sopenharmony_ci	rip = ip;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
15362306a36Sopenharmony_ci		asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
15462306a36Sopenharmony_ci			      [syscall_insn] "rm" (ip - 2));
15562306a36Sopenharmony_ci		errx(1, "[FAIL]\tSyscall trampoline returned");
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	printf("[OK]\tWe survived\n");
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ciint main()
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	/*
16462306a36Sopenharmony_ci	 * When the kernel returns from a slow-path syscall, it will
16562306a36Sopenharmony_ci	 * detect whether SYSRET is appropriate.  If it incorrectly
16662306a36Sopenharmony_ci	 * thinks that SYSRET is appropriate when RIP is noncanonical,
16762306a36Sopenharmony_ci	 * it'll crash on Intel CPUs.
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	sethandler(SIGUSR1, sigusr1, 0);
17062306a36Sopenharmony_ci	for (int i = 47; i < 64; i++)
17162306a36Sopenharmony_ci		test_sigreturn_to(1UL<<i);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	clearhandler(SIGUSR1);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	/* One extra test to check that we didn't screw up the mremap logic. */
17862306a36Sopenharmony_ci	test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* These are the interesting cases. */
18162306a36Sopenharmony_ci	for (int i = 47; i < 64; i++) {
18262306a36Sopenharmony_ci		test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
18362306a36Sopenharmony_ci		test_syscall_fallthrough_to(1UL<<i);
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
188