Introduce new ptrace(2) API to allow/prevent exection of LWP

Introduce new API for debuggers to allow/prevent execution of the specified
thread.

New ptrace(2) operations:

     PT_RESUME     Allow execution of a specified thread, change its state
                   from suspended to continued.  The addr argument is unused.
                   The data argument specifies the LWP ID.

                   This call is equivalent to _lwp_continue(2) called by a
                   traced process.  This call does not change the general
                   process state from stopped to continued.

     PT_SUSPEND    Prevent execution of a specified thread, change its state
                   from continued to suspended.  The addr argument is unused.
                   The data argument specifies the requested LWP ID.

                   This call is equivalent to _lwp_suspend(2) called by a
                   traced process.  This call does not change the general
                   process state from continued to stopped.

This interface is modeled after FreeBSD, however with NetBSD specific arguments
passed to ptrace(2) -- FreeBSD passes only thread id, NetBSD passes process and
thread id.

Extend PT_LWPINFO operation in ptrace(2) to report suspended threads. In the
ptrace_lwpinfo structure in pl_event next to PL_EVENT_NONE and PL_EVENT_SIGNAL
add new value PL_EVENT_SUSPENDED.

Add new errno(2) value EDEADLK that might be returned by ptrace(2). It prevents
dead-locking in a scenario of resuming a process or thread that is prevented
from execution. This fixes bug that old API was vulnerable to this scenario.

Kernel bump delayed till introduction of PT_GETDBREGS/PT_SETDBREGS soon.

Add new ATF tests:
 - resume1
   Verify that a thread can be suspended by a debugger and later
   resumed by the debugger

 - suspend1
   Verify that a thread can be suspended by a debugger and later
   resumed by a tracee

 - suspend2
   Verify that the while the only thread within a process is
   suspended, the whole process cannot be unstopped

Sponsored by <The NetBSD Foundation>
This commit is contained in:
kamil 2017-02-22 23:43:43 +00:00
parent c56d982263
commit f9b2093d06
4 changed files with 459 additions and 13 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: ptrace.2,v 1.59 2017/02/12 06:09:53 kamil Exp $
.\" $NetBSD: ptrace.2,v 1.60 2017/02/22 23:43:43 kamil Exp $
.\"
.\" This file is in the public domain.
.Dd February 12, 2016
@ -355,6 +355,7 @@ Possible values are:
.Bl -tag -width 30n -offset indent -compact
.It Dv PL_EVENT_NONE
.It Dv PL_EVENT_SIGNAL
.It Dv PL_EVENT_SUSPENDED
.El
.Pp
The
@ -539,6 +540,34 @@ The
.Fa data
argument contains the LWP ID of the thread whose mask is to be read.
If zero is supplied, the first thread of the process is read.
.It Dv PT_RESUME
Allow execution of a specified thread,
change its state from suspended to continued.
The
.Fa addr
argument is unused.
The
.Fa data
argument specifies the LWP ID.
.Pp
This call is equivalent to
.Xr _lwp_continue 2
called by a traced process.
This call does not change the general process state from stopped to continued.
.It Dv PT_SUSPEND
Prevent execution of a specified thread,
change its state from continued to suspended.
The
.Fa addr
argument is unused.
The
.Fa data
argument specifies the requested LWP ID.
.Pp
This call is equivalent to
.Xr _lwp_suspend 2
called by a traced process.
This call does not change the general process state from continued to stopped.
.El
.Pp
Additionally, the following requests exist but are
@ -692,6 +721,8 @@ A request (other than
.Dv PT_ATTACH )
specified a process that wasn't stopped.
.El
.It Bq Er EDEADLK
An attempt to unstop a process with locked threads.
.It Bq Er EINVAL
.Bl -bullet -compact
.It
@ -761,3 +792,10 @@ should be able to sidestep this.
.Dv PTRACE_VFORK
is currently unimplemented and it will return
.Er ENOTSUP .
.Pp
.Dv PT_SET_SIGINFO ,
.Dv PT_RESUME
and
.Dv PT_SUSPEND
can change the image of process returned by
.Dv PT_LWPINFO .

View File

@ -1,4 +1,4 @@
/* $NetBSD: sys_ptrace_common.c,v 1.14 2017/02/12 06:09:52 kamil Exp $ */
/* $NetBSD: sys_ptrace_common.c,v 1.15 2017/02/22 23:43:43 kamil Exp $ */
/*-
* Copyright (c) 2008, 2009 The NetBSD Foundation, Inc.
@ -118,7 +118,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_ptrace_common.c,v 1.14 2017/02/12 06:09:52 kamil Exp $");
__KERNEL_RCSID(0, "$NetBSD: sys_ptrace_common.c,v 1.15 2017/02/22 23:43:43 kamil Exp $");
#ifdef _KERNEL_OPT
#include "opt_ptrace.h"
@ -236,6 +236,8 @@ ptrace_listener_cb(kauth_cred_t cred, kauth_action_t action, void *cookie,
case PT_SYSCALL:
case PT_SYSCALLEMU:
case PT_DUMPCORE:
case PT_RESUME:
case PT_SUSPEND:
result = KAUTH_RESULT_ALLOW;
break;
@ -457,6 +459,8 @@ do_ptrace(struct ptrace_methods *ptm, struct lwp *l, int req, pid_t pid,
case PT_SET_EVENT_MASK:
case PT_GET_EVENT_MASK:
case PT_GET_PROCESS_STATE:
case PT_RESUME:
case PT_SUSPEND:
/*
* You can't do what you want to the process if:
* (1) It's not being traced at all,
@ -755,6 +759,34 @@ do_ptrace(struct ptrace_methods *ptm, struct lwp *l, int req, pid_t pid,
break;
}
/* Prevent process deadlock */
if (resume_all) {
#ifdef PT_STEP
if (req == PT_STEP) {
if (lt->l_flag & LW_WSUSPEND) {
error = EDEADLK;
break;
}
} else
#endif
{
error = EDEADLK;
LIST_FOREACH(lt2, &t->p_lwps, l_sibling) {
if ((lt2->l_flag & LW_WSUSPEND) == 0) {
error = 0;
break;
}
}
if (error != 0)
break;
}
} else {
if (lt->l_flag & LW_WSUSPEND) {
error = EDEADLK;
break;
}
}
/* If the address parameter is not (int *)1, set the pc. */
if ((int *)addr != (int *)1) {
error = process_set_pc(lt, addr);
@ -968,15 +1000,18 @@ do_ptrace(struct ptrace_methods *ptm, struct lwp *l, int req, pid_t pid,
if (lt) {
lwp_addref(lt);
pl.pl_lwpid = lt->l_lid;
if (lt->l_flag & LW_WSUSPEND)
pl.pl_event = PL_EVENT_SUSPENDED;
/*
* If we match the lwp, or it was sent to every lwp,
* we set PL_EVENT_SIGNAL.
* XXX: ps_lwp == 0 means everyone and noone, so
* check ps_signo too.
*/
if (lt->l_lid == t->p_sigctx.ps_lwp
|| (t->p_sigctx.ps_lwp == 0 &&
t->p_sigctx.ps_info._signo))
else if (lt->l_lid == t->p_sigctx.ps_lwp
|| (t->p_sigctx.ps_lwp == 0 &&
t->p_sigctx.ps_info._signo))
pl.pl_event = PL_EVENT_SIGNAL;
}
mutex_exit(t->p_lock);
@ -1072,6 +1107,37 @@ do_ptrace(struct ptrace_methods *ptm, struct lwp *l, int req, pid_t pid,
break;
case PT_RESUME:
write = 1;
case PT_SUSPEND:
/* write = 0 done above. */
tmp = data;
if (tmp != 0 && t->p_nlwps > 1) {
lwp_delref(lt);
mutex_enter(t->p_lock);
lt = lwp_find(t, tmp);
if (lt == NULL) {
mutex_exit(t->p_lock);
error = ESRCH;
break;
}
lwp_addref(lt);
mutex_exit(t->p_lock);
}
if (lt->l_flag & LW_SYSTEM) {
error = EINVAL;
} else {
lwp_lock(lt);
if (write == 0)
lt->l_flag |= LW_WSUSPEND;
else
lt->l_flag &= ~LW_WSUSPEND;
lwp_unlock(lt);
}
break;
#ifdef PT_SETREGS
case PT_SETREGS:
write = 1;

View File

@ -1,4 +1,4 @@
/* $NetBSD: ptrace.h,v 1.56 2017/02/12 06:09:52 kamil Exp $ */
/* $NetBSD: ptrace.h,v 1.57 2017/02/22 23:43:43 kamil Exp $ */
/*-
* Copyright (c) 1984, 1993
@ -57,6 +57,8 @@
#define PT_GET_SIGINFO 20 /* get signal state, defined below */
#define PT_SET_SIGMASK 21 /* set signal mask */
#define PT_GET_SIGMASK 22 /* get signal mask */
#define PT_RESUME 23 /* allow execution of the LWP */
#define PT_SUSPEND 24 /* prevent execution of the LWP */
#define PT_FIRSTMACH 32 /* for machine-specific requests */
#include <machine/ptrace.h> /* machine-specific requests, if any */
@ -83,8 +85,10 @@
/* 18 */ "PT_GET_PROCESS_STATE", \
/* 19 */ "PT_SET_SIGINFO", \
/* 20 */ "PT_GET_SIGINFO", \
/* 20 */ "PT_GET_SIGMASK", \
/* 20 */ "PT_GET_SIGMASK",
/* 21 */ "PT_GET_SIGMASK", \
/* 22 */ "PT_GET_SIGMASK", \
/* 23 */ "PT_RESUME", \
/* 24 */ "PT_SUSPEND",
/* PT_{G,S}EVENT_MASK */
typedef struct ptrace_event {
@ -135,8 +139,9 @@ struct ptrace_lwpinfo {
/* Add fields at the end */
};
#define PL_EVENT_NONE 0
#define PL_EVENT_SIGNAL 1
#define PL_EVENT_NONE 0
#define PL_EVENT_SIGNAL 1
#define PL_EVENT_SUSPENDED 2
/*
* Hardware Watchpoints

View File

@ -1,4 +1,4 @@
/* $NetBSD: t_ptrace_wait.c,v 1.70 2017/02/12 06:09:52 kamil Exp $ */
/* $NetBSD: t_ptrace_wait.c,v 1.71 2017/02/22 23:43:43 kamil Exp $ */
/*-
* Copyright (c) 2016 The NetBSD Foundation, Inc.
@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: t_ptrace_wait.c,v 1.70 2017/02/12 06:09:52 kamil Exp $");
__RCSID("$NetBSD: t_ptrace_wait.c,v 1.71 2017/02/22 23:43:43 kamil Exp $");
#include <sys/param.h>
#include <sys/types.h>
@ -6947,6 +6947,338 @@ ATF_TC_BODY(setsigmask4, tc)
TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}
static void
lwp_main_stop(void *arg)
{
the_lwp_id = _lwp_self();
raise(SIGTRAP);
_lwp_exit();
}
ATF_TC(suspend1);
ATF_TC_HEAD(suspend1, tc)
{
atf_tc_set_md_var(tc, "descr",
"Verify that a thread can be suspended by a debugger and later "
"resumed by a tracee");
}
ATF_TC_BODY(suspend1, tc)
{
const int exitval = 5;
const int sigval = SIGSTOP;
pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
int status;
#endif
ucontext_t uc;
lwpid_t lid;
static const size_t ssize = 16*1024;
void *stack;
struct ptrace_lwpinfo pl;
struct ptrace_siginfo psi;
volatile int go = 0;
printf("Before forking process PID=%d\n", getpid());
ATF_REQUIRE((child = fork()) != -1);
if (child == 0) {
printf("Before calling PT_TRACE_ME from child %d\n", getpid());
FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
printf("Before raising %s from child\n", strsignal(sigval));
FORKEE_ASSERT(raise(sigval) == 0);
printf("Before allocating memory for stack in child\n");
FORKEE_ASSERT((stack = malloc(ssize)) != NULL);
printf("Before making context for new lwp in child\n");
_lwp_makecontext(&uc, lwp_main_stop, NULL, NULL, stack, ssize);
printf("Before creating new in child\n");
FORKEE_ASSERT(_lwp_create(&uc, 0, &lid) == 0);
while (go == 0)
continue;
raise(SIGINT);
FORKEE_ASSERT(_lwp_continue(lid) == 0);
printf("Before waiting for lwp %d to exit\n", lid);
FORKEE_ASSERT(_lwp_wait(lid, NULL) == 0);
printf("Before verifying that reported %d and running lid %d "
"are the same\n", lid, the_lwp_id);
FORKEE_ASSERT_EQ(lid, the_lwp_id);
printf("Before exiting of the child process\n");
_exit(exitval);
}
printf("Parent process PID=%d, child's PID=%d\n", getpid(), child);
printf("Before calling %s() for the child\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, sigval);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected stopped "
"SIGTRAP\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, SIGTRAP);
printf("Before reading siginfo and lwpid_t\n");
ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1);
printf("Before suspending LWP %d\n", psi.psi_lwpid);
ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1);
printf("Write new go to tracee (PID=%d) from tracer (PID=%d)\n",
child, getpid());
ATF_REQUIRE(ptrace(PT_WRITE_D, child, __UNVOLATILE(&go), 1) != -1);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected stopped "
"SIGINT\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, SIGINT);
pl.pl_lwpid = 0;
ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1);
while (pl.pl_lwpid != 0) {
ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1);
switch (pl.pl_lwpid) {
case 1:
ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SIGNAL);
break;
case 2:
ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SUSPENDED);
break;
}
}
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected exited\n",
TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_exited(status, exitval);
printf("Before calling %s() for the child - expected no process\n",
TWAIT_FNAME);
TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}
ATF_TC(suspend2);
ATF_TC_HEAD(suspend2, tc)
{
atf_tc_set_md_var(tc, "descr",
"Verify that the while the only thread within a process is "
"suspended, the whole process cannot be unstopped");
}
ATF_TC_BODY(suspend2, tc)
{
const int exitval = 5;
const int sigval = SIGSTOP;
pid_t child, wpid;
#if defined(TWAIT_HAVE_STATUS)
int status;
#endif
struct ptrace_siginfo psi;
printf("Before forking process PID=%d\n", getpid());
ATF_REQUIRE((child = fork()) != -1);
if (child == 0) {
printf("Before calling PT_TRACE_ME from child %d\n", getpid());
FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
printf("Before raising %s from child\n", strsignal(sigval));
FORKEE_ASSERT(raise(sigval) == 0);
printf("Before exiting of the child process\n");
_exit(exitval);
}
printf("Parent process PID=%d, child's PID=%d\n", getpid(), child);
printf("Before calling %s() for the child\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, sigval);
printf("Before reading siginfo and lwpid_t\n");
ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1);
printf("Before suspending LWP %d\n", psi.psi_lwpid);
ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE_ERRNO(EDEADLK,
ptrace(PT_CONTINUE, child, (void *)1, 0) == -1);
printf("Before resuming LWP %d\n", psi.psi_lwpid);
ATF_REQUIRE(ptrace(PT_RESUME, child, NULL, psi.psi_lwpid) != -1);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected exited\n",
TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_exited(status, exitval);
printf("Before calling %s() for the child - expected no process\n",
TWAIT_FNAME);
TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
}
ATF_TC(resume1);
ATF_TC_HEAD(resume1, tc)
{
atf_tc_set_md_var(tc, "descr",
"Verify that a thread can be suspended by a debugger and later "
"resumed by the debugger");
}
ATF_TC_BODY(resume1, tc)
{
struct msg_fds fds;
const int exitval = 5;
const int sigval = SIGSTOP;
pid_t child, wpid;
uint8_t msg = 0xde; /* dummy message for IPC based on pipe(2) */
#if defined(TWAIT_HAVE_STATUS)
int status;
#endif
ucontext_t uc;
lwpid_t lid;
static const size_t ssize = 16*1024;
void *stack;
struct ptrace_lwpinfo pl;
struct ptrace_siginfo psi;
ATF_REQUIRE(msg_open(&fds) == 0);
printf("Before forking process PID=%d\n", getpid());
ATF_REQUIRE((child = fork()) != -1);
if (child == 0) {
printf("Before calling PT_TRACE_ME from child %d\n", getpid());
FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
printf("Before raising %s from child\n", strsignal(sigval));
FORKEE_ASSERT(raise(sigval) == 0);
printf("Before allocating memory for stack in child\n");
FORKEE_ASSERT((stack = malloc(ssize)) != NULL);
printf("Before making context for new lwp in child\n");
_lwp_makecontext(&uc, lwp_main_stop, NULL, NULL, stack, ssize);
printf("Before creating new in child\n");
FORKEE_ASSERT(_lwp_create(&uc, 0, &lid) == 0);
CHILD_TO_PARENT("Message", fds, msg);
raise(SIGINT);
printf("Before waiting for lwp %d to exit\n", lid);
FORKEE_ASSERT(_lwp_wait(lid, NULL) == 0);
printf("Before verifying that reported %d and running lid %d "
"are the same\n", lid, the_lwp_id);
FORKEE_ASSERT_EQ(lid, the_lwp_id);
printf("Before exiting of the child process\n");
_exit(exitval);
}
printf("Parent process PID=%d, child's PID=%d\n", getpid(), child);
printf("Before calling %s() for the child\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, sigval);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected stopped "
"SIGTRAP\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, SIGTRAP);
printf("Before reading siginfo and lwpid_t\n");
ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1);
printf("Before suspending LWP %d\n", psi.psi_lwpid);
ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1);
PARENT_FROM_CHILD("Message", fds, msg);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected stopped "
"SIGINT\n", TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_stopped(status, SIGINT);
pl.pl_lwpid = 0;
ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1);
while (pl.pl_lwpid != 0) {
ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1);
switch (pl.pl_lwpid) {
case 1:
ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SIGNAL);
break;
case 2:
ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SUSPENDED);
break;
}
}
printf("Before resuming LWP %d\n", psi.psi_lwpid);
ATF_REQUIRE(ptrace(PT_RESUME, child, NULL, psi.psi_lwpid) != -1);
printf("Before resuming the child process where it left off and "
"without signal to be sent\n");
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1);
printf("Before calling %s() for the child - expected exited\n",
TWAIT_FNAME);
TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child);
validate_status_exited(status, exitval);
printf("Before calling %s() for the child - expected no process\n",
TWAIT_FNAME);
TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0));
msg_close(&fds);
}
ATF_TP_ADD_TCS(tp)
{
setvbuf(stdout, NULL, _IONBF, 0);
@ -7060,6 +7392,11 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, signal9);
ATF_TP_ADD_TC(tp, signal10);
ATF_TP_ADD_TC(tp, suspend1);
ATF_TP_ADD_TC(tp, suspend2);
ATF_TP_ADD_TC(tp, resume1);
ATF_TP_ADD_TC(tp, getsigmask1);
ATF_TP_ADD_TC(tp, getsigmask2);