1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * syscall_arg_fault.c - tests faults 32-bit fast syscall stack args 4 * Copyright (c) 2015 Andrew Lutomirski 5 */ 6 7 #define _GNU_SOURCE 8 9 #include <stdlib.h> 10 #include <stdio.h> 11 #include <string.h> 12 #include <sys/signal.h> 13 #include <sys/ucontext.h> 14 #include <err.h> 15 #include <setjmp.h> 16 #include <errno.h> 17 18 #include "helpers.h" 19 20 static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), 21 int flags) 22 { 23 struct sigaction sa; 24 memset(&sa, 0, sizeof(sa)); 25 sa.sa_sigaction = handler; 26 sa.sa_flags = SA_SIGINFO | flags; 27 sigemptyset(&sa.sa_mask); 28 if (sigaction(sig, &sa, 0)) 29 err(1, "sigaction"); 30 } 31 32 static sigjmp_buf jmpbuf; 33 34 static volatile sig_atomic_t n_errs; 35 36 #ifdef __x86_64__ 37 #define REG_AX REG_RAX 38 #define REG_IP REG_RIP 39 #else 40 #define REG_AX REG_EAX 41 #define REG_IP REG_EIP 42 #endif 43 44 static void sigsegv_or_sigbus(int sig, siginfo_t *info, void *ctx_void) 45 { 46 ucontext_t *ctx = (ucontext_t*)ctx_void; 47 long ax = (long)ctx->uc_mcontext.gregs[REG_AX]; 48 49 if (ax != -EFAULT && ax != -ENOSYS) { 50 printf("[FAIL]\tAX had the wrong value: 0x%lx\n", 51 (unsigned long)ax); 52 printf("\tIP = 0x%lx\n", (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); 53 n_errs++; 54 } else { 55 printf("[OK]\tSeems okay\n"); 56 } 57 58 siglongjmp(jmpbuf, 1); 59 } 60 61 static volatile sig_atomic_t sigtrap_consecutive_syscalls; 62 63 static void sigtrap(int sig, siginfo_t *info, void *ctx_void) 64 { 65 /* 66 * KVM has some bugs that can cause us to stop making progress. 67 * detect them and complain, but don't infinite loop or fail the 68 * test. 69 */ 70 71 ucontext_t *ctx = (ucontext_t*)ctx_void; 72 unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP]; 73 74 if (*ip == 0x340f || *ip == 0x050f) { 75 /* The trap was on SYSCALL or SYSENTER */ 76 sigtrap_consecutive_syscalls++; 77 if (sigtrap_consecutive_syscalls > 3) { 78 printf("[WARN]\tGot stuck single-stepping -- you probably have a KVM bug\n"); 79 siglongjmp(jmpbuf, 1); 80 } 81 } else { 82 sigtrap_consecutive_syscalls = 0; 83 } 84 } 85 86 static void sigill(int sig, siginfo_t *info, void *ctx_void) 87 { 88 ucontext_t *ctx = (ucontext_t*)ctx_void; 89 unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP]; 90 91 if (*ip == 0x0b0f) { 92 /* one of the ud2 instructions faulted */ 93 printf("[OK]\tSYSCALL returned normally\n"); 94 } else { 95 printf("[SKIP]\tIllegal instruction\n"); 96 } 97 siglongjmp(jmpbuf, 1); 98 } 99 100 int main() 101 { 102 stack_t stack = { 103 /* Our sigaltstack scratch space. */ 104 .ss_sp = malloc(sizeof(char) * SIGSTKSZ), 105 .ss_size = SIGSTKSZ, 106 }; 107 if (sigaltstack(&stack, NULL) != 0) 108 err(1, "sigaltstack"); 109 110 sethandler(SIGSEGV, sigsegv_or_sigbus, SA_ONSTACK); 111 /* 112 * The actual exception can vary. On Atom CPUs, we get #SS 113 * instead of #PF when the vDSO fails to access the stack when 114 * ESP is too close to 2^32, and #SS causes SIGBUS. 115 */ 116 sethandler(SIGBUS, sigsegv_or_sigbus, SA_ONSTACK); 117 sethandler(SIGILL, sigill, SA_ONSTACK); 118 119 /* 120 * Exercise another nasty special case. The 32-bit SYSCALL 121 * and SYSENTER instructions (even in compat mode) each 122 * clobber one register. A Linux system call has a syscall 123 * number and six arguments, and the user stack pointer 124 * needs to live in some register on return. That means 125 * that we need eight registers, but SYSCALL and SYSENTER 126 * only preserve seven registers. As a result, one argument 127 * ends up on the stack. The stack is user memory, which 128 * means that the kernel can fail to read it. 129 * 130 * The 32-bit fast system calls don't have a defined ABI: 131 * we're supposed to invoke them through the vDSO. So we'll 132 * fudge it: we set all regs to invalid pointer values and 133 * invoke the entry instruction. The return will fail no 134 * matter what, and we completely lose our program state, 135 * but we can fix it up with a signal handler. 136 */ 137 138 printf("[RUN]\tSYSENTER with invalid state\n"); 139 if (sigsetjmp(jmpbuf, 1) == 0) { 140 asm volatile ( 141 "movl $-1, %%eax\n\t" 142 "movl $-1, %%ebx\n\t" 143 "movl $-1, %%ecx\n\t" 144 "movl $-1, %%edx\n\t" 145 "movl $-1, %%esi\n\t" 146 "movl $-1, %%edi\n\t" 147 "movl $-1, %%ebp\n\t" 148 "movl $-1, %%esp\n\t" 149 "sysenter" 150 : : : "memory", "flags"); 151 } 152 153 printf("[RUN]\tSYSCALL with invalid state\n"); 154 if (sigsetjmp(jmpbuf, 1) == 0) { 155 asm volatile ( 156 "movl $-1, %%eax\n\t" 157 "movl $-1, %%ebx\n\t" 158 "movl $-1, %%ecx\n\t" 159 "movl $-1, %%edx\n\t" 160 "movl $-1, %%esi\n\t" 161 "movl $-1, %%edi\n\t" 162 "movl $-1, %%ebp\n\t" 163 "movl $-1, %%esp\n\t" 164 "syscall\n\t" 165 "ud2" /* make sure we recover cleanly */ 166 : : : "memory", "flags"); 167 } 168 169 printf("[RUN]\tSYSENTER with TF and invalid state\n"); 170 sethandler(SIGTRAP, sigtrap, SA_ONSTACK); 171 172 if (sigsetjmp(jmpbuf, 1) == 0) { 173 sigtrap_consecutive_syscalls = 0; 174 set_eflags(get_eflags() | X86_EFLAGS_TF); 175 asm volatile ( 176 "movl $-1, %%eax\n\t" 177 "movl $-1, %%ebx\n\t" 178 "movl $-1, %%ecx\n\t" 179 "movl $-1, %%edx\n\t" 180 "movl $-1, %%esi\n\t" 181 "movl $-1, %%edi\n\t" 182 "movl $-1, %%ebp\n\t" 183 "movl $-1, %%esp\n\t" 184 "sysenter" 185 : : : "memory", "flags"); 186 } 187 set_eflags(get_eflags() & ~X86_EFLAGS_TF); 188 189 printf("[RUN]\tSYSCALL with TF and invalid state\n"); 190 if (sigsetjmp(jmpbuf, 1) == 0) { 191 sigtrap_consecutive_syscalls = 0; 192 set_eflags(get_eflags() | X86_EFLAGS_TF); 193 asm volatile ( 194 "movl $-1, %%eax\n\t" 195 "movl $-1, %%ebx\n\t" 196 "movl $-1, %%ecx\n\t" 197 "movl $-1, %%edx\n\t" 198 "movl $-1, %%esi\n\t" 199 "movl $-1, %%edi\n\t" 200 "movl $-1, %%ebp\n\t" 201 "movl $-1, %%esp\n\t" 202 "syscall\n\t" 203 "ud2" /* make sure we recover cleanly */ 204 : : : "memory", "flags"); 205 } 206 set_eflags(get_eflags() & ~X86_EFLAGS_TF); 207 208 #ifdef __x86_64__ 209 printf("[RUN]\tSYSENTER with TF, invalid state, and GSBASE < 0\n"); 210 211 if (sigsetjmp(jmpbuf, 1) == 0) { 212 sigtrap_consecutive_syscalls = 0; 213 214 asm volatile ("wrgsbase %%rax\n\t" 215 :: "a" (0xffffffffffff0000UL)); 216 217 set_eflags(get_eflags() | X86_EFLAGS_TF); 218 asm volatile ( 219 "movl $-1, %%eax\n\t" 220 "movl $-1, %%ebx\n\t" 221 "movl $-1, %%ecx\n\t" 222 "movl $-1, %%edx\n\t" 223 "movl $-1, %%esi\n\t" 224 "movl $-1, %%edi\n\t" 225 "movl $-1, %%ebp\n\t" 226 "movl $-1, %%esp\n\t" 227 "sysenter" 228 : : : "memory", "flags"); 229 } 230 set_eflags(get_eflags() & ~X86_EFLAGS_TF); 231 #endif 232 233 free(stack.ss_sp); 234 return 0; 235 } 236