// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/logging.h" #include "test/util/signal_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" ABSL_FLAG(int32, scratch_uid, 65534, "scratch UID"); ABSL_FLAG(int32, scratch_gid, 65534, "scratch GID"); using ::testing::Ge; namespace gvisor { namespace testing { namespace { TEST(KillTest, CanKillValidPid) { // If pid is positive, then signal sig is sent to the process with the ID // specified by pid. EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds()); // If pid equals 0, then sig is sent to every process in the process group of // the calling process. EXPECT_THAT(kill(0, 0), SyscallSucceeds()); ScopedThread([] { EXPECT_THAT(kill(gettid(), 0), SyscallSucceeds()); }); } void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); } // If pid equals -1, then sig is sent to every process for which the calling // process has permission to send signals, except for process 1 (init). TEST(KillTest, CanKillAllPIDs) { int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_fd(pipe_fds[0]); FileDescriptor write_fd(pipe_fds[1]); pid_t pid = fork(); if (pid == 0) { read_fd.reset(); struct sigaction sa; sa.sa_sigaction = SigHandler; sigfillset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; TEST_PCHECK(sigaction(SIGWINCH, &sa, nullptr) == 0); MaybeSave(); // Indicate to the parent that we're ready. write_fd.reset(); // Wait until we get the signal from the parent. while (true) { pause(); } } ASSERT_THAT(pid, SyscallSucceeds()); write_fd.reset(); // Wait for the child to indicate that it's unmasked the signal by closing // the write end. char buf; ASSERT_THAT(ReadFd(read_fd.get(), &buf, 1), SyscallSucceedsWithValue(0)); // Signal the child and wait for it to die with status 0, indicating that // it got the expected signal. EXPECT_THAT(kill(-1, SIGWINCH), SyscallSucceeds()); int status; ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceedsWithValue(pid)); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); } TEST(KillTest, CannotKillInvalidPID) { // We need an unused pid to verify that kill fails when given one. // // There is no way to guarantee that a PID is unused, but the PID of a // recently exited process likely won't be reused soon. pid_t fake_pid = fork(); if (fake_pid == 0) { _exit(0); } ASSERT_THAT(fake_pid, SyscallSucceeds()); int status; ASSERT_THAT(RetryEINTR(waitpid)(fake_pid, &status, 0), SyscallSucceedsWithValue(fake_pid)); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); EXPECT_THAT(kill(fake_pid, 0), SyscallFailsWithErrno(ESRCH)); } TEST(KillTest, CannotUseInvalidSignal) { EXPECT_THAT(kill(getpid(), 200), SyscallFailsWithErrno(EINVAL)); } TEST(KillTest, CanKillRemoteProcess) { pid_t pid = fork(); if (pid == 0) { while (true) { pause(); } } ASSERT_THAT(pid, SyscallSucceeds()); EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); int status; ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceedsWithValue(pid)); EXPECT_TRUE(WIFSIGNALED(status)); EXPECT_EQ(SIGKILL, WTERMSIG(status)); } TEST(KillTest, CanKillOwnProcess) { EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds()); } // Verify that you can kill a process even using a tid from a thread other than // the group leader. TEST(KillTest, CannotKillTid) { pid_t tid; bool tid_available = false; bool finished = false; absl::Mutex mu; ScopedThread t([&] { mu.Lock(); tid = gettid(); tid_available = true; mu.Await(absl::Condition(&finished)); mu.Unlock(); }); mu.LockWhen(absl::Condition(&tid_available)); EXPECT_THAT(kill(tid, 0), SyscallSucceeds()); finished = true; mu.Unlock(); } TEST(KillTest, SetPgid) { for (int i = 0; i < 10; i++) { // The following in the normal pattern for creating a new process group. // Both the parent and child process will call setpgid in order to avoid any // race conditions. We do this ten times to catch races. pid_t pid = fork(); if (pid == 0) { setpgid(0, 0); while (true) { pause(); } } ASSERT_THAT(pid, SyscallSucceeds()); // Set the child's group and exit. ASSERT_THAT(setpgid(pid, pid), SyscallSucceeds()); EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); int status; EXPECT_THAT(RetryEINTR(waitpid)(-pid, &status, 0), SyscallSucceedsWithValue(pid)); EXPECT_TRUE(WIFSIGNALED(status)); EXPECT_EQ(SIGKILL, WTERMSIG(status)); } } TEST(KillTest, ProcessGroups) { // Fork a new child. // // other_child is used as a placeholder process. We use this PID as our "does // not exist" process group to ensure some amount of safety. (It is still // possible to violate this assumption, but extremely unlikely.) pid_t child = fork(); if (child == 0) { while (true) { pause(); } } ASSERT_THAT(child, SyscallSucceeds()); pid_t other_child = fork(); if (other_child == 0) { while (true) { pause(); } } ASSERT_THAT(other_child, SyscallSucceeds()); // Ensure the kill does not succeed without the new group. EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH)); // Put the child in its own process group. ASSERT_THAT(setpgid(child, child), SyscallSucceeds()); // This should be not allowed: you can only create a new group with the same // id or join an existing one. The other_child group should not exist. ASSERT_THAT(setpgid(child, other_child), SyscallFailsWithErrno(EPERM)); // Done with other_child; kill it. EXPECT_THAT(kill(other_child, SIGKILL), SyscallSucceeds()); int status; EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0), SyscallSucceeds()); // Linux returns success for the no-op call. ASSERT_THAT(setpgid(child, child), SyscallSucceeds()); // Kill the child's process group. ASSERT_THAT(kill(-child, SIGKILL), SyscallSucceeds()); // Wait on the process group; ensure that the signal was as expected. EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0), SyscallSucceedsWithValue(child)); EXPECT_TRUE(WIFSIGNALED(status)); EXPECT_EQ(SIGKILL, WTERMSIG(status)); // Try to kill the process group again; ensure that the wait fails. EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH)); EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0), SyscallFailsWithErrno(ECHILD)); } TEST(KillTest, ChildDropsPrivsCannotKill) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); const int uid = absl::GetFlag(FLAGS_scratch_uid); const int gid = absl::GetFlag(FLAGS_scratch_gid); // Create the child that drops privileges and tries to kill the parent. pid_t pid = fork(); if (pid == 0) { TEST_PCHECK(setresgid(gid, gid, gid) == 0); MaybeSave(); TEST_PCHECK(setresuid(uid, uid, uid) == 0); MaybeSave(); // setresuid should have dropped CAP_KILL. Make sure. TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie()); // Try to kill parent with every signal-sending syscall possible. pid_t parent = getppid(); TEST_CHECK(kill(parent, SIGKILL) < 0); TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno"); MaybeSave(); TEST_CHECK(tgkill(parent, parent, SIGKILL) < 0); TEST_PCHECK_MSG(errno == EPERM, "tgkill failed with wrong errno"); MaybeSave(); TEST_CHECK(syscall(SYS_tkill, parent, SIGKILL) < 0); TEST_PCHECK_MSG(errno == EPERM, "tkill failed with wrong errno"); MaybeSave(); siginfo_t uinfo; uinfo.si_code = -1; // SI_QUEUE (allowed). TEST_CHECK(syscall(SYS_rt_sigqueueinfo, parent, SIGKILL, &uinfo) < 0); TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno"); MaybeSave(); TEST_CHECK(syscall(SYS_rt_tgsigqueueinfo, parent, parent, SIGKILL, &uinfo) < 0); TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno"); MaybeSave(); _exit(0); } ASSERT_THAT(pid, SyscallSucceeds()); int status; EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceedsWithValue(pid)); EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << "status = " << status; } TEST(KillTest, CanSIGCONTSameSession) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); pid_t stopped_child = fork(); if (stopped_child == 0) { raise(SIGSTOP); _exit(0); } ASSERT_THAT(stopped_child, SyscallSucceeds()); // Put the child in its own process group. The child and parent process // groups also share a session. ASSERT_THAT(setpgid(stopped_child, stopped_child), SyscallSucceeds()); // Make sure child stopped. int status; EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, WUNTRACED), SyscallSucceedsWithValue(stopped_child)); EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) << "status " << status; const int uid = absl::GetFlag(FLAGS_scratch_uid); const int gid = absl::GetFlag(FLAGS_scratch_gid); // Drop privileges only in child process, or else this parent process won't be // able to open some log files after the test ends. pid_t other_child = fork(); if (other_child == 0) { // Drop privileges. TEST_PCHECK(setresgid(gid, gid, gid) == 0); MaybeSave(); TEST_PCHECK(setresuid(uid, uid, uid) == 0); MaybeSave(); // setresuid should have dropped CAP_KILL. TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie()); // Child 2 and child should now not share a thread group and any UIDs. // Child 2 should have no privileges. That means any signal other than // SIGCONT should fail. TEST_CHECK(kill(stopped_child, SIGKILL) < 0); TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno"); MaybeSave(); TEST_PCHECK(kill(stopped_child, SIGCONT) == 0); MaybeSave(); _exit(0); } ASSERT_THAT(stopped_child, SyscallSucceeds()); // Make sure child exited normally. EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, 0), SyscallSucceedsWithValue(stopped_child)); EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << "status " << status; // Make sure other_child exited normally. EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0), SyscallSucceedsWithValue(other_child)); EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << "status " << status; } } // namespace } // namespace testing } // namespace gvisor