From 7273c6fb3c08fbcb25e452406e7f69d05d1a84e1 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Thu, 13 Dec 2018 22:07:26 +0000 Subject: [PATCH] Implement PTRACE_GET_SYSCALL_INFO decoder * defs.h (audit_arch): New prototype. * process.c: Include "ptrace_syscall_info.h". (SYS_FUNC(ptrace)) : Call print_ptrace_syscall_info on exiting syscall. * ptrace_syscall_info.h (print_ptrace_syscall_info): New prototype. * ptrace_syscall_info.c: Include "print_fields.h" and "xlat/ptrace_syscall_info_op.h". (print_ptrace_syscall_info): New function. * xlat/ptrace_syscall_info_op.in: New file. * tests/ptrace_syscall_info.c: New file. * tests/gen_tests.in (ptrace_syscall_info): New test. * tests/pure_executables.list: Add ptrace_syscall_info. * tests/.gitignore: Likewise. --- defs.h | 1 + process.c | 6 + ptrace_syscall_info.c | 86 ++++++ ptrace_syscall_info.h | 2 + tests/.gitignore | 1 + tests/gen_tests.in | 1 + tests/ptrace_syscall_info.c | 460 +++++++++++++++++++++++++++++++++ tests/pure_executables.list | 1 + xlat/ptrace_syscall_info_op.in | 5 + 9 files changed, 563 insertions(+) create mode 100644 tests/ptrace_syscall_info.c create mode 100644 xlat/ptrace_syscall_info_op.in diff --git a/defs.h b/defs.h index 389e4486..15f84991 100644 --- a/defs.h +++ b/defs.h @@ -312,6 +312,7 @@ extern const struct xlat evdev_abs[]; /** Number of elements in evdev_abs array without the terminating record. */ extern const size_t evdev_abs_size; +extern const struct xlat audit_arch[]; extern const struct xlat evdev_ev[]; extern const struct xlat iffflags[]; extern const struct xlat ip_type_of_services[]; diff --git a/process.c b/process.c index 04b2a114..3e57295d 100644 --- a/process.c +++ b/process.c @@ -22,6 +22,7 @@ #endif #include "ptrace.h" +#include "ptrace_syscall_info.h" #include "regs.h" #include "xlat/nt_descriptor_types.h" @@ -220,6 +221,7 @@ SYS_FUNC(ptrace) case PTRACE_GETSIGMASK: case PTRACE_PEEKSIGINFO: case PTRACE_SECCOMP_GET_FILTER: + case PTRACE_GET_SYSCALL_INFO: if (verbose(tcp)) { /* print data on exiting syscall */ return 0; @@ -283,7 +285,11 @@ SYS_FUNC(ptrace) tprints(", ..."); tprints("}"); + break; } + case PTRACE_GET_SYSCALL_INFO: + print_ptrace_syscall_info(tcp, data, addr); + break; } } return 0; diff --git a/ptrace_syscall_info.c b/ptrace_syscall_info.c index 9f884e4a..55eafddf 100644 --- a/ptrace_syscall_info.c +++ b/ptrace_syscall_info.c @@ -7,6 +7,7 @@ #include "defs.h" #include "kill_save_errno.h" +#include "print_fields.h" #include "ptrace.h" #include "ptrace_syscall_info.h" #include "scno.h" @@ -14,6 +15,8 @@ #include #include +#include "xlat/ptrace_syscall_info_op.h" + bool ptrace_get_syscall_info_supported; static int @@ -30,6 +33,8 @@ 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 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 @@ -250,3 +255,84 @@ done: 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("}"); +} diff --git a/ptrace_syscall_info.h b/ptrace_syscall_info.h index 25850f8a..3bbafc28 100644 --- a/ptrace_syscall_info.h +++ b/ptrace_syscall_info.h @@ -10,5 +10,7 @@ extern bool ptrace_get_syscall_info_supported; extern bool test_ptrace_get_syscall_info(void); +extern void print_ptrace_syscall_info(struct tcb *, kernel_ulong_t addr, + kernel_ulong_t len); #endif /* !STRACE_PTRACE_SYSCALL_INFO_H */ diff --git a/tests/.gitignore b/tests/.gitignore index c9dbabb8..e25d4f92 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -400,6 +400,7 @@ process_vm_readv process_vm_writev pselect6 ptrace +ptrace_syscall_info pure_executables.am pwritev qual_fault diff --git a/tests/gen_tests.in b/tests/gen_tests.in index 31a06274..31443ffd 100644 --- a/tests/gen_tests.in +++ b/tests/gen_tests.in @@ -332,6 +332,7 @@ process_vm_readv -s5 -a37 process_vm_writev -s5 -a38 pselect6 ptrace -a23 -e signal=none +ptrace_syscall_info -a35 -e signal=none -e trace=ptrace pwritev -a22 -s7 quotactl quotactl-v -v -e trace=quotactl diff --git a/tests/ptrace_syscall_info.c b/tests/ptrace_syscall_info.c new file mode 100644 index 00000000..ea29d22e --- /dev/null +++ b/tests/ptrace_syscall_info.c @@ -0,0 +1,460 @@ +/* + * Check decoding of ptrace PTRACE_GET_SYSCALL_INFO request. + * + * Copyright (c) 2018 Dmitry V. Levin + * All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "tests.h" + +#include "ptrace.h" +#include +#include "scno.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tests/pure_executables.list b/tests/pure_executables.list index c1ac75ed..2c6eabd7 100755 --- a/tests/pure_executables.list +++ b/tests/pure_executables.list @@ -336,6 +336,7 @@ process_vm_readv process_vm_writev pselect6 ptrace +ptrace_syscall_info pwritev quotactl quotactl-xfs diff --git a/xlat/ptrace_syscall_info_op.in b/xlat/ptrace_syscall_info_op.in new file mode 100644 index 00000000..96e76209 --- /dev/null +++ b/xlat/ptrace_syscall_info_op.in @@ -0,0 +1,5 @@ +#value_indexed +PTRACE_SYSCALL_INFO_NONE +PTRACE_SYSCALL_INFO_ENTRY +PTRACE_SYSCALL_INFO_EXIT +PTRACE_SYSCALL_INFO_SECCOMP -- 2.40.0