1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test that seccomp, tracepoints and audit observe the correct syscall 4 * arguments after a ptracer has modified them at syscall-enter-stop. 5 * 6 * On arm64, both the first argument and the return value of a syscall 7 * are passed in register x0. The original x0 is saved in 8 * pt_regs::orig_x0 during syscall entry and returned as the first 9 * argument by syscall_get_arguments(). Because ptrace modifications 10 * to x0 are not automatically reflected in orig_x0, seccomp, tracepoints 11 * and audit may see a stale value unless orig_x0 is explicitly 12 * re-synchronised after a ptrace stop. 13 * 14 * This test sets up a seccomp filter that allows write(2, ...) but kills 15 * the task for any other fd. A ptracer changes the fd argument from 2 16 * to 1 at the syscall-enter stop. If the orig_x0 re-sync works, seccomp 17 * sees the modified argument (fd=1) and kills the child with SIGSYS 18 * (test passes). If orig_x0 is not re-synced, seccomp sees the original 19 * fd=2, the write succeeds and the child exits normally (test fails, 20 * vulnerability present). 21 */ 22 #include <errno.h> 23 #include <stdbool.h> 24 #include <stddef.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <sys/prctl.h> 30 #include <sys/ptrace.h> 31 #include <sys/uio.h> 32 #include <sys/wait.h> 33 #include <asm/ptrace.h> 34 #include <linux/elf.h> 35 #include <linux/filter.h> 36 #include <linux/seccomp.h> 37 #include <asm/unistd.h> 38 39 #include "kselftest.h" 40 41 #define EXPECTED_TESTS 1 42 43 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 44 #define ARG0_OFFSET (offsetof(struct seccomp_data, args)) 45 #else 46 #define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4) 47 #endif 48 49 static int do_child(void) 50 { 51 if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) 52 ksft_exit_fail_perror("PTRACE_TRACEME"); 53 54 if (raise(SIGSTOP)) 55 ksft_exit_fail_perror("raise(SIGSTOP)"); 56 57 /* 58 * Seccomp filter: 59 * If syscall is not write -> ALLOW 60 * If syscall is write: 61 * - If args[0] (fd) == 2 -> ALLOW 62 * - Otherwise -> KILL 63 */ 64 struct sock_filter filter[] = { 65 BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* nr */ 66 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 3), 67 BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET), /* args[0] */ 68 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 1, 0), 69 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), 70 BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), 71 }; 72 struct sock_fprog prog = { 73 .len = ARRAY_SIZE(filter), 74 .filter = filter, 75 }; 76 77 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) 78 ksft_exit_fail_perror("prctl NO_NEW_PRIVS"); 79 80 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) 81 ksft_exit_fail_perror("prctl SECCOMP"); 82 83 /* 84 * Invoke write(2, ...) while the tracer will change the first 85 * argument (fd) from 2 to 1 at syscall entry. 86 */ 87 syscall(__NR_write, 2, NULL, 0); 88 _exit(0); 89 } 90 91 static int do_parent(pid_t child) 92 { 93 bool bypass = false; 94 int status; 95 96 /* Wait for the initial SIGSTOP */ 97 if (waitpid(child, &status, 0) != child) 98 ksft_exit_fail_msg("waitpid failed"); 99 100 if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) 101 ksft_exit_fail_msg("unexpected stop status"); 102 103 if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_EXITKILL)) 104 ksft_exit_fail_perror("PTRACE_SETOPTIONS"); 105 106 if (ptrace(PTRACE_SYSCALL, child, 0, 0)) 107 ksft_exit_fail_perror("PTRACE_SYSCALL"); 108 109 while (1) { 110 int sig; 111 112 if (waitpid(child, &status, 0) != child) 113 ksft_exit_fail_msg("waitpid lost child"); 114 115 if (WIFEXITED(status)) { 116 /* Child exited normally – bypass succeeded */ 117 bypass = true; 118 break; 119 } 120 121 if (WIFSIGNALED(status)) { 122 sig = WTERMSIG(status); 123 if (sig == SIGSYS) 124 break; 125 ksft_exit_fail_msg("child died unexpectedly from signal %d (%s)", 126 sig, strsignal(sig)); 127 } 128 129 if (!WIFSTOPPED(status)) 130 ksft_exit_fail_msg("unexpected wait status"); 131 132 sig = WSTOPSIG(status); 133 134 if (sig == (SIGTRAP | 0x80)) { 135 struct user_regs_struct regs; 136 struct iovec iov = { 137 .iov_base = ®s, 138 .iov_len = sizeof(regs), 139 }; 140 141 if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov)) 142 ksft_exit_fail_perror("PTRACE_GETREGSET"); 143 144 unsigned long syscall_nr = regs.regs[8]; 145 unsigned long x0 = regs.regs[0]; 146 147 /* Modify fd from 2 to 1 at write entry */ 148 if (syscall_nr == __NR_write && x0 == 2) { 149 regs.regs[0] = 1; 150 if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov)) 151 ksft_exit_fail_perror("PTRACE_SETREGSET"); 152 } 153 154 if (ptrace(PTRACE_SYSCALL, child, 0, 0)) 155 ksft_exit_fail_perror("PTRACE_SYSCALL"); 156 } else { 157 /* Forward other signals */ 158 if (ptrace(PTRACE_SYSCALL, child, 0, sig)) 159 ksft_exit_fail_perror("PTRACE_SYSCALL"); 160 } 161 } 162 163 /* bypass == true means vulnerability exists -> test fails */ 164 return bypass ? EXIT_FAILURE : EXIT_SUCCESS; 165 } 166 167 int main(void) 168 { 169 pid_t child; 170 171 ksft_print_header(); 172 ksft_set_plan(EXPECTED_TESTS); 173 174 child = fork(); 175 if (child < 0) 176 ksft_exit_fail_msg("fork failed: %s", strerror(errno)); 177 178 if (!child) 179 return do_child(); 180 181 /* 182 * do_parent() returns EXIT_SUCCESS if the child was killed by 183 * SIGSYS (i.e. seccomp correctly saw the modified argument), 184 * and EXIT_FAILURE if the child exited normally (bypass). 185 */ 186 int result = do_parent(child); 187 188 ksft_test_result(result == EXIT_SUCCESS, "seccomp_ptrace_x0_bypass\n"); 189 190 ksft_print_cnts(); 191 return result; 192 } 193