Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
get_syscall_info
get_set_sud
peeksiginfo
vmaccess
set_syscall_info
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -std=c99 -pthread -Wall $(KHDR_INCLUDES)
TEST_GEN_PROGS := get_syscall_info set_syscall_info peeksiginfo vmaccess get_set_sud
include ../lib.mk
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include "kselftest_harness.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include "linux/ptrace.h"
static int sys_ptrace(int request, pid_t pid, void *addr, void *data)
{
return syscall(SYS_ptrace, request, pid, addr, data);
}
TEST(get_set_sud)
{
struct ptrace_sud_config config;
pid_t child;
int ret = 0;
int status;
child = fork();
ASSERT_GE(child, 0);
if (child == 0) {
ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) {
TH_LOG("PTRACE_TRACEME: %m");
}
kill(getpid(), SIGSTOP);
_exit(1);
}
waitpid(child, &status, 0);
memset(&config, 0xff, sizeof(config));
config.mode = PR_SYS_DISPATCH_ON;
ret = sys_ptrace(PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, child,
(void *)sizeof(config), &config);
ASSERT_EQ(ret, 0);
ASSERT_EQ(config.mode, PR_SYS_DISPATCH_OFF);
ASSERT_EQ(config.selector, 0);
ASSERT_EQ(config.offset, 0);
ASSERT_EQ(config.len, 0);
config.mode = PR_SYS_DISPATCH_ON;
config.selector = 0;
config.offset = 0x400000;
config.len = 0x1000;
ret = sys_ptrace(PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG, child,
(void *)sizeof(config), &config);
ASSERT_EQ(ret, 0);
memset(&config, 1, sizeof(config));
ret = sys_ptrace(PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, child,
(void *)sizeof(config), &config);
ASSERT_EQ(ret, 0);
ASSERT_EQ(config.mode, PR_SYS_DISPATCH_ON);
ASSERT_EQ(config.selector, 0);
ASSERT_EQ(config.offset, 0x400000);
ASSERT_EQ(config.len, 0x1000);
kill(child, SIGKILL);
}
TEST_HARNESS_MAIN
@@ -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
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <linux/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/mman.h>
#include "linux/ptrace.h"
static int sys_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *uinfo)
{
return syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo);
}
static int sys_rt_tgsigqueueinfo(pid_t tgid, pid_t tid,
int sig, siginfo_t *uinfo)
{
return syscall(SYS_rt_tgsigqueueinfo, tgid, tid, sig, uinfo);
}
static int sys_ptrace(int request, pid_t pid, void *addr, void *data)
{
return syscall(SYS_ptrace, request, pid, addr, data);
}
#define SIGNR 10
#define TEST_SICODE_PRIV -1
#define TEST_SICODE_SHARE -2
#ifndef PAGE_SIZE
#define PAGE_SIZE sysconf(_SC_PAGESIZE)
#endif
#define err(fmt, ...) \
fprintf(stderr, \
"Error (%s:%d): " fmt, \
__FILE__, __LINE__, ##__VA_ARGS__)
static int check_error_paths(pid_t child)
{
struct ptrace_peeksiginfo_args arg;
int ret, exit_code = -1;
void *addr_rw, *addr_ro;
/*
* Allocate two contiguous pages. The first one is for read-write,
* another is for read-only.
*/
addr_rw = mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr_rw == MAP_FAILED) {
err("mmap() failed: %m\n");
return 1;
}
addr_ro = mmap(addr_rw + PAGE_SIZE, PAGE_SIZE, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (addr_ro == MAP_FAILED) {
err("mmap() failed: %m\n");
goto out;
}
arg.nr = SIGNR;
arg.off = 0;
/* Unsupported flags */
arg.flags = ~0;
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_rw);
if (ret != -1 || errno != EINVAL) {
err("sys_ptrace() returns %d (expected -1),"
" errno %d (expected %d): %m\n",
ret, errno, EINVAL);
goto out;
}
arg.flags = 0;
/* A part of the buffer is read-only */
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg,
addr_ro - sizeof(siginfo_t) * 2);
if (ret != 2) {
err("sys_ptrace() returns %d (expected 2): %m\n", ret);
goto out;
}
/* Read-only buffer */
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_ro);
if (ret != -1 && errno != EFAULT) {
err("sys_ptrace() returns %d (expected -1),"
" errno %d (expected %d): %m\n",
ret, errno, EFAULT);
goto out;
}
exit_code = 0;
out:
munmap(addr_rw, 2 * PAGE_SIZE);
return exit_code;
}
int check_direct_path(pid_t child, int shared, int nr)
{
struct ptrace_peeksiginfo_args arg = {.flags = 0, .nr = nr, .off = 0};
int i, j, ret, exit_code = -1;
siginfo_t siginfo[SIGNR];
int si_code;
if (shared == 1) {
arg.flags = PTRACE_PEEKSIGINFO_SHARED;
si_code = TEST_SICODE_SHARE;
} else {
arg.flags = 0;
si_code = TEST_SICODE_PRIV;
}
for (i = 0; i < SIGNR; ) {
arg.off = i;
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, siginfo);
if (ret == -1) {
err("ptrace() failed: %m\n");
goto out;
}
if (ret == 0)
break;
for (j = 0; j < ret; j++, i++) {
if (siginfo[j].si_code == si_code &&
siginfo[j].si_int == i)
continue;
err("%d: Wrong siginfo i=%d si_code=%d si_int=%d\n",
shared, i, siginfo[j].si_code, siginfo[j].si_int);
goto out;
}
}
if (i != SIGNR) {
err("Only %d signals were read\n", i);
goto out;
}
exit_code = 0;
out:
return exit_code;
}
int main(int argc, char *argv[])
{
siginfo_t siginfo;
int i, exit_code = 1;
sigset_t blockmask;
pid_t child;
sigemptyset(&blockmask);
sigaddset(&blockmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &blockmask, NULL);
child = fork();
if (child == -1) {
err("fork() failed: %m");
return 1;
} else if (child == 0) {
pid_t ppid = getppid();
while (1) {
if (ppid != getppid())
break;
sleep(1);
}
return 1;
}
/* Send signals in process-wide and per-thread queues */
for (i = 0; i < SIGNR; i++) {
siginfo.si_code = TEST_SICODE_SHARE;
siginfo.si_int = i;
sys_rt_sigqueueinfo(child, SIGRTMIN, &siginfo);
siginfo.si_code = TEST_SICODE_PRIV;
siginfo.si_int = i;
sys_rt_tgsigqueueinfo(child, child, SIGRTMIN, &siginfo);
}
if (sys_ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1)
return 1;
waitpid(child, NULL, 0);
/* Dump signals one by one*/
if (check_direct_path(child, 0, 1))
goto out;
/* Dump all signals for one call */
if (check_direct_path(child, 0, SIGNR))
goto out;
/*
* Dump signal from the process-wide queue.
* The number of signals is not multiple to the buffer size
*/
if (check_direct_path(child, 1, 3))
goto out;
if (check_error_paths(child))
goto out;
printf("PASS\n");
exit_code = 0;
out:
if (sys_ptrace(PTRACE_KILL, child, NULL, NULL) == -1)
return 1;
waitpid(child, NULL, 0);
return exit_code;
}
@@ -0,0 +1,519 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2018-2025 Dmitry V. Levin <ldv@strace.io>
* All rights reserved.
*
* Check whether PTRACE_SET_SYSCALL_INFO semantics implemented in the kernel
* matches userspace expectations.
*/
#include "kselftest_harness.h"
#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <asm/unistd.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#if defined(_MIPS_SIM) && _MIPS_SIM == _MIPS_SIM_NABI32
/*
* MIPS N32 is the only architecture where __kernel_ulong_t
* does not match the bitness of syscall arguments.
*/
typedef unsigned long long kernel_ulong_t;
#else
typedef __kernel_ulong_t kernel_ulong_t;
#endif
struct si_entry {
int nr;
kernel_ulong_t args[6];
};
struct si_exit {
unsigned int is_error;
int rval;
};
static unsigned int ptrace_stop;
static pid_t tracee_pid;
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(tracee_pid); \
TH_LOG("wait #%d: " fmt, \
ptrace_stop, ##__VA_ARGS__); \
} while (0)
static void
check_psi_entry(struct __test_metadata *_metadata,
const struct ptrace_syscall_info *info,
const struct si_entry *exp_entry,
const char *text)
{
unsigned int i;
int exp_nr = exp_entry->nr;
#if defined __s390__ || defined __s390x__
/* s390 is the only architecture that has 16-bit syscall numbers */
exp_nr &= 0xffff;
#endif
ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info->op) {
LOG_KILL_TRACEE("%s: entry stop mismatch", text);
}
ASSERT_TRUE(info->arch) {
LOG_KILL_TRACEE("%s: entry stop mismatch", text);
}
ASSERT_TRUE(info->instruction_pointer) {
LOG_KILL_TRACEE("%s: entry stop mismatch", text);
}
ASSERT_TRUE(info->stack_pointer) {
LOG_KILL_TRACEE("%s: entry stop mismatch", text);
}
ASSERT_EQ(exp_nr, info->entry.nr) {
LOG_KILL_TRACEE("%s: syscall nr mismatch", text);
}
for (i = 0; i < ARRAY_SIZE(exp_entry->args); ++i) {
ASSERT_EQ(exp_entry->args[i], info->entry.args[i]) {
LOG_KILL_TRACEE("%s: syscall arg #%u mismatch",
text, i);
}
}
}
static void
check_psi_exit(struct __test_metadata *_metadata,
const struct ptrace_syscall_info *info,
const struct si_exit *exp_exit,
const char *text)
{
ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info->op) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
ASSERT_TRUE(info->arch) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
ASSERT_TRUE(info->instruction_pointer) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
ASSERT_TRUE(info->stack_pointer) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
ASSERT_EQ(exp_exit->is_error, info->exit.is_error) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
ASSERT_EQ(exp_exit->rval, info->exit.rval) {
LOG_KILL_TRACEE("%s: exit stop mismatch", text);
}
}
TEST(set_syscall_info)
{
const pid_t tracer_pid = getpid();
const kernel_ulong_t dummy[] = {
(kernel_ulong_t) 0xdad0bef0bad0fed0ULL,
(kernel_ulong_t) 0xdad1bef1bad1fed1ULL,
(kernel_ulong_t) 0xdad2bef2bad2fed2ULL,
(kernel_ulong_t) 0xdad3bef3bad3fed3ULL,
(kernel_ulong_t) 0xdad4bef4bad4fed4ULL,
(kernel_ulong_t) 0xdad5bef5bad5fed5ULL,
};
int splice_in[2], splice_out[2];
ASSERT_EQ(0, pipe(splice_in));
ASSERT_EQ(0, pipe(splice_out));
ASSERT_EQ(sizeof(dummy), write(splice_in[1], dummy, sizeof(dummy)));
const struct {
struct si_entry entry[2];
struct si_exit exit[2];
} si[] = {
/* change scno, keep non-error rval */
{
{
{
__NR_gettid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_getppid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 0, tracer_pid }, { 0, tracer_pid }
}
},
/* set scno to -1, keep error rval */
{
{
{
__NR_chdir,
{
(uintptr_t) ".",
dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
-1,
{
(uintptr_t) ".",
dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 1, -ENOSYS }, { 1, -ENOSYS }
}
},
/* keep scno, change non-error rval */
{
{
{
__NR_getppid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_getppid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 0, tracer_pid }, { 0, tracer_pid + 1 }
}
},
/* change arg1, keep non-error rval */
{
{
{
__NR_chdir,
{
(uintptr_t) "",
dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_chdir,
{
(uintptr_t) ".",
dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 0, 0 }, { 0, 0 }
}
},
/* set scno to -1, change error rval to non-error */
{
{
{
__NR_gettid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
-1,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 1, -ENOSYS }, { 0, tracer_pid }
}
},
/* change scno, change non-error rval to error */
{
{
{
__NR_chdir,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_getppid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 0, tracer_pid }, { 1, -EISDIR }
}
},
/* change scno and all args, change non-error rval */
{
{
{
__NR_gettid,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_splice,
{
splice_in[0], 0, splice_out[1], 0,
sizeof(dummy), SPLICE_F_NONBLOCK
}
}
}, {
{ 0, sizeof(dummy) }, { 0, sizeof(dummy) + 1 }
}
},
/* change arg1, no exit stop */
{
{
{
__NR_exit_group,
{
dummy[0], dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}, {
__NR_exit_group,
{
0, dummy[1], dummy[2],
dummy[3], dummy[4], dummy[5]
}
}
}, {
{ 0, 0 }, { 0, 0 }
}
},
};
long rc;
unsigned int i;
tracee_pid = fork();
ASSERT_LE(0, tracee_pid) {
TH_LOG("fork: %m");
}
if (tracee_pid == 0) {
/* get the pid before PTRACE_TRACEME */
tracee_pid = getpid();
ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) {
TH_LOG("PTRACE_TRACEME: %m");
}
ASSERT_EQ(0, kill(tracee_pid, SIGSTOP)) {
/* cannot happen */
TH_LOG("kill SIGSTOP: %m");
}
for (i = 0; i < ARRAY_SIZE(si); ++i) {
rc = syscall(si[i].entry[0].nr,
si[i].entry[0].args[0],
si[i].entry[0].args[1],
si[i].entry[0].args[2],
si[i].entry[0].args[3],
si[i].entry[0].args[4],
si[i].entry[0].args[5]);
if (si[i].exit[1].is_error) {
if (rc != -1 || errno != -si[i].exit[1].rval)
break;
} else {
if (rc != si[i].exit[1].rval)
break;
}
}
/*
* Something went wrong, but in this state tracee
* cannot reliably issue syscalls, so just crash.
*/
*(volatile unsigned char *) (uintptr_t) i = 42;
/* unreachable */
_exit(i + 1);
}
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_entry_size =
(void *) &info.entry.args[6] - (void *) &info;
const int expected_exit_size =
(void *) (&info.exit.is_error + 1) -
(void *) &info;
int status;
ASSERT_EQ(tracee_pid, wait(&status)) {
/* cannot happen */
LOG_KILL_TRACEE("wait: %m");
}
if (WIFEXITED(status)) {
tracee_pid = 0; /* the tracee is no more */
ASSERT_EQ(0, WEXITSTATUS(status)) {
LOG_KILL_TRACEE("unexpected exit status %u",
WEXITSTATUS(status));
}
break;
}
ASSERT_FALSE(WIFSIGNALED(status)) {
tracee_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);
}
ASSERT_LT(ptrace_stop, ARRAY_SIZE(si) * 2) {
LOG_KILL_TRACEE("ptrace stop overflow");
}
switch (WSTOPSIG(status)) {
case SIGSTOP:
ASSERT_EQ(0, ptrace_stop) {
LOG_KILL_TRACEE("unexpected signal stop");
}
ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, tracee_pid,
0, PTRACE_O_TRACESYSGOOD)) {
LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m");
}
break;
case SIGTRAP | 0x80:
ASSERT_LT(0, ptrace_stop) {
LOG_KILL_TRACEE("unexpected syscall stop");
}
ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1: %m");
}
if (ptrace_stop & 1) {
/* entering syscall */
const struct si_entry *exp_entry =
&si[ptrace_stop / 2].entry[0];
const struct si_entry *set_entry =
&si[ptrace_stop / 2].entry[1];
/* check ptrace_syscall_info before the changes */
ASSERT_EQ(expected_entry_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
": entry stop mismatch");
}
check_psi_entry(_metadata, &info, exp_entry,
"PTRACE_GET_SYSCALL_INFO #1");
/* apply the changes */
info.entry.nr = set_entry->nr;
for (i = 0; i < ARRAY_SIZE(set_entry->args); ++i)
info.entry.args[i] = set_entry->args[i];
ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info)) {
LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m");
}
/* check ptrace_syscall_info after the changes */
memset(&info, 0, sizeof(info));
info.op = 0xff;
ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m");
}
ASSERT_EQ(expected_entry_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
": entry stop mismatch");
}
check_psi_entry(_metadata, &info, set_entry,
"PTRACE_GET_SYSCALL_INFO #2");
} else {
/* exiting syscall */
const struct si_exit *exp_exit =
&si[ptrace_stop / 2 - 1].exit[0];
const struct si_exit *set_exit =
&si[ptrace_stop / 2 - 1].exit[1];
/* check ptrace_syscall_info before the changes */
ASSERT_EQ(expected_exit_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
": exit stop mismatch");
}
check_psi_exit(_metadata, &info, exp_exit,
"PTRACE_GET_SYSCALL_INFO #1");
/* apply the changes */
info.exit.is_error = set_exit->is_error;
info.exit.rval = set_exit->rval;
ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info)) {
LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m");
}
/* check ptrace_syscall_info after the changes */
memset(&info, 0, sizeof(info));
info.op = 0xff;
ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2: %m");
}
ASSERT_EQ(expected_exit_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
": exit stop mismatch");
}
check_psi_exit(_metadata, &info, set_exit,
"PTRACE_GET_SYSCALL_INFO #2");
}
break;
default:
LOG_KILL_TRACEE("unexpected stop signal %u",
WSTOPSIG(status));
abort();
}
ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, tracee_pid, 0, 0)) {
LOG_KILL_TRACEE("PTRACE_SYSCALL: %m");
}
}
ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2020 Bernd Edlinger <bernd.edlinger@hotmail.de>
* All rights reserved.
*
* Check whether /proc/$pid/mem can be accessed without causing deadlocks
* when de_thread is blocked with ->cred_guard_mutex held.
*/
#include "kselftest_harness.h"
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <sys/ptrace.h>
static void *thread(void *arg)
{
ptrace(PTRACE_TRACEME, 0, 0L, 0L);
return NULL;
}
TEST(vmaccess)
{
int f, pid = fork();
char mm[64];
if (!pid) {
pthread_t pt;
pthread_create(&pt, NULL, thread, NULL);
pthread_join(pt, NULL);
execlp("true", "true", NULL);
}
sleep(1);
sprintf(mm, "/proc/%d/mem", pid);
f = open(mm, O_RDONLY);
ASSERT_GE(f, 0);
close(f);
f = kill(pid, SIGCONT);
ASSERT_EQ(f, 0);
}
TEST(attach)
{
int s, k, pid = fork();
if (!pid) {
pthread_t pt;
pthread_create(&pt, NULL, thread, NULL);
pthread_join(pt, NULL);
execlp("sleep", "sleep", "2", NULL);
}
sleep(1);
k = ptrace(PTRACE_ATTACH, pid, 0L, 0L);
ASSERT_EQ(errno, EAGAIN);
ASSERT_EQ(k, -1);
k = waitpid(-1, &s, WNOHANG);
ASSERT_NE(k, -1);
ASSERT_NE(k, 0);
ASSERT_NE(k, pid);
ASSERT_EQ(WIFEXITED(s), 1);
ASSERT_EQ(WEXITSTATUS(s), 0);
sleep(1);
k = ptrace(PTRACE_ATTACH, pid, 0L, 0L);
ASSERT_EQ(k, 0);
k = waitpid(-1, &s, 0);
ASSERT_EQ(k, pid);
ASSERT_EQ(WIFSTOPPED(s), 1);
ASSERT_EQ(WSTOPSIG(s), SIGSTOP);
k = ptrace(PTRACE_DETACH, pid, 0L, 0L);
ASSERT_EQ(k, 0);
k = waitpid(-1, &s, 0);
ASSERT_EQ(k, pid);
ASSERT_EQ(WIFEXITED(s), 1);
ASSERT_EQ(WEXITSTATUS(s), 0);
k = waitpid(-1, NULL, 0);
ASSERT_EQ(k, -1);
ASSERT_EQ(errno, ECHILD);
}
TEST_HARNESS_MAIN