blob: 27b2222e57ba6405584be34fd45506b7860c10f6 [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 "components/nacl/zygote/nacl_fork_delegate_linux.h"
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <memory>
#include <set>
#include "base/command_line.h"
#include "base/cpu.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/pickle.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/global_descriptors.h"
#include "base/posix/unix_domain_socket.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "build/build_config.h"
#include "components/nacl/common/nacl_nonsfi_util.h"
#include "components/nacl/common/nacl_paths.h"
#include "components/nacl/common/nacl_switches.h"
#include "components/nacl/loader/nacl_helper_linux.h"
#include "content/public/common/content_descriptors.h"
#include "content/public/common/content_switches.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
#include "sandbox/linux/suid/client/setuid_sandbox_host.h"
#include "sandbox/linux/suid/common/sandbox.h"
#include "services/service_manager/sandbox/switches.h"
namespace {
// Note these need to match up with their counterparts in nacl_helper_linux.c
// and nacl_helper_bootstrap_linux.c.
const char kNaClHelperReservedAtZero[] =
"--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
// This is an environment variable which controls which (if any) other
// environment variables are passed through to NaCl processes. e.g.,
// NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child
// process.
const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH";
char kNaClEnvPassthroughDelimiter = ',';
// The following environment variables are always passed through if they exist
// in the parent process.
const char kNaClExeStderr[] = "NACL_EXE_STDERR";
const char kNaClExeStdout[] = "NACL_EXE_STDOUT";
const char kNaClVerbosity[] = "NACLVERBOSITY";
#if defined(ARCH_CPU_X86)
bool NonZeroSegmentBaseIsSlow() {
base::CPU cpuid;
// Using a non-zero segment base is known to be very slow on Intel
// Atom CPUs. See "Segmentation-based Memory Protection Mechanism
// on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
// Potenza, Intel).
//
// The following list of CPU model numbers is taken from:
// "Intel 64 and IA-32 Architectures Software Developer's Manual"
// (http://download.intel.com/products/processor/manual/325462.pdf),
// "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
// (Volume 3C, 35-1), which contains:
// "06_36H - Intel Atom S Processor Family
// 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
if (cpuid.family() == 6) {
switch (cpuid.model()) {
case 0x1c:
case 0x26:
case 0x27:
case 0x35:
case 0x36:
return true;
}
}
return false;
}
#endif
// Send an IPC request on |ipc_channel|. The request is contained in
// |request_pickle| and can have file descriptors attached in |attached_fds|.
// |reply_data_buffer| must be allocated by the caller and will contain the
// reply. The size of the reply will be written to |reply_size|.
// This code assumes that only one thread can write to |ipc_channel| to make
// requests.
bool SendIPCRequestAndReadReply(int ipc_channel,
const std::vector<int>& attached_fds,
const base::Pickle& request_pickle,
char* reply_data_buffer,
size_t reply_data_buffer_size,
ssize_t* reply_size) {
DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength),
reply_data_buffer_size);
DCHECK(reply_size);
if (!base::UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
request_pickle.size(), attached_fds)) {
LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
return false;
}
// Then read the remote reply.
std::vector<base::ScopedFD> received_fds;
const ssize_t msg_len =
base::UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer,
reply_data_buffer_size, &received_fds);
if (msg_len <= 0) {
LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
return false;
}
*reply_size = msg_len;
return true;
}
} // namespace.
namespace nacl {
void AddNaClZygoteForkDelegates(
std::vector<std::unique_ptr<service_manager::ZygoteForkDelegate>>*
delegates) {
delegates->push_back(
std::make_unique<NaClForkDelegate>(false /* nonsfi_mode */));
delegates->push_back(
std::make_unique<NaClForkDelegate>(true /* nonsfi_mode */));
}
NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode)
: nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) {
}
void NaClForkDelegate::Init(const int sandboxdesc,
const bool enable_layer1_sandbox) {
VLOG(1) << "NaClForkDelegate::Init()";
// Only launch the non-SFI helper process if non-SFI mode is enabled.
if (nonsfi_mode_ && !IsNonSFIModeEnabled()) {
return;
}
// TODO(rickyz): Make IsSuidSandboxChild a static function.
std::unique_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client(
sandbox::SetuidSandboxClient::Create());
const bool using_setuid_sandbox = setuid_sandbox_client->IsSuidSandboxChild();
const bool using_namespace_sandbox =
sandbox::NamespaceSandbox::InNewUserNamespace();
CHECK(!(using_setuid_sandbox && using_namespace_sandbox));
if (enable_layer1_sandbox) {
CHECK(using_setuid_sandbox || using_namespace_sandbox);
}
std::unique_ptr<sandbox::SetuidSandboxHost> setuid_sandbox_host(
sandbox::SetuidSandboxHost::Create());
// For communications between the NaCl loader process and
// the browser process.
int nacl_sandbox_descriptor = base::GlobalDescriptors::kBaseDescriptor +
service_manager::kSandboxIPCChannel;
// Confirm a hard-wired assumption.
DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor);
int fds[2];
PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds));
bool use_nacl_bootstrap = false;
// For non-SFI mode, we do not use fixed address space.
if (!nonsfi_mode_) {
// Using nacl_helper_bootstrap is not necessary on x86-64 because
// NaCl's x86-64 sandbox is not zero-address-based. Starting
// nacl_helper through nacl_helper_bootstrap works on x86-64, but it
// leaves nacl_helper_bootstrap mapped at a fixed address at the
// bottom of the address space, which is undesirable because it
// effectively defeats ASLR.
#if defined(ARCH_CPU_X86_64)
use_nacl_bootstrap = false;
#elif defined(ARCH_CPU_X86)
// Performance vs. security trade-off: We prefer using a
// non-zero-address-based sandbox on x86-32 because it provides some
// ASLR and so is more secure. However, on Atom CPUs, using a
// non-zero segment base is very slow, so we use a zero-based
// sandbox on those.
use_nacl_bootstrap = NonZeroSegmentBaseIsSlow();
#else
use_nacl_bootstrap = true;
#endif
}
status_ = kNaClHelperUnused;
base::FilePath helper_exe;
base::FilePath helper_bootstrap_exe;
if (!base::PathService::Get(
nonsfi_mode_ ? nacl::FILE_NACL_HELPER_NONSFI : nacl::FILE_NACL_HELPER,
&helper_exe)) {
status_ = kNaClHelperMissing;
} else if (use_nacl_bootstrap &&
!base::PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
&helper_bootstrap_exe)) {
status_ = kNaClHelperBootstrapMissing;
} else {
base::CommandLine::StringVector argv_to_launch;
{
base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
if (use_nacl_bootstrap)
cmd_line.SetProgram(helper_bootstrap_exe);
else
cmd_line.SetProgram(helper_exe);
// Append any switches that need to be forwarded to the NaCl helper.
static constexpr const char* kForwardSwitches[] = {
service_manager::switches::kAllowSandboxDebugging,
service_manager::switches::kDisableSeccompFilterSandbox,
service_manager::switches::kNoSandbox,
switches::kEnableNaClDebug,
switches::kNaClDangerousNoSandboxNonSfi,
};
const base::CommandLine& current_cmd_line =
*base::CommandLine::ForCurrentProcess();
cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches,
base::size(kForwardSwitches));
// The command line needs to be tightly controlled to use
// |helper_bootstrap_exe|. So from now on, argv_to_launch should be
// modified directly.
argv_to_launch = cmd_line.argv();
}
if (use_nacl_bootstrap) {
// Arguments to the bootstrap helper which need to be at the start
// of the command line, right after the helper's path.
base::CommandLine::StringVector bootstrap_prepend;
bootstrap_prepend.push_back(helper_exe.value());
bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
bootstrap_prepend.push_back(kNaClHelperRDebug);
argv_to_launch.insert(argv_to_launch.begin() + 1,
bootstrap_prepend.begin(),
bootstrap_prepend.end());
}
base::LaunchOptions options;
options.fds_to_remap.push_back(
std::make_pair(fds[1], kNaClZygoteDescriptor));
options.fds_to_remap.push_back(
std::make_pair(sandboxdesc, nacl_sandbox_descriptor));
base::ScopedFD dummy_fd;
if (using_setuid_sandbox) {
// NaCl needs to keep tight control of the cmd_line, so prepend the
// setuid sandbox wrapper manually.
base::FilePath sandbox_path = setuid_sandbox_host->GetSandboxBinaryPath();
argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
setuid_sandbox_host->SetupLaunchOptions(&options, &dummy_fd);
setuid_sandbox_host->SetupLaunchEnvironment();
}
// The NaCl processes spawned may need to exceed the ambient soft limit
// on RLIMIT_AS to allocate the untrusted address space and its guard
// regions. The nacl_helper itself cannot just raise its own limit,
// because the existing limit may prevent the initial exec of
// nacl_helper_bootstrap from succeeding, with its large address space
// reservation.
std::vector<int> max_these_limits;
max_these_limits.push_back(RLIMIT_AS);
options.maximize_rlimits = &max_these_limits;
// To avoid information leaks in Non-SFI mode, clear the environment for
// the NaCl Helper process.
options.clear_environ = true;
AddPassthroughEnvToOptions(&options);
base::Process process =
using_namespace_sandbox
? sandbox::NamespaceSandbox::LaunchProcess(argv_to_launch, options)
: base::LaunchProcess(argv_to_launch, options);
if (!process.IsValid())
status_ = kNaClHelperLaunchFailed;
// parent and error cases are handled below
if (using_setuid_sandbox) {
// Sanity check that dummy_fd was kept alive for LaunchProcess.
DCHECK(dummy_fd.is_valid());
}
}
if (IGNORE_EINTR(close(fds[1])) != 0)
LOG(ERROR) << "close(fds[1]) failed";
if (status_ == kNaClHelperUnused) {
const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
char buf[kExpectedLength];
// Wait for ack from nacl_helper, indicating it is ready to help
const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
if (nread == kExpectedLength &&
memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
// all is well
status_ = kNaClHelperSuccess;
fd_ = fds[0];
return;
}
status_ = kNaClHelperAckFailed;
LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
}
// TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
// becomes the default.
fd_ = -1;
if (IGNORE_EINTR(close(fds[0])) != 0)
LOG(ERROR) << "close(fds[0]) failed";
}
void NaClForkDelegate::InitialUMA(std::string* uma_name,
int* uma_sample,
int* uma_boundary_value) {
*uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState"
: "NaCl.Client.Helper.InitState";
*uma_sample = status_;
*uma_boundary_value = kNaClHelperStatusBoundary;
}
NaClForkDelegate::~NaClForkDelegate() {
// side effect of close: delegate process will terminate
if (status_ == kNaClHelperSuccess) {
if (IGNORE_EINTR(close(fd_)) != 0)
LOG(ERROR) << "close(fd_) failed";
}
}
bool NaClForkDelegate::CanHelp(const std::string& process_type,
std::string* uma_name,
int* uma_sample,
int* uma_boundary_value) {
// We can only help with a specific process type depending on nonsfi_mode_.
const char* helpable_process_type = nonsfi_mode_
? switches::kNaClLoaderNonSfiProcess
: switches::kNaClLoaderProcess;
if (process_type != helpable_process_type)
return false;
*uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork"
: "NaCl.Client.Helper.StateOnFork";
*uma_sample = status_;
*uma_boundary_value = kNaClHelperStatusBoundary;
return true;
}
pid_t NaClForkDelegate::Fork(const std::string& process_type,
const std::vector<int>& fds,
const std::string& channel_id) {
VLOG(1) << "NaClForkDelegate::Fork";
DCHECK(fds.size() == kNumPassedFDs);
if (status_ != kNaClHelperSuccess) {
LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
return -1;
}
// First, send a remote fork request.
base::Pickle write_pickle;
write_pickle.WriteInt(nacl::kNaClForkRequest);
// TODO(hamaji): When we split the helper binary for non-SFI mode
// from nacl_helper, stop sending this information.
write_pickle.WriteBool(nonsfi_mode_);
write_pickle.WriteString(channel_id);
char reply_buf[kNaClMaxIPCMessageLength];
ssize_t reply_size = 0;
bool got_reply =
SendIPCRequestAndReadReply(fd_, fds, write_pickle,
reply_buf, sizeof(reply_buf), &reply_size);
if (!got_reply) {
LOG(ERROR) << "Could not perform remote fork.";
return -1;
}
// Now see if the other end managed to fork.
base::Pickle reply_pickle(reply_buf, reply_size);
base::PickleIterator iter(reply_pickle);
pid_t nacl_child;
if (!iter.ReadInt(&nacl_child)) {
LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
return -1;
}
VLOG(1) << "nacl_child is " << nacl_child;
return nacl_child;
}
bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
base::TerminationStatus* status,
int* exit_code) {
VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
DCHECK(status);
DCHECK(exit_code);
base::Pickle write_pickle;
write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
write_pickle.WriteInt(pid);
write_pickle.WriteBool(known_dead);
const std::vector<int> empty_fds;
char reply_buf[kNaClMaxIPCMessageLength];
ssize_t reply_size = 0;
bool got_reply =
SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
reply_buf, sizeof(reply_buf), &reply_size);
if (!got_reply) {
LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
return false;
}
base::Pickle reply_pickle(reply_buf, reply_size);
base::PickleIterator iter(reply_pickle);
int termination_status;
if (!iter.ReadInt(&termination_status) ||
termination_status < 0 ||
termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
LOG(ERROR) << "GetTerminationStatus: pickle failed";
return false;
}
int remote_exit_code;
if (!iter.ReadInt(&remote_exit_code)) {
LOG(ERROR) << "GetTerminationStatus: pickle failed";
return false;
}
*status = static_cast<base::TerminationStatus>(termination_status);
*exit_code = remote_exit_code;
return true;
}
// static
void NaClForkDelegate::AddPassthroughEnvToOptions(
base::LaunchOptions* options) {
std::unique_ptr<base::Environment> env(base::Environment::Create());
std::string pass_through_string;
std::vector<std::string> pass_through_vars;
if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) {
pass_through_vars = base::SplitString(
pass_through_string, std::string(1, kNaClEnvPassthroughDelimiter),
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
}
pass_through_vars.push_back(kNaClExeStderr);
pass_through_vars.push_back(kNaClExeStdout);
pass_through_vars.push_back(kNaClVerbosity);
pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest);
for (size_t i = 0; i < pass_through_vars.size(); ++i) {
std::string temp;
if (env->GetVar(pass_through_vars[i], &temp))
options->environ[pass_through_vars[i]] = temp;
}
}
} // namespace nacl