Do not allow a process to reap an orphan (a child currently being
traced by another process such as a debugger). The parent process does need to check for matching orphan pids to avoid returning ECHILD if an orphan has exited, but it should not return the exited status for the child until after the debugger has detached from the orphan process either explicitly or implicitly via wait(). Add two tests for for this case: one where the debugger is the direct child (thus the parent has a non-empty children list) and one where the debugger is not a direct child (so the only "child" of the parent is the orphan). Differential Revision: https://reviews.freebsd.org/D2644 Reviewed by: kib MFC after: 2 weeks
This commit is contained in:
parent
b6b2fb599b
commit
57c74f5b64
@ -955,7 +955,8 @@ proc_reap(struct thread *td, struct proc *p, int *status, int options)
|
||||
|
||||
static int
|
||||
proc_to_reap(struct thread *td, struct proc *p, idtype_t idtype, id_t id,
|
||||
int *status, int options, struct __wrusage *wrusage, siginfo_t *siginfo)
|
||||
int *status, int options, struct __wrusage *wrusage, siginfo_t *siginfo,
|
||||
int check_only)
|
||||
{
|
||||
struct proc *q;
|
||||
struct rusage *rup;
|
||||
@ -1093,7 +1094,7 @@ proc_to_reap(struct thread *td, struct proc *p, idtype_t idtype, id_t id,
|
||||
calccru(p, &rup->ru_utime, &rup->ru_stime);
|
||||
}
|
||||
|
||||
if (p->p_state == PRS_ZOMBIE) {
|
||||
if (p->p_state == PRS_ZOMBIE && !check_only) {
|
||||
PROC_SLOCK(p);
|
||||
proc_reap(td, p, status, options);
|
||||
return (-1);
|
||||
@ -1187,7 +1188,7 @@ kern_wait6(struct thread *td, idtype_t idtype, id_t id, int *status,
|
||||
sx_xlock(&proctree_lock);
|
||||
LIST_FOREACH(p, &q->p_children, p_sibling) {
|
||||
ret = proc_to_reap(td, p, idtype, id, status, options,
|
||||
wrusage, siginfo);
|
||||
wrusage, siginfo, 0);
|
||||
if (ret == 0)
|
||||
continue;
|
||||
else if (ret == 1)
|
||||
@ -1289,15 +1290,17 @@ kern_wait6(struct thread *td, idtype_t idtype, id_t id, int *status,
|
||||
* for. By maintaining a list of orphans we allow the parent
|
||||
* to successfully wait until the child becomes a zombie.
|
||||
*/
|
||||
LIST_FOREACH(p, &q->p_orphans, p_orphan) {
|
||||
ret = proc_to_reap(td, p, idtype, id, status, options,
|
||||
wrusage, siginfo);
|
||||
if (ret == 0)
|
||||
continue;
|
||||
else if (ret == 1)
|
||||
nfound++;
|
||||
else
|
||||
return (0);
|
||||
if (nfound == 0) {
|
||||
LIST_FOREACH(p, &q->p_orphans, p_orphan) {
|
||||
ret = proc_to_reap(td, p, idtype, id, NULL, options,
|
||||
NULL, NULL, 1);
|
||||
if (ret != 0) {
|
||||
KASSERT(ret != -1, ("reaped an orphan (pid %d)",
|
||||
(int)td->td_retval[0]));
|
||||
nfound++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nfound == 0) {
|
||||
sx_xunlock(&proctree_lock);
|
||||
|
@ -29,6 +29,8 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
@ -133,11 +135,255 @@ ATF_TC_BODY(ptrace__parent_wait_after_attach, tc)
|
||||
ATF_REQUIRE(errno == ECHILD);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that a parent process "sees" the exit of a debugged process only
|
||||
* after the debugger has seen it.
|
||||
*/
|
||||
ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_child_debugger);
|
||||
ATF_TC_BODY(ptrace__parent_sees_exit_after_child_debugger, tc)
|
||||
{
|
||||
pid_t child, debugger, wpid;
|
||||
int cpipe[2], dpipe[2], status;
|
||||
char c;
|
||||
|
||||
ATF_REQUIRE(pipe(cpipe) == 0);
|
||||
ATF_REQUIRE((child = fork()) != -1);
|
||||
|
||||
if (child == 0) {
|
||||
/* Child process. */
|
||||
close(cpipe[0]);
|
||||
|
||||
/* Wait for parent to be ready. */
|
||||
ATF_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
close(cpipe[1]);
|
||||
|
||||
ATF_REQUIRE(pipe(dpipe) == 0);
|
||||
ATF_REQUIRE((debugger = fork()) != -1);
|
||||
|
||||
if (debugger == 0) {
|
||||
/* Debugger process. */
|
||||
close(dpipe[0]);
|
||||
|
||||
ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1);
|
||||
|
||||
wpid = waitpid(child, &status, 0);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFSTOPPED(status));
|
||||
ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
|
||||
|
||||
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
|
||||
|
||||
/* Signal parent that debugger is attached. */
|
||||
ATF_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
/* Wait for parent's failed wait. */
|
||||
ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0);
|
||||
|
||||
wpid = waitpid(child, &status, 0);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 1);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
close(dpipe[1]);
|
||||
|
||||
/* Parent process. */
|
||||
|
||||
/* Wait for the debugger to attach to the child. */
|
||||
ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
/* Release the child. */
|
||||
ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c));
|
||||
ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
|
||||
close(cpipe[0]);
|
||||
|
||||
/*
|
||||
* Wait for the child to exit. This is kind of gross, but
|
||||
* there is not a better way.
|
||||
*/
|
||||
for (;;) {
|
||||
struct kinfo_proc kp;
|
||||
size_t len;
|
||||
int mib[4];
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = child;
|
||||
len = sizeof(kp);
|
||||
if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
|
||||
/* The KERN_PROC_PID sysctl fails for zombies. */
|
||||
ATF_REQUIRE(errno == ESRCH);
|
||||
break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
/*
|
||||
* This wait should return an empty pid. The parent should
|
||||
* see the child as non-exited until the debugger sees the
|
||||
* exit.
|
||||
*/
|
||||
wpid = waitpid(child, &status, WNOHANG);
|
||||
ATF_REQUIRE(wpid == 0);
|
||||
|
||||
/* Signal the debugger to wait for the child. */
|
||||
close(dpipe[0]);
|
||||
|
||||
/* Wait for the debugger. */
|
||||
wpid = waitpid(debugger, &status, 0);
|
||||
ATF_REQUIRE(wpid == debugger);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 0);
|
||||
|
||||
/* The child process should now be ready. */
|
||||
wpid = waitpid(child, &status, WNOHANG);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that a parent process "sees" the exit of a debugged process
|
||||
* only after a non-direct-child debugger has seen it. In particular,
|
||||
* various wait() calls in the parent must avoid failing with ESRCH by
|
||||
* checking the parent's orphan list for the debugee.
|
||||
*/
|
||||
ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_unrelated_debugger);
|
||||
ATF_TC_BODY(ptrace__parent_sees_exit_after_unrelated_debugger, tc)
|
||||
{
|
||||
pid_t child, debugger, fpid, wpid;
|
||||
int cpipe[2], dpipe[2], status;
|
||||
char c;
|
||||
|
||||
ATF_REQUIRE(pipe(cpipe) == 0);
|
||||
ATF_REQUIRE((child = fork()) != -1);
|
||||
|
||||
if (child == 0) {
|
||||
/* Child process. */
|
||||
close(cpipe[0]);
|
||||
|
||||
/* Wait for parent to be ready. */
|
||||
ATF_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
close(cpipe[1]);
|
||||
|
||||
ATF_REQUIRE(pipe(dpipe) == 0);
|
||||
ATF_REQUIRE((debugger = fork()) != -1);
|
||||
|
||||
if (debugger == 0) {
|
||||
/* Debugger parent. */
|
||||
|
||||
/*
|
||||
* Fork again and drop the debugger parent so that the
|
||||
* debugger is not a child of the main parent.
|
||||
*/
|
||||
ATF_REQUIRE((fpid = fork()) != -1);
|
||||
if (fpid != 0)
|
||||
exit(2);
|
||||
|
||||
/* Debugger process. */
|
||||
close(dpipe[0]);
|
||||
|
||||
ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1);
|
||||
|
||||
wpid = waitpid(child, &status, 0);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFSTOPPED(status));
|
||||
ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
|
||||
|
||||
ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
|
||||
|
||||
/* Signal parent that debugger is attached. */
|
||||
ATF_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
/* Wait for parent's failed wait. */
|
||||
ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0);
|
||||
|
||||
wpid = waitpid(child, &status, 0);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 1);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Parent process. */
|
||||
|
||||
/* Wait for the debugger parent process to exit. */
|
||||
wpid = waitpid(debugger, &status, 0);
|
||||
ATF_REQUIRE(wpid == debugger);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 2);
|
||||
|
||||
/* A WNOHANG wait here should see the non-exited child. */
|
||||
wpid = waitpid(child, &status, WNOHANG);
|
||||
ATF_REQUIRE(wpid == 0);
|
||||
|
||||
/* Wait for the debugger to attach to the child. */
|
||||
ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c));
|
||||
|
||||
/* Release the child. */
|
||||
ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c));
|
||||
ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
|
||||
close(cpipe[0]);
|
||||
|
||||
/*
|
||||
* Wait for the child to exit. This is kind of gross, but
|
||||
* there is not a better way.
|
||||
*/
|
||||
for (;;) {
|
||||
struct kinfo_proc kp;
|
||||
size_t len;
|
||||
int mib[4];
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = child;
|
||||
len = sizeof(kp);
|
||||
if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
|
||||
/* The KERN_PROC_PID sysctl fails for zombies. */
|
||||
ATF_REQUIRE(errno == ESRCH);
|
||||
break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
/*
|
||||
* This wait should return an empty pid. The parent should
|
||||
* see the child as non-exited until the debugger sees the
|
||||
* exit.
|
||||
*/
|
||||
wpid = waitpid(child, &status, WNOHANG);
|
||||
ATF_REQUIRE(wpid == 0);
|
||||
|
||||
/* Signal the debugger to wait for the child. */
|
||||
close(dpipe[0]);
|
||||
|
||||
/* Wait for the debugger. */
|
||||
ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0);
|
||||
|
||||
/* The child process should now be ready. */
|
||||
wpid = waitpid(child, &status, WNOHANG);
|
||||
ATF_REQUIRE(wpid == child);
|
||||
ATF_REQUIRE(WIFEXITED(status));
|
||||
ATF_REQUIRE(WEXITSTATUS(status) == 1);
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
|
||||
ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_trace_me);
|
||||
ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_attach);
|
||||
ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_child_debugger);
|
||||
ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_unrelated_debugger);
|
||||
|
||||
return (atf_no_error());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user