Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Andy Lutomirski and committed by
Ingo Molnar
04235c00 3b56aae3

+342 -1
+2 -1
tools/testing/selftests/x86/Makefile
··· 4 4 5 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 8 TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault sigreturn test_syscall_vdso unwind_vdso 9 9 10 10 TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) ··· 60 60 61 61 # Some tests have additional dependencies. 62 62 sysret_ss_attrs_64: thunks.S 63 + ptrace_syscall_32: raw_syscall_helper_32.S 63 64 test_syscall_vdso_32: thunks_32.S
+294
tools/testing/selftests/x86/ptrace_syscall.c
··· 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, &regs) != 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, &regs) != 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, &regs) != 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, &regs) != 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, &regs) != 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 + }
+46
tools/testing/selftests/x86/raw_syscall_helper_32.S
··· 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