root/components/nacl/zygote/nacl_fork_delegate_linux.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. NonZeroSegmentBaseIsSlow
  2. SendIPCRequestAndReadReply
  3. fd_
  4. Init
  5. InitialUMA
  6. CanHelp
  7. AckChild
  8. GetTerminationStatus

// 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::vector<int> max_these_limits;
    max_these_limits.push_back(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 &&
      process_type != switches::kNaClLoaderNonSfiProcess)
    return false;
  *uma_name = "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) {
  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);
  // TODO(hamaji): When we split the helper binary for non-SFI mode
  // from nacl_helper, stop sending this information.
  const bool uses_nonsfi_mode =
    process_type == switches::kNaClLoaderNonSfiProcess;
  write_pickle.WriteBool(uses_nonsfi_mode);

  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;
}

/* [<][>][^][v][top][bottom][index][help] */