18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
48c2ecf20Sopenharmony_ci * Copyright (c) 2014-2016 Andrew Lutomirski
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#define _GNU_SOURCE
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <stdlib.h>
108c2ecf20Sopenharmony_ci#include <unistd.h>
118c2ecf20Sopenharmony_ci#include <stdio.h>
128c2ecf20Sopenharmony_ci#include <string.h>
138c2ecf20Sopenharmony_ci#include <inttypes.h>
148c2ecf20Sopenharmony_ci#include <sys/signal.h>
158c2ecf20Sopenharmony_ci#include <sys/ucontext.h>
168c2ecf20Sopenharmony_ci#include <sys/syscall.h>
178c2ecf20Sopenharmony_ci#include <err.h>
188c2ecf20Sopenharmony_ci#include <stddef.h>
198c2ecf20Sopenharmony_ci#include <stdbool.h>
208c2ecf20Sopenharmony_ci#include <setjmp.h>
218c2ecf20Sopenharmony_ci#include <sys/user.h>
228c2ecf20Sopenharmony_ci#include <sys/mman.h>
238c2ecf20Sopenharmony_ci#include <assert.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ciasm (
278c2ecf20Sopenharmony_ci	".pushsection \".text\", \"ax\"\n\t"
288c2ecf20Sopenharmony_ci	".balign 4096\n\t"
298c2ecf20Sopenharmony_ci	"test_page: .globl test_page\n\t"
308c2ecf20Sopenharmony_ci	".fill 4094,1,0xcc\n\t"
318c2ecf20Sopenharmony_ci	"test_syscall_insn:\n\t"
328c2ecf20Sopenharmony_ci	"syscall\n\t"
338c2ecf20Sopenharmony_ci	".ifne . - test_page - 4096\n\t"
348c2ecf20Sopenharmony_ci	".error \"test page is not one page long\"\n\t"
358c2ecf20Sopenharmony_ci	".endif\n\t"
368c2ecf20Sopenharmony_ci	".popsection"
378c2ecf20Sopenharmony_ci    );
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ciextern const char test_page[];
408c2ecf20Sopenharmony_cistatic void const *current_test_page_addr = test_page;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
438c2ecf20Sopenharmony_ci		       int flags)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct sigaction sa;
468c2ecf20Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
478c2ecf20Sopenharmony_ci	sa.sa_sigaction = handler;
488c2ecf20Sopenharmony_ci	sa.sa_flags = SA_SIGINFO | flags;
498c2ecf20Sopenharmony_ci	sigemptyset(&sa.sa_mask);
508c2ecf20Sopenharmony_ci	if (sigaction(sig, &sa, 0))
518c2ecf20Sopenharmony_ci		err(1, "sigaction");
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic void clearhandler(int sig)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct sigaction sa;
578c2ecf20Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
588c2ecf20Sopenharmony_ci	sa.sa_handler = SIG_DFL;
598c2ecf20Sopenharmony_ci	sigemptyset(&sa.sa_mask);
608c2ecf20Sopenharmony_ci	if (sigaction(sig, &sa, 0))
618c2ecf20Sopenharmony_ci		err(1, "sigaction");
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci/* State used by our signal handlers. */
658c2ecf20Sopenharmony_cistatic gregset_t initial_regs;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic volatile unsigned long rip;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
748c2ecf20Sopenharmony_ci		printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
758c2ecf20Sopenharmony_ci		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
768c2ecf20Sopenharmony_ci		fflush(stdout);
778c2ecf20Sopenharmony_ci		_exit(1);
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic void sigusr1(int sig, siginfo_t *info, void *ctx_void)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/* Set IP and CX to match so that SYSRET can happen. */
928c2ecf20Sopenharmony_ci	ctx->uc_mcontext.gregs[REG_RIP] = rip;
938c2ecf20Sopenharmony_ci	ctx->uc_mcontext.gregs[REG_RCX] = rip;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	/* R11 and EFLAGS should already match. */
968c2ecf20Sopenharmony_ci	assert(ctx->uc_mcontext.gregs[REG_EFL] ==
978c2ecf20Sopenharmony_ci	       ctx->uc_mcontext.gregs[REG_R11]);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	return;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic void test_sigreturn_to(unsigned long ip)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	rip = ip;
1078c2ecf20Sopenharmony_ci	printf("[RUN]\tsigreturn to 0x%lx\n", ip);
1088c2ecf20Sopenharmony_ci	raise(SIGUSR1);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic jmp_buf jmpbuf;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
1188c2ecf20Sopenharmony_ci		printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
1198c2ecf20Sopenharmony_ci		       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
1208c2ecf20Sopenharmony_ci		fflush(stdout);
1218c2ecf20Sopenharmony_ci		_exit(1);
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	siglongjmp(jmpbuf, 1);
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic void test_syscall_fallthrough_to(unsigned long ip)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	void *new_address = (void *)(ip - 4096);
1308c2ecf20Sopenharmony_ci	void *ret;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	ret = mremap((void *)current_test_page_addr, 4096, 4096,
1358c2ecf20Sopenharmony_ci		     MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
1368c2ecf20Sopenharmony_ci	if (ret == MAP_FAILED) {
1378c2ecf20Sopenharmony_ci		if (ip <= (1UL << 47) - PAGE_SIZE) {
1388c2ecf20Sopenharmony_ci			err(1, "mremap to %p", new_address);
1398c2ecf20Sopenharmony_ci		} else {
1408c2ecf20Sopenharmony_ci			printf("[OK]\tmremap to %p failed\n", new_address);
1418c2ecf20Sopenharmony_ci			return;
1428c2ecf20Sopenharmony_ci		}
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	if (ret != new_address)
1468c2ecf20Sopenharmony_ci		errx(1, "mremap malfunctioned: asked for %p but got %p\n",
1478c2ecf20Sopenharmony_ci		     new_address, ret);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	current_test_page_addr = new_address;
1508c2ecf20Sopenharmony_ci	rip = ip;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
1538c2ecf20Sopenharmony_ci		asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
1548c2ecf20Sopenharmony_ci			      [syscall_insn] "rm" (ip - 2));
1558c2ecf20Sopenharmony_ci		errx(1, "[FAIL]\tSyscall trampoline returned");
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	printf("[OK]\tWe survived\n");
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ciint main()
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	/*
1648c2ecf20Sopenharmony_ci	 * When the kernel returns from a slow-path syscall, it will
1658c2ecf20Sopenharmony_ci	 * detect whether SYSRET is appropriate.  If it incorrectly
1668c2ecf20Sopenharmony_ci	 * thinks that SYSRET is appropriate when RIP is noncanonical,
1678c2ecf20Sopenharmony_ci	 * it'll crash on Intel CPUs.
1688c2ecf20Sopenharmony_ci	 */
1698c2ecf20Sopenharmony_ci	sethandler(SIGUSR1, sigusr1, 0);
1708c2ecf20Sopenharmony_ci	for (int i = 47; i < 64; i++)
1718c2ecf20Sopenharmony_ci		test_sigreturn_to(1UL<<i);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	clearhandler(SIGUSR1);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* One extra test to check that we didn't screw up the mremap logic. */
1788c2ecf20Sopenharmony_ci	test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/* These are the interesting cases. */
1818c2ecf20Sopenharmony_ci	for (int i = 47; i < 64; i++) {
1828c2ecf20Sopenharmony_ci		test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
1838c2ecf20Sopenharmony_ci		test_syscall_fallthrough_to(1UL<<i);
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	return 0;
1878c2ecf20Sopenharmony_ci}
188