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:
John Baldwin 2015-05-26 10:29:37 +00:00
parent b6b2fb599b
commit 57c74f5b64
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=283562
2 changed files with 261 additions and 12 deletions

View File

@ -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);

View File

@ -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());
}