diff options
Diffstat (limited to 'test/syscalls/linux/kill.cc')
-rw-r--r-- | test/syscalls/linux/kill.cc | 382 |
1 files changed, 0 insertions, 382 deletions
diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc deleted file mode 100644 index 18ad923b8..000000000 --- a/test/syscalls/linux/kill.cc +++ /dev/null @@ -1,382 +0,0 @@ -// 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 <errno.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <cerrno> -#include <csignal> - -#include "gtest/gtest.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" - -DEFINE_int32(scratch_uid, 65534, "scratch UID"); -DEFINE_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))); - - int uid = FLAGS_scratch_uid; - int gid = 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; - - int uid = FLAGS_scratch_uid; - int gid = 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 |