diff options
-rw-r--r-- | pkg/sentry/syscalls/linux/sys_thread.go | 67 | ||||
-rw-r--r-- | test/syscalls/linux/wait.cc | 124 |
2 files changed, 117 insertions, 74 deletions
diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index 14fa7ef92..26f7e8ead 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -181,6 +181,32 @@ func Vfork(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return clone(t, syscall.CLONE_VM|syscall.CLONE_VFORK|int(syscall.SIGCHLD), 0, 0, 0, 0) } +// parseCommonWaitOptions applies the options common to wait4 and waitid to +// wopts. +func parseCommonWaitOptions(wopts *kernel.WaitOptions, options int) error { + switch options & (linux.WCLONE | linux.WALL) { + case 0: + wopts.NonCloneTasks = true + case linux.WCLONE: + wopts.CloneTasks = true + case linux.WALL: + wopts.NonCloneTasks = true + wopts.CloneTasks = true + default: + return syscall.EINVAL + } + if options&linux.WCONTINUED != 0 { + wopts.Events |= kernel.EventGroupContinue + } + if options&linux.WNOHANG == 0 { + wopts.BlockInterruptErr = kernel.ERESTARTSYS + } + if options&linux.WNOTHREAD == 0 { + wopts.SiblingChildren = true + } + return nil +} + // wait4 waits for the given child process to exit. func wait4(t *kernel.Task, pid int, statusAddr usermem.Addr, options int, rusageAddr usermem.Addr) (uintptr, error) { if options&^(linux.WNOHANG|linux.WUNTRACED|linux.WCONTINUED|linux.WNOTHREAD|linux.WALL|linux.WCLONE) != 0 { @@ -207,29 +233,12 @@ func wait4(t *kernel.Task, pid int, statusAddr usermem.Addr, options int, rusage wopts.SpecificTID = kernel.ThreadID(pid) } - switch options & (linux.WCLONE | linux.WALL) { - case 0: - wopts.NonCloneTasks = true - case linux.WCLONE: - wopts.CloneTasks = true - case linux.WALL: - wopts.NonCloneTasks = true - wopts.CloneTasks = true - default: - return 0, syscall.EINVAL + if err := parseCommonWaitOptions(&wopts, options); err != nil { + return 0, err } if options&linux.WUNTRACED != 0 { wopts.Events |= kernel.EventChildGroupStop } - if options&linux.WCONTINUED != 0 { - wopts.Events |= kernel.EventGroupContinue - } - if options&linux.WNOHANG == 0 { - wopts.BlockInterruptErr = kernel.ERESTARTSYS - } - if options&linux.WNOTHREAD == 0 { - wopts.SiblingChildren = true - } wr, err := t.Wait(&wopts) if err != nil { @@ -281,16 +290,15 @@ func Waitid(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal options := int(args[3].Uint()) rusageAddr := args[4].Pointer() - if options&^(linux.WNOHANG|linux.WEXITED|linux.WSTOPPED|linux.WCONTINUED|linux.WNOWAIT|linux.WNOTHREAD) != 0 { + if options&^(linux.WNOHANG|linux.WEXITED|linux.WSTOPPED|linux.WCONTINUED|linux.WNOWAIT|linux.WNOTHREAD|linux.WALL|linux.WCLONE) != 0 { return 0, nil, syscall.EINVAL } if options&(linux.WEXITED|linux.WSTOPPED|linux.WCONTINUED) == 0 { return 0, nil, syscall.EINVAL } wopts := kernel.WaitOptions{ - NonCloneTasks: true, - Events: kernel.EventTraceeStop, - ConsumeEvent: options&linux.WNOWAIT == 0, + Events: kernel.EventTraceeStop, + ConsumeEvent: options&linux.WNOWAIT == 0, } switch idtype { case linux.P_ALL: @@ -301,21 +309,16 @@ func Waitid(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal default: return 0, nil, syscall.EINVAL } + + if err := parseCommonWaitOptions(&wopts, options); err != nil { + return 0, nil, err + } if options&linux.WEXITED != 0 { wopts.Events |= kernel.EventExit } if options&linux.WSTOPPED != 0 { wopts.Events |= kernel.EventChildGroupStop } - if options&linux.WCONTINUED != 0 { - wopts.Events |= kernel.EventGroupContinue - } - if options&linux.WNOHANG == 0 { - wopts.BlockInterruptErr = kernel.ERESTARTSYS - } - if options&linux.WNOTHREAD == 0 { - wopts.SiblingChildren = true - } wr, err := t.Wait(&wopts) if err != nil { diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc index da3b97828..f413ee6ae 100644 --- a/test/syscalls/linux/wait.cc +++ b/test/syscalls/linux/wait.cc @@ -233,18 +233,14 @@ TEST_P(WaitAnyChildTest, ForkAndClone) { // Return immediately if no child has exited. TEST_P(WaitAnyChildTest, WaitWNOHANG) { - EXPECT_THAT( - WaitAnyWithOptions(0, WNOHANG), - PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitAnyWithOptions(0, WNOHANG), + PosixErrorIs(ECHILD, ::testing::_)); } // Bad options passed TEST_P(WaitAnyChildTest, BadOption) { - EXPECT_THAT( - WaitAnyWithOptions(0, 123456), - PosixErrorIs(EINVAL, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitAnyWithOptions(0, 123456), + PosixErrorIs(EINVAL, ::testing::_)); } TEST_P(WaitAnyChildTest, WaitedChildRusage) { @@ -295,9 +291,7 @@ TEST_P(WaitAnyChildTest, IgnoredChildRusage) { pid_t child; ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)), SyscallSucceeds()); - ASSERT_THAT(WaitAny(0), PosixErrorIs(ECHILD, ::testing::AnyOf( - ::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + ASSERT_THAT(WaitAny(0), PosixErrorIs(ECHILD, ::testing::_)); const absl::Duration end = absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC)); EXPECT_GE(end - start, kSpin - kSpinGrace); @@ -501,10 +495,8 @@ TEST_P(WaitSpecificChildTest, SiblingChildrenWNOTHREAD) { mu.Await(absl::Condition(&ready)); // This thread can't wait on child. - EXPECT_THAT( - WaitForWithOptions(child, __WNOTHREAD, 0), - PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitForWithOptions(child, __WNOTHREAD, 0), + PosixErrorIs(ECHILD, ::testing::_)); // Keep the sibling alive until after we've waited so the child isn't // reparented. @@ -538,10 +530,7 @@ TEST_P(WaitSpecificChildTest, CloneNoSIGCHLD) { int child; ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); - EXPECT_THAT( - WaitFor(child, 0), - PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_)); } // Waiting after the child has already exited returns immediately. @@ -571,10 +560,7 @@ TEST_P(WaitSpecificChildTest, CloneThread) { ASSERT_THAT(child = CloneAndExit(15, stack, CLONE_THREAD), SyscallSucceeds()); auto start = absl::Now(); - EXPECT_THAT( - WaitFor(child, 0), - PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_)); // Ensure wait4 didn't block. EXPECT_LE(absl::Now() - start, absl::Seconds(10)); @@ -584,12 +570,81 @@ TEST_P(WaitSpecificChildTest, CloneThread) { absl::SleepFor(absl::Seconds(5)); } +// A child that does not send a SIGCHLD on exit may be waited on with +// the __WCLONE flag. +TEST_P(WaitSpecificChildTest, CloneWCLONE) { + // Linux added WCLONE support to waitid(2) in + // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to + // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. + // + // Skip the test if it isn't supported yet. + if (Sysno() == SYS_waitid) { + int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE); + SKIP_IF(ret < 0 && errno == EINVAL); + } + + uintptr_t stack; + ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); + auto free = + Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); + + int child; + ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); + + EXPECT_NO_ERRNO(WaitForWithOptions(child, __WCLONE, 0)); +} + +// A forked child cannot be waited on with WCLONE. +TEST_P(WaitSpecificChildTest, ForkWCLONE) { + // Linux added WCLONE support to waitid(2) in + // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to + // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. + // + // Skip the test if it isn't supported yet. + if (Sysno() == SYS_waitid) { + int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE); + SKIP_IF(ret < 0 && errno == EINVAL); + } + + pid_t child; + ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); + + EXPECT_THAT(WaitForWithOptions(child, WNOHANG | __WCLONE, 0), + PosixErrorIs(ECHILD, ::testing::_)); + + EXPECT_NO_ERRNO(WaitFor(child, 0)); +} + +// Any type of child can be waited on with WALL. +TEST_P(WaitSpecificChildTest, WALL) { + // Linux added WALL support to waitid(2) in + // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to + // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. + // + // Skip the test if it isn't supported yet. + if (Sysno() == SYS_waitid) { + int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WALL); + SKIP_IF(ret < 0 && errno == EINVAL); + } + + pid_t child; + ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); + + EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0)); + + uintptr_t stack; + ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); + auto free = + Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); + + ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); + + EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0)); +} + // Return ECHILD for bad child. TEST_P(WaitSpecificChildTest, BadChild) { - EXPECT_THAT( - WaitFor(42, 0), - PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"), - ::testing::StrEq("wait4")))); + EXPECT_THAT(WaitFor(42, 0), PosixErrorIs(ECHILD, ::testing::_)); } // Wait for a child process that only exits after calling execve(2) from a @@ -694,21 +749,6 @@ TEST(WaitTest, SignalExit) { EXPECT_EQ(SIGKILL, WTERMSIG(status)); } -// A child that does not send a SIGCHLD on exit may be waited on with -// the __WCLONE flag. -TEST(WaitTest, CloneWCLONE) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); - - EXPECT_THAT(Wait4(child, nullptr, __WCLONE, nullptr), - SyscallSucceedsWithValue(child)); -} - // waitid requires at least one option. TEST(WaitTest, WaitidOptions) { EXPECT_THAT(Waitid(P_ALL, 0, nullptr, 0), SyscallFailsWithErrno(EINVAL)); |