Home | History | Annotate | Download | only in zygote
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/nacl/zygote/nacl_fork_delegate_linux.h"
      6 
      7 #include <signal.h>
      8 #include <stdlib.h>
      9 #include <sys/resource.h>
     10 #include <sys/socket.h>
     11 
     12 #include <set>
     13 
     14 #include "base/basictypes.h"
     15 #include "base/command_line.h"
     16 #include "base/cpu.h"
     17 #include "base/files/file_path.h"
     18 #include "base/files/scoped_file.h"
     19 #include "base/logging.h"
     20 #include "base/memory/scoped_ptr.h"
     21 #include "base/memory/scoped_vector.h"
     22 #include "base/path_service.h"
     23 #include "base/pickle.h"
     24 #include "base/posix/eintr_wrapper.h"
     25 #include "base/posix/global_descriptors.h"
     26 #include "base/posix/unix_domain_socket_linux.h"
     27 #include "base/process/kill.h"
     28 #include "base/process/launch.h"
     29 #include "base/strings/string_split.h"
     30 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
     31 #include "build/build_config.h"
     32 #include "components/nacl/common/nacl_nonsfi_util.h"
     33 #include "components/nacl/common/nacl_paths.h"
     34 #include "components/nacl/common/nacl_switches.h"
     35 #include "components/nacl/loader/nacl_helper_linux.h"
     36 #include "content/public/common/content_descriptors.h"
     37 #include "content/public/common/content_switches.h"
     38 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
     39 #include "sandbox/linux/suid/common/sandbox.h"
     40 
     41 namespace {
     42 
     43 // Note these need to match up with their counterparts in nacl_helper_linux.c
     44 // and nacl_helper_bootstrap_linux.c.
     45 const char kNaClHelperReservedAtZero[] =
     46     "--reserved_at_zero=0xXXXXXXXXXXXXXXXX";
     47 const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
     48 
     49 // This is an environment variable which controls which (if any) other
     50 // environment variables are passed through to NaCl processes.  e.g.,
     51 // NACL_ENV_PASSTHROUGH="PATH,CWD" would pass both $PATH and $CWD to the child
     52 // process.
     53 const char kNaClEnvPassthrough[] = "NACL_ENV_PASSTHROUGH";
     54 char kNaClEnvPassthroughDelimiter = ',';
     55 
     56 // The following environment variables are always passed through if they exist
     57 // in the parent process.
     58 const char kNaClExeStderr[] = "NACL_EXE_STDERR";
     59 const char kNaClExeStdout[] = "NACL_EXE_STDOUT";
     60 const char kNaClVerbosity[] = "NACLVERBOSITY";
     61 
     62 #if defined(ARCH_CPU_X86)
     63 bool NonZeroSegmentBaseIsSlow() {
     64   base::CPU cpuid;
     65   // Using a non-zero segment base is known to be very slow on Intel
     66   // Atom CPUs.  See "Segmentation-based Memory Protection Mechanism
     67   // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo
     68   // Potenza, Intel).
     69   //
     70   // The following list of CPU model numbers is taken from:
     71   // "Intel 64 and IA-32 Architectures Software Developer's Manual"
     72   // (http://download.intel.com/products/processor/manual/325462.pdf),
     73   // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel"
     74   // (Volume 3C, 35-1), which contains:
     75   //   "06_36H - Intel Atom S Processor Family
     76   //    06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family"
     77   if (cpuid.family() == 6) {
     78     switch (cpuid.model()) {
     79       case 0x1c:
     80       case 0x26:
     81       case 0x27:
     82       case 0x35:
     83       case 0x36:
     84         return true;
     85     }
     86   }
     87   return false;
     88 }
     89 #endif
     90 
     91 // Send an IPC request on |ipc_channel|. The request is contained in
     92 // |request_pickle| and can have file descriptors attached in |attached_fds|.
     93 // |reply_data_buffer| must be allocated by the caller and will contain the
     94 // reply. The size of the reply will be written to |reply_size|.
     95 // This code assumes that only one thread can write to |ipc_channel| to make
     96 // requests.
     97 bool SendIPCRequestAndReadReply(int ipc_channel,
     98                                 const std::vector<int>& attached_fds,
     99                                 const Pickle& request_pickle,
    100                                 char* reply_data_buffer,
    101                                 size_t reply_data_buffer_size,
    102                                 ssize_t* reply_size) {
    103   DCHECK_LE(static_cast<size_t>(kNaClMaxIPCMessageLength),
    104             reply_data_buffer_size);
    105   DCHECK(reply_size);
    106 
    107   if (!UnixDomainSocket::SendMsg(ipc_channel, request_pickle.data(),
    108                                  request_pickle.size(), attached_fds)) {
    109     LOG(ERROR) << "SendIPCRequestAndReadReply: SendMsg failed";
    110     return false;
    111   }
    112 
    113   // Then read the remote reply.
    114   ScopedVector<base::ScopedFD> received_fds;
    115   const ssize_t msg_len =
    116       UnixDomainSocket::RecvMsg(ipc_channel, reply_data_buffer,
    117                                 reply_data_buffer_size, &received_fds);
    118   if (msg_len <= 0) {
    119     LOG(ERROR) << "SendIPCRequestAndReadReply: RecvMsg failed";
    120     return false;
    121   }
    122   *reply_size = msg_len;
    123   return true;
    124 }
    125 
    126 }  // namespace.
    127 
    128 namespace nacl {
    129 
    130 void AddNaClZygoteForkDelegates(
    131     ScopedVector<content::ZygoteForkDelegate>* delegates) {
    132   delegates->push_back(new NaClForkDelegate(false /* nonsfi_mode */));
    133   delegates->push_back(new NaClForkDelegate(true /* nonsfi_mode */));
    134 }
    135 
    136 NaClForkDelegate::NaClForkDelegate(bool nonsfi_mode)
    137     : nonsfi_mode_(nonsfi_mode), status_(kNaClHelperUnused), fd_(-1) {
    138 }
    139 
    140 void NaClForkDelegate::Init(const int sandboxdesc,
    141                             const bool enable_layer1_sandbox) {
    142   VLOG(1) << "NaClForkDelegate::Init()";
    143 
    144   // Only launch the non-SFI helper process if non-SFI mode is enabled.
    145   if (nonsfi_mode_ && !IsNonSFIModeEnabled()) {
    146     return;
    147   }
    148 
    149   scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client(
    150       sandbox::SetuidSandboxClient::Create());
    151 
    152   // For communications between the NaCl loader process and
    153   // the SUID sandbox.
    154   int nacl_sandbox_descriptor =
    155       base::GlobalDescriptors::kBaseDescriptor + kSandboxIPCChannel;
    156   // Confirm a hard-wired assumption.
    157   DCHECK_EQ(sandboxdesc, nacl_sandbox_descriptor);
    158 
    159   int fds[2];
    160   PCHECK(0 == socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds));
    161   base::FileHandleMappingVector fds_to_map;
    162   fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor));
    163   fds_to_map.push_back(std::make_pair(sandboxdesc, nacl_sandbox_descriptor));
    164 
    165   bool use_nacl_bootstrap = false;
    166   // For non-SFI mode, we do not use fixed address space.
    167   if (!nonsfi_mode_) {
    168     // Using nacl_helper_bootstrap is not necessary on x86-64 because
    169     // NaCl's x86-64 sandbox is not zero-address-based.  Starting
    170     // nacl_helper through nacl_helper_bootstrap works on x86-64, but it
    171     // leaves nacl_helper_bootstrap mapped at a fixed address at the
    172     // bottom of the address space, which is undesirable because it
    173     // effectively defeats ASLR.
    174 #if defined(ARCH_CPU_X86_64)
    175     use_nacl_bootstrap = false;
    176 #elif defined(ARCH_CPU_X86)
    177     // Performance vs. security trade-off: We prefer using a
    178     // non-zero-address-based sandbox on x86-32 because it provides some
    179     // ASLR and so is more secure.  However, on Atom CPUs, using a
    180     // non-zero segment base is very slow, so we use a zero-based
    181     // sandbox on those.
    182     use_nacl_bootstrap = NonZeroSegmentBaseIsSlow();
    183 #else
    184     use_nacl_bootstrap = true;
    185 #endif
    186   }
    187 
    188   status_ = kNaClHelperUnused;
    189   base::FilePath helper_exe;
    190   base::FilePath helper_bootstrap_exe;
    191   if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) {
    192     status_ = kNaClHelperMissing;
    193   } else if (use_nacl_bootstrap &&
    194              !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP,
    195                                &helper_bootstrap_exe)) {
    196     status_ = kNaClHelperBootstrapMissing;
    197   } else if (RunningOnValgrind()) {
    198     status_ = kNaClHelperValgrind;
    199   } else {
    200     CommandLine::StringVector argv_to_launch;
    201     {
    202       CommandLine cmd_line(CommandLine::NO_PROGRAM);
    203       if (use_nacl_bootstrap)
    204         cmd_line.SetProgram(helper_bootstrap_exe);
    205       else
    206         cmd_line.SetProgram(helper_exe);
    207 
    208       // Append any switches that need to be forwarded to the NaCl helper.
    209       static const char* kForwardSwitches[] = {
    210         switches::kDisableSeccompFilterSandbox,
    211         switches::kNaClDangerousNoSandboxNonSfi,
    212         switches::kNoSandbox,
    213       };
    214       const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess();
    215       cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches,
    216                                 arraysize(kForwardSwitches));
    217 
    218       // The command line needs to be tightly controlled to use
    219       // |helper_bootstrap_exe|. So from now on, argv_to_launch should be
    220       // modified directly.
    221       argv_to_launch = cmd_line.argv();
    222     }
    223     if (use_nacl_bootstrap) {
    224       // Arguments to the bootstrap helper which need to be at the start
    225       // of the command line, right after the helper's path.
    226       CommandLine::StringVector bootstrap_prepend;
    227       bootstrap_prepend.push_back(helper_exe.value());
    228       bootstrap_prepend.push_back(kNaClHelperReservedAtZero);
    229       bootstrap_prepend.push_back(kNaClHelperRDebug);
    230       argv_to_launch.insert(argv_to_launch.begin() + 1,
    231                             bootstrap_prepend.begin(),
    232                             bootstrap_prepend.end());
    233     }
    234 
    235     base::LaunchOptions options;
    236 
    237     base::ScopedFD dummy_fd;
    238     if (enable_layer1_sandbox) {
    239       // NaCl needs to keep tight control of the cmd_line, so prepend the
    240       // setuid sandbox wrapper manually.
    241       base::FilePath sandbox_path =
    242           setuid_sandbox_client->GetSandboxBinaryPath();
    243       argv_to_launch.insert(argv_to_launch.begin(), sandbox_path.value());
    244       setuid_sandbox_client->SetupLaunchOptions(
    245           &options, &fds_to_map, &dummy_fd);
    246       setuid_sandbox_client->SetupLaunchEnvironment();
    247     }
    248 
    249     options.fds_to_remap = &fds_to_map;
    250 
    251     // The NaCl processes spawned may need to exceed the ambient soft limit
    252     // on RLIMIT_AS to allocate the untrusted address space and its guard
    253     // regions.  The nacl_helper itself cannot just raise its own limit,
    254     // because the existing limit may prevent the initial exec of
    255     // nacl_helper_bootstrap from succeeding, with its large address space
    256     // reservation.
    257     std::vector<int> max_these_limits;
    258     max_these_limits.push_back(RLIMIT_AS);
    259     options.maximize_rlimits = &max_these_limits;
    260 
    261     // To avoid information leaks in Non-SFI mode, clear the environment for
    262     // the NaCl Helper process.
    263     options.clear_environ = true;
    264     AddPassthroughEnvToOptions(&options);
    265 
    266     if (!base::LaunchProcess(argv_to_launch, options, NULL))
    267       status_ = kNaClHelperLaunchFailed;
    268     // parent and error cases are handled below
    269 
    270     if (enable_layer1_sandbox) {
    271       // Sanity check that dummy_fd was kept alive for LaunchProcess.
    272       DCHECK(dummy_fd.is_valid());
    273     }
    274   }
    275   if (IGNORE_EINTR(close(fds[1])) != 0)
    276     LOG(ERROR) << "close(fds[1]) failed";
    277   if (status_ == kNaClHelperUnused) {
    278     const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck);
    279     char buf[kExpectedLength];
    280 
    281     // Wait for ack from nacl_helper, indicating it is ready to help
    282     const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
    283     if (nread == kExpectedLength &&
    284         memcmp(buf, kNaClHelperStartupAck, nread) == 0) {
    285       // all is well
    286       status_ = kNaClHelperSuccess;
    287       fd_ = fds[0];
    288       return;
    289     }
    290 
    291     status_ = kNaClHelperAckFailed;
    292     LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)";
    293   }
    294   // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper
    295   // becomes the default.
    296   fd_ = -1;
    297   if (IGNORE_EINTR(close(fds[0])) != 0)
    298     LOG(ERROR) << "close(fds[0]) failed";
    299 }
    300 
    301 void NaClForkDelegate::InitialUMA(std::string* uma_name,
    302                                   int* uma_sample,
    303                                   int* uma_boundary_value) {
    304   *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.InitState"
    305                            : "NaCl.Client.Helper.InitState";
    306   *uma_sample = status_;
    307   *uma_boundary_value = kNaClHelperStatusBoundary;
    308 }
    309 
    310 NaClForkDelegate::~NaClForkDelegate() {
    311   // side effect of close: delegate process will terminate
    312   if (status_ == kNaClHelperSuccess) {
    313     if (IGNORE_EINTR(close(fd_)) != 0)
    314       LOG(ERROR) << "close(fd_) failed";
    315   }
    316 }
    317 
    318 bool NaClForkDelegate::CanHelp(const std::string& process_type,
    319                                std::string* uma_name,
    320                                int* uma_sample,
    321                                int* uma_boundary_value) {
    322   // We can only help with a specific process type depending on nonsfi_mode_.
    323   const char* helpable_process_type = nonsfi_mode_
    324                                           ? switches::kNaClLoaderNonSfiProcess
    325                                           : switches::kNaClLoaderProcess;
    326   if (process_type != helpable_process_type)
    327     return false;
    328   *uma_name = nonsfi_mode_ ? "NaCl.Client.HelperNonSFI.StateOnFork"
    329                            : "NaCl.Client.Helper.StateOnFork";
    330   *uma_sample = status_;
    331   *uma_boundary_value = kNaClHelperStatusBoundary;
    332   return true;
    333 }
    334 
    335 pid_t NaClForkDelegate::Fork(const std::string& process_type,
    336                              const std::vector<int>& fds,
    337                              const std::string& channel_id) {
    338   VLOG(1) << "NaClForkDelegate::Fork";
    339 
    340   DCHECK(fds.size() == kNumPassedFDs);
    341 
    342   if (status_ != kNaClHelperSuccess) {
    343     LOG(ERROR) << "Cannot launch NaCl process: nacl_helper failed to start";
    344     return -1;
    345   }
    346 
    347   // First, send a remote fork request.
    348   Pickle write_pickle;
    349   write_pickle.WriteInt(nacl::kNaClForkRequest);
    350   // TODO(hamaji): When we split the helper binary for non-SFI mode
    351   // from nacl_helper, stop sending this information.
    352   write_pickle.WriteBool(nonsfi_mode_);
    353   write_pickle.WriteString(channel_id);
    354 
    355   char reply_buf[kNaClMaxIPCMessageLength];
    356   ssize_t reply_size = 0;
    357   bool got_reply =
    358       SendIPCRequestAndReadReply(fd_, fds, write_pickle,
    359                                  reply_buf, sizeof(reply_buf), &reply_size);
    360   if (!got_reply) {
    361     LOG(ERROR) << "Could not perform remote fork.";
    362     return -1;
    363   }
    364 
    365   // Now see if the other end managed to fork.
    366   Pickle reply_pickle(reply_buf, reply_size);
    367   PickleIterator iter(reply_pickle);
    368   pid_t nacl_child;
    369   if (!iter.ReadInt(&nacl_child)) {
    370     LOG(ERROR) << "NaClForkDelegate::Fork: pickle failed";
    371     return -1;
    372   }
    373   VLOG(1) << "nacl_child is " << nacl_child;
    374   return nacl_child;
    375 }
    376 
    377 bool NaClForkDelegate::GetTerminationStatus(pid_t pid, bool known_dead,
    378                                             base::TerminationStatus* status,
    379                                             int* exit_code) {
    380   VLOG(1) << "NaClForkDelegate::GetTerminationStatus";
    381   DCHECK(status);
    382   DCHECK(exit_code);
    383 
    384   Pickle write_pickle;
    385   write_pickle.WriteInt(nacl::kNaClGetTerminationStatusRequest);
    386   write_pickle.WriteInt(pid);
    387   write_pickle.WriteBool(known_dead);
    388 
    389   const std::vector<int> empty_fds;
    390   char reply_buf[kNaClMaxIPCMessageLength];
    391   ssize_t reply_size = 0;
    392   bool got_reply =
    393       SendIPCRequestAndReadReply(fd_, empty_fds, write_pickle,
    394                                  reply_buf, sizeof(reply_buf), &reply_size);
    395   if (!got_reply) {
    396     LOG(ERROR) << "Could not perform remote GetTerminationStatus.";
    397     return false;
    398   }
    399 
    400   Pickle reply_pickle(reply_buf, reply_size);
    401   PickleIterator iter(reply_pickle);
    402   int termination_status;
    403   if (!iter.ReadInt(&termination_status) ||
    404       termination_status < 0 ||
    405       termination_status >= base::TERMINATION_STATUS_MAX_ENUM) {
    406     LOG(ERROR) << "GetTerminationStatus: pickle failed";
    407     return false;
    408   }
    409 
    410   int remote_exit_code;
    411   if (!iter.ReadInt(&remote_exit_code)) {
    412     LOG(ERROR) << "GetTerminationStatus: pickle failed";
    413     return false;
    414   }
    415 
    416   *status = static_cast<base::TerminationStatus>(termination_status);
    417   *exit_code = remote_exit_code;
    418   return true;
    419 }
    420 
    421 // static
    422 void NaClForkDelegate::AddPassthroughEnvToOptions(
    423     base::LaunchOptions* options) {
    424   scoped_ptr<base::Environment> env(base::Environment::Create());
    425   std::string pass_through_string;
    426   std::vector<std::string> pass_through_vars;
    427   if (env->GetVar(kNaClEnvPassthrough, &pass_through_string)) {
    428     base::SplitString(
    429         pass_through_string, kNaClEnvPassthroughDelimiter, &pass_through_vars);
    430   }
    431   pass_through_vars.push_back(kNaClExeStderr);
    432   pass_through_vars.push_back(kNaClExeStdout);
    433   pass_through_vars.push_back(kNaClVerbosity);
    434   pass_through_vars.push_back(sandbox::kSandboxEnvironmentApiRequest);
    435   for (size_t i = 0; i < pass_through_vars.size(); ++i) {
    436     std::string temp;
    437     if (env->GetVar(pass_through_vars[i].c_str(), &temp))
    438       options->environ[pass_through_vars[i]] = temp;
    439   }
    440 }
    441 
    442 }  // namespace nacl
    443