#include "defs.h"
#include "kill_save_errno.h"
+#include "print_fields.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;
static int
offsetofend(struct ptrace_syscall_info, entry.args);
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
return ptrace_get_syscall_info_supported;
}
+
+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;
+ }
+
+ PRINT_FIELD_XVAL_INDEX("{", info, op, ptrace_syscall_info_op,
+ "PTRACE_SYSCALL_INFO_???");
+ if (fetch_size < offsetofend(struct ptrace_syscall_info, arch))
+ goto printed;
+ PRINT_FIELD_XVAL(", ", info, arch, audit_arch, "AUDIT_ARCH_???");
+
+ if (fetch_size < offsetofend(struct ptrace_syscall_info,
+ instruction_pointer))
+ goto printed;
+ PRINT_FIELD_ADDR64(", ", info, instruction_pointer);
+
+ if (fetch_size < offsetofend(struct ptrace_syscall_info, stack_pointer))
+ goto printed;
+ 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:
+ case PTRACE_SYSCALL_INFO_SECCOMP:
+ PRINT_FIELD_U((info.op == PTRACE_SYSCALL_INFO_ENTRY
+ ? ", entry={" : ", seccomp={"),
+ info.entry, nr);
+ for (unsigned int i = 0;
+ i < ARRAY_SIZE(info.entry.args); ++i) {
+ const unsigned int i_size =
+ offsetofend(struct ptrace_syscall_info,
+ entry.args[i]);
+ if (fetch_size < i_size) {
+ if (i)
+ break;
+ goto entry_printed;
+ }
+ tprintf(", %s%#" PRIx64,
+ (i ? "" : "arg=["),
+ (uint64_t) info.entry.args[i]);
+ }
+ tprints("]");
+ if (info.op == PTRACE_SYSCALL_INFO_SECCOMP
+ && fetch_size >= expected_seccomp_size)
+ PRINT_FIELD_U(", ", info.seccomp, ret_data);
+entry_printed:
+ tprints("}");
+ break;
+ case PTRACE_SYSCALL_INFO_EXIT:
+ tprints(", exit={");
+ if (fetch_size >= expected_exit_size
+ && info.exit.is_error) {
+ uint64_t err = -info.exit.rval;
+
+ tprints("rval=-");
+ print_xlat_ex(err, err_name(err),
+ XLAT_STYLE_FMT_U);
+ } else {
+ PRINT_FIELD_D("", info.exit, rval);
+ }
+ if (fetch_size >= expected_exit_size)
+ PRINT_FIELD_U(", ", info.exit, is_error);
+ tprints("}");
+ break;
+ }
+
+printed:
+ tprints("}");
+}
--- /dev/null
+/*
+ * Check decoding of ptrace PTRACE_GET_SYSCALL_INFO request.
+ *
+ * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "tests.h"
+
+#include "ptrace.h"
+#include <asm/unistd.h>
+#include "scno.h"
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/audit.h>
+
+#include "xlat.h"
+#include "xlat/audit_arch.h"
+
+static const char *errstr;
+
+static long
+do_ptrace(unsigned long request, unsigned long pid,
+ unsigned long addr, unsigned long data)
+{
+ long rc = syscall(__NR_ptrace, request, pid, addr, data);
+ errstr = sprintrc(rc);
+ return rc;
+}
+
+static pid_t pid;
+
+static void
+kill_tracee(void)
+{
+ if (!pid)
+ return;
+ int saved_errno = errno;
+ kill(pid, SIGKILL);
+ errno = saved_errno;
+}
+
+#define FAIL(fmt_, ...) \
+ do { \
+ kill_tracee(); \
+ error_msg_and_fail("%s:%d: " fmt_, \
+ __FILE__, __LINE__, ##__VA_ARGS__); \
+ } while (0)
+
+#define PFAIL(fmt_, ...) \
+ do { \
+ kill_tracee(); \
+ perror_msg_and_fail("%s:%d: " fmt_, \
+ __FILE__, __LINE__, ##__VA_ARGS__); \
+ } while (0)
+
+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
+ }
+};
+
+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);
+static const unsigned int expected_exit_size =
+ offsetofend(struct ptrace_syscall_info, exit.is_error);
+
+static unsigned long end_of_page;
+static unsigned int ptrace_stop;
+
+static bool
+test_none(void)
+{
+ do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 1, 0);
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 1, NULL) = %s\n",
+ pid, errstr);
+
+ do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 1, end_of_page);
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 1, %#lx) = %s\n",
+ pid, end_of_page, errstr);
+
+ for (unsigned int size = 0;
+ size <= sizeof(struct ptrace_syscall_info); ++size) {
+ unsigned long buf = end_of_page - size;
+ memset((void *) buf, -1, size);
+
+ long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
+ if (rc < 0) {
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, %#lx)"
+ " = %s\n",
+ pid, (unsigned int) size, buf, errstr);
+ return false;
+ }
+ if (rc < (long) expected_none_size)
+ FAIL("signal stop mismatch");
+
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
+ pid, size);
+ if (!size) {
+ printf("%#lx) = %s\n", buf, errstr);
+ continue;
+ }
+
+ /* copy to a local structure to avoid unaligned access */
+ struct ptrace_syscall_info info;
+ memcpy(&info, (void *) buf, MIN(size, expected_none_size));
+
+ if (info.op != PTRACE_SYSCALL_INFO_NONE)
+ FAIL("signal stop mismatch");
+ printf("{op=PTRACE_SYSCALL_INFO_NONE");
+
+ if (size < offsetofend(struct ptrace_syscall_info, arch))
+ goto printed_none;
+ if (!info.arch)
+ FAIL("signal stop mismatch");
+ printf(", arch=");
+ printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ instruction_pointer))
+ goto printed_none;
+ if (!info.instruction_pointer)
+ FAIL("signal stop mismatch");
+ printf(", instruction_pointer=%#llx",
+ (unsigned long long) info.instruction_pointer);
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ stack_pointer))
+ goto printed_none;
+ if (!info.stack_pointer)
+ FAIL("signal stop mismatch");
+ printf(", stack_pointer=%#llx",
+ (unsigned long long) info.stack_pointer);
+
+printed_none:
+ printf("}) = %s\n", errstr);
+ }
+
+ return true;
+}
+
+static void
+test_entry(void)
+{
+ for (unsigned int size = 0;
+ size <= sizeof(struct ptrace_syscall_info); ++size) {
+ unsigned long buf = end_of_page - size;
+ memset((void *) buf, -1, size);
+
+ long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
+ if (rc < 0)
+ PFAIL("PTRACE_GET_SYSCALL_INFO");
+
+ if (rc < (long) expected_entry_size)
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
+ pid, size);
+ if (!size) {
+ printf("%#lx) = %s\n", buf, errstr);
+ continue;
+ }
+
+ /* copy to a local structure to avoid unaligned access */
+ struct ptrace_syscall_info info;
+ memcpy(&info, (void *) buf, MIN(size, expected_entry_size));
+
+ if (info.op != PTRACE_SYSCALL_INFO_ENTRY)
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf("{op=PTRACE_SYSCALL_INFO_ENTRY");
+
+ if (size < offsetofend(struct ptrace_syscall_info, arch))
+ goto printed_entry_common;
+ if (!info.arch)
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf(", arch=");
+ printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ instruction_pointer))
+ goto printed_entry_common;
+ if (!info.instruction_pointer)
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf(", instruction_pointer=%#llx",
+ (unsigned long long) info.instruction_pointer);
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ stack_pointer))
+ goto printed_entry_common;
+ if (!info.stack_pointer)
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf(", stack_pointer=%#llx",
+ (unsigned long long) info.stack_pointer);
+
+ if (size < offsetofend(struct ptrace_syscall_info, entry.nr))
+ goto printed_entry_common;
+ const unsigned long *exp_args = args[ptrace_stop / 2];
+ if (info.entry.nr != exp_args[0])
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf(", entry={nr=%llu", (unsigned long long) info.entry.nr);
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(info.entry.args); ++i) {
+ const unsigned int i_size =
+ offsetofend(struct ptrace_syscall_info,
+ entry.args[i]);
+ if (size < i_size) {
+ if (i)
+ break;
+ goto printed_entry_nr;
+ }
+ if (info.entry.args[i] != exp_args[i + 1])
+ FAIL("#%d: entry stop mismatch", ptrace_stop);
+ printf("%s%#llx", (i ? ", " : ", arg=["),
+ (unsigned long long) info.entry.args[i]);
+ }
+ printf("]");
+
+printed_entry_nr:
+ printf("}");
+
+printed_entry_common:
+ printf("}) = %s\n", errstr);
+ }
+}
+
+static void
+test_exit(void)
+{
+ for (unsigned int size = 0;
+ size <= sizeof(struct ptrace_syscall_info); ++size) {
+ unsigned long buf = end_of_page - size;
+ memset((void *) buf, -1, size);
+
+ long rc = do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, buf);
+ if (rc < 0)
+ PFAIL("PTRACE_GET_SYSCALL_INFO");
+
+ if (rc < (long) expected_exit_size)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, %u, ",
+ pid, size);
+ if (!size) {
+ printf("%#lx) = %s\n", buf, errstr);
+ continue;
+ }
+
+ /* copy to a local structure to avoid unaligned access */
+ struct ptrace_syscall_info info;
+ memcpy(&info, (void *) buf, MIN(size, expected_exit_size));
+
+ if (info.op != PTRACE_SYSCALL_INFO_EXIT)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ printf("{op=PTRACE_SYSCALL_INFO_EXIT");
+
+ if (size < offsetofend(struct ptrace_syscall_info, arch))
+ goto printed_exit_common;
+ if (!info.arch)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ printf(", arch=");
+ printxval(audit_arch, info.arch, "AUDIT_ARCH_???");
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ instruction_pointer))
+ goto printed_exit_common;
+ if (!info.instruction_pointer)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ printf(", instruction_pointer=%#llx",
+ (unsigned long long) info.instruction_pointer);
+
+ if (size < offsetofend(struct ptrace_syscall_info,
+ stack_pointer))
+ goto printed_exit_common;
+ if (!info.stack_pointer)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ printf(", stack_pointer=%#llx",
+ (unsigned long long) info.stack_pointer);
+
+ const struct {
+ unsigned int is_error;
+ int rval;
+ const char *str;
+ } exit_param[] = {
+ { 1, -ENOENT, "-ENOENT" }, /* chdir */
+ { 0, pid, NULL } /* gettid */
+ }, *exp_param = &exit_param[ptrace_stop / 2 - 1];
+
+ if (size < offsetofend(struct ptrace_syscall_info, exit.rval))
+ goto printed_exit_common;
+ if (info.exit.rval != exp_param->rval)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ if (size >= expected_exit_size && info.exit.is_error) {
+ printf(", exit={rval=%s", exp_param->str);
+ } else {
+ printf(", exit={rval=%lld", (long long) info.exit.rval);
+ }
+
+ if (size >= expected_exit_size) {
+ if (info.exit.is_error != exp_param->is_error)
+ FAIL("#%d: exit stop mismatch", ptrace_stop);
+ printf(", is_error=%u",
+ (unsigned int) info.exit.is_error);
+ }
+
+ printf("}");
+
+printed_exit_common:
+ printf("}) = %s\n", errstr);
+ }
+}
+
+int
+main(void)
+{
+ end_of_page = (unsigned long) tail_alloc(1) + 1;
+
+ pid = getpid();
+ do_ptrace(PTRACE_GET_SYSCALL_INFO, pid, 0, 0);
+ printf("ptrace(PTRACE_GET_SYSCALL_INFO, %d, 0, NULL) = %s\n",
+ pid, errstr);
+
+ pid = fork();
+ if (pid < 0)
+ PFAIL("fork");
+
+ if (pid == 0) {
+ /* get the pid before PTRACE_TRACEME */
+ pid = getpid();
+ if (do_ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
+ /* exit with a nonzero exit status */
+ PFAIL("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);
+ }
+
+ for (ptrace_stop = 0; ; ++ptrace_stop) {
+ int status;
+ long rc = waitpid(pid, &status, 0);
+ if (rc != pid) {
+ /* cannot happen */
+ PFAIL("#%d: unexpected wait result %ld",
+ ptrace_stop, rc);
+ }
+ if (WIFEXITED(status)) {
+ /* tracee is no more */
+ pid = 0;
+ if (WEXITSTATUS(status) == 0)
+ break;
+ FAIL("#%d: unexpected exit status %u",
+ ptrace_stop, WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ /* tracee is no more */
+ pid = 0;
+ FAIL("#%d: unexpected signal %u",
+ ptrace_stop, WTERMSIG(status));
+ }
+ if (!WIFSTOPPED(status)) {
+ /* cannot happen */
+ FAIL("#%d: unexpected wait status %#x",
+ ptrace_stop, status);
+ }
+
+ switch (WSTOPSIG(status)) {
+ case SIGSTOP:
+ if (ptrace_stop)
+ FAIL("#%d: unexpected signal stop",
+ ptrace_stop);
+ if (do_ptrace(PTRACE_SETOPTIONS, pid, 0,
+ PTRACE_O_TRACESYSGOOD) < 0) {
+ /* cannot happen */
+ PFAIL("PTRACE_SETOPTIONS");
+ }
+ printf("ptrace(PTRACE_SETOPTIONS, %d, NULL"
+ ", PTRACE_O_TRACESYSGOOD) = 0\n", pid);
+
+ if (!test_none())
+ goto done;
+ break;
+
+ case SIGTRAP | 0x80:
+ switch (ptrace_stop) {
+ case 1: /* entering chdir */
+ case 3: /* entering gettid */
+ case 5: /* entering exit_group */
+ test_entry();
+ break;
+ case 2: /* exiting chdir */
+ case 4: /* exiting gettid */
+ test_exit();
+ break;
+ default:
+ FAIL("#%d: unexpected syscall stop",
+ ptrace_stop);
+ }
+ break;
+
+ default:
+ FAIL("#%d: unexpected stop signal %#x",
+ ptrace_stop, WSTOPSIG(status));
+ }
+
+ if (do_ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) {
+ /* cannot happen */
+ PFAIL("PTRACE_SYSCALL");
+ }
+ printf("ptrace(PTRACE_SYSCALL, %d, NULL, 0) = 0\n", pid);
+ }
+
+done:
+ if (pid) {
+ kill_tracee();
+ waitpid(pid, NULL, 0);
+ }
+
+ puts("+++ exited with 0 +++");
+ return 0;
+}