[PATCH] riscv: selftests: Add a ptrace test to check a0 of restarted syscall

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a riscv selftest that checks that a0 of syscalls are able to be
replaced. When entering a syscall, a0 contains the first argument to the
syscall and upon exiting, a0 contains the return value. To replace the
a0 argument instead of the a0 return value with ptrace after halting the
program with PTRACE_SYSCALL, orig_a0 must be used. This test checks that
orig_a0 allows a syscall argument to be modified, and that changing a0
does not change the syscall argument.

Signed-off-by: Charlie Jenkins <charlie@xxxxxxxxxxxx>
---
 .../riscv/abi/ptrace_restart_syscall.c        | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c

diff --git a/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c b/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c
new file mode 100644
index 000000000000..e74ae02c6850
--- /dev/null
+++ b/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include <linux/elf.h>
+#include <linux/unistd.h>
+#include <asm/ptrace.h>
+
+#include "../../kselftest_harness.h"
+
+#define DEFAULT_A0		0x6
+
+#define ORIG_A0_AFTER_MODIFIED  0x5
+#define MODIFY_A0               0x01
+#define MODIFY_ORIG_A0          0x02
+
+#define perr_and_exit(fmt, ...) do {			\
+	char buf[256];					\
+	snprintf(buf, sizeof(buf), "%s:%d: " fmt ": %m\n",	\
+			__func__, __LINE__, ##__VA_ARGS__);	\
+	perror(buf);						\
+	exit(-1);						\
+} while (0)
+
+static inline void resume_and_wait_tracee(pid_t pid, int flag)
+{
+	int status;
+
+	if (ptrace(flag, pid, 0, 0))
+		perr_and_exit("failed to resume the tracee %d\n", pid);
+
+	if (waitpid(pid, &status, 0) != pid)
+		perr_and_exit("failed to wait for the tracee %d\n", pid);
+}
+
+static void ptrace_restart_syscall(int opt, int *result)
+{
+	int status;
+	pid_t pid;
+
+	struct user_regs_struct regs;
+	struct iovec iov = {
+		.iov_base = &regs,
+		.iov_len = sizeof(regs),
+	};
+
+	pid = fork();
+	if (pid == 0) {
+		/* Mark oneself being traced */
+		long val = ptrace(PTRACE_TRACEME, 0, 0, 0);
+		if (val)
+			perr_and_exit("failed to request for tracer to trace me: %ld\n", val);
+
+		kill(getpid(), SIGSTOP);
+
+		/* Perform exit syscall that will be intercepted */
+		exit(DEFAULT_A0);
+	} else if (pid < 0) {
+		exit(1);
+	}
+
+	if (waitpid(pid, &status, 0) != pid)
+		perr_and_exit("failed to wait for the tracee %d\n", pid);
+
+	/* Stop at the entry point of the restarted syscall */
+	resume_and_wait_tracee(pid, PTRACE_SYSCALL);
+
+	/* Now, check regs.a0 of the restarted syscall */
+	if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov))
+		perr_and_exit("failed to get tracee registers\n");
+
+	/* Modify a0/orig_a0 for the restarted syscall */
+	switch (opt) {
+	case MODIFY_A0:
+		regs.a0 = ORIG_A0_AFTER_MODIFIED;
+		break;
+	case MODIFY_ORIG_A0:
+		regs.orig_a0 = ORIG_A0_AFTER_MODIFIED;
+		break;
+	}
+
+	if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov))
+		perr_and_exit("failed to set tracee registers\n");
+
+	/* Resume the tracee */
+	ptrace(PTRACE_CONT, pid, 0, 0);
+	if (waitpid(pid, &status, 0) != pid)
+		perr_and_exit("failed to wait for the tracee\n");
+
+	*result = WEXITSTATUS(status);
+}
+
+TEST(ptrace_modify_a0)
+{
+	int result;
+
+	ptrace_restart_syscall(MODIFY_A0, &result);
+
+	/* The tracer's modification of a0 cannot affect the restarted tracee */
+	EXPECT_EQ(DEFAULT_A0, result);
+}
+
+TEST(ptrace_modify_orig_a0)
+{
+	int result;
+
+	ptrace_restart_syscall(MODIFY_ORIG_A0, &result);
+
+	/* The tracer must modify orig_a0 to actually change the tracee's a0 */
+	EXPECT_EQ(ORIG_A0_AFTER_MODIFIED, result);
+}
+
+TEST_HARNESS_MAIN
-- 
2.44.0

> 
> The current test may not intuitively reflect the issue. If possible, I will
> provide a more comprehensive test based on everyone's suggestions.
> 
> Thanks,
> Quan
> 
> > > 
> > > Compared to the test program, a more common scenario is the use of the
> > > exece syscall, which sends a signal in the kernel path to restart
> > > the syscall.
> > > 
> > > Signed-off-by: Quan Zhou <zhouquan@xxxxxxxxxxx>
> > > ---
> > >   tools/testing/selftests/riscv/Makefile        |   2 +-
> > >   tools/testing/selftests/riscv/abi/.gitignore  |   1 +
> > >   tools/testing/selftests/riscv/abi/Makefile    |  12 ++
> > >   .../riscv/abi/ptrace_restart_syscall.c        | 148 ++++++++++++++++++
> > >   4 files changed, 162 insertions(+), 1 deletion(-)
> > >   create mode 100644 tools/testing/selftests/riscv/abi/.gitignore
> > >   create mode 100644 tools/testing/selftests/riscv/abi/Makefile
> > >   create mode 100644 tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c
> > > 
> > > diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile
> > > index 7ce03d832b64..98541dc2f164 100644
> > > --- a/tools/testing/selftests/riscv/Makefile
> > > +++ b/tools/testing/selftests/riscv/Makefile
> > > @@ -5,7 +5,7 @@
> > >   ARCH ?= $(shell uname -m 2>/dev/null || echo not)
> > >   ifneq (,$(filter $(ARCH),riscv))
> > > -RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn
> > > +RISCV_SUBTARGETS ?= hwprobe vector mm sigreturn abi
> > >   else
> > >   RISCV_SUBTARGETS :=
> > >   endif
> > > diff --git a/tools/testing/selftests/riscv/abi/.gitignore b/tools/testing/selftests/riscv/abi/.gitignore
> > > new file mode 100644
> > > index 000000000000..e1e00ffb9db9
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/riscv/abi/.gitignore
> > > @@ -0,0 +1 @@
> > > +abi
> > 
> > The gitignore should contain a list of all of the generated binaries
> > that should be ignored. Can you put ptrace_restart_syscall in here
> > instead of abi?
> > 
> 
> ...yeah, I will fix it later.
> 
> > > diff --git a/tools/testing/selftests/riscv/abi/Makefile b/tools/testing/selftests/riscv/abi/Makefile
> > > new file mode 100644
> > > index 000000000000..634fa7de74e6
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/riscv/abi/Makefile
> > > @@ -0,0 +1,12 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +# Copyright (C) 2021 ARM Limited
> > > +# Originally tools/testing/arm64/abi/Makefile
> > > +
> > > +CFLAGS += -I$(top_srcdir)/tools/include
> > > +
> > > +TEST_GEN_PROGS := ptrace_restart_syscall
> > > +
> > > +include ../../lib.mk
> > > +
> > > +$(OUTPUT)/ptrace_restart_syscall: ptrace_restart_syscall.c
> > > +	$(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
> > > diff --git a/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c b/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c
> > > new file mode 100644
> > > index 000000000000..3e25548cb95e
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/riscv/abi/ptrace_restart_syscall.c
> > > @@ -0,0 +1,148 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +#include <unistd.h>
> > > +#include <fcntl.h>
> > > +#include <signal.h>
> > > +#include <errno.h>
> > > +#include <sys/types.h>
> > > +#include <sys/ptrace.h>
> > > +#include <sys/stat.h>
> > > +#include <sys/user.h>
> > > +#include <sys/wait.h>
> > > +#include <sys/uio.h>
> > > +#include <linux/elf.h>
> > > +#include <linux/unistd.h>
> > > +#include <asm/ptrace.h>
> > > +
> > > +#include "../../kselftest_harness.h"
> > > +
> > > +#define ORIG_A0_AFTER_MODIFIED  0x5
> > > +#define MODIFY_A0               0x01
> > > +#define MODIFY_ORIG_A0          0x02
> > > +
> > > +#define perr_and_exit(fmt, ...) do {			\
> > > +	char buf[256];					\
> > > +	snprintf(buf, sizeof(buf), "%s:%d: " fmt ": %m\n",	\
> > > +			__func__, __LINE__, ##__VA_ARGS__);	\
> > > +	perror(buf);						\
> > > +	exit(-1);						\
> > > +} while (0)
> > > +
> > > +static inline void resume_and_wait_tracee(pid_t pid, int flag)
> > > +{
> > > +	int status;
> > > +
> > > +	if (ptrace(flag, pid, 0, 0))
> > > +		perr_and_exit("failed to resume the tracee %d", pid);
> > > +
> > > +	if (waitpid(pid, &status, 0) != pid)
> > > +		perr_and_exit("failed to wait for the tracee %d", pid);
> > > +}
> > > +
> > > +static void ptrace_restart_syscall(int opt, int *result)
> > > +{
> > > +	int status;
> > > +	int p[2], fd_zero;
> > > +	pid_t pid;
> > > +
> > > +	struct user_regs_struct regs;
> > > +	struct iovec iov = {
> > > +		.iov_base = &regs,
> > > +		.iov_len = sizeof(regs),
> > > +	};
> > > +
> > > +	if (pipe(p))
> > > +		perr_and_exit("failed to create a pipe");
> > > +
> > > +	fd_zero = open("/dev/zero", O_RDONLY);
> > > +	if (fd_zero < 0)
> > > +		perr_and_exit("failed to open /dev/zero");
> > > +
> > > +	pid = fork();
> > > +	if (pid == 0) {
> > > +		char c;
> > > +
> > > +		/* Mark oneself being traced */
> > > +		if (ptrace(PTRACE_TRACEME, 0, 0, 0))
> > > +			perr_and_exit("failed to request for tracer to trace me");
> > > +
> > > +		kill(getpid(), SIGSTOP);
> > > +
> > > +		if (read(p[0], &c, 1) != 1)
> > > +			exit(1);
> > > +
> > > +		exit(0);
> > > +	} else if (pid < 0)
> > > +		exit(1);
> > > +
> > > +	if (waitpid(pid, &status, 0) != pid)
> > > +		perr_and_exit("failed to wait for the tracee %d\n", pid);
> > > +
> > > +	/* Resume the tracee until the next syscall */
> > > +	resume_and_wait_tracee(pid, PTRACE_SYSCALL);
> > > +
> > > +	/* Deliver a signal to interrupt the syscall */
> > > +	kill(pid, SIGUSR1);
> > > +
> > > +	/* The tracee stops at syscall exit */
> > > +	resume_and_wait_tracee(pid, PTRACE_SYSCALL);
> > > +
> > > +	/* Check tracee orig_a0 before syscall restart */
> > > +	if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov))
> > > +		perr_and_exit("failed to get tracee registers");
> > > +	if (regs.orig_a0 != p[0])
> > > +		perr_and_exit("unexpected a0");
> > > +
> > > +	/* Modify a0/orig_a0 for the restarted syscall */
> > > +	switch (opt) {
> > > +	case MODIFY_A0:
> > > +		regs.a0 = fd_zero;
> > > +		break;
> > > +	case MODIFY_ORIG_A0:
> > > +		regs.orig_a0 = fd_zero;
> > > +		break;
> > > +	}
> > > +
> > > +	if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov))
> > > +		perr_and_exit("failed to set tracee registers");
> > > +
> > > +	/* Ignore SIGUSR1 signal */
> > > +	resume_and_wait_tracee(pid, PTRACE_SYSCALL);
> > > +
> > > +	/* Stop at the entry point of the restarted syscall */
> > > +	resume_and_wait_tracee(pid, PTRACE_SYSCALL);
> > > +
> > > +	/* Now, check regs.a0 of the restarted syscall */
> > > +	if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov))
> > > +		perr_and_exit("failed to get tracee registers");
> > > +	*result = regs.a0;
> > > +
> > > +	/* Resume the tracee */
> > > +	ptrace(PTRACE_CONT, pid, 0, 0);
> > > +	if (waitpid(pid, &status, 0) != pid)
> > > +		perr_and_exit("failed to wait for the tracee");
> > > +}
> > > +
> > > +TEST(ptrace_modify_a0)
> > > +{
> > > +	int result;
> > > +
> > > +	ptrace_restart_syscall(MODIFY_A0, &result);
> > > +
> > > +	/* The tracer's modification of a0 cannot affect the restarted tracee */
> > > +	EXPECT_NE(ORIG_A0_AFTER_MODIFIED, result);
> > > +}
> > > +
> > > +TEST(ptrace_modify_orig_a0)
> > > +{
> > > +	int result;
> > > +
> > > +	ptrace_restart_syscall(MODIFY_ORIG_A0, &result);
> > > +
> > > +	/* The tracer must modify orig_a0 to actually change the tracee's a0 */
> > > +	EXPECT_EQ(ORIG_A0_AFTER_MODIFIED, result);
> > 
> > How does the value end up being 5?
> > 
> > - Charlie
> > 
> 
> The tracer ultimately sets `fd_zero` to the restarted syscall.
> 
> Since 0, 1, and 2 are standard input, output, and error, the file
> descriptors will be allocated in this order: `p[0] -> p[1] -> fd_zero`.
> Thus, fd_zero will be 5.
> 
> > > +}
> > > +
> > > +TEST_HARNESS_MAIN
> > > -- 
> > > 2.34.1
> > > 
> 




[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Share Photos]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux