linux/tools/testing/selftests/x86/sysret_rip.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
   4 * Copyright (c) 2014-2016 Andrew Lutomirski
   5 */
   6
   7#define _GNU_SOURCE
   8
   9#include <stdlib.h>
  10#include <unistd.h>
  11#include <stdio.h>
  12#include <string.h>
  13#include <inttypes.h>
  14#include <sys/signal.h>
  15#include <sys/ucontext.h>
  16#include <sys/syscall.h>
  17#include <err.h>
  18#include <stddef.h>
  19#include <stdbool.h>
  20#include <setjmp.h>
  21#include <sys/user.h>
  22#include <sys/mman.h>
  23#include <assert.h>
  24
  25
  26asm (
  27        ".pushsection \".text\", \"ax\"\n\t"
  28        ".balign 4096\n\t"
  29        "test_page: .globl test_page\n\t"
  30        ".fill 4094,1,0xcc\n\t"
  31        "test_syscall_insn:\n\t"
  32        "syscall\n\t"
  33        ".ifne . - test_page - 4096\n\t"
  34        ".error \"test page is not one page long\"\n\t"
  35        ".endif\n\t"
  36        ".popsection"
  37    );
  38
  39extern const char test_page[];
  40static void const *current_test_page_addr = test_page;
  41
  42static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
  43                       int flags)
  44{
  45        struct sigaction sa;
  46        memset(&sa, 0, sizeof(sa));
  47        sa.sa_sigaction = handler;
  48        sa.sa_flags = SA_SIGINFO | flags;
  49        sigemptyset(&sa.sa_mask);
  50        if (sigaction(sig, &sa, 0))
  51                err(1, "sigaction");
  52}
  53
  54static void clearhandler(int sig)
  55{
  56        struct sigaction sa;
  57        memset(&sa, 0, sizeof(sa));
  58        sa.sa_handler = SIG_DFL;
  59        sigemptyset(&sa.sa_mask);
  60        if (sigaction(sig, &sa, 0))
  61                err(1, "sigaction");
  62}
  63
  64/* State used by our signal handlers. */
  65static gregset_t initial_regs;
  66
  67static volatile unsigned long rip;
  68
  69static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
  70{
  71        ucontext_t *ctx = (ucontext_t*)ctx_void;
  72
  73        if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
  74                printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
  75                       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
  76                fflush(stdout);
  77                _exit(1);
  78        }
  79
  80        memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
  81
  82        printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
  83}
  84
  85static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
  86{
  87        ucontext_t *ctx = (ucontext_t*)ctx_void;
  88
  89        memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
  90
  91        /* Set IP and CX to match so that SYSRET can happen. */
  92        ctx->uc_mcontext.gregs[REG_RIP] = rip;
  93        ctx->uc_mcontext.gregs[REG_RCX] = rip;
  94
  95        /* R11 and EFLAGS should already match. */
  96        assert(ctx->uc_mcontext.gregs[REG_EFL] ==
  97               ctx->uc_mcontext.gregs[REG_R11]);
  98
  99        sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
 100
 101        return;
 102}
 103
 104static void test_sigreturn_to(unsigned long ip)
 105{
 106        rip = ip;
 107        printf("[RUN]\tsigreturn to 0x%lx\n", ip);
 108        raise(SIGUSR1);
 109}
 110
 111static jmp_buf jmpbuf;
 112
 113static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
 114{
 115        ucontext_t *ctx = (ucontext_t*)ctx_void;
 116
 117        if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
 118                printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
 119                       rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
 120                fflush(stdout);
 121                _exit(1);
 122        }
 123
 124        siglongjmp(jmpbuf, 1);
 125}
 126
 127static void test_syscall_fallthrough_to(unsigned long ip)
 128{
 129        void *new_address = (void *)(ip - 4096);
 130        void *ret;
 131
 132        printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
 133
 134        ret = mremap((void *)current_test_page_addr, 4096, 4096,
 135                     MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
 136        if (ret == MAP_FAILED) {
 137                if (ip <= (1UL << 47) - PAGE_SIZE) {
 138                        err(1, "mremap to %p", new_address);
 139                } else {
 140                        printf("[OK]\tmremap to %p failed\n", new_address);
 141                        return;
 142                }
 143        }
 144
 145        if (ret != new_address)
 146                errx(1, "mremap malfunctioned: asked for %p but got %p\n",
 147                     new_address, ret);
 148
 149        current_test_page_addr = new_address;
 150        rip = ip;
 151
 152        if (sigsetjmp(jmpbuf, 1) == 0) {
 153                asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
 154                              [syscall_insn] "rm" (ip - 2));
 155                errx(1, "[FAIL]\tSyscall trampoline returned");
 156        }
 157
 158        printf("[OK]\tWe survived\n");
 159}
 160
 161int main()
 162{
 163        /*
 164         * When the kernel returns from a slow-path syscall, it will
 165         * detect whether SYSRET is appropriate.  If it incorrectly
 166         * thinks that SYSRET is appropriate when RIP is noncanonical,
 167         * it'll crash on Intel CPUs.
 168         */
 169        sethandler(SIGUSR1, sigusr1, 0);
 170        for (int i = 47; i < 64; i++)
 171                test_sigreturn_to(1UL<<i);
 172
 173        clearhandler(SIGUSR1);
 174
 175        sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
 176
 177        /* One extra test to check that we didn't screw up the mremap logic. */
 178        test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
 179
 180        /* These are the interesting cases. */
 181        for (int i = 47; i < 64; i++) {
 182                test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
 183                test_syscall_fallthrough_to(1UL<<i);
 184        }
 185
 186        return 0;
 187}
 188