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

selftests/powerpc: New PTRACE_SYSEMU test

This patch adds a new test for the new PTRACE_SYSEMU ptrace request.

This test also relies on PTRACE_GETREGS and PTRACE_SETREGS requests to
run properly, since the trace instruction (gettid() syscall) is being
modified at run-time (by PTRACE_SETREGS) and re-executed three times.
PTRACE_GETREGS is being used to check that the registers are still
sane.

This test basically creates a child process that executes syscalls
and the parent process check if it is being traced appropriately. The
parent process guarantees that the SYSCALLs are being traced, with
PTRACE_SYSEMU, and ptrace stops the child application before a syscall is
executed. The way the tests validates it, is by guaranteeing that the
system calls arguments, as argv[0] (r3) which is the same register that
will have the syscall return value on powerpc, are not being corrupted on
PTRACE_SYSEMU with a return value, i.e, it continues to have the current
arguments instead, meaning that the registers where not clobbered.

This test is basically the same test for x86 located at
tools/testing/selftests/x86/ptrace_syscall.c, limited to test PTRACE_SYSEMU
request, and ported to PowerPC.

Signed-off-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>

authored by

Breno Leitao and committed by
Michael Ellerman
fc35ef12 5521eb4b

+229 -1
+1 -1
tools/testing/selftests/powerpc/ptrace/Makefile
··· 2 2 TEST_PROGS := ptrace-gpr ptrace-tm-gpr ptrace-tm-spd-gpr \ 3 3 ptrace-tar ptrace-tm-tar ptrace-tm-spd-tar ptrace-vsx ptrace-tm-vsx \ 4 4 ptrace-tm-spd-vsx ptrace-tm-spr ptrace-hwbreak ptrace-pkey core-pkey \ 5 - perf-hwbreak 5 + perf-hwbreak ptrace-syscall 6 6 7 7 include ../../lib.mk 8 8
+228
tools/testing/selftests/powerpc/ptrace/ptrace-syscall.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * A ptrace test for testing PTRACE_SYSEMU, PTRACE_SETREGS and 4 + * PTRACE_GETREG. This test basically create a child process that executes 5 + * syscalls and the parent process check if it is being traced appropriated. 6 + * 7 + * This test is heavily based on tools/testing/selftests/x86/ptrace_syscall.c 8 + * test, and it was adapted to run on Powerpc by 9 + * Breno Leitao <leitao@debian.org> 10 + */ 11 + #define _GNU_SOURCE 12 + 13 + #include <sys/ptrace.h> 14 + #include <sys/types.h> 15 + #include <sys/wait.h> 16 + #include <sys/syscall.h> 17 + #include <sys/user.h> 18 + #include <unistd.h> 19 + #include <errno.h> 20 + #include <stddef.h> 21 + #include <stdio.h> 22 + #include <err.h> 23 + #include <string.h> 24 + #include <sys/auxv.h> 25 + #include "utils.h" 26 + 27 + /* Bitness-agnostic defines for user_regs_struct fields. */ 28 + #define user_syscall_nr gpr[0] 29 + #define user_arg0 gpr[3] 30 + #define user_arg1 gpr[4] 31 + #define user_arg2 gpr[5] 32 + #define user_arg3 gpr[6] 33 + #define user_arg4 gpr[7] 34 + #define user_arg5 gpr[8] 35 + #define user_ip nip 36 + 37 + #define PTRACE_SYSEMU 0x1d 38 + 39 + static int nerrs; 40 + 41 + static void wait_trap(pid_t chld) 42 + { 43 + siginfo_t si; 44 + 45 + if (waitid(P_PID, chld, &si, WEXITED|WSTOPPED) != 0) 46 + err(1, "waitid"); 47 + if (si.si_pid != chld) 48 + errx(1, "got unexpected pid in event\n"); 49 + if (si.si_code != CLD_TRAPPED) 50 + errx(1, "got unexpected event type %d\n", si.si_code); 51 + } 52 + 53 + static void test_ptrace_syscall_restart(void) 54 + { 55 + int status; 56 + struct pt_regs regs; 57 + pid_t chld; 58 + 59 + printf("[RUN]\tptrace-induced syscall restart\n"); 60 + 61 + chld = fork(); 62 + if (chld < 0) 63 + err(1, "fork"); 64 + 65 + /* 66 + * Child process is running 4 syscalls after ptrace. 67 + * 68 + * 1) getpid() 69 + * 2) gettid() 70 + * 3) tgkill() -> Send SIGSTOP 71 + * 4) gettid() -> Where the tests will happen essentially 72 + */ 73 + if (chld == 0) { 74 + if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) 75 + err(1, "PTRACE_TRACEME"); 76 + 77 + pid_t pid = getpid(), tid = syscall(SYS_gettid); 78 + 79 + printf("\tChild will make one syscall\n"); 80 + syscall(SYS_tgkill, pid, tid, SIGSTOP); 81 + 82 + syscall(SYS_gettid, 10, 11, 12, 13, 14, 15); 83 + _exit(0); 84 + } 85 + /* Parent process below */ 86 + 87 + /* Wait for SIGSTOP sent by tgkill above. */ 88 + if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) 89 + err(1, "waitpid"); 90 + 91 + printf("[RUN]\tSYSEMU\n"); 92 + if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) 93 + err(1, "PTRACE_SYSEMU"); 94 + wait_trap(chld); 95 + 96 + if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0) 97 + err(1, "PTRACE_GETREGS"); 98 + 99 + /* 100 + * Ptrace trapped prior to executing the syscall, thus r3 still has 101 + * the syscall number instead of the sys_gettid() result 102 + */ 103 + if (regs.user_syscall_nr != SYS_gettid || 104 + regs.user_arg0 != 10 || regs.user_arg1 != 11 || 105 + regs.user_arg2 != 12 || regs.user_arg3 != 13 || 106 + regs.user_arg4 != 14 || regs.user_arg5 != 15) { 107 + printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", 108 + (unsigned long)regs.user_syscall_nr, 109 + (unsigned long)regs.user_arg0, 110 + (unsigned long)regs.user_arg1, 111 + (unsigned long)regs.user_arg2, 112 + (unsigned long)regs.user_arg3, 113 + (unsigned long)regs.user_arg4, 114 + (unsigned long)regs.user_arg5); 115 + nerrs++; 116 + } else { 117 + printf("[OK]\tInitial nr and args are correct\n"); } 118 + 119 + printf("[RUN]\tRestart the syscall (ip = 0x%lx)\n", 120 + (unsigned long)regs.user_ip); 121 + 122 + /* 123 + * Rewind to retry the same syscall again. This will basically test 124 + * the rewind process together with PTRACE_SETREGS and PTRACE_GETREGS. 125 + */ 126 + regs.user_ip -= 4; 127 + if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0) 128 + err(1, "PTRACE_SETREGS"); 129 + 130 + if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) 131 + err(1, "PTRACE_SYSEMU"); 132 + wait_trap(chld); 133 + 134 + if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0) 135 + err(1, "PTRACE_GETREGS"); 136 + 137 + if (regs.user_syscall_nr != SYS_gettid || 138 + regs.user_arg0 != 10 || regs.user_arg1 != 11 || 139 + regs.user_arg2 != 12 || regs.user_arg3 != 13 || 140 + regs.user_arg4 != 14 || regs.user_arg5 != 15) { 141 + printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", 142 + (unsigned long)regs.user_syscall_nr, 143 + (unsigned long)regs.user_arg0, 144 + (unsigned long)regs.user_arg1, 145 + (unsigned long)regs.user_arg2, 146 + (unsigned long)regs.user_arg3, 147 + (unsigned long)regs.user_arg4, 148 + (unsigned long)regs.user_arg5); 149 + nerrs++; 150 + } else { 151 + printf("[OK]\tRestarted nr and args are correct\n"); 152 + } 153 + 154 + printf("[RUN]\tChange nr and args and restart the syscall (ip = 0x%lx)\n", 155 + (unsigned long)regs.user_ip); 156 + 157 + /* 158 + * Inject a new syscall (getpid) in the same place the previous 159 + * syscall (gettid), rewind and re-execute. 160 + */ 161 + regs.user_syscall_nr = SYS_getpid; 162 + regs.user_arg0 = 20; 163 + regs.user_arg1 = 21; 164 + regs.user_arg2 = 22; 165 + regs.user_arg3 = 23; 166 + regs.user_arg4 = 24; 167 + regs.user_arg5 = 25; 168 + regs.user_ip -= 4; 169 + 170 + if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0) 171 + err(1, "PTRACE_SETREGS"); 172 + 173 + if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0) 174 + err(1, "PTRACE_SYSEMU"); 175 + wait_trap(chld); 176 + 177 + if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0) 178 + err(1, "PTRACE_GETREGS"); 179 + 180 + /* Check that ptrace stopped at the new syscall that was 181 + * injected, and guarantee that it haven't executed, i.e, user_args 182 + * contain the arguments and not the syscall return value, for 183 + * instance. 184 + */ 185 + if (regs.user_syscall_nr != SYS_getpid 186 + || regs.user_arg0 != 20 || regs.user_arg1 != 21 187 + || regs.user_arg2 != 22 || regs.user_arg3 != 23 188 + || regs.user_arg4 != 24 || regs.user_arg5 != 25) { 189 + 190 + printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", 191 + (unsigned long)regs.user_syscall_nr, 192 + (unsigned long)regs.user_arg0, 193 + (unsigned long)regs.user_arg1, 194 + (unsigned long)regs.user_arg2, 195 + (unsigned long)regs.user_arg3, 196 + (unsigned long)regs.user_arg4, 197 + (unsigned long)regs.user_arg5); 198 + nerrs++; 199 + } else { 200 + printf("[OK]\tReplacement nr and args are correct\n"); 201 + } 202 + 203 + if (ptrace(PTRACE_CONT, chld, 0, 0) != 0) 204 + err(1, "PTRACE_CONT"); 205 + 206 + if (waitpid(chld, &status, 0) != chld) 207 + err(1, "waitpid"); 208 + 209 + /* Guarantee that the process executed properly, returning 0 */ 210 + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 211 + printf("[FAIL]\tChild failed\n"); 212 + nerrs++; 213 + } else { 214 + printf("[OK]\tChild exited cleanly\n"); 215 + } 216 + } 217 + 218 + int ptrace_syscall(void) 219 + { 220 + test_ptrace_syscall_restart(); 221 + 222 + return nerrs; 223 + } 224 + 225 + int main(void) 226 + { 227 + return test_harness(ptrace_syscall, "ptrace_syscall"); 228 + }