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