Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
0004  * Copyright (c) 2014-2016 Andrew Lutomirski
0005  */
0006 
0007 #define _GNU_SOURCE
0008 
0009 #include <stdlib.h>
0010 #include <unistd.h>
0011 #include <stdio.h>
0012 #include <string.h>
0013 #include <inttypes.h>
0014 #include <sys/signal.h>
0015 #include <sys/ucontext.h>
0016 #include <sys/syscall.h>
0017 #include <err.h>
0018 #include <stddef.h>
0019 #include <stdbool.h>
0020 #include <setjmp.h>
0021 #include <sys/user.h>
0022 #include <sys/mman.h>
0023 #include <assert.h>
0024 
0025 
0026 asm (
0027     ".pushsection \".text\", \"ax\"\n\t"
0028     ".balign 4096\n\t"
0029     "test_page: .globl test_page\n\t"
0030     ".fill 4094,1,0xcc\n\t"
0031     "test_syscall_insn:\n\t"
0032     "syscall\n\t"
0033     ".ifne . - test_page - 4096\n\t"
0034     ".error \"test page is not one page long\"\n\t"
0035     ".endif\n\t"
0036     ".popsection"
0037     );
0038 
0039 extern const char test_page[];
0040 static void const *current_test_page_addr = test_page;
0041 
0042 static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
0043                int flags)
0044 {
0045     struct sigaction sa;
0046     memset(&sa, 0, sizeof(sa));
0047     sa.sa_sigaction = handler;
0048     sa.sa_flags = SA_SIGINFO | flags;
0049     sigemptyset(&sa.sa_mask);
0050     if (sigaction(sig, &sa, 0))
0051         err(1, "sigaction");
0052 }
0053 
0054 static void clearhandler(int sig)
0055 {
0056     struct sigaction sa;
0057     memset(&sa, 0, sizeof(sa));
0058     sa.sa_handler = SIG_DFL;
0059     sigemptyset(&sa.sa_mask);
0060     if (sigaction(sig, &sa, 0))
0061         err(1, "sigaction");
0062 }
0063 
0064 /* State used by our signal handlers. */
0065 static gregset_t initial_regs;
0066 
0067 static volatile unsigned long rip;
0068 
0069 static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
0070 {
0071     ucontext_t *ctx = (ucontext_t*)ctx_void;
0072 
0073     if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
0074         printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
0075                rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
0076         fflush(stdout);
0077         _exit(1);
0078     }
0079 
0080     memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
0081 
0082     printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
0083 }
0084 
0085 static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
0086 {
0087     ucontext_t *ctx = (ucontext_t*)ctx_void;
0088 
0089     memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
0090 
0091     /* Set IP and CX to match so that SYSRET can happen. */
0092     ctx->uc_mcontext.gregs[REG_RIP] = rip;
0093     ctx->uc_mcontext.gregs[REG_RCX] = rip;
0094 
0095     /* R11 and EFLAGS should already match. */
0096     assert(ctx->uc_mcontext.gregs[REG_EFL] ==
0097            ctx->uc_mcontext.gregs[REG_R11]);
0098 
0099     sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
0100 
0101     return;
0102 }
0103 
0104 static void test_sigreturn_to(unsigned long ip)
0105 {
0106     rip = ip;
0107     printf("[RUN]\tsigreturn to 0x%lx\n", ip);
0108     raise(SIGUSR1);
0109 }
0110 
0111 static jmp_buf jmpbuf;
0112 
0113 static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
0114 {
0115     ucontext_t *ctx = (ucontext_t*)ctx_void;
0116 
0117     if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
0118         printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
0119                rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
0120         fflush(stdout);
0121         _exit(1);
0122     }
0123 
0124     siglongjmp(jmpbuf, 1);
0125 }
0126 
0127 static void test_syscall_fallthrough_to(unsigned long ip)
0128 {
0129     void *new_address = (void *)(ip - 4096);
0130     void *ret;
0131 
0132     printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
0133 
0134     ret = mremap((void *)current_test_page_addr, 4096, 4096,
0135              MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
0136     if (ret == MAP_FAILED) {
0137         if (ip <= (1UL << 47) - PAGE_SIZE) {
0138             err(1, "mremap to %p", new_address);
0139         } else {
0140             printf("[OK]\tmremap to %p failed\n", new_address);
0141             return;
0142         }
0143     }
0144 
0145     if (ret != new_address)
0146         errx(1, "mremap malfunctioned: asked for %p but got %p\n",
0147              new_address, ret);
0148 
0149     current_test_page_addr = new_address;
0150     rip = ip;
0151 
0152     if (sigsetjmp(jmpbuf, 1) == 0) {
0153         asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
0154                   [syscall_insn] "rm" (ip - 2));
0155         errx(1, "[FAIL]\tSyscall trampoline returned");
0156     }
0157 
0158     printf("[OK]\tWe survived\n");
0159 }
0160 
0161 int main()
0162 {
0163     /*
0164      * When the kernel returns from a slow-path syscall, it will
0165      * detect whether SYSRET is appropriate.  If it incorrectly
0166      * thinks that SYSRET is appropriate when RIP is noncanonical,
0167      * it'll crash on Intel CPUs.
0168      */
0169     sethandler(SIGUSR1, sigusr1, 0);
0170     for (int i = 47; i < 64; i++)
0171         test_sigreturn_to(1UL<<i);
0172 
0173     clearhandler(SIGUSR1);
0174 
0175     sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
0176 
0177     /* One extra test to check that we didn't screw up the mremap logic. */
0178     test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
0179 
0180     /* These are the interesting cases. */
0181     for (int i = 47; i < 64; i++) {
0182         test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
0183         test_syscall_fallthrough_to(1UL<<i);
0184     }
0185 
0186     return 0;
0187 }