From f9b2093d0684d3f2c151bb51070f1cd52905f326 Mon Sep 17 00:00:00 2001 From: kamil Date: Wed, 22 Feb 2017 23:43:43 +0000 Subject: [PATCH] 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 --- lib/libc/sys/ptrace.2 | 40 +++- sys/kern/sys_ptrace_common.c | 76 +++++++- sys/sys/ptrace.h | 15 +- tests/kernel/t_ptrace_wait.c | 341 ++++++++++++++++++++++++++++++++++- 4 files changed, 459 insertions(+), 13 deletions(-) diff --git a/lib/libc/sys/ptrace.2 b/lib/libc/sys/ptrace.2 index 2a8d33121408..b2e583c5fccc 100644 --- a/lib/libc/sys/ptrace.2 +++ b/lib/libc/sys/ptrace.2 @@ -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 . diff --git a/sys/kern/sys_ptrace_common.c b/sys/kern/sys_ptrace_common.c index 1313a5ef5e99..5851cf7bc305 100644 --- a/sys/kern/sys_ptrace_common.c +++ b/sys/kern/sys_ptrace_common.c @@ -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 -__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; diff --git a/sys/sys/ptrace.h b/sys/sys/ptrace.h index 82d43532496f..5179513935da 100644 --- a/sys/sys/ptrace.h +++ b/sys/sys/ptrace.h @@ -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-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 diff --git a/tests/kernel/t_ptrace_wait.c b/tests/kernel/t_ptrace_wait.c index f2a3b2a2ee88..59d18c9aa3ba 100644 --- a/tests/kernel/t_ptrace_wait.c +++ b/tests/kernel/t_ptrace_wait.c @@ -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 -__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 #include @@ -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);