blob: caaf20b4a4bfeda10e4b0a844c28906955a5d355 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cros-disks/sandboxed_init.h"
#include <string>
#include <utility>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <base/bind.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/strings/string_number_conversions.h>
#include <base/test/bind.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace cros_disks {
namespace {
// Writes a string to a file descriptor.
void Write(int fd, base::StringPiece s) {
if (!base::WriteFileDescriptor(fd, s))
PLOG(FATAL) << "Cannot write '" << s << "' to file descriptor " << fd;
}
// Reads a string from a file descriptor.
std::string Read(int fd) {
char buffer[PIPE_BUF];
const ssize_t n = HANDLE_EINTR(read(fd, buffer, PIPE_BUF));
if (n < 0)
PLOG(FATAL) << "Cannot read from file descriptor " << fd;
DCHECK_GE(n, 0);
DCHECK_LE(n, PIPE_BUF);
return std::string(buffer, n);
}
// Reads a string from a file descriptor.
std::string Read(const base::ScopedFD& fd) {
return Read(fd.get());
}
class SandboxedInitTest : public testing::Test {
protected:
template <typename F>
void RunUnderInit(F launcher) {
SubprocessPipe in(SubprocessPipe::kParentToChild);
SubprocessPipe out(SubprocessPipe::kChildToParent);
SubprocessPipe ctrl(SubprocessPipe::kChildToParent);
const pid_t pid = fork();
PLOG_IF(FATAL, pid < 0) << "Cannot create 'init' process";
if (pid > 0) {
// In parent process.
pid_ = pid;
in_ = std::move(in.parent_fd);
out_ = std::move(out.parent_fd);
ctrl_ = std::move(ctrl.parent_fd);
termination_pipe_.child_fd.reset();
PCHECK(base::SetNonBlocking(ctrl_.get()));
return;
}
// In 'init' process.
DCHECK_EQ(0, pid);
LOG(INFO) << "The 'init' process started";
// Connect stdin and stdout to the matching pipes.
PCHECK(dup2(in.child_fd.get(), STDIN_FILENO) >= 0);
PCHECK(dup2(out.child_fd.get(), STDOUT_FILENO) >= 0);
// Close pipe ends that are now unused in the 'init' process.
in.child_fd.reset();
in.parent_fd.reset();
out.child_fd.reset();
out.parent_fd.reset();
ctrl.parent_fd.reset();
termination_pipe_.parent_fd.reset();
// Make the 'init' process a child subreaper, so that it adopts the orphaned
// 'daemon' process.
PCHECK(prctl(PR_SET_CHILD_SUBREAPER, 1) >= 0)
<< "Cannot make the 'init' process a child subreaper";
// Sets a signal handler for SIGUSR1. This signal handler doesn't do
// anything, but it is put in place so that SIGUSR1 doesn't terminate the
// 'init' process.
PCHECK(signal(SIGUSR1,
[](int sig) {
RAW_LOG(INFO, "The 'init' process received SIGUSR1");
RAW_CHECK(sig == SIGUSR1);
}) != SIG_ERR)
<< "Cannot set up SIGUSR1 handler";
// Run the main 'init' process loop.
SandboxedInit(base::BindLambdaForTesting(std::move(launcher)),
std::move(ctrl.child_fd),
std::move(termination_pipe_.child_fd))
.Run();
NOTREACHED();
}
// Waits for the 'init' process to terminate if |no_hang == false|. Returns
// the process's exit code, or -1 if the process is still running and |no_hang
// == true|.
int WaitForInit(bool no_hang = false) {
CHECK_LT(0, pid_);
if (no_hang) {
LOG(INFO) << "Checking if 'init' is still running...";
} else {
LOG(INFO) << "Waiting for 'init' process to finish...";
}
int wstatus;
const int ret =
HANDLE_EINTR(waitpid(pid_, &wstatus, no_hang ? WNOHANG : 0));
if (ret < 0)
PLOG(FATAL) << "Cannot wait for the 'init' process PID " << pid_;
if (ret == 0) {
CHECK(no_hang);
LOG(INFO) << "The 'init' process is still running";
return -1;
}
const int exit_code = SandboxedInit::WaitStatusToExitCode(wstatus);
LOG(INFO) << "The 'init' process finished with exit code " << exit_code;
pid_ = -1;
return exit_code;
}
// Waits for the 'launcher' process to finish and returns its exit code.
int WaitForLauncher() {
EXPECT_TRUE(ctrl_.is_valid());
LOG(INFO) << "Waiting for the 'launcher' process to finish...";
const int exit_code = SandboxedInit::WaitForLauncher(&ctrl_);
LOG(INFO) << "The 'launcher' process finished with exit code " << exit_code;
EXPECT_FALSE(ctrl_.is_valid());
return exit_code;
}
// Polls the 'launcher' process. Returns its nonnegative exit code if it has
// already finished, or -1 if it is still running.
int PollLauncher() {
EXPECT_TRUE(ctrl_.is_valid());
LOG(INFO) << "Checking if the 'launcher' process is still running...";
const int exit_code = SandboxedInit::PollLauncher(&ctrl_);
if (exit_code < 0) {
LOG(INFO) << "The 'launcher' process is still running";
EXPECT_TRUE(ctrl_.is_valid());
} else {
LOG(INFO) << "The 'launcher' process finished with exit code "
<< exit_code;
EXPECT_FALSE(ctrl_.is_valid());
}
return exit_code;
}
// PID of the 'init' process.
pid_t pid_ = -1;
// Parent-side of the pipes.
base::ScopedFD in_, out_, ctrl_;
// Because one of the tests verifies that closing the parent's end of
// termination_pipe_ before the init process is even started, we must make it
// a member of the test class instead of creating it with the other pipes.
SubprocessPipe termination_pipe_{SubprocessPipe::kParentToChild};
};
} // namespace
TEST_F(SandboxedInitTest, LauncherTerminatesSuccessfully) {
RunUnderInit([]() { return 0; });
EXPECT_EQ(0, WaitForLauncher());
EXPECT_EQ(0, WaitForInit());
}
TEST_F(SandboxedInitTest, LauncherTerminatesWithError) {
RunUnderInit([]() { return 12; });
EXPECT_EQ(12, WaitForLauncher());
EXPECT_EQ(12, WaitForInit());
}
TEST_F(SandboxedInitTest, LauncherCrashes) {
RunUnderInit([]() {
raise(SIGALRM);
pause();
return 35;
});
EXPECT_EQ(128 + SIGALRM, WaitForLauncher());
EXPECT_EQ(128 + SIGALRM, WaitForInit());
}
TEST_F(SandboxedInitTest, CtrlPipeIsClosed) {
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
// Signal that the 'launcher' process was unblocked
Write(STDOUT_FILENO, "Received: " + s);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
// Close reading end of control pipe.
EXPECT_TRUE(ctrl_.is_valid());
ctrl_.reset();
EXPECT_FALSE(ctrl_.is_valid());
// Unblock the 'launcher' process.
Write(in_.get(), "Continue");
// Wait for the 'launcher' process to continue.
EXPECT_EQ("Received: Continue", Read(out_));
EXPECT_EQ("", Read(out_));
// Wait for the 'init' process to finish.
EXPECT_EQ(12, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsClosed) {
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
// Kill init by closing the write end of the termination pipe.
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
termination_pipe_.parent_fd.reset();
EXPECT_FALSE(termination_pipe_.parent_fd.is_valid());
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsWrittenTo) {
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
// Kill init by closing the write end of the termination pipe.
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
Write(termination_pipe_.parent_fd.get(), "Test");
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsWrittenToAndClosed) {
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
// Kill init by closing the write end of the termination pipe.
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
Write(termination_pipe_.parent_fd.get(), "Test");
termination_pipe_.parent_fd.reset();
EXPECT_FALSE(termination_pipe_.parent_fd.is_valid());
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsClosedBeforeInitStarts) {
// Request init to be terminated by closing the write end of the
// termination pipe (before init is even started).
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
termination_pipe_.parent_fd.reset();
EXPECT_FALSE(termination_pipe_.parent_fd.is_valid());
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsWrittenToBeforeInitStarts) {
// Request init to be terminated by closing the write end of the
// termination pipe (before init is even started).
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
Write(termination_pipe_.parent_fd.get(), "Test");
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, TerminationPipeIsWrittenToAndClosedBeforeInitStarts) {
// Request init to be terminated by closing the write end of the
// termination pipe (before init is even started).
EXPECT_TRUE(termination_pipe_.parent_fd.is_valid());
Write(termination_pipe_.parent_fd.get(), "Test");
termination_pipe_.parent_fd.reset();
EXPECT_FALSE(termination_pipe_.parent_fd.is_valid());
RunUnderInit([]() {
// Signal that the 'launcher' process started
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked
const std::string s = Read(STDIN_FILENO);
return 12;
});
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGKILL, WaitForInit());
}
TEST_F(SandboxedInitTest, LauncherWritesToStdOut) {
RunUnderInit([]() {
Write(STDOUT_FILENO, "Sent to stdout");
Write(STDERR_FILENO, "This message is written to stderr\n");
LOG(INFO) << "This is a LOG(INFO) message";
LOG(WARNING) << "This is a LOG(WARNING) message";
LOG(ERROR) << "This is a LOG(ERROR) message";
return 12;
});
EXPECT_EQ("Sent to stdout", Read(out_));
EXPECT_EQ(12, WaitForLauncher());
EXPECT_EQ(12, WaitForInit());
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, LauncherStopsAndContinues) {
RunUnderInit([]() {
// Signal that the 'launcher' process started.
Write(STDOUT_FILENO, base::NumberToString(getpid()));
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'launcher' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 12;
});
// Wait for the 'launcher' process to start.
pid_t launcher_pid;
EXPECT_TRUE(base::StringToInt(Read(out_), &launcher_pid));
EXPECT_LT(0, launcher_pid);
LOG(INFO) << "The 'launcher' process has PID " << launcher_pid;
// Send SIGSTOP to the 'launcher' process.
EXPECT_EQ(0, kill(launcher_pid, SIGSTOP));
usleep(100'000);
EXPECT_EQ(-1, PollLauncher());
// Try to unblock the 'launcher' process by writing to its stdin. The
// 'launcher' process won't be able to react since it is still stopped.
Write(in_.get(), "Continue");
usleep(100'000);
// The 'launcher' process is still stopped.
EXPECT_EQ(-1, PollLauncher());
// Send SIGCONT to wake up the 'launcher' process.
EXPECT_EQ(0, kill(launcher_pid, SIGCONT));
// Wait for the 'launcher' process to continue.
EXPECT_EQ("Received: Continue", Read(out_));
EXPECT_EQ(12, WaitForLauncher());
// Wait for the 'init' process to finish.
EXPECT_EQ(12, WaitForInit());
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, InitUndisturbedBySignal) {
RunUnderInit([]() {
// Signal that the 'launcher' process started.
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'launcher' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
for (int i = 0; i < 5; ++i) {
// Send SIGUSR1 to the 'init' process. Because of the signal handler set in
// RunUnderInit(), this signal shouldn't disturb or crash the 'init'
// process.
EXPECT_EQ(0, kill(pid_, SIGUSR1));
// Send SIGPIPE to the 'init' process. This signal should be ignored, and it
// shouldn't disturb or crash the 'init' process.
EXPECT_EQ(0, kill(pid_, SIGPIPE));
// Send SIGIO to the 'init' process. This signal should be ignored, and it
// shouldn't disturb or crash the 'init' process.
EXPECT_EQ(0, kill(pid_, SIGIO));
usleep(100'000);
}
// Unblock the 'launcher' process.
Write(in_.get(), "Continue");
// Wait for the 'launcher' process to continue.
EXPECT_EQ("Received: Continue", Read(out_));
EXPECT_EQ(12, WaitForLauncher());
// Wait for the 'init' process to finish.
EXPECT_EQ(12, WaitForInit());
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, InitCrashesWhileLauncherIsRunning) {
RunUnderInit([]() {
// Signal that the 'launcher' process started.
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'launcher' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 12;
});
// Wait for the 'launcher' process to start.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ(-1, PollLauncher());
// Send SIGALRM to crash the 'init' process.
EXPECT_EQ(kill(pid_, SIGALRM), 0);
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGALRM, WaitForInit());
// Since the 'init' process is not monitoring the 'launcher' process anymore,
// it reports the 'launcher' process as having been terminated by SIGKILL
// (which would have happened if this 'init' process was running in a PID
// namespace).
EXPECT_EQ(128 + SIGKILL, WaitForLauncher());
// Actually, in this test, the 'launcher' process has been orphaned, but it is
// still alive. Unblock the 'launcher' process and wait for it to finish.
Write(in_.get(), "Continue");
EXPECT_EQ("Received: Continue", Read(out_));
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, InitCrashesWhileDaemonIsRunning) {
RunUnderInit([]() {
// Launcher process starts a 'daemon' process.
if (daemon(0, 1) < 0)
PLOG(FATAL) << "Cannot daemon";
// Signal that the 'daemon' process started.
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'daemon' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 12;
});
// The 'launcher' process should terminate first.
EXPECT_EQ(0, WaitForLauncher());
// Wait for 'daemon' process to start.
EXPECT_EQ("Begin", Read(out_));
// The 'init' process should still be there, having adopted the 'daemon'
// process.
EXPECT_EQ(-1, WaitForInit(true));
// Send SIGALRM to crash the 'init' process.
EXPECT_EQ(kill(pid_, SIGALRM), 0);
// Wait for the 'init' process to finish.
EXPECT_EQ(128 + SIGALRM, WaitForInit());
// If the 'init' process was in a PID namespace, the 'daemon' process would
// have been killed by a SIGKILL sent by the kernel. But, in this test, the
// 'daemon' process has simply been orphaned, and it is still alive. Unblock
// the 'daemon' process and wait for it to finish.
Write(in_.get(), "Continue");
EXPECT_EQ("Received: Continue", Read(out_));
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, DaemonBlocksAndTerminates) {
RunUnderInit([]() {
// Launcher process starts a 'daemon' process.
if (daemon(0, 1) < 0)
PLOG(FATAL) << "Cannot daemon";
// Signal that the 'daemon' process started.
Write(STDOUT_FILENO, "Begin");
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'daemon' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 42;
});
// The 'launcher' process should terminate first.
EXPECT_EQ(0, WaitForLauncher());
// Wait for 'daemon' process to start.
EXPECT_EQ("Begin", Read(out_));
// The 'init' process should still be there, having adopted the 'daemon'
// process.
EXPECT_EQ(-1, WaitForInit(true));
// Unblock the 'daemon' process.
Write(in_.get(), "Continue");
// Wait for 'daemon' process to continue and finish.
EXPECT_EQ("Received: Continue", Read(out_));
// Wait for the 'init' process to finish and relay the 'daemon' process exit
// code.
EXPECT_EQ(42, WaitForInit());
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, DaemonCrashes) {
RunUnderInit([]() {
// Launcher process starts a 'daemon' process.
if (daemon(0, 1) < 0)
PLOG(FATAL) << "Cannot daemon";
// Signal that the 'daemon' process started.
Write(STDOUT_FILENO, "Begin");
// Raise a signal that should terminate this 'daemon' process.
raise(SIGALRM);
// Wait to be unblocked.
const std::string s = Read(STDIN_FILENO);
// Signal that the 'daemon' process was unblocked.
Write(STDOUT_FILENO, "Received: " + s);
return 42;
});
// The 'launcher' process should terminate first.
EXPECT_EQ(0, WaitForLauncher());
// Wait for the 'init' process to finish and relay the 'daemon' process exit
// code.
EXPECT_EQ(128 + SIGALRM, WaitForInit());
// The 'daemon' process should have only written these lines to its stdout.
EXPECT_EQ("Begin", Read(out_));
EXPECT_EQ("", Read(out_));
}
TEST_F(SandboxedInitTest, DISABLED_InitRelaysSigTerm) {
RunUnderInit([]() {
// Launcher process starts a 'daemon' process.
if (daemon(0, 1) < 0)
PLOG(FATAL) << "Cannot daemon";
// Signal that the 'daemon' process started.
Write(STDOUT_FILENO, "Begin");
// Set SIGTERM handler.
static bool terminate = false;
const auto term_handler = [](int sig) { terminate = true; };
CHECK_NE(SIG_ERR, signal(SIGTERM, term_handler));
while (!terminate) {
LOG(INFO) << "Daemon is waiting for a signal...";
pause();
LOG(INFO) << "Daemon received a signal";
}
// Signal that the 'daemon' process was unblocked.
LOG(INFO) << "Daemon is finishing...";
Write(STDOUT_FILENO, "End");
return 43;
});
// The 'launcher' process should terminate first.
EXPECT_EQ(0, WaitForLauncher());
// Wait for 'daemon' process to start.
EXPECT_EQ("Begin", Read(out_));
// The 'init' process should still be there, having adopted the 'daemon'
// process.
EXPECT_EQ(-1, WaitForInit(true));
// Send SIGTERM to the 'init' process.
if (kill(pid_, SIGTERM) < 0)
PLOG(FATAL) << "Cannot send SIGKILL to 'init' process PID " << pid_;
// The SIGTERM signal should be relayed by 'init' to the 'daemon' process, and
// that should gracefully terminate the daemon, and the 'init' process.
EXPECT_EQ("End", Read(out_));
// Wait for the 'init' process to finish and relay the 'daemon' process exit
// code.
EXPECT_EQ(43, WaitForInit());
EXPECT_EQ("", Read(out_));
}
} // namespace cros_disks