| // 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 <stdlib.h> |
| #include <sys/resource.h> |
| #include <sys/socket.h> |
| |
| #include <set> |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/cpu.h" |
| #include "base/files/file_path.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_linux.h" |
| #include "base/process/kill.h" |
| #include "base/process/launch.h" |
| #include "base/third_party/dynamic_annotations/dynamic_annotations.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" |
| |
| 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"; |
| |
| #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 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 (!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<int> received_fds; |
| const ssize_t msg_len = |
| 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. |
| |
| NaClForkDelegate::NaClForkDelegate() |
| : status_(kNaClHelperUnused), |
| fd_(-1) {} |
| |
| void NaClForkDelegate::Init(const int sandboxdesc) { |
| VLOG(1) << "NaClForkDelegate::Init()"; |
| int fds[2]; |
| |
| // For communications between the NaCl loader process and |
| // the SUID sandbox. |
| int nacl_sandbox_descriptor = |
| base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel; |
| // Confirm a hard-wired assumption. |
| DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor); |
| |
| CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); |
| base::FileHandleMappingVector fds_to_map; |
| fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor)); |
| fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor)); |
| |
| // 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) |
| bool kUseNaClBootstrap = 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. |
| bool kUseNaClBootstrap = NonZeroSegmentBaseIsSlow(); |
| #else |
| bool kUseNaClBootstrap = true; |
| #endif |
| |
| status_ = kNaClHelperUnused; |
| base::FilePath helper_exe; |
| base::FilePath helper_bootstrap_exe; |
| if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) { |
| status_ = kNaClHelperMissing; |
| } else if (kUseNaClBootstrap && |
| !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP, |
| &helper_bootstrap_exe)) { |
| status_ = kNaClHelperBootstrapMissing; |
| } else if (RunningOnValgrind()) { |
| status_ = kNaClHelperValgrind; |
| } else { |
| CommandLine::StringVector argv_to_launch; |
| { |
| CommandLine cmd_line(CommandLine::NO_PROGRAM); |
| if (kUseNaClBootstrap) |
| 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 const char* kForwardSwitches[] = { |
| switches::kDisableSeccompFilterSandbox, |
| switches::kNoSandbox, |
| }; |
| const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess(); |
| cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches, |
| arraysize(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 (kUseNaClBootstrap) { |
| // Arguments to the bootstrap helper which need to be at the start |
| // of the command line, right after the helper's path. |
| 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 = &fds_to_map; |
| options.clone_flags = CLONE_FS | SIGCHLD; |
| |
| // 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::set<int> max_these_limits; |
| max_these_limits.insert(RLIMIT_AS); |
| options.maximize_rlimits = &max_these_limits; |
| |
| if (!base::LaunchProcess(argv_to_launch, options, NULL)) |
| status_ = kNaClHelperLaunchFailed; |
| // parent and error cases are handled below |
| } |
| 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 = "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) { |
| if (process_type != switches::kNaClLoaderProcess) |
| return false; |
| *uma_name = "NaCl.Client.Helper.StateOnFork"; |
| *uma_sample = status_; |
| *uma_boundary_value = kNaClHelperStatusBoundary; |
| return true; |
| } |
| |
| pid_t NaClForkDelegate::Fork(const std::vector<int>& fds) { |
| 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. |
| Pickle write_pickle; |
| write_pickle.WriteInt(nacl::kNaClForkRequest); |
| |
| 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. |
| Pickle reply_pickle(reply_buf, reply_size); |
| 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::AckChild(const int fd, |
| const std::string& channel_switch) { |
| int nwritten = HANDLE_EINTR(write(fd, channel_switch.c_str(), |
| channel_switch.length())); |
| if (nwritten != static_cast<int>(channel_switch.length())) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead, |
| base::TerminationStatus* status, |
| int* exit_code) { |
| VLOG(1) << "NaClForkDelegate::GetTerminationStatus"; |
| DCHECK(status); |
| DCHECK(exit_code); |
| |
| 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; |
| } |
| |
| Pickle reply_pickle(reply_buf, reply_size); |
| 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; |
| } |