162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * A ptrace test for testing PTRACE_SYSEMU, PTRACE_SETREGS and
462306a36Sopenharmony_ci * PTRACE_GETREG.  This test basically create a child process that executes
562306a36Sopenharmony_ci * syscalls and the parent process check if it is being traced appropriated.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This test is heavily based on tools/testing/selftests/x86/ptrace_syscall.c
862306a36Sopenharmony_ci * test, and it was adapted to run on Powerpc by
962306a36Sopenharmony_ci * Breno Leitao <leitao@debian.org>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#define _GNU_SOURCE
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <sys/ptrace.h>
1462306a36Sopenharmony_ci#include <sys/types.h>
1562306a36Sopenharmony_ci#include <sys/wait.h>
1662306a36Sopenharmony_ci#include <sys/syscall.h>
1762306a36Sopenharmony_ci#include <sys/user.h>
1862306a36Sopenharmony_ci#include <unistd.h>
1962306a36Sopenharmony_ci#include <errno.h>
2062306a36Sopenharmony_ci#include <stddef.h>
2162306a36Sopenharmony_ci#include <stdio.h>
2262306a36Sopenharmony_ci#include <err.h>
2362306a36Sopenharmony_ci#include <string.h>
2462306a36Sopenharmony_ci#include <sys/auxv.h>
2562306a36Sopenharmony_ci#include "utils.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* Bitness-agnostic defines for user_regs_struct fields. */
2862306a36Sopenharmony_ci#define user_syscall_nr	gpr[0]
2962306a36Sopenharmony_ci#define user_arg0		gpr[3]
3062306a36Sopenharmony_ci#define user_arg1		gpr[4]
3162306a36Sopenharmony_ci#define user_arg2		gpr[5]
3262306a36Sopenharmony_ci#define user_arg3		gpr[6]
3362306a36Sopenharmony_ci#define user_arg4		gpr[7]
3462306a36Sopenharmony_ci#define user_arg5		gpr[8]
3562306a36Sopenharmony_ci#define user_ip		nip
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define PTRACE_SYSEMU		0x1d
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic int nerrs;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic void wait_trap(pid_t chld)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	siginfo_t si;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (waitid(P_PID, chld, &si, WEXITED|WSTOPPED) != 0)
4662306a36Sopenharmony_ci		err(1, "waitid");
4762306a36Sopenharmony_ci	if (si.si_pid != chld)
4862306a36Sopenharmony_ci		errx(1, "got unexpected pid in event\n");
4962306a36Sopenharmony_ci	if (si.si_code != CLD_TRAPPED)
5062306a36Sopenharmony_ci		errx(1, "got unexpected event type %d\n", si.si_code);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void test_ptrace_syscall_restart(void)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	int status;
5662306a36Sopenharmony_ci	struct pt_regs regs;
5762306a36Sopenharmony_ci	pid_t chld;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	printf("[RUN]\tptrace-induced syscall restart\n");
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	chld = fork();
6262306a36Sopenharmony_ci	if (chld < 0)
6362306a36Sopenharmony_ci		err(1, "fork");
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/*
6662306a36Sopenharmony_ci	 * Child process is running 4 syscalls after ptrace.
6762306a36Sopenharmony_ci	 *
6862306a36Sopenharmony_ci	 * 1) getpid()
6962306a36Sopenharmony_ci	 * 2) gettid()
7062306a36Sopenharmony_ci	 * 3) tgkill() -> Send SIGSTOP
7162306a36Sopenharmony_ci	 * 4) gettid() -> Where the tests will happen essentially
7262306a36Sopenharmony_ci	 */
7362306a36Sopenharmony_ci	if (chld == 0) {
7462306a36Sopenharmony_ci		if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
7562306a36Sopenharmony_ci			err(1, "PTRACE_TRACEME");
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		pid_t pid = getpid(), tid = syscall(SYS_gettid);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		printf("\tChild will make one syscall\n");
8062306a36Sopenharmony_ci		syscall(SYS_tgkill, pid, tid, SIGSTOP);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		syscall(SYS_gettid, 10, 11, 12, 13, 14, 15);
8362306a36Sopenharmony_ci		_exit(0);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci	/* Parent process below */
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* Wait for SIGSTOP sent by tgkill above. */
8862306a36Sopenharmony_ci	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
8962306a36Sopenharmony_ci		err(1, "waitpid");
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	printf("[RUN]\tSYSEMU\n");
9262306a36Sopenharmony_ci	if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
9362306a36Sopenharmony_ci		err(1, "PTRACE_SYSEMU");
9462306a36Sopenharmony_ci	wait_trap(chld);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
9762306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/*
10062306a36Sopenharmony_ci	 * Ptrace trapped prior to executing the syscall, thus r3 still has
10162306a36Sopenharmony_ci	 * the syscall number instead of the sys_gettid() result
10262306a36Sopenharmony_ci	 */
10362306a36Sopenharmony_ci	if (regs.user_syscall_nr != SYS_gettid ||
10462306a36Sopenharmony_ci	    regs.user_arg0 != 10 || regs.user_arg1 != 11 ||
10562306a36Sopenharmony_ci	    regs.user_arg2 != 12 || regs.user_arg3 != 13 ||
10662306a36Sopenharmony_ci	    regs.user_arg4 != 14 || regs.user_arg5 != 15) {
10762306a36Sopenharmony_ci		printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n",
10862306a36Sopenharmony_ci			(unsigned long)regs.user_syscall_nr,
10962306a36Sopenharmony_ci			(unsigned long)regs.user_arg0,
11062306a36Sopenharmony_ci			(unsigned long)regs.user_arg1,
11162306a36Sopenharmony_ci			(unsigned long)regs.user_arg2,
11262306a36Sopenharmony_ci			(unsigned long)regs.user_arg3,
11362306a36Sopenharmony_ci			(unsigned long)regs.user_arg4,
11462306a36Sopenharmony_ci			(unsigned long)regs.user_arg5);
11562306a36Sopenharmony_ci		 nerrs++;
11662306a36Sopenharmony_ci	} else {
11762306a36Sopenharmony_ci		printf("[OK]\tInitial nr and args are correct\n"); }
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	printf("[RUN]\tRestart the syscall (ip = 0x%lx)\n",
12062306a36Sopenharmony_ci	       (unsigned long)regs.user_ip);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/*
12362306a36Sopenharmony_ci	 * Rewind to retry the same syscall again. This will basically test
12462306a36Sopenharmony_ci	 * the rewind process together with PTRACE_SETREGS and PTRACE_GETREGS.
12562306a36Sopenharmony_ci	 */
12662306a36Sopenharmony_ci	regs.user_ip -= 4;
12762306a36Sopenharmony_ci	if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0)
12862306a36Sopenharmony_ci		err(1, "PTRACE_SETREGS");
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
13162306a36Sopenharmony_ci		err(1, "PTRACE_SYSEMU");
13262306a36Sopenharmony_ci	wait_trap(chld);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
13562306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	if (regs.user_syscall_nr != SYS_gettid ||
13862306a36Sopenharmony_ci	    regs.user_arg0 != 10 || regs.user_arg1 != 11 ||
13962306a36Sopenharmony_ci	    regs.user_arg2 != 12 || regs.user_arg3 != 13 ||
14062306a36Sopenharmony_ci	    regs.user_arg4 != 14 || regs.user_arg5 != 15) {
14162306a36Sopenharmony_ci		printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n",
14262306a36Sopenharmony_ci			(unsigned long)regs.user_syscall_nr,
14362306a36Sopenharmony_ci			(unsigned long)regs.user_arg0,
14462306a36Sopenharmony_ci			(unsigned long)regs.user_arg1,
14562306a36Sopenharmony_ci			(unsigned long)regs.user_arg2,
14662306a36Sopenharmony_ci			(unsigned long)regs.user_arg3,
14762306a36Sopenharmony_ci			(unsigned long)regs.user_arg4,
14862306a36Sopenharmony_ci			(unsigned long)regs.user_arg5);
14962306a36Sopenharmony_ci		nerrs++;
15062306a36Sopenharmony_ci	} else {
15162306a36Sopenharmony_ci		printf("[OK]\tRestarted nr and args are correct\n");
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	printf("[RUN]\tChange nr and args and restart the syscall (ip = 0x%lx)\n",
15562306a36Sopenharmony_ci	       (unsigned long)regs.user_ip);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/*
15862306a36Sopenharmony_ci	 * Inject a new syscall (getpid) in the same place the previous
15962306a36Sopenharmony_ci	 * syscall (gettid), rewind and re-execute.
16062306a36Sopenharmony_ci	 */
16162306a36Sopenharmony_ci	regs.user_syscall_nr = SYS_getpid;
16262306a36Sopenharmony_ci	regs.user_arg0 = 20;
16362306a36Sopenharmony_ci	regs.user_arg1 = 21;
16462306a36Sopenharmony_ci	regs.user_arg2 = 22;
16562306a36Sopenharmony_ci	regs.user_arg3 = 23;
16662306a36Sopenharmony_ci	regs.user_arg4 = 24;
16762306a36Sopenharmony_ci	regs.user_arg5 = 25;
16862306a36Sopenharmony_ci	regs.user_ip -= 4;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0)
17162306a36Sopenharmony_ci		err(1, "PTRACE_SETREGS");
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
17462306a36Sopenharmony_ci		err(1, "PTRACE_SYSEMU");
17562306a36Sopenharmony_ci	wait_trap(chld);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
17862306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* Check that ptrace stopped at the new syscall that was
18162306a36Sopenharmony_ci	 * injected, and guarantee that it haven't executed, i.e, user_args
18262306a36Sopenharmony_ci	 * contain the arguments and not the syscall return value, for
18362306a36Sopenharmony_ci	 * instance.
18462306a36Sopenharmony_ci	 */
18562306a36Sopenharmony_ci	if (regs.user_syscall_nr != SYS_getpid
18662306a36Sopenharmony_ci		|| regs.user_arg0 != 20 || regs.user_arg1 != 21
18762306a36Sopenharmony_ci		|| regs.user_arg2 != 22 || regs.user_arg3 != 23
18862306a36Sopenharmony_ci		|| regs.user_arg4 != 24 || regs.user_arg5 != 25) {
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n",
19162306a36Sopenharmony_ci			(unsigned long)regs.user_syscall_nr,
19262306a36Sopenharmony_ci			(unsigned long)regs.user_arg0,
19362306a36Sopenharmony_ci			(unsigned long)regs.user_arg1,
19462306a36Sopenharmony_ci			(unsigned long)regs.user_arg2,
19562306a36Sopenharmony_ci			(unsigned long)regs.user_arg3,
19662306a36Sopenharmony_ci			(unsigned long)regs.user_arg4,
19762306a36Sopenharmony_ci			(unsigned long)regs.user_arg5);
19862306a36Sopenharmony_ci		nerrs++;
19962306a36Sopenharmony_ci	} else {
20062306a36Sopenharmony_ci		printf("[OK]\tReplacement nr and args are correct\n");
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (ptrace(PTRACE_CONT, chld, 0, 0) != 0)
20462306a36Sopenharmony_ci		err(1, "PTRACE_CONT");
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (waitpid(chld, &status, 0) != chld)
20762306a36Sopenharmony_ci		err(1, "waitpid");
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* Guarantee that the process executed properly, returning 0 */
21062306a36Sopenharmony_ci	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
21162306a36Sopenharmony_ci		printf("[FAIL]\tChild failed\n");
21262306a36Sopenharmony_ci		nerrs++;
21362306a36Sopenharmony_ci	} else {
21462306a36Sopenharmony_ci		printf("[OK]\tChild exited cleanly\n");
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ciint ptrace_syscall(void)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	test_ptrace_syscall_restart();
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return nerrs;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ciint main(void)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	return test_harness(ptrace_syscall, "ptrace_syscall");
22862306a36Sopenharmony_ci}
229