gvisor/test/syscalls/linux/kill.cc

382 lines
11 KiB
C++

// Copyright 2018 Google LLC
//
// 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;
auto cleanup = ScopedSigaction(SIGWINCH, sa).ValueOrDie();
// 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