summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/kill.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/kill.cc')
-rw-r--r--test/syscalls/linux/kill.cc383
1 files changed, 383 insertions, 0 deletions
diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc
new file mode 100644
index 000000000..db29bd59c
--- /dev/null
+++ b/test/syscalls/linux/kill.cc
@@ -0,0 +1,383 @@
+// 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/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_t, scratch_uid, 65534, "scratch UID");
+ABSL_FLAG(int32_t, 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