Home | History | Annotate | Download | only in nacl
      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 // A mini-zygote specifically for Native Client.
      6 
      7 #include "components/nacl/common/nacl_helper_linux.h"
      8 
      9 #include <errno.h>
     10 #include <fcntl.h>
     11 #include <link.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <sys/socket.h>
     15 #include <sys/stat.h>
     16 #include <sys/types.h>
     17 
     18 #include <string>
     19 #include <vector>
     20 
     21 #include "base/at_exit.h"
     22 #include "base/command_line.h"
     23 #include "base/json/string_escape.h"
     24 #include "base/logging.h"
     25 #include "base/message_loop/message_loop.h"
     26 #include "base/posix/eintr_wrapper.h"
     27 #include "base/posix/global_descriptors.h"
     28 #include "base/posix/unix_domain_socket_linux.h"
     29 #include "base/rand_util.h"
     30 #include "components/nacl/loader/nacl_listener.h"
     31 #include "components/nacl/loader/nacl_sandbox_linux.h"
     32 #include "crypto/nss_util.h"
     33 #include "ipc/ipc_descriptors.h"
     34 #include "ipc/ipc_switches.h"
     35 #include "sandbox/linux/services/libc_urandom_override.h"
     36 
     37 namespace {
     38 
     39 // The child must mimic the behavior of zygote_main_linux.cc on the child
     40 // side of the fork. See zygote_main_linux.cc:HandleForkRequest from
     41 //   if (!child) {
     42 void BecomeNaClLoader(const std::vector<int>& child_fds,
     43                       size_t prereserved_sandbox_size,
     44                       int number_of_cores) {
     45   VLOG(1) << "NaCl loader: setting up IPC descriptor";
     46   // don't need zygote FD any more
     47   if (HANDLE_EINTR(close(kNaClZygoteDescriptor)) != 0)
     48     LOG(ERROR) << "close(kNaClZygoteDescriptor) failed.";
     49   bool sandbox_initialized = InitializeBpfSandbox();
     50   if (!sandbox_initialized) {
     51     LOG(ERROR) << "Could not initialize NaCl's second "
     52       << "layer sandbox (seccomp-bpf).";
     53   }
     54   base::GlobalDescriptors::GetInstance()->Set(kPrimaryIPCChannel,
     55                                               child_fds[kNaClBrowserFDIndex]);
     56 
     57   base::MessageLoopForIO main_message_loop;
     58   NaClListener listener;
     59   listener.set_prereserved_sandbox_size(prereserved_sandbox_size);
     60   listener.set_number_of_cores(number_of_cores);
     61   listener.Listen();
     62   _exit(0);
     63 }
     64 
     65 // Some of this code was lifted from
     66 // content/browser/zygote_main_linux.cc:ForkWithRealPid()
     67 void HandleForkRequest(const std::vector<int>& child_fds,
     68                        size_t prereserved_sandbox_size,
     69                        int number_of_cores) {
     70   VLOG(1) << "nacl_helper: forking";
     71   pid_t childpid = fork();
     72   if (childpid < 0) {
     73     perror("fork");
     74     LOG(ERROR) << "*** HandleForkRequest failed\n";
     75     // fall through to parent case below
     76   } else if (childpid == 0) {  // In the child process.
     77     bool validack = false;
     78     const size_t kMaxReadSize = 1024;
     79     char buffer[kMaxReadSize];
     80     // Wait until the parent process has discovered our PID.  We
     81     // should not fork any child processes (which the seccomp
     82     // sandbox does) until then, because that can interfere with the
     83     // parent's discovery of our PID.
     84     const int nread = HANDLE_EINTR(read(child_fds[kNaClParentFDIndex], buffer,
     85                                         kMaxReadSize));
     86     const std::string switch_prefix = std::string("--") +
     87         switches::kProcessChannelID + std::string("=");
     88     const size_t len = switch_prefix.length();
     89 
     90     if (nread < 0) {
     91       perror("read");
     92       LOG(ERROR) << "read returned " << nread;
     93     } else if (nread > static_cast<int>(len)) {
     94       if (switch_prefix.compare(0, len, buffer, 0, len) == 0) {
     95         VLOG(1) << "NaCl loader is synchronised with Chrome zygote";
     96         CommandLine::ForCurrentProcess()->AppendSwitchASCII(
     97             switches::kProcessChannelID,
     98             std::string(&buffer[len], nread - len));
     99         validack = true;
    100       }
    101     }
    102     if (HANDLE_EINTR(close(child_fds[kNaClDummyFDIndex])) != 0)
    103       LOG(ERROR) << "close(child_fds[kNaClDummyFDIndex]) failed";
    104     if (HANDLE_EINTR(close(child_fds[kNaClParentFDIndex])) != 0)
    105       LOG(ERROR) << "close(child_fds[kNaClParentFDIndex]) failed";
    106     if (validack) {
    107       BecomeNaClLoader(child_fds, prereserved_sandbox_size, number_of_cores);
    108     } else {
    109       LOG(ERROR) << "Failed to synch with zygote";
    110     }
    111     // NOTREACHED
    112     return;
    113   }
    114   // I am the parent.
    115   // First, close the dummy_fd so the sandbox won't find me when
    116   // looking for the child's pid in /proc. Also close other fds.
    117   for (size_t i = 0; i < child_fds.size(); i++) {
    118     if (HANDLE_EINTR(close(child_fds[i])) != 0)
    119       LOG(ERROR) << "close(child_fds[i]) failed";
    120   }
    121   VLOG(1) << "nacl_helper: childpid is " << childpid;
    122   // Now tell childpid to the Chrome zygote.
    123   if (HANDLE_EINTR(send(kNaClZygoteDescriptor,
    124                         &childpid, sizeof(childpid), MSG_EOR))
    125       != sizeof(childpid)) {
    126     LOG(ERROR) << "*** send() to zygote failed";
    127   }
    128 }
    129 
    130 // This is a poor man's check on whether we are sandboxed.
    131 bool IsSandboxed() {
    132   int proc_fd = open("/proc/self/exe", O_RDONLY);
    133   if (proc_fd >= 0) {
    134     HANDLE_EINTR(close(proc_fd));
    135     return false;
    136   }
    137   return true;
    138 }
    139 
    140 }  // namespace
    141 
    142 static const char kNaClHelperReservedAtZero[] = "reserved_at_zero";
    143 static const char kNaClHelperRDebug[] = "r_debug";
    144 
    145 // Since we were started by nacl_helper_bootstrap rather than in the
    146 // usual way, the debugger cannot figure out where our executable
    147 // or the dynamic linker or the shared libraries are in memory,
    148 // so it won't find any symbols.  But we can fake it out to find us.
    149 //
    150 // The zygote passes --r_debug=0xXXXXXXXXXXXXXXXX.
    151 // nacl_helper_bootstrap replaces the Xs with the address of its _r_debug
    152 // structure.  The debugger will look for that symbol by name to
    153 // discover the addresses of key dynamic linker data structures.
    154 // Since all it knows about is the original main executable, which
    155 // is the bootstrap program, it finds the symbol defined there.  The
    156 // dynamic linker's structure is somewhere else, but it is filled in
    157 // after initialization.  The parts that really matter to the
    158 // debugger never change.  So we just copy the contents of the
    159 // dynamic linker's structure into the address provided by the option.
    160 // Hereafter, if someone attaches a debugger (or examines a core dump),
    161 // the debugger will find all the symbols in the normal way.
    162 static void CheckRDebug(char* argv0) {
    163   std::string r_debug_switch_value =
    164       CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kNaClHelperRDebug);
    165   if (!r_debug_switch_value.empty()) {
    166     char* endp;
    167     uintptr_t r_debug_addr = strtoul(r_debug_switch_value.c_str(), &endp, 0);
    168     if (r_debug_addr != 0 && *endp == '\0') {
    169       r_debug* bootstrap_r_debug = reinterpret_cast<r_debug*>(r_debug_addr);
    170       *bootstrap_r_debug = _r_debug;
    171 
    172       // Since the main executable (the bootstrap program) does not
    173       // have a dynamic section, the debugger will not skip the
    174       // first element of the link_map list as it usually would for
    175       // an executable or PIE that was loaded normally.  But the
    176       // dynamic linker has set l_name for the PIE to "" as is
    177       // normal for the main executable.  So the debugger doesn't
    178       // know which file it is.  Fill in the actual file name, which
    179       // came in as our argv[0].
    180       link_map* l = _r_debug.r_map;
    181       if (l->l_name[0] == '\0')
    182         l->l_name = argv0;
    183     }
    184   }
    185 }
    186 
    187 // The zygote passes --reserved_at_zero=0xXXXXXXXXXXXXXXXX.
    188 // nacl_helper_bootstrap replaces the Xs with the amount of prereserved
    189 // sandbox memory.
    190 //
    191 // CheckReservedAtZero parses the value of the argument reserved_at_zero
    192 // and returns the amount of prereserved sandbox memory.
    193 static size_t CheckReservedAtZero() {
    194   size_t prereserved_sandbox_size = 0;
    195   std::string reserved_at_zero_switch_value =
    196       CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    197           kNaClHelperReservedAtZero);
    198   if (!reserved_at_zero_switch_value.empty()) {
    199     char* endp;
    200     prereserved_sandbox_size =
    201         strtoul(reserved_at_zero_switch_value.c_str(), &endp, 0);
    202     if (*endp != '\0')
    203       LOG(ERROR) << "Could not parse reserved_at_zero argument value of "
    204                  << reserved_at_zero_switch_value;
    205   }
    206   return prereserved_sandbox_size;
    207 }
    208 
    209 #if defined(ADDRESS_SANITIZER)
    210 // Do not install the SIGSEGV handler in ASan. This should make the NaCl
    211 // platform qualification test pass.
    212 static const char kAsanDefaultOptionsNaCl[] = "handle_segv=0";
    213 
    214 // Override the default ASan options for the NaCl helper.
    215 // __asan_default_options should not be instrumented, because it is called
    216 // before ASan is initialized.
    217 extern "C"
    218 __attribute__((no_address_safety_analysis))
    219 const char* __asan_default_options() {
    220   return kAsanDefaultOptionsNaCl;
    221 }
    222 #endif
    223 
    224 int main(int argc, char* argv[]) {
    225   CommandLine::Init(argc, argv);
    226   base::AtExitManager exit_manager;
    227   base::RandUint64();  // acquire /dev/urandom fd before sandbox is raised
    228   // Allows NSS to fopen() /dev/urandom.
    229   sandbox::InitLibcUrandomOverrides();
    230 #if defined(USE_NSS)
    231   // Configure NSS for use inside the NaCl process.
    232   // The fork check has not caused problems for NaCl, but this appears to be
    233   // best practice (see other places LoadNSSLibraries is called.)
    234   crypto::DisableNSSForkCheck();
    235   // Without this line on Linux, HMAC::Init will instantiate a singleton that
    236   // in turn attempts to open a file.  Disabling this behavior avoids a ~70 ms
    237   // stall the first time HMAC is used.
    238   crypto::ForceNSSNoDBInit();
    239   // Load shared libraries before sandbox is raised.
    240   // NSS is needed to perform hashing for validation caching.
    241   crypto::LoadNSSLibraries();
    242 #endif
    243   std::vector<int> empty; // for SendMsg() calls
    244   size_t prereserved_sandbox_size = CheckReservedAtZero();
    245   int number_of_cores = sysconf(_SC_NPROCESSORS_ONLN);
    246 
    247   CheckRDebug(argv[0]);
    248 
    249   // Check that IsSandboxed() works. We should not be sandboxed at this point.
    250   CHECK(!IsSandboxed()) << "Unexpectedly sandboxed!";
    251 
    252   // Send the zygote a message to let it know we are ready to help
    253   if (!UnixDomainSocket::SendMsg(kNaClZygoteDescriptor,
    254                                  kNaClHelperStartupAck,
    255                                  sizeof(kNaClHelperStartupAck), empty)) {
    256     LOG(ERROR) << "*** send() to zygote failed";
    257   }
    258 
    259   while (true) {
    260     int badpid = -1;
    261     std::vector<int> fds;
    262     static const unsigned kMaxMessageLength = 2048;
    263     char buf[kMaxMessageLength];
    264     const ssize_t msglen = UnixDomainSocket::RecvMsg(kNaClZygoteDescriptor,
    265                                                      &buf, sizeof(buf), &fds);
    266     // If the Zygote has started handling requests, we should be sandboxed via
    267     // the setuid sandbox.
    268     if (!IsSandboxed()) {
    269       LOG(ERROR) << "NaCl helper process running without a sandbox!\n"
    270                  << "Most likely you need to configure your SUID sandbox "
    271                  << "correctly";
    272     }
    273     if (msglen == 0 || (msglen == -1 && errno == ECONNRESET)) {
    274       // EOF from the browser. Goodbye!
    275       _exit(0);
    276     } else if (msglen < 0) {
    277       LOG(ERROR) << "nacl_helper: receive from zygote failed, errno = "
    278                  << errno;
    279     } else if (msglen == sizeof(kNaClForkRequest) - 1 &&
    280                memcmp(buf, kNaClForkRequest, msglen) == 0) {
    281       if (kNaClParentFDIndex + 1 == fds.size()) {
    282         HandleForkRequest(fds, prereserved_sandbox_size, number_of_cores);
    283         continue;  // fork succeeded. Note: child does not return
    284       } else {
    285         LOG(ERROR) << "nacl_helper: unexpected number of fds, got "
    286                    << fds.size();
    287       }
    288     } else {
    289       LOG(ERROR) << "nacl_helper unrecognized request: "
    290                  << base::GetDoubleQuotedJson(std::string(buf, buf + msglen));
    291       _exit(-1);
    292     }
    293     // if fork fails, send PID=-1 to zygote
    294     if (!UnixDomainSocket::SendMsg(kNaClZygoteDescriptor, &badpid,
    295                                    sizeof(badpid), empty)) {
    296       LOG(ERROR) << "*** send() to zygote failed";
    297     }
    298   }
    299   CHECK(false);  // This routine must not return
    300 }
    301