summaryrefslogtreecommitdiff
path: root/src/ptrace_syscall_info.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ptrace_syscall_info.c')
-rw-r--r--src/ptrace_syscall_info.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/ptrace_syscall_info.c b/src/ptrace_syscall_info.c
new file mode 100644
index 000000000..e4085a9cf
--- /dev/null
+++ b/src/ptrace_syscall_info.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2018 Dmitry V. Levin <ldv@strace.io>
+ * Copyright (c) 2018-2020 The strace developers.
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "defs.h"
+#include "kill_save_errno.h"
+#include "ptrace.h"
+#include "ptrace_syscall_info.h"
+#include "scno.h"
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "xlat/ptrace_syscall_info_op.h"
+
+bool ptrace_get_syscall_info_supported;
+
+#define FAIL do { ptrace_stop = -1U; goto done; } while (0)
+
+#ifdef HAVE_FORK
+static int
+kill_tracee(pid_t pid)
+{
+ return kill_save_errno(pid, SIGKILL);
+}
+
+static const unsigned int expected_none_size =
+ offsetof(struct_ptrace_syscall_info, entry);
+static const unsigned int expected_entry_size =
+ offsetofend(struct_ptrace_syscall_info, entry.args);
+#endif /* HAVE_FORK */
+static const unsigned int expected_exit_size =
+ offsetofend(struct_ptrace_syscall_info, exit.is_error);
+static const unsigned int expected_seccomp_size =
+ offsetofend(struct_ptrace_syscall_info, seccomp.ret_data);
+
+/*
+ * Test that PTRACE_GET_SYSCALL_INFO API is supported by the kernel, and
+ * that the semantics implemented in the kernel matches our expectations.
+ */
+bool
+test_ptrace_get_syscall_info(void)
+{
+ /*
+ * NOMMU provides no forks necessary for PTRACE_GET_SYSCALL_INFO test,
+ * leave the default unchanged.
+ */
+#ifdef HAVE_FORK
+ 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;
+
+# if SIZEOF_KERNEL_LONG_T > SIZEOF_LONG
+# define CAST (unsigned long)
+# else
+# define CAST
+# endif
+
+ int pid = fork();
+ if (pid < 0)
+ perror_func_msg_and_die("fork");
+
+ if (pid == 0) {
+ /* get the pid before PTRACE_TRACEME */
+ pid = getpid();
+ if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
+ /* exit with a nonzero exit status */
+ perror_func_msg_and_die("PTRACE_TRACEME");
+ }
+ kill(pid, SIGSTOP);
+ 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);
+ int status;
+ long rc = waitpid(pid, &status, 0);
+ if (rc != pid) {
+ /* cannot happen */
+ kill_tracee(pid);
+ perror_func_msg_and_die("#%d: unexpected wait result"
+ " %ld", ptrace_stop, rc);
+ }
+ if (WIFEXITED(status)) {
+ /* tracee is no more */
+ pid = 0;
+ if (WEXITSTATUS(status) == 0)
+ break;
+ debug_func_msg("#%d: unexpected exit status %u",
+ ptrace_stop, WEXITSTATUS(status));
+ FAIL;
+ }
+ if (WIFSIGNALED(status)) {
+ /* tracee is no more */
+ pid = 0;
+ debug_func_msg("#%d: unexpected signal %u",
+ ptrace_stop, WTERMSIG(status));
+ FAIL;
+ }
+ if (!WIFSTOPPED(status)) {
+ /* cannot happen */
+ kill_tracee(pid);
+ error_func_msg_and_die("#%d: unexpected wait status"
+ " %#x", ptrace_stop, status);
+ }
+
+ switch (WSTOPSIG(status)) {
+ case SIGSTOP:
+ if (ptrace_stop) {
+ debug_func_msg("#%d: unexpected signal stop",
+ ptrace_stop);
+ FAIL;
+ }
+ if (ptrace(PTRACE_SETOPTIONS, pid, 0L,
+ PTRACE_O_TRACESYSGOOD) < 0) {
+ /* cannot happen */
+ kill_tracee(pid);
+ perror_func_msg_and_die("PTRACE_SETOPTIONS");
+ }
+ rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
+ (void *) size, &info);
+ if (rc < 0) {
+ debug_perror_msg("PTRACE_GET_SYSCALL_INFO");
+ FAIL;
+ }
+ if (rc < (long) expected_none_size
+ || info.op != PTRACE_SYSCALL_INFO_NONE
+ || !info.arch
+ || !info.instruction_pointer
+ || !info.stack_pointer) {
+ debug_func_msg("signal stop mismatch");
+ FAIL;
+ }
+ break;
+
+ case SIGTRAP | 0x80:
+ rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
+ (void *) size, &info);
+ if (rc < 0) {
+ debug_perror_msg("#%d: PTRACE_GET_SYSCALL_INFO",
+ ptrace_stop);
+ FAIL;
+ }
+ switch (ptrace_stop) {
+ case 1: /* entering chdir */
+ case 3: /* entering gettid */
+ case 5: /* entering exit_group */
+ exp_args = args[ptrace_stop / 2];
+ if (rc < (long) expected_entry_size
+ || info.op != PTRACE_SYSCALL_INFO_ENTRY
+ || !info.arch
+ || !info.instruction_pointer
+ || !info.stack_pointer
+ || (info.entry.nr != exp_args[0])
+ || (CAST info.entry.args[0] != exp_args[1])
+ || (CAST info.entry.args[1] != exp_args[2])
+ || (CAST info.entry.args[2] != exp_args[3])
+ || (CAST info.entry.args[3] != exp_args[4])
+ || (CAST info.entry.args[4] != exp_args[5])
+ || (CAST info.entry.args[5] != exp_args[6])) {
+ debug_func_msg("#%d: entry stop"
+ " mismatch",
+ ptrace_stop);
+ FAIL;
+ }
+ break;
+ case 2: /* exiting chdir */
+ case 4: /* exiting gettid */
+ exp_param = &exit_param[ptrace_stop / 2 - 1];
+ if (rc < (long) expected_exit_size
+ || info.op != PTRACE_SYSCALL_INFO_EXIT
+ || !info.arch
+ || !info.instruction_pointer
+ || !info.stack_pointer
+ || info.exit.is_error != exp_param->is_error
+ || info.exit.rval != exp_param->rval) {
+ debug_func_msg("#%d: exit stop"
+ " mismatch",
+ ptrace_stop);
+ FAIL;
+ }
+ break;
+ default:
+ debug_func_msg("#%d: unexpected syscall stop",
+ ptrace_stop);
+ FAIL;
+ }
+ break;
+
+ default:
+ debug_func_msg("#%d: unexpected stop signal %#x",
+ ptrace_stop, WSTOPSIG(status));
+ FAIL;
+ }
+
+ if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
+ /* cannot happen */
+ kill_tracee(pid);
+ perror_func_msg_and_die("PTRACE_SYSCALL");
+ }
+ }
+
+done:
+ if (pid) {
+ kill_tracee(pid);
+ waitpid(pid, NULL, 0);
+ ptrace_stop = -1U;
+ }
+
+ ptrace_get_syscall_info_supported =
+ ptrace_stop == ARRAY_SIZE(args) * 2;
+
+ if (ptrace_get_syscall_info_supported)
+ debug_msg("PTRACE_GET_SYSCALL_INFO works");
+ else
+ debug_msg("PTRACE_GET_SYSCALL_INFO does not work");
+#endif /* HAVE_FORK */
+
+ return ptrace_get_syscall_info_supported;
+}
+
+static void
+print_psi_entry(const typeof_field(struct_ptrace_syscall_info, entry) *const p,
+ const kernel_ulong_t fetch_size, struct tcb *const tcp)
+{
+ tprint_struct_begin();
+ PRINT_FIELD_U(*p, nr);
+ const kernel_ulong_t nargs =
+ (fetch_size - offsetof(struct_ptrace_syscall_info, entry.args))
+ / sizeof(p->args[0]);
+ if (nargs) {
+ tprint_struct_next();
+ PRINT_FIELD_ARRAY_UPTO(*p, args, nargs, tcp,
+ print_xint64_array_member);
+ }
+ tprint_struct_end();
+}
+
+static void
+print_psi_seccomp(const typeof_field(struct_ptrace_syscall_info, seccomp) *const p,
+ const kernel_ulong_t fetch_size, struct tcb *const tcp)
+{
+ tprint_struct_begin();
+ PRINT_FIELD_U(*p, nr);
+ const kernel_ulong_t nargs =
+ (fetch_size - offsetof(struct_ptrace_syscall_info, seccomp.args))
+ / sizeof(p->args[0]);
+ if (nargs) {
+ tprint_struct_next();
+ PRINT_FIELD_ARRAY_UPTO(*p, args, nargs, tcp,
+ print_xint64_array_member);
+ }
+ if (fetch_size >= expected_seccomp_size) {
+ tprint_struct_next();
+ PRINT_FIELD_U(*p, ret_data);
+ }
+ tprint_struct_end();
+}
+
+static void
+print_psi_exit(const typeof_field(struct_ptrace_syscall_info, exit) *const p,
+ const kernel_ulong_t fetch_size, struct tcb *const tcp)
+{
+ tprint_struct_begin();
+ if (fetch_size >= expected_exit_size && p->is_error) {
+ PRINT_FIELD_ERR_D(*p, rval);
+ } else {
+ PRINT_FIELD_D(*p, rval);
+ }
+ if (fetch_size >= expected_exit_size) {
+ tprint_struct_next();
+ PRINT_FIELD_U(*p, is_error);
+ }
+ tprint_struct_end();
+}
+
+void
+print_ptrace_syscall_info(struct tcb *tcp, kernel_ulong_t addr,
+ kernel_ulong_t user_len)
+{
+ struct_ptrace_syscall_info info;
+ kernel_ulong_t kernel_len = tcp->u_rval;
+ kernel_ulong_t ret_len = MIN(user_len, kernel_len);
+ kernel_ulong_t fetch_size = MIN(ret_len, expected_seccomp_size);
+
+ if (!fetch_size || !tfetch_mem(tcp, addr, fetch_size, &info)) {
+ printaddr(addr);
+ return;
+ }
+
+ tprint_struct_begin();
+ PRINT_FIELD_XVAL(info, op, ptrace_syscall_info_op,
+ "PTRACE_SYSCALL_INFO_???");
+ if (fetch_size < offsetofend(struct_ptrace_syscall_info, arch))
+ goto printed;
+ tprint_struct_next();
+ PRINT_FIELD_XVAL(info, arch, audit_arch, "AUDIT_ARCH_???");
+
+ if (fetch_size < offsetofend(struct_ptrace_syscall_info,
+ instruction_pointer))
+ goto printed;
+ tprint_struct_next();
+ PRINT_FIELD_ADDR64(info, instruction_pointer);
+
+ if (fetch_size < offsetofend(struct_ptrace_syscall_info, stack_pointer))
+ goto printed;
+ tprint_struct_next();
+ PRINT_FIELD_ADDR64(info, stack_pointer);
+
+ if (fetch_size < offsetofend(struct_ptrace_syscall_info, entry.nr))
+ goto printed;
+
+ switch(info.op) {
+ case PTRACE_SYSCALL_INFO_ENTRY:
+ tprint_struct_next();
+ PRINT_FIELD_OBJ_PTR(info, entry,
+ print_psi_entry, fetch_size, tcp);
+ break;
+ case PTRACE_SYSCALL_INFO_SECCOMP:
+ tprint_struct_next();
+ PRINT_FIELD_OBJ_PTR(info, seccomp,
+ print_psi_seccomp, fetch_size, tcp);
+ break;
+ case PTRACE_SYSCALL_INFO_EXIT:
+ tprint_struct_next();
+ PRINT_FIELD_OBJ_PTR(info, exit,
+ print_psi_exit, fetch_size, tcp);
+ break;
+ }
+
+printed:
+ tprint_struct_end();
+}