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