blob: 70df032e078d85c144973265755bc0ca936d7f7f [file] [log] [blame]
// Copyright (c) 2012 The Chromium 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 "content/browser/zygote_host/zygote_host_impl_linux.h"
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "base/allocator/allocator_extension.h"
#include "base/files/file_enumerator.h"
#include "base/posix/unix_domain_socket.h"
#include "base/process/kill.h"
#include "base/process/memory.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/sandbox_host_linux.h"
#include "content/common/sandbox_linux/sandbox_linux.h"
#include "content/common/zygote_commands_linux.h"
#include "content/public/common/common_sandbox_support_linux.h"
#include "content/public/common/content_switches.h"
#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "sandbox/linux/suid/client/setuid_sandbox_host.h"
#include "sandbox/linux/suid/common/sandbox.h"
namespace content {
namespace {
// Receive a fixed message on fd and return the sender's PID.
// Returns true if the message received matches the expected message.
bool ReceiveFixedMessage(int fd,
const char* expect_msg,
size_t expect_len,
base::ProcessId* sender_pid) {
// Allocate an extra byte of buffer space so we can check that we received
// exactly |expect_len| bytes, and the message wasn't just truncated to fit.
char buf[expect_len + 1];
std::vector<base::ScopedFD> fds_vec;
const ssize_t len = base::UnixDomainSocket::RecvMsgWithPid(
fd, buf, sizeof(buf), &fds_vec, sender_pid);
if (static_cast<size_t>(len) != expect_len)
return false;
if (memcmp(buf, expect_msg, expect_len) != 0)
return false;
if (!fds_vec.empty())
return false;
return true;
}
} // namespace
// static
ZygoteHost* ZygoteHost::GetInstance() {
return ZygoteHostImpl::GetInstance();
}
ZygoteHostImpl::ZygoteHostImpl()
: use_namespace_sandbox_(false),
use_suid_sandbox_(false),
use_suid_sandbox_for_adj_oom_score_(false),
sandbox_binary_(),
zygote_pids_lock_(),
zygote_pids_() {}
ZygoteHostImpl::~ZygoteHostImpl() {}
// static
ZygoteHostImpl* ZygoteHostImpl::GetInstance() {
return base::Singleton<ZygoteHostImpl>::get();
}
void ZygoteHostImpl::Init(const base::CommandLine& command_line) {
if (command_line.HasSwitch(switches::kNoSandbox)) {
return;
}
// Exit early if running as root without --no-sandbox. See crbug.com/638180.
// When running as root with the sandbox enabled, the browser process
// crashes on zygote initialization. Running as root with the sandbox
// is not supported, and if Chrome were able to display UI it would be showing
// an error message. With the zygote crashing it doesn't even get to that,
// so print an error message on the console.
uid_t uid = 0;
gid_t gid = 0;
if (!sandbox::Credentials::GetRESIds(&uid, &gid) || uid == 0) {
LOG(ERROR) << "Running as root without --" << switches::kNoSandbox
<< " is not supported. See https://crbug.com/638180.";
exit(EXIT_FAILURE);
}
{
std::unique_ptr<sandbox::SetuidSandboxHost> setuid_sandbox_host(
sandbox::SetuidSandboxHost::Create());
sandbox_binary_ = setuid_sandbox_host->GetSandboxBinaryPath().value();
}
if (!command_line.HasSwitch(switches::kDisableNamespaceSandbox) &&
sandbox::Credentials::CanCreateProcessInNewUserNS()) {
use_namespace_sandbox_ = true;
#if defined(OS_CHROMEOS)
// Chrome OS has a kernel patch that restricts oom_score_adj. See
// crbug.com/576409 for details.
if (!sandbox_binary_.empty()) {
use_suid_sandbox_for_adj_oom_score_ = true;
} else {
LOG(ERROR) << "SUID sandbox binary is missing. Won't be able to adjust "
"OOM scores.";
}
#endif
} else if (!command_line.HasSwitch(switches::kDisableSetuidSandbox) &&
!sandbox_binary_.empty()) {
use_suid_sandbox_ = true;
// Use the SUID sandbox for adjusting OOM scores when we are using
// the setuid sandbox. This is needed beacuse the processes are
// non-dumpable, so /proc/pid/oom_score_adj can only be written by
// root.
use_suid_sandbox_for_adj_oom_score_ = use_suid_sandbox_;
} else {
LOG(FATAL)
<< "No usable sandbox! Update your kernel or see "
"https://chromium.googlesource.com/chromium/src/+/master/"
"docs/linux_suid_sandbox_development.md for more information on "
"developing with the SUID sandbox. "
"If you want to live dangerously and need an immediate workaround, "
"you can try using --"
<< switches::kNoSandbox << ".";
}
}
void ZygoteHostImpl::AddZygotePid(pid_t pid) {
base::AutoLock lock(zygote_pids_lock_);
zygote_pids_.insert(pid);
}
bool ZygoteHostImpl::IsZygotePid(pid_t pid) {
base::AutoLock lock(zygote_pids_lock_);
return zygote_pids_.find(pid) != zygote_pids_.end();
}
void ZygoteHostImpl::SetRendererSandboxStatus(int status) {
renderer_sandbox_status_ = status;
}
int ZygoteHostImpl::GetRendererSandboxStatus() const {
return renderer_sandbox_status_;
}
pid_t ZygoteHostImpl::LaunchZygote(base::CommandLine* cmd_line,
base::ScopedFD* control_fd) {
int fds[2];
CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
CHECK(base::UnixDomainSocket::EnableReceiveProcessId(fds[0]));
base::LaunchOptions options;
options.fds_to_remap.push_back(std::make_pair(fds[1], kZygoteSocketPairFd));
// Start up the sandbox host process and get the file descriptor for the
// sandboxed processes to talk to it.
const int sfd = SandboxHostLinux::GetInstance()->GetChildSocket();
options.fds_to_remap.push_back(std::make_pair(sfd, GetSandboxFD()));
base::ScopedFD dummy_fd;
if (use_suid_sandbox_) {
std::unique_ptr<sandbox::SetuidSandboxHost> sandbox_host(
sandbox::SetuidSandboxHost::Create());
sandbox_host->PrependWrapper(cmd_line);
sandbox_host->SetupLaunchOptions(&options, &dummy_fd);
sandbox_host->SetupLaunchEnvironment();
}
base::Process process =
use_namespace_sandbox_
? sandbox::NamespaceSandbox::LaunchProcess(*cmd_line, options)
: base::LaunchProcess(*cmd_line, options);
CHECK(process.IsValid()) << "Failed to launch zygote process";
dummy_fd.reset();
close(fds[1]);
control_fd->reset(fds[0]);
pid_t pid = process.Pid();
if (use_namespace_sandbox_ || use_suid_sandbox_) {
// The namespace and SUID sandbox will execute the zygote in a new
// PID namespace, and the main zygote process will then fork from
// there. Watch now our elaborate dance to find and validate the
// zygote's PID.
// First we receive a message from the zygote boot process.
base::ProcessId boot_pid;
CHECK(ReceiveFixedMessage(fds[0], kZygoteBootMessage,
sizeof(kZygoteBootMessage), &boot_pid));
// Within the PID namespace, the zygote boot process thinks it's PID 1,
// but its real PID can never be 1. This gives us a reliable test that
// the kernel is translating the sender's PID to our namespace.
CHECK_GT(boot_pid, 1)
<< "Received invalid process ID for zygote; kernel might be too old? "
"See crbug.com/357670 or try using --"
<< switches::kNoSandbox << " to workaround.";
// Now receive the message that the zygote's ready to go, along with the
// main zygote process's ID.
pid_t real_pid;
CHECK(ReceiveFixedMessage(fds[0], kZygoteHelloMessage,
sizeof(kZygoteHelloMessage), &real_pid));
CHECK_GT(real_pid, 1);
if (real_pid != pid) {
// Reap the sandbox.
base::EnsureProcessGetsReaped(pid);
}
pid = real_pid;
}
AddZygotePid(pid);
return pid;
}
#if !defined(OS_OPENBSD)
void ZygoteHostImpl::AdjustRendererOOMScore(base::ProcessHandle pid,
int score) {
// 1) You can't change the oom_score_adj of a non-dumpable process
// (EPERM) unless you're root. Because of this, we can't set the
// oom_adj from the browser process.
//
// 2) We can't set the oom_score_adj before entering the sandbox
// because the zygote is in the sandbox and the zygote is as
// critical as the browser process. Its oom_adj value shouldn't
// be changed.
//
// 3) A non-dumpable process can't even change its own oom_score_adj
// because it's root owned 0644. The sandboxed processes don't
// even have /proc, but one could imagine passing in a descriptor
// from outside.
//
// So, in the normal case, we use the SUID binary to change it for us.
// However, Fedora (and other SELinux systems) don't like us touching other
// process's oom_score_adj (or oom_adj) values
// (https://bugzilla.redhat.com/show_bug.cgi?id=581256).
//
// The offical way to get the SELinux mode is selinux_getenforcemode, but I
// don't want to add another library to the build as it's sure to cause
// problems with other, non-SELinux distros.
//
// So we just check for files in /selinux. This isn't foolproof, but it's not
// bad and it's easy.
static bool selinux;
static bool selinux_valid = false;
if (!selinux_valid) {
const base::FilePath kSelinuxPath("/selinux");
base::FileEnumerator en(kSelinuxPath, false, base::FileEnumerator::FILES);
bool has_selinux_files = !en.Next().empty();
selinux =
has_selinux_files && access(kSelinuxPath.value().c_str(), X_OK) == 0;
selinux_valid = true;
}
if (!use_suid_sandbox_for_adj_oom_score_) {
if (!base::AdjustOOMScore(pid, score))
PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid;
return;
}
if (selinux)
return;
// If heap profiling is running, these processes are not exiting, at least
// on ChromeOS. The easiest thing to do is not launch them when profiling.
// TODO(stevenjb): Investigate further and fix.
if (base::allocator::IsHeapProfilerRunning())
return;
std::vector<std::string> adj_oom_score_cmdline;
adj_oom_score_cmdline.push_back(sandbox_binary_);
adj_oom_score_cmdline.push_back(sandbox::kAdjustOOMScoreSwitch);
adj_oom_score_cmdline.push_back(base::Int64ToString(pid));
adj_oom_score_cmdline.push_back(base::IntToString(score));
// sandbox_helper_process is a setuid binary.
base::LaunchOptions options;
options.allow_new_privs = true;
base::Process sandbox_helper_process =
base::LaunchProcess(adj_oom_score_cmdline, options);
if (sandbox_helper_process.IsValid())
base::EnsureProcessGetsReaped(sandbox_helper_process.Pid());
}
#endif
} // namespace content