xref: /linux/tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c (revision 85cdaca6970028bf6f544c355c90035586836ddf)
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 = &regs,
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