forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.1 KiB
236 lines
6.1 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* syscall_arg_fault.c - tests faults 32-bit fast syscall stack args |
|
* Copyright (c) 2015 Andrew Lutomirski |
|
*/ |
|
|
|
#define _GNU_SOURCE |
|
|
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <sys/signal.h> |
|
#include <sys/ucontext.h> |
|
#include <err.h> |
|
#include <setjmp.h> |
|
#include <errno.h> |
|
|
|
#include "helpers.h" |
|
|
|
static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), |
|
int flags) |
|
{ |
|
struct sigaction sa; |
|
memset(&sa, 0, sizeof(sa)); |
|
sa.sa_sigaction = handler; |
|
sa.sa_flags = SA_SIGINFO | flags; |
|
sigemptyset(&sa.sa_mask); |
|
if (sigaction(sig, &sa, 0)) |
|
err(1, "sigaction"); |
|
} |
|
|
|
static volatile sig_atomic_t sig_traps; |
|
static sigjmp_buf jmpbuf; |
|
|
|
static volatile sig_atomic_t n_errs; |
|
|
|
#ifdef __x86_64__ |
|
#define REG_AX REG_RAX |
|
#define REG_IP REG_RIP |
|
#else |
|
#define REG_AX REG_EAX |
|
#define REG_IP REG_EIP |
|
#endif |
|
|
|
static void sigsegv_or_sigbus(int sig, siginfo_t *info, void *ctx_void) |
|
{ |
|
ucontext_t *ctx = (ucontext_t*)ctx_void; |
|
long ax = (long)ctx->uc_mcontext.gregs[REG_AX]; |
|
|
|
if (ax != -EFAULT && ax != -ENOSYS) { |
|
printf("[FAIL]\tAX had the wrong value: 0x%lx\n", |
|
(unsigned long)ax); |
|
printf("\tIP = 0x%lx\n", (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); |
|
n_errs++; |
|
} else { |
|
printf("[OK]\tSeems okay\n"); |
|
} |
|
|
|
siglongjmp(jmpbuf, 1); |
|
} |
|
|
|
static volatile sig_atomic_t sigtrap_consecutive_syscalls; |
|
|
|
static void sigtrap(int sig, siginfo_t *info, void *ctx_void) |
|
{ |
|
/* |
|
* KVM has some bugs that can cause us to stop making progress. |
|
* detect them and complain, but don't infinite loop or fail the |
|
* test. |
|
*/ |
|
|
|
ucontext_t *ctx = (ucontext_t*)ctx_void; |
|
unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP]; |
|
|
|
if (*ip == 0x340f || *ip == 0x050f) { |
|
/* The trap was on SYSCALL or SYSENTER */ |
|
sigtrap_consecutive_syscalls++; |
|
if (sigtrap_consecutive_syscalls > 3) { |
|
printf("[WARN]\tGot stuck single-stepping -- you probably have a KVM bug\n"); |
|
siglongjmp(jmpbuf, 1); |
|
} |
|
} else { |
|
sigtrap_consecutive_syscalls = 0; |
|
} |
|
} |
|
|
|
static void sigill(int sig, siginfo_t *info, void *ctx_void) |
|
{ |
|
ucontext_t *ctx = (ucontext_t*)ctx_void; |
|
unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP]; |
|
|
|
if (*ip == 0x0b0f) { |
|
/* one of the ud2 instructions faulted */ |
|
printf("[OK]\tSYSCALL returned normally\n"); |
|
} else { |
|
printf("[SKIP]\tIllegal instruction\n"); |
|
} |
|
siglongjmp(jmpbuf, 1); |
|
} |
|
|
|
int main() |
|
{ |
|
stack_t stack = { |
|
/* Our sigaltstack scratch space. */ |
|
.ss_sp = malloc(sizeof(char) * SIGSTKSZ), |
|
.ss_size = SIGSTKSZ, |
|
}; |
|
if (sigaltstack(&stack, NULL) != 0) |
|
err(1, "sigaltstack"); |
|
|
|
sethandler(SIGSEGV, sigsegv_or_sigbus, SA_ONSTACK); |
|
/* |
|
* The actual exception can vary. On Atom CPUs, we get #SS |
|
* instead of #PF when the vDSO fails to access the stack when |
|
* ESP is too close to 2^32, and #SS causes SIGBUS. |
|
*/ |
|
sethandler(SIGBUS, sigsegv_or_sigbus, SA_ONSTACK); |
|
sethandler(SIGILL, sigill, SA_ONSTACK); |
|
|
|
/* |
|
* Exercise another nasty special case. The 32-bit SYSCALL |
|
* and SYSENTER instructions (even in compat mode) each |
|
* clobber one register. A Linux system call has a syscall |
|
* number and six arguments, and the user stack pointer |
|
* needs to live in some register on return. That means |
|
* that we need eight registers, but SYSCALL and SYSENTER |
|
* only preserve seven registers. As a result, one argument |
|
* ends up on the stack. The stack is user memory, which |
|
* means that the kernel can fail to read it. |
|
* |
|
* The 32-bit fast system calls don't have a defined ABI: |
|
* we're supposed to invoke them through the vDSO. So we'll |
|
* fudge it: we set all regs to invalid pointer values and |
|
* invoke the entry instruction. The return will fail no |
|
* matter what, and we completely lose our program state, |
|
* but we can fix it up with a signal handler. |
|
*/ |
|
|
|
printf("[RUN]\tSYSENTER with invalid state\n"); |
|
if (sigsetjmp(jmpbuf, 1) == 0) { |
|
asm volatile ( |
|
"movl $-1, %%eax\n\t" |
|
"movl $-1, %%ebx\n\t" |
|
"movl $-1, %%ecx\n\t" |
|
"movl $-1, %%edx\n\t" |
|
"movl $-1, %%esi\n\t" |
|
"movl $-1, %%edi\n\t" |
|
"movl $-1, %%ebp\n\t" |
|
"movl $-1, %%esp\n\t" |
|
"sysenter" |
|
: : : "memory", "flags"); |
|
} |
|
|
|
printf("[RUN]\tSYSCALL with invalid state\n"); |
|
if (sigsetjmp(jmpbuf, 1) == 0) { |
|
asm volatile ( |
|
"movl $-1, %%eax\n\t" |
|
"movl $-1, %%ebx\n\t" |
|
"movl $-1, %%ecx\n\t" |
|
"movl $-1, %%edx\n\t" |
|
"movl $-1, %%esi\n\t" |
|
"movl $-1, %%edi\n\t" |
|
"movl $-1, %%ebp\n\t" |
|
"movl $-1, %%esp\n\t" |
|
"syscall\n\t" |
|
"ud2" /* make sure we recover cleanly */ |
|
: : : "memory", "flags"); |
|
} |
|
|
|
printf("[RUN]\tSYSENTER with TF and invalid state\n"); |
|
sethandler(SIGTRAP, sigtrap, SA_ONSTACK); |
|
|
|
if (sigsetjmp(jmpbuf, 1) == 0) { |
|
sigtrap_consecutive_syscalls = 0; |
|
set_eflags(get_eflags() | X86_EFLAGS_TF); |
|
asm volatile ( |
|
"movl $-1, %%eax\n\t" |
|
"movl $-1, %%ebx\n\t" |
|
"movl $-1, %%ecx\n\t" |
|
"movl $-1, %%edx\n\t" |
|
"movl $-1, %%esi\n\t" |
|
"movl $-1, %%edi\n\t" |
|
"movl $-1, %%ebp\n\t" |
|
"movl $-1, %%esp\n\t" |
|
"sysenter" |
|
: : : "memory", "flags"); |
|
} |
|
set_eflags(get_eflags() & ~X86_EFLAGS_TF); |
|
|
|
printf("[RUN]\tSYSCALL with TF and invalid state\n"); |
|
if (sigsetjmp(jmpbuf, 1) == 0) { |
|
sigtrap_consecutive_syscalls = 0; |
|
set_eflags(get_eflags() | X86_EFLAGS_TF); |
|
asm volatile ( |
|
"movl $-1, %%eax\n\t" |
|
"movl $-1, %%ebx\n\t" |
|
"movl $-1, %%ecx\n\t" |
|
"movl $-1, %%edx\n\t" |
|
"movl $-1, %%esi\n\t" |
|
"movl $-1, %%edi\n\t" |
|
"movl $-1, %%ebp\n\t" |
|
"movl $-1, %%esp\n\t" |
|
"syscall\n\t" |
|
"ud2" /* make sure we recover cleanly */ |
|
: : : "memory", "flags"); |
|
} |
|
set_eflags(get_eflags() & ~X86_EFLAGS_TF); |
|
|
|
#ifdef __x86_64__ |
|
printf("[RUN]\tSYSENTER with TF, invalid state, and GSBASE < 0\n"); |
|
|
|
if (sigsetjmp(jmpbuf, 1) == 0) { |
|
sigtrap_consecutive_syscalls = 0; |
|
|
|
asm volatile ("wrgsbase %%rax\n\t" |
|
:: "a" (0xffffffffffff0000UL)); |
|
|
|
set_eflags(get_eflags() | X86_EFLAGS_TF); |
|
asm volatile ( |
|
"movl $-1, %%eax\n\t" |
|
"movl $-1, %%ebx\n\t" |
|
"movl $-1, %%ecx\n\t" |
|
"movl $-1, %%edx\n\t" |
|
"movl $-1, %%esi\n\t" |
|
"movl $-1, %%edi\n\t" |
|
"movl $-1, %%ebp\n\t" |
|
"movl $-1, %%esp\n\t" |
|
"sysenter" |
|
: : : "memory", "flags"); |
|
} |
|
set_eflags(get_eflags() & ~X86_EFLAGS_TF); |
|
#endif |
|
|
|
free(stack.ss_sp); |
|
return 0; |
|
}
|
|
|