diff options
-rw-r--r-- | pkg/sentry/kernel/task_exit.go | 10 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 1 | ||||
-rw-r--r-- | test/syscalls/linux/wait.cc | 46 |
3 files changed, 54 insertions, 3 deletions
diff --git a/pkg/sentry/kernel/task_exit.go b/pkg/sentry/kernel/task_exit.go index 2e1e46582..158e665d3 100644 --- a/pkg/sentry/kernel/task_exit.go +++ b/pkg/sentry/kernel/task_exit.go @@ -803,13 +803,17 @@ type WaitOptions struct { } // Preconditions: The TaskSet mutex must be locked (for reading or writing). -func (o *WaitOptions) matchesTask(t *Task, pidns *PIDNamespace) bool { +func (o *WaitOptions) matchesTask(t *Task, pidns *PIDNamespace, tracee bool) bool { if o.SpecificTID != 0 && o.SpecificTID != pidns.tids[t] { return false } if o.SpecificPGID != 0 && o.SpecificPGID != pidns.pgids[t.tg.processGroup] { return false } + // Tracees are always eligible. + if tracee { + return true + } if t == t.tg.leader && t.tg.terminationSignal == linux.SIGCHLD { return o.NonCloneTasks } @@ -903,7 +907,7 @@ func (t *Task) waitParentLocked(opts *WaitOptions, parent *Task) (*WaitResult, b anyWaitableTasks := false for child := range parent.children { - if !opts.matchesTask(child, parent.tg.pidns) { + if !opts.matchesTask(child, parent.tg.pidns, false) { continue } // Non-leaders don't notify parents on exit and aren't eligible to @@ -946,7 +950,7 @@ func (t *Task) waitParentLocked(opts *WaitOptions, parent *Task) (*WaitResult, b } } for tracee := range parent.ptraceTracees { - if !opts.matchesTask(tracee, parent.tg.pidns) { + if !opts.matchesTask(tracee, parent.tg.pidns, true) { continue } // Non-leaders do notify tracers on exit. diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 750f3a1e2..ec57ec129 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -3179,6 +3179,7 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:cleanup", + "//test/util:file_descriptor", "//test/util:logging", "//test/util:multiprocess_util", "//test/util:posix_error", diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc index aa27194cb..944149d5e 100644 --- a/test/syscalls/linux/wait.cc +++ b/test/syscalls/linux/wait.cc @@ -14,6 +14,7 @@ #include <signal.h> #include <sys/mman.h> +#include <sys/ptrace.h> #include <sys/resource.h> #include <sys/time.h> #include <sys/types.h> @@ -31,6 +32,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" #include "test/util/logging.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" @@ -861,6 +863,50 @@ TEST(WaitTest, WaitidRusage) { EXPECT_GE(RusageCpuTime(rusage), kSpin); } +// After bf959931ddb88c4e4366e96dd22e68fa0db9527c ("wait/ptrace: assume __WALL +// if the child is traced") (Linux 4.7), tracees are always eligible for +// waiting, regardless of type. +TEST(WaitTest, TraceeWALL) { + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + pid_t child = fork(); + if (child == 0) { + // Child. + rfd.reset(); + + TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == 0); + + // Notify parent that we're now a tracee. + wfd.reset(); + + _exit(0); + } + ASSERT_THAT(child, SyscallSucceeds()); + + wfd.reset(); + + // Wait for child to become tracee. + char c; + EXPECT_THAT(ReadFd(rfd.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0)); + + // We can wait on the fork child with WCLONE, as it is a tracee. + int status; + if (IsRunningOnGvisor()) { + ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr), + SyscallSucceedsWithValue(child)); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; + } else { + // On older versions of Linux, we may get ECHILD. + ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr), + ::testing::AnyOf(SyscallSucceedsWithValue(child), + SyscallFailsWithErrno(ECHILD))); + } +} + } // namespace } // namespace testing |