12fcbc4adSJinjie Ruan // SPDX-License-Identifier: GPL-2.0 22fcbc4adSJinjie Ruan /* 32fcbc4adSJinjie Ruan * Test that seccomp, tracepoints and audit observe the correct syscall 42fcbc4adSJinjie Ruan * arguments after a ptracer has modified them at syscall-enter-stop. 52fcbc4adSJinjie Ruan * 62fcbc4adSJinjie Ruan * On arm64, both the first argument and the return value of a syscall 72fcbc4adSJinjie Ruan * are passed in register x0. The original x0 is saved in 82fcbc4adSJinjie Ruan * pt_regs::orig_x0 during syscall entry and returned as the first 92fcbc4adSJinjie Ruan * argument by syscall_get_arguments(). Because ptrace modifications 102fcbc4adSJinjie Ruan * to x0 are not automatically reflected in orig_x0, seccomp, tracepoints 112fcbc4adSJinjie Ruan * and audit may see a stale value unless orig_x0 is explicitly 122fcbc4adSJinjie Ruan * re-synchronised after a ptrace stop. 132fcbc4adSJinjie Ruan * 142fcbc4adSJinjie Ruan * This test sets up a seccomp filter that allows write(2, ...) but kills 152fcbc4adSJinjie Ruan * the task for any other fd. A ptracer changes the fd argument from 2 162fcbc4adSJinjie Ruan * to 1 at the syscall-enter stop. If the orig_x0 re-sync works, seccomp 172fcbc4adSJinjie Ruan * sees the modified argument (fd=1) and kills the child with SIGSYS 182fcbc4adSJinjie Ruan * (test passes). If orig_x0 is not re-synced, seccomp sees the original 192fcbc4adSJinjie Ruan * fd=2, the write succeeds and the child exits normally (test fails, 202fcbc4adSJinjie Ruan * vulnerability present). 212fcbc4adSJinjie Ruan */ 222fcbc4adSJinjie Ruan #include <errno.h> 232fcbc4adSJinjie Ruan #include <stdbool.h> 242fcbc4adSJinjie Ruan #include <stddef.h> 252fcbc4adSJinjie Ruan #include <stdio.h> 262fcbc4adSJinjie Ruan #include <stdlib.h> 272fcbc4adSJinjie Ruan #include <string.h> 282fcbc4adSJinjie Ruan #include <unistd.h> 292fcbc4adSJinjie Ruan #include <sys/prctl.h> 302fcbc4adSJinjie Ruan #include <sys/ptrace.h> 312fcbc4adSJinjie Ruan #include <sys/uio.h> 322fcbc4adSJinjie Ruan #include <sys/wait.h> 332fcbc4adSJinjie Ruan #include <asm/ptrace.h> 342fcbc4adSJinjie Ruan #include <linux/elf.h> 352fcbc4adSJinjie Ruan #include <linux/filter.h> 362fcbc4adSJinjie Ruan #include <linux/seccomp.h> 37*eb78ef9cSJinjie Ruan #include <asm/unistd.h> 382fcbc4adSJinjie Ruan 392fcbc4adSJinjie Ruan #include "kselftest.h" 402fcbc4adSJinjie Ruan 412fcbc4adSJinjie Ruan #define EXPECTED_TESTS 1 422fcbc4adSJinjie Ruan 432fcbc4adSJinjie Ruan #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 442fcbc4adSJinjie Ruan #define ARG0_OFFSET (offsetof(struct seccomp_data, args)) 452fcbc4adSJinjie Ruan #else 462fcbc4adSJinjie Ruan #define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4) 472fcbc4adSJinjie Ruan #endif 482fcbc4adSJinjie Ruan 492fcbc4adSJinjie Ruan static int do_child(void) 502fcbc4adSJinjie Ruan { 512fcbc4adSJinjie Ruan if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) 522fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_TRACEME"); 532fcbc4adSJinjie Ruan 542fcbc4adSJinjie Ruan if (raise(SIGSTOP)) 552fcbc4adSJinjie Ruan ksft_exit_fail_perror("raise(SIGSTOP)"); 562fcbc4adSJinjie Ruan 572fcbc4adSJinjie Ruan /* 582fcbc4adSJinjie Ruan * Seccomp filter: 592fcbc4adSJinjie Ruan * If syscall is not write -> ALLOW 602fcbc4adSJinjie Ruan * If syscall is write: 612fcbc4adSJinjie Ruan * - If args[0] (fd) == 2 -> ALLOW 622fcbc4adSJinjie Ruan * - Otherwise -> KILL 632fcbc4adSJinjie Ruan */ 642fcbc4adSJinjie Ruan struct sock_filter filter[] = { 652fcbc4adSJinjie Ruan BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* nr */ 662fcbc4adSJinjie Ruan BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 3), 672fcbc4adSJinjie Ruan BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET), /* args[0] */ 682fcbc4adSJinjie Ruan BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 1, 0), 692fcbc4adSJinjie Ruan BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), 702fcbc4adSJinjie Ruan BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), 712fcbc4adSJinjie Ruan }; 722fcbc4adSJinjie Ruan struct sock_fprog prog = { 732fcbc4adSJinjie Ruan .len = ARRAY_SIZE(filter), 742fcbc4adSJinjie Ruan .filter = filter, 752fcbc4adSJinjie Ruan }; 762fcbc4adSJinjie Ruan 772fcbc4adSJinjie Ruan if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) 782fcbc4adSJinjie Ruan ksft_exit_fail_perror("prctl NO_NEW_PRIVS"); 792fcbc4adSJinjie Ruan 802fcbc4adSJinjie Ruan if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) 812fcbc4adSJinjie Ruan ksft_exit_fail_perror("prctl SECCOMP"); 822fcbc4adSJinjie Ruan 832fcbc4adSJinjie Ruan /* 842fcbc4adSJinjie Ruan * Invoke write(2, ...) while the tracer will change the first 852fcbc4adSJinjie Ruan * argument (fd) from 2 to 1 at syscall entry. 862fcbc4adSJinjie Ruan */ 872fcbc4adSJinjie Ruan syscall(__NR_write, 2, NULL, 0); 882fcbc4adSJinjie Ruan _exit(0); 892fcbc4adSJinjie Ruan } 902fcbc4adSJinjie Ruan 912fcbc4adSJinjie Ruan static int do_parent(pid_t child) 922fcbc4adSJinjie Ruan { 932fcbc4adSJinjie Ruan bool bypass = false; 942fcbc4adSJinjie Ruan int status; 952fcbc4adSJinjie Ruan 962fcbc4adSJinjie Ruan /* Wait for the initial SIGSTOP */ 972fcbc4adSJinjie Ruan if (waitpid(child, &status, 0) != child) 982fcbc4adSJinjie Ruan ksft_exit_fail_msg("waitpid failed"); 992fcbc4adSJinjie Ruan 1002fcbc4adSJinjie Ruan if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) 1012fcbc4adSJinjie Ruan ksft_exit_fail_msg("unexpected stop status"); 1022fcbc4adSJinjie Ruan 1032fcbc4adSJinjie Ruan if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_EXITKILL)) 1042fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_SETOPTIONS"); 1052fcbc4adSJinjie Ruan 1062fcbc4adSJinjie Ruan if (ptrace(PTRACE_SYSCALL, child, 0, 0)) 1072fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_SYSCALL"); 1082fcbc4adSJinjie Ruan 1092fcbc4adSJinjie Ruan while (1) { 1102fcbc4adSJinjie Ruan int sig; 1112fcbc4adSJinjie Ruan 1122fcbc4adSJinjie Ruan if (waitpid(child, &status, 0) != child) 1132fcbc4adSJinjie Ruan ksft_exit_fail_msg("waitpid lost child"); 1142fcbc4adSJinjie Ruan 1152fcbc4adSJinjie Ruan if (WIFEXITED(status)) { 1162fcbc4adSJinjie Ruan /* Child exited normally – bypass succeeded */ 1172fcbc4adSJinjie Ruan bypass = true; 1182fcbc4adSJinjie Ruan break; 1192fcbc4adSJinjie Ruan } 1202fcbc4adSJinjie Ruan 1212fcbc4adSJinjie Ruan if (WIFSIGNALED(status)) { 1222fcbc4adSJinjie Ruan sig = WTERMSIG(status); 1232fcbc4adSJinjie Ruan if (sig == SIGSYS) 1242fcbc4adSJinjie Ruan break; 1252fcbc4adSJinjie Ruan ksft_exit_fail_msg("child died unexpectedly from signal %d (%s)", 1262fcbc4adSJinjie Ruan sig, strsignal(sig)); 1272fcbc4adSJinjie Ruan } 1282fcbc4adSJinjie Ruan 1292fcbc4adSJinjie Ruan if (!WIFSTOPPED(status)) 1302fcbc4adSJinjie Ruan ksft_exit_fail_msg("unexpected wait status"); 1312fcbc4adSJinjie Ruan 1322fcbc4adSJinjie Ruan sig = WSTOPSIG(status); 1332fcbc4adSJinjie Ruan 1342fcbc4adSJinjie Ruan if (sig == (SIGTRAP | 0x80)) { 1352fcbc4adSJinjie Ruan struct user_regs_struct regs; 1362fcbc4adSJinjie Ruan struct iovec iov = { 1372fcbc4adSJinjie Ruan .iov_base = ®s, 1382fcbc4adSJinjie Ruan .iov_len = sizeof(regs), 1392fcbc4adSJinjie Ruan }; 1402fcbc4adSJinjie Ruan 1412fcbc4adSJinjie Ruan if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov)) 1422fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_GETREGSET"); 1432fcbc4adSJinjie Ruan 1442fcbc4adSJinjie Ruan unsigned long syscall_nr = regs.regs[8]; 1452fcbc4adSJinjie Ruan unsigned long x0 = regs.regs[0]; 1462fcbc4adSJinjie Ruan 1472fcbc4adSJinjie Ruan /* Modify fd from 2 to 1 at write entry */ 1482fcbc4adSJinjie Ruan if (syscall_nr == __NR_write && x0 == 2) { 1492fcbc4adSJinjie Ruan regs.regs[0] = 1; 1502fcbc4adSJinjie Ruan if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov)) 1512fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_SETREGSET"); 1522fcbc4adSJinjie Ruan } 1532fcbc4adSJinjie Ruan 1542fcbc4adSJinjie Ruan if (ptrace(PTRACE_SYSCALL, child, 0, 0)) 1552fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_SYSCALL"); 1562fcbc4adSJinjie Ruan } else { 1572fcbc4adSJinjie Ruan /* Forward other signals */ 1582fcbc4adSJinjie Ruan if (ptrace(PTRACE_SYSCALL, child, 0, sig)) 1592fcbc4adSJinjie Ruan ksft_exit_fail_perror("PTRACE_SYSCALL"); 1602fcbc4adSJinjie Ruan } 1612fcbc4adSJinjie Ruan } 1622fcbc4adSJinjie Ruan 1632fcbc4adSJinjie Ruan /* bypass == true means vulnerability exists -> test fails */ 1642fcbc4adSJinjie Ruan return bypass ? EXIT_FAILURE : EXIT_SUCCESS; 1652fcbc4adSJinjie Ruan } 1662fcbc4adSJinjie Ruan 1672fcbc4adSJinjie Ruan int main(void) 1682fcbc4adSJinjie Ruan { 1692fcbc4adSJinjie Ruan pid_t child; 1702fcbc4adSJinjie Ruan 1712fcbc4adSJinjie Ruan ksft_print_header(); 1722fcbc4adSJinjie Ruan ksft_set_plan(EXPECTED_TESTS); 1732fcbc4adSJinjie Ruan 1742fcbc4adSJinjie Ruan child = fork(); 1752fcbc4adSJinjie Ruan if (child < 0) 1762fcbc4adSJinjie Ruan ksft_exit_fail_msg("fork failed: %s", strerror(errno)); 1772fcbc4adSJinjie Ruan 1782fcbc4adSJinjie Ruan if (!child) 1792fcbc4adSJinjie Ruan return do_child(); 1802fcbc4adSJinjie Ruan 1812fcbc4adSJinjie Ruan /* 1822fcbc4adSJinjie Ruan * do_parent() returns EXIT_SUCCESS if the child was killed by 1832fcbc4adSJinjie Ruan * SIGSYS (i.e. seccomp correctly saw the modified argument), 1842fcbc4adSJinjie Ruan * and EXIT_FAILURE if the child exited normally (bypass). 1852fcbc4adSJinjie Ruan */ 1862fcbc4adSJinjie Ruan int result = do_parent(child); 1872fcbc4adSJinjie Ruan 1882fcbc4adSJinjie Ruan ksft_test_result(result == EXIT_SUCCESS, "seccomp_ptrace_x0_bypass\n"); 1892fcbc4adSJinjie Ruan 1902fcbc4adSJinjie Ruan ksft_print_cnts(); 1912fcbc4adSJinjie Ruan return result; 1922fcbc4adSJinjie Ruan } 193