cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

sysret_rip.c (4545B)


      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}