selftests/ptrace: add a test case for PTRACE_GET_SYSCALL_INFO
authorDmitry V. Levin <ldv@altlinux.org>
Tue, 16 Jul 2019 23:29:46 +0000 (16:29 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 17 Jul 2019 02:23:24 +0000 (19:23 -0700)
Check whether PTRACE_GET_SYSCALL_INFO semantics implemented in the
kernel matches userspace expectations.

[akpm@linux-foundation.org: coding-style fixes]
Link: http://lkml.kernel.org/r/20190510152852.GG28558@altlinux.org
Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
Acked-by: Shuah Khan <shuah@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Elvira Khabirova <lineprinter@altlinux.org>
Cc: Eugene Syromyatnikov <esyr@redhat.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Greentime Hu <greentime@andestech.com>
Cc: Helge Deller <deller@gmx.de> [parisc]
Cc: James E.J. Bottomley <jejb@parisc-linux.org>
Cc: James Hogan <jhogan@kernel.org>
Cc: kbuild test robot <lkp@intel.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Paul Burton <paul.burton@mips.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Richard Kuo <rkuo@codeaurora.org>
Cc: Vincent Chen <deanbo422@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
tools/testing/selftests/ptrace/.gitignore
tools/testing/selftests/ptrace/Makefile
tools/testing/selftests/ptrace/get_syscall_info.c [new file with mode: 0644]

index b3e59d41fd827050f76f7b84533b427d140e4319..cfcc49a7def7b215d195672dd7e59fe4a4f5846b 100644 (file)
@@ -1 +1,2 @@
+get_syscall_info
 peeksiginfo
index cb21c76a18ca88e4514907c5bfa357cb25c769c5..c0b7f89f09300043b31900ac870d0062e30bfeab 100644 (file)
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 CFLAGS += -iquote../../../../include/uapi -Wall
 
-TEST_GEN_PROGS := peeksiginfo
+TEST_GEN_PROGS := get_syscall_info peeksiginfo
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ptrace/get_syscall_info.c b/tools/testing/selftests/ptrace/get_syscall_info.c
new file mode 100644 (file)
index 0000000..5bcd1c7
--- /dev/null
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * Check whether PTRACE_GET_SYSCALL_INFO semantics implemented in the kernel
+ * matches userspace expectations.
+ */
+
+#include "../kselftest_harness.h"
+#include <err.h>
+#include <signal.h>
+#include <asm/unistd.h>
+#include "linux/ptrace.h"
+
+static int
+kill_tracee(pid_t pid)
+{
+       if (!pid)
+               return 0;
+
+       int saved_errno = errno;
+
+       int rc = kill(pid, SIGKILL);
+
+       errno = saved_errno;
+       return rc;
+}
+
+static long
+sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data)
+{
+       return syscall(__NR_ptrace, request, pid, addr, data);
+}
+
+#define LOG_KILL_TRACEE(fmt, ...)                              \
+       do {                                                    \
+               kill_tracee(pid);                               \
+               TH_LOG("wait #%d: " fmt,                        \
+                      ptrace_stop, ##__VA_ARGS__);             \
+       } while (0)
+
+TEST(get_syscall_info)
+{
+       static const unsigned long args[][7] = {
+               /* a sequence of architecture-agnostic syscalls */
+               {
+                       __NR_chdir,
+                       (unsigned long) "",
+                       0xbad1fed1,
+                       0xbad2fed2,
+                       0xbad3fed3,
+                       0xbad4fed4,
+                       0xbad5fed5
+               },
+               {
+                       __NR_gettid,
+                       0xcaf0bea0,
+                       0xcaf1bea1,
+                       0xcaf2bea2,
+                       0xcaf3bea3,
+                       0xcaf4bea4,
+                       0xcaf5bea5
+               },
+               {
+                       __NR_exit_group,
+                       0,
+                       0xfac1c0d1,
+                       0xfac2c0d2,
+                       0xfac3c0d3,
+                       0xfac4c0d4,
+                       0xfac5c0d5
+               }
+       };
+       const unsigned long *exp_args;
+
+       pid_t pid = fork();
+
+       ASSERT_LE(0, pid) {
+               TH_LOG("fork: %m");
+       }
+
+       if (pid == 0) {
+               /* get the pid before PTRACE_TRACEME */
+               pid = getpid();
+               ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) {
+                       TH_LOG("PTRACE_TRACEME: %m");
+               }
+               ASSERT_EQ(0, kill(pid, SIGSTOP)) {
+                       /* cannot happen */
+                       TH_LOG("kill SIGSTOP: %m");
+               }
+               for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) {
+                       syscall(args[i][0],
+                               args[i][1], args[i][2], args[i][3],
+                               args[i][4], args[i][5], args[i][6]);
+               }
+               /* unreachable */
+               _exit(1);
+       }
+
+       const struct {
+               unsigned int is_error;
+               int rval;
+       } *exp_param, exit_param[] = {
+               { 1, -ENOENT }, /* chdir */
+               { 0, pid }      /* gettid */
+       };
+
+       unsigned int ptrace_stop;
+
+       for (ptrace_stop = 0; ; ++ptrace_stop) {
+               struct ptrace_syscall_info info = {
+                       .op = 0xff      /* invalid PTRACE_SYSCALL_INFO_* op */
+               };
+               const size_t size = sizeof(info);
+               const int expected_none_size =
+                       (void *) &info.entry - (void *) &info;
+               const int expected_entry_size =
+                       (void *) &info.entry.args[6] - (void *) &info;
+               const int expected_exit_size =
+                       (void *) (&info.exit.is_error + 1) -
+                       (void *) &info;
+               int status;
+               long rc;
+
+               ASSERT_EQ(pid, wait(&status)) {
+                       /* cannot happen */
+                       LOG_KILL_TRACEE("wait: %m");
+               }
+               if (WIFEXITED(status)) {
+                       pid = 0;        /* the tracee is no more */
+                       ASSERT_EQ(0, WEXITSTATUS(status));
+                       break;
+               }
+               ASSERT_FALSE(WIFSIGNALED(status)) {
+                       pid = 0;        /* the tracee is no more */
+                       LOG_KILL_TRACEE("unexpected signal %u",
+                                       WTERMSIG(status));
+               }
+               ASSERT_TRUE(WIFSTOPPED(status)) {
+                       /* cannot happen */
+                       LOG_KILL_TRACEE("unexpected wait status %#x", status);
+               }
+
+               switch (WSTOPSIG(status)) {
+               case SIGSTOP:
+                       ASSERT_EQ(0, ptrace_stop) {
+                               LOG_KILL_TRACEE("unexpected signal stop");
+                       }
+                       ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, pid, 0,
+                                               PTRACE_O_TRACESYSGOOD)) {
+                               LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m");
+                       }
+                       ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
+                                                     pid, size,
+                                                     (unsigned long) &info))) {
+                               LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
+                       }
+                       ASSERT_EQ(expected_none_size, rc) {
+                               LOG_KILL_TRACEE("signal stop mismatch");
+                       }
+                       ASSERT_EQ(PTRACE_SYSCALL_INFO_NONE, info.op) {
+                               LOG_KILL_TRACEE("signal stop mismatch");
+                       }
+                       ASSERT_TRUE(info.arch) {
+                               LOG_KILL_TRACEE("signal stop mismatch");
+                       }
+                       ASSERT_TRUE(info.instruction_pointer) {
+                               LOG_KILL_TRACEE("signal stop mismatch");
+                       }
+                       ASSERT_TRUE(info.stack_pointer) {
+                               LOG_KILL_TRACEE("signal stop mismatch");
+                       }
+                       break;
+
+               case SIGTRAP | 0x80:
+                       ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
+                                                     pid, size,
+                                                     (unsigned long) &info))) {
+                               LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
+                       }
+                       switch (ptrace_stop) {
+                       case 1: /* entering chdir */
+                       case 3: /* entering gettid */
+                       case 5: /* entering exit_group */
+                               exp_args = args[ptrace_stop / 2];
+                               ASSERT_EQ(expected_entry_size, rc) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info.op) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_TRUE(info.arch) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_TRUE(info.instruction_pointer) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_TRUE(info.stack_pointer) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[0], info.entry.nr) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[1], info.entry.args[0]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[2], info.entry.args[1]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[3], info.entry.args[2]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[4], info.entry.args[3]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[5], info.entry.args[4]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               ASSERT_EQ(exp_args[6], info.entry.args[5]) {
+                                       LOG_KILL_TRACEE("entry stop mismatch");
+                               }
+                               break;
+                       case 2: /* exiting chdir */
+                       case 4: /* exiting gettid */
+                               exp_param = &exit_param[ptrace_stop / 2 - 1];
+                               ASSERT_EQ(expected_exit_size, rc) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info.op) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_TRUE(info.arch) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_TRUE(info.instruction_pointer) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_TRUE(info.stack_pointer) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_EQ(exp_param->is_error,
+                                         info.exit.is_error) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               ASSERT_EQ(exp_param->rval, info.exit.rval) {
+                                       LOG_KILL_TRACEE("exit stop mismatch");
+                               }
+                               break;
+                       default:
+                               LOG_KILL_TRACEE("unexpected syscall stop");
+                               abort();
+                       }
+                       break;
+
+               default:
+                       LOG_KILL_TRACEE("unexpected stop signal %#x",
+                                       WSTOPSIG(status));
+                       abort();
+               }
+
+               ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, pid, 0, 0)) {
+                       LOG_KILL_TRACEE("PTRACE_SYSCALL: %m");
+               }
+       }
+
+       ASSERT_EQ(ARRAY_SIZE(args) * 2, ptrace_stop);
+}
+
+TEST_HARNESS_MAIN