2018-12-10 22:41:40 +00:00
|
|
|
// 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;
|
2019-01-11 22:47:45 +00:00
|
|
|
auto cleanup = ScopedSigaction(SIGWINCH, sa).ValueOrDie();
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// Indicate to the parent that we're ready.
|
|
|
|
write_fd.reset();
|
|
|
|
|
|
|
|
// Wait until we get the signal from the parent.
|
|
|
|
while (true) {
|
|
|
|
pause();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(pid, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(fake_pid, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(pid, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(pid, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(child, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
pid_t other_child = fork();
|
|
|
|
if (other_child == 0) {
|
|
|
|
while (true) {
|
|
|
|
pause();
|
|
|
|
}
|
|
|
|
}
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(other_child, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(pid, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(stopped_child, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:47:45 +00:00
|
|
|
ASSERT_THAT(stopped_child, SyscallSucceeds());
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
// 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
|