diff options
author | Andy Lutomirski <luto@kernel.org> | 2015-10-05 20:47:52 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2015-10-07 05:34:07 -0400 |
commit | 04235c00b6bb72b589e99efcc18883378ee76f1b (patch) | |
tree | 814f6686f3983a319235928bc269209e121e6f6c /tools/testing | |
parent | 3b56aae34bc695638b8673fc8459be1837c18730 (diff) |
selftests/x86: Add a test for ptrace syscall restart and arg modification
This tests assumptions about how fast syscall works wrt pt_regs
and, in particular, what happens if IP is decremented by 2
during a syscall.
Signed-off-by: Andy Lutomirski <luto@kernel.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Shuah Khan <shuahkh@osg.samsung.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-kernel@vger.kernel.org
Link: http://lkml.kernel.org/r/1c44dbfe59000ba135bbf35ccc5d2433a0b31618.1444091584.git.luto@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'tools/testing')
-rw-r--r-- | tools/testing/selftests/x86/Makefile | 3 | ||||
-rw-r--r-- | tools/testing/selftests/x86/ptrace_syscall.c | 294 | ||||
-rw-r--r-- | tools/testing/selftests/x86/raw_syscall_helper_32.S | 46 |
3 files changed, 342 insertions, 1 deletions
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 75413529f4a2..389701f59940 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile | |||
@@ -4,7 +4,7 @@ include ../lib.mk | |||
4 | 4 | ||
5 | .PHONY: all all_32 all_64 warn_32bit_failure clean | 5 | .PHONY: all all_32 all_64 warn_32bit_failure clean |
6 | 6 | ||
7 | TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs ldt_gdt syscall_nt | 7 | TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs ldt_gdt syscall_nt ptrace_syscall |
8 | TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso | 8 | TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso |
9 | 9 | ||
10 | TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) | 10 | TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) |
@@ -60,4 +60,5 @@ endif | |||
60 | 60 | ||
61 | # Some tests have additional dependencies. | 61 | # Some tests have additional dependencies. |
62 | sysret_ss_attrs_64: thunks.S | 62 | sysret_ss_attrs_64: thunks.S |
63 | ptrace_syscall_32: raw_syscall_helper_32.S | ||
63 | test_syscall_vdso_32: thunks_32.S | 64 | test_syscall_vdso_32: thunks_32.S |
diff --git a/tools/testing/selftests/x86/ptrace_syscall.c b/tools/testing/selftests/x86/ptrace_syscall.c new file mode 100644 index 000000000000..5105b49cd8aa --- /dev/null +++ b/tools/testing/selftests/x86/ptrace_syscall.c | |||
@@ -0,0 +1,294 @@ | |||
1 | #define _GNU_SOURCE | ||
2 | |||
3 | #include <sys/ptrace.h> | ||
4 | #include <sys/types.h> | ||
5 | #include <sys/wait.h> | ||
6 | #include <sys/syscall.h> | ||
7 | #include <sys/user.h> | ||
8 | #include <unistd.h> | ||
9 | #include <errno.h> | ||
10 | #include <stddef.h> | ||
11 | #include <stdio.h> | ||
12 | #include <err.h> | ||
13 | #include <string.h> | ||
14 | #include <asm/ptrace-abi.h> | ||
15 | #include <sys/auxv.h> | ||
16 | |||
17 | /* Bitness-agnostic defines for user_regs_struct fields. */ | ||
18 | #ifdef __x86_64__ | ||
19 | # define user_syscall_nr orig_rax | ||
20 | # define user_arg0 rdi | ||
21 | # define user_arg1 rsi | ||
22 | # define user_arg2 rdx | ||
23 | # define user_arg3 r10 | ||
24 | # define user_arg4 r8 | ||
25 | # define user_arg5 r9 | ||
26 | # define user_ip rip | ||
27 | # define user_ax rax | ||
28 | #else | ||
29 | # define user_syscall_nr orig_eax | ||
30 | # define user_arg0 ebx | ||
31 | # define user_arg1 ecx | ||
32 | # define user_arg2 edx | ||
33 | # define user_arg3 esi | ||
34 | # define user_arg4 edi | ||
35 | # define user_arg5 ebp | ||
36 | # define user_ip eip | ||
37 | # define user_ax eax | ||
38 | #endif | ||
39 | |||
40 | static int nerrs = 0; | ||
41 | |||
42 | struct syscall_args32 { | ||
43 | uint32_t nr, arg0, arg1, arg2, arg3, arg4, arg5; | ||
44 | }; | ||
45 | |||
46 | #ifdef __i386__ | ||
47 | extern void sys32_helper(struct syscall_args32 *, void *); | ||
48 | extern void int80_and_ret(void); | ||
49 | #endif | ||
50 | |||
51 | /* | ||
52 | * Helper to invoke int80 with controlled regs and capture the final regs. | ||
53 | */ | ||
54 | static void do_full_int80(struct syscall_args32 *args) | ||
55 | { | ||
56 | #ifdef __x86_64__ | ||
57 | register unsigned long bp asm("bp") = args->arg5; | ||
58 | asm volatile ("int $0x80" | ||
59 | : "+a" (args->nr), | ||
60 | "+b" (args->arg0), "+c" (args->arg1), "+d" (args->arg2), | ||
61 | "+S" (args->arg3), "+D" (args->arg4), "+r" (bp)); | ||
62 | args->arg5 = bp; | ||
63 | #else | ||
64 | sys32_helper(args, int80_and_ret); | ||
65 | #endif | ||
66 | } | ||
67 | |||
68 | #ifdef __i386__ | ||
69 | static void (*vsyscall32)(void); | ||
70 | |||
71 | /* | ||
72 | * Nasty helper to invoke AT_SYSINFO (i.e. __kernel_vsyscall) with | ||
73 | * controlled regs and capture the final regs. This is so nasty that it | ||
74 | * crashes my copy of gdb :) | ||
75 | */ | ||
76 | static void do_full_vsyscall32(struct syscall_args32 *args) | ||
77 | { | ||
78 | sys32_helper(args, vsyscall32); | ||
79 | } | ||
80 | #endif | ||
81 | |||
82 | static siginfo_t wait_trap(pid_t chld) | ||
83 | { | ||
84 | siginfo_t si; | ||
85 | if (waitid(P_PID, chld, &si, WEXITED|WSTOPPED) != 0) | ||
86 | err(1, "waitid"); | ||
87 | if (si.si_pid != chld) | ||
88 | errx(1, "got unexpected pid in event\n"); | ||
89 | if (si.si_code != CLD_TRAPPED) | ||
90 | errx(1, "got unexpected event type %d\n", si.si_code); | ||
91 | return si; | ||
92 | } | ||
93 | |||
94 | static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), | ||
95 | int flags) | ||
96 | { | ||
97 | struct sigaction sa; | ||
98 | memset(&sa, 0, sizeof(sa)); | ||
99 | sa.sa_sigaction = handler; | ||
100 | sa.sa_flags = SA_SIGINFO | flags; | ||
101 | sigemptyset(&sa.sa_mask); | ||
102 | if (sigaction(sig, &sa, 0)) | ||
103 | err(1, "sigaction"); | ||
104 | } | ||
105 | |||
106 | static void clearhandler(int sig) | ||
107 | { | ||
108 | struct sigaction sa; | ||
109 | memset(&sa, 0, sizeof(sa)); | ||
110 | sa.sa_handler = SIG_DFL; | ||
111 | sigemptyset(&sa.sa_mask); | ||
112 | if (sigaction(sig, &sa, 0)) | ||
113 | err(1, "sigaction"); | ||
114 | } | ||
115 | |||
116 | #ifdef __x86_64__ | ||
117 | # define REG_BP REG_RBP | ||
118 | #else | ||
119 | # define REG_BP REG_EBP | ||
120 | #endif | ||
121 | |||
122 | static void empty_handler(int sig, siginfo_t *si, void *ctx_void) | ||
123 | { | ||
124 | } | ||
125 | |||
126 | static void test_sys32_regs(void (*do_syscall)(struct syscall_args32 *)) | ||
127 | { | ||
128 | struct syscall_args32 args = { | ||
129 | .nr = 224, /* gettid */ | ||
130 | .arg0 = 10, .arg1 = 11, .arg2 = 12, | ||
131 | .arg3 = 13, .arg4 = 14, .arg5 = 15, | ||
132 | }; | ||
133 | |||
134 | do_syscall(&args); | ||
135 | |||
136 | if (args.nr != getpid() || | ||
137 | args.arg0 != 10 || args.arg1 != 11 || args.arg2 != 12 || | ||
138 | args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) { | ||
139 | printf("[FAIL]\tgetpid() failed to preseve regs\n"); | ||
140 | nerrs++; | ||
141 | } else { | ||
142 | printf("[OK]\tgetpid() preserves regs\n"); | ||
143 | } | ||
144 | |||
145 | sethandler(SIGUSR1, empty_handler, 0); | ||
146 | |||
147 | args.nr = 37; /* kill */ | ||
148 | args.arg0 = getpid(); | ||
149 | args.arg1 = SIGUSR1; | ||
150 | do_syscall(&args); | ||
151 | if (args.nr != 0 || | ||
152 | args.arg0 != getpid() || args.arg1 != SIGUSR1 || args.arg2 != 12 || | ||
153 | args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) { | ||
154 | printf("[FAIL]\tkill(getpid(), SIGUSR1) failed to preseve regs\n"); | ||
155 | nerrs++; | ||
156 | } else { | ||
157 | printf("[OK]\tkill(getpid(), SIGUSR1) preserves regs\n"); | ||
158 | } | ||
159 | clearhandler(SIGUSR1); | ||
160 | } | ||
161 | |||
162 | static void test_ptrace_syscall_restart(void) | ||
163 | { | ||
164 | printf("[RUN]\tptrace-induced syscall restart\n"); | ||
165 | pid_t chld = fork(); | ||
166 | if (chld < 0) | ||
167 | err(1, "fork"); | ||
168 | |||
169 | if (chld == 0) { | ||
170 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) | ||
171 | err(1, "PTRACE_TRACEME"); | ||
172 | |||
173 | printf("\tChild will make one syscall\n"); | ||
174 | raise(SIGSTOP); | ||
175 | |||
176 | syscall(SYS_gettid, 10, 11, 12, 13, 14, 15); | ||
177 | _exit(0); | ||
178 | } | ||
179 | |||
180 | int status; | ||
181 | |||
182 | /* Wait for SIGSTOP. */ | ||
183 | if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) | ||
184 | err(1, "waitpid"); | ||
185 | |||
186 | struct user_regs_struct regs; | ||
187 | |||
188 | printf("[RUN]\tSYSEMU\n"); | ||
189 | if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) | ||
190 | err(1, "PTRACE_SYSCALL"); | ||
191 | wait_trap(chld); | ||
192 | |||
193 | if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) | ||
194 | err(1, "PTRACE_GETREGS"); | ||
195 | |||
196 | if (regs.user_syscall_nr != SYS_gettid || | ||
197 | regs.user_arg0 != 10 || regs.user_arg1 != 11 || | ||
198 | regs.user_arg2 != 12 || regs.user_arg3 != 13 || | ||
199 | regs.user_arg4 != 14 || regs.user_arg5 != 15) { | ||
200 | printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); | ||
201 | nerrs++; | ||
202 | } else { | ||
203 | printf("[OK]\tInitial nr and args are correct\n"); | ||
204 | } | ||
205 | |||
206 | printf("[RUN]\tRestart the syscall (ip = 0x%lx)\n", | ||
207 | (unsigned long)regs.user_ip); | ||
208 | |||
209 | /* | ||
210 | * This does exactly what it appears to do if syscall is int80 or | ||
211 | * SYSCALL64. For SYSCALL32 or SYSENTER, though, this is highly | ||
212 | * magical. It needs to work so that ptrace and syscall restart | ||
213 | * work as expected. | ||
214 | */ | ||
215 | regs.user_ax = regs.user_syscall_nr; | ||
216 | regs.user_ip -= 2; | ||
217 | if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0) | ||
218 | err(1, "PTRACE_SETREGS"); | ||
219 | |||
220 | if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) | ||
221 | err(1, "PTRACE_SYSCALL"); | ||
222 | wait_trap(chld); | ||
223 | |||
224 | if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) | ||
225 | err(1, "PTRACE_GETREGS"); | ||
226 | |||
227 | if (regs.user_syscall_nr != SYS_gettid || | ||
228 | regs.user_arg0 != 10 || regs.user_arg1 != 11 || | ||
229 | regs.user_arg2 != 12 || regs.user_arg3 != 13 || | ||
230 | regs.user_arg4 != 14 || regs.user_arg5 != 15) { | ||
231 | printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); | ||
232 | nerrs++; | ||
233 | } else { | ||
234 | printf("[OK]\tRestarted nr and args are correct\n"); | ||
235 | } | ||
236 | |||
237 | printf("[RUN]\tChange nr and args and restart the syscall (ip = 0x%lx)\n", | ||
238 | (unsigned long)regs.user_ip); | ||
239 | |||
240 | regs.user_ax = SYS_getpid; | ||
241 | regs.user_arg0 = 20; | ||
242 | regs.user_arg1 = 21; | ||
243 | regs.user_arg2 = 22; | ||
244 | regs.user_arg3 = 23; | ||
245 | regs.user_arg4 = 24; | ||
246 | regs.user_arg5 = 25; | ||
247 | regs.user_ip -= 2; | ||
248 | |||
249 | if (ptrace(PTRACE_SETREGS, chld, 0, ®s) != 0) | ||
250 | err(1, "PTRACE_SETREGS"); | ||
251 | |||
252 | if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) | ||
253 | err(1, "PTRACE_SYSCALL"); | ||
254 | wait_trap(chld); | ||
255 | |||
256 | if (ptrace(PTRACE_GETREGS, chld, 0, ®s) != 0) | ||
257 | err(1, "PTRACE_GETREGS"); | ||
258 | |||
259 | if (regs.user_syscall_nr != SYS_getpid || | ||
260 | regs.user_arg0 != 20 || regs.user_arg1 != 21 || regs.user_arg2 != 22 || | ||
261 | regs.user_arg3 != 23 || regs.user_arg4 != 24 || regs.user_arg5 != 25) { | ||
262 | printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5); | ||
263 | nerrs++; | ||
264 | } else { | ||
265 | printf("[OK]\tReplacement nr and args are correct\n"); | ||
266 | } | ||
267 | |||
268 | if (ptrace(PTRACE_CONT, chld, 0, 0) != 0) | ||
269 | err(1, "PTRACE_CONT"); | ||
270 | if (waitpid(chld, &status, 0) != chld) | ||
271 | err(1, "waitpid"); | ||
272 | if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { | ||
273 | printf("[FAIL]\tChild failed\n"); | ||
274 | nerrs++; | ||
275 | } else { | ||
276 | printf("[OK]\tChild exited cleanly\n"); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | int main() | ||
281 | { | ||
282 | printf("[RUN]\tCheck int80 return regs\n"); | ||
283 | test_sys32_regs(do_full_int80); | ||
284 | |||
285 | #if defined(__i386__) && (!defined(__GLIBC__) || __GLIBC__ > 2 || __GLIBC_MINOR__ >= 16) | ||
286 | vsyscall32 = (void *)getauxval(AT_SYSINFO); | ||
287 | printf("[RUN]\tCheck AT_SYSINFO return regs\n"); | ||
288 | test_sys32_regs(do_full_vsyscall32); | ||
289 | #endif | ||
290 | |||
291 | test_ptrace_syscall_restart(); | ||
292 | |||
293 | return 0; | ||
294 | } | ||
diff --git a/tools/testing/selftests/x86/raw_syscall_helper_32.S b/tools/testing/selftests/x86/raw_syscall_helper_32.S new file mode 100644 index 000000000000..534e71e35c6a --- /dev/null +++ b/tools/testing/selftests/x86/raw_syscall_helper_32.S | |||
@@ -0,0 +1,46 @@ | |||
1 | .global sys32_helper | ||
2 | sys32_helper: | ||
3 | /* Args: syscall_args_32*, function pointer */ | ||
4 | pushl %ebp | ||
5 | pushl %ebx | ||
6 | pushl %esi | ||
7 | pushl %edi | ||
8 | movl 5*4(%esp), %eax /* pointer to args struct */ | ||
9 | |||
10 | movl 1*4(%eax), %ebx | ||
11 | movl 2*4(%eax), %ecx | ||
12 | movl 3*4(%eax), %edx | ||
13 | movl 4*4(%eax), %esi | ||
14 | movl 5*4(%eax), %edi | ||
15 | movl 6*4(%eax), %ebp | ||
16 | movl 0*4(%eax), %eax | ||
17 | |||
18 | call *(6*4)(%esp) /* Do the syscall */ | ||
19 | |||
20 | /* Now we need to recover without losing any reg values */ | ||
21 | pushl %eax | ||
22 | movl 6*4(%esp), %eax | ||
23 | popl 0*4(%eax) | ||
24 | movl %ebx, 1*4(%eax) | ||
25 | movl %ecx, 2*4(%eax) | ||
26 | movl %edx, 3*4(%eax) | ||
27 | movl %esi, 4*4(%eax) | ||
28 | movl %edi, 5*4(%eax) | ||
29 | movl %ebp, 6*4(%eax) | ||
30 | |||
31 | popl %edi | ||
32 | popl %esi | ||
33 | popl %ebx | ||
34 | popl %ebp | ||
35 | ret | ||
36 | |||
37 | .type sys32_helper, @function | ||
38 | .size sys32_helper, .-sys32_helper | ||
39 | |||
40 | .global int80_and_ret | ||
41 | int80_and_ret: | ||
42 | int $0x80 | ||
43 | ret | ||
44 | |||
45 | .type int80_and_ret, @function | ||
46 | .size int80_and_ret, .-int80_and_ret | ||