strace/tests/ptrace_syscall_info.c
Dmitry V. Levin 7273c6fb3c Implement PTRACE_GET_SYSCALL_INFO decoder
* defs.h (audit_arch): New prototype.
* process.c: Include "ptrace_syscall_info.h".
(SYS_FUNC(ptrace)) <PTRACE_GET_SYSCALL_INFO>: 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.
2018-12-13 22:07:26 +00:00

461 lines
11 KiB
C

/*
* 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;
}