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