| // 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 |