Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / zygote / zygote_linux.cc
index cc509c1..a464fcd 100644 (file)
 
 #include "base/command_line.h"
 #include "base/debug/trace_event.h"
-#include "base/file_util.h"
+#include "base/files/file_util.h"
 #include "base/linux_util.h"
 #include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.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 "content/common/child_process_sandbox_support_impl_linux.h"
-#include "content/common/sandbox_linux.h"
+#include "content/common/sandbox_linux/sandbox_linux.h"
 #include "content/common/set_process_title.h"
 #include "content/common/zygote_commands_linux.h"
 #include "content/public/common/content_descriptors.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_switches.h"
 
+#if defined(ADDRESS_SANITIZER)
+#include <sanitizer/asan_interface.h>
+#endif
+
 // See http://code.google.com/p/chromium/wiki/LinuxZygote
 
 namespace content {
@@ -49,21 +55,41 @@ int LookUpFd(const base::GlobalDescriptors::Mapping& fd_mapping, uint32_t key) {
   return -1;
 }
 
-}  // namespace
+void CreatePipe(base::ScopedFD* read_pipe, base::ScopedFD* write_pipe) {
+  int raw_pipe[2];
+  PCHECK(0 == pipe(raw_pipe));
+  read_pipe->reset(raw_pipe[0]);
+  write_pipe->reset(raw_pipe[1]);
+}
 
-Zygote::Zygote(int sandbox_flags,
-               ZygoteForkDelegate* helper)
-    : sandbox_flags_(sandbox_flags),
-      helper_(helper),
-      initial_uma_sample_(0),
-      initial_uma_boundary_value_(0) {
-  if (helper_) {
-    helper_->InitialUMA(&initial_uma_name_,
-                        &initial_uma_sample_,
-                        &initial_uma_boundary_value_);
+void KillAndReap(pid_t pid, ZygoteForkDelegate* helper) {
+  if (helper) {
+    // Helper children may be forked in another PID namespace, so |pid| might
+    // be meaningless to us; or we just might not be able to directly send it
+    // signals.  So we can't kill it.
+    // Additionally, we're not its parent, so we can't reap it anyway.
+    // TODO(mdempsky): Extend the ZygoteForkDelegate API to handle this.
+    LOG(WARNING) << "Unable to kill or reap helper children";
+    return;
   }
+
+  // Kill the child process in case it's not already dead, so we can safely
+  // perform a blocking wait.
+  PCHECK(0 == kill(pid, SIGKILL));
+  PCHECK(pid == HANDLE_EINTR(waitpid(pid, NULL, 0)));
 }
 
+}  // namespace
+
+Zygote::Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers,
+               const std::vector<base::ProcessHandle>& extra_children,
+               const std::vector<int>& extra_fds)
+    : sandbox_flags_(sandbox_flags),
+      helpers_(helpers.Pass()),
+      initial_uma_index_(0),
+      extra_children_(extra_children),
+      extra_fds_(extra_fds) {}
+
 Zygote::~Zygote() {
 }
 
@@ -83,10 +109,10 @@ bool Zygote::ProcessRequests() {
   if (UsingSUIDSandbox()) {
     // Let the ZygoteHost know we are ready to go.
     // The receiving code is in content/browser/zygote_host_linux.cc.
-    std::vector<int> empty;
     bool r = UnixDomainSocket::SendMsg(kZygoteSocketPairFd,
                                        kZygoteHelloMessage,
-                                       sizeof(kZygoteHelloMessage), empty);
+                                       sizeof(kZygoteHelloMessage),
+                                       std::vector<int>());
 #if defined(OS_CHROMEOS)
     LOG_IF(WARNING, !r) << "Sending zygote magic failed";
     // Exit normally on chromeos because session manager may send SIGTERM
@@ -122,12 +148,28 @@ bool Zygote::UsingSUIDSandbox() const {
 }
 
 bool Zygote::HandleRequestFromBrowser(int fd) {
-  std::vector<int> fds;
+  ScopedVector<base::ScopedFD> fds;
   char buf[kZygoteMaxMessageLength];
   const ssize_t len = UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds);
 
   if (len == 0 || (len == -1 && errno == ECONNRESET)) {
     // EOF from the browser. We should die.
+    // TODO(earthdok): call __sanititizer_cov_dump() here to obtain code
+    // coverage  for the Zygote. Currently it's not possible because of
+    // confusion over who is responsible for closing the file descriptor.
+    for (std::vector<int>::iterator it = extra_fds_.begin();
+         it < extra_fds_.end(); ++it) {
+      PCHECK(0 == IGNORE_EINTR(close(*it)));
+    }
+#if !defined(ADDRESS_SANITIZER)
+    // TODO(earthdok): add watchdog thread before using this in non-ASAN builds.
+    CHECK(extra_children_.empty());
+#endif
+    for (std::vector<base::ProcessHandle>::iterator it =
+             extra_children_.begin();
+         it < extra_children_.end(); ++it) {
+      PCHECK(*it == HANDLE_EINTR(waitpid(*it, NULL, 0)));
+    }
     _exit(0);
     return false;
   }
@@ -145,7 +187,7 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
     switch (kind) {
       case kZygoteCommandFork:
         // This function call can return multiple times, once per fork().
-        return HandleForkRequest(fd, pickle, iter, fds);
+        return HandleForkRequest(fd, pickle, iter, fds.Pass());
 
       case kZygoteCommandReap:
         if (!fds.empty())
@@ -160,6 +202,13 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
       case kZygoteCommandGetSandboxStatus:
         HandleGetSandboxStatus(fd, pickle, iter);
         return false;
+      case kZygoteCommandForkRealPID:
+        // This shouldn't happen in practice, but some failure paths in
+        // HandleForkRequest (e.g., if ReadArgsAndFork fails during depickling)
+        // could leave this command pending on the socket.
+        LOG(ERROR) << "Unexpected real PID message from browser";
+        NOTREACHED();
+        return false;
       default:
         NOTREACHED();
         break;
@@ -167,9 +216,6 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
   }
 
   LOG(WARNING) << "Error parsing message from browser";
-  for (std::vector<int>::const_iterator
-       i = fds.begin(); i != fds.end(); ++i)
-    close(*i);
   return false;
 }
 
@@ -192,8 +238,21 @@ void Zygote::HandleReapRequest(int fd,
   }
 
   if (!child_info.started_from_helper) {
+    // Do not call base::EnsureProcessTerminated() under ThreadSanitizer, as it
+    // spawns a separate thread which may live until the call to fork() in the
+    // zygote. As a result, ThreadSanitizer will report an error and almost
+    // disable race detection in the child process.
+    // Not calling EnsureProcessTerminated() may result in zombie processes
+    // sticking around. This will only happen during testing, so we can live
+    // with this for now.
+#if !defined(THREAD_SANITIZER)
     // TODO(jln): this old code is completely broken. See crbug.com/274855.
     base::EnsureProcessTerminated(child_info.internal_pid);
+#else
+    LOG(WARNING) << "Zygote process omitting a call to "
+        << "base::EnsureProcessTerminated() for child pid " << child
+        << " under ThreadSanitizer. See http://crbug.com/274855.";
+#endif
   } else {
     // For processes from the helper, send a GetTerminationStatus request
     // with known_dead set to true.
@@ -223,9 +282,8 @@ bool Zygote::GetTerminationStatus(base::ProcessHandle real_pid,
   // We know about |real_pid|.
   const base::ProcessHandle child = child_info.internal_pid;
   if (child_info.started_from_helper) {
-    // Let the helper handle the request.
-    DCHECK(helper_);
-    if (!helper_->GetTerminationStatus(child, known_dead, status, exit_code)) {
+    if (!child_info.started_from_helper->GetTerminationStatus(
+            child, known_dead, status, exit_code)) {
       return false;
     }
   } else {
@@ -283,60 +341,53 @@ void Zygote::HandleGetTerminationStatus(int fd,
 
 int Zygote::ForkWithRealPid(const std::string& process_type,
                             const base::GlobalDescriptors::Mapping& fd_mapping,
-                            const std::string& channel_switch,
+                            const std::string& channel_id,
+                            base::ScopedFD pid_oracle,
                             std::string* uma_name,
                             int* uma_sample,
                             int* uma_boundary_value) {
-  const bool use_helper = (helper_ && helper_->CanHelp(process_type,
-                                                       uma_name,
-                                                       uma_sample,
-                                                       uma_boundary_value));
-  int dummy_fd;
-  ino_t dummy_inode;
-  int pipe_fds[2] = { -1, -1 };
-  base::ProcessId pid = 0;
-
-  dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
-  if (dummy_fd < 0) {
-    LOG(ERROR) << "Failed to create dummy FD";
-    goto error;
-  }
-  if (!base::FileDescriptorGetInode(&dummy_inode, dummy_fd)) {
-    LOG(ERROR) << "Failed to get inode for dummy FD";
-    goto error;
-  }
-  if (pipe(pipe_fds) != 0) {
-    LOG(ERROR) << "Failed to create pipe";
-    goto error;
+  ZygoteForkDelegate* helper = NULL;
+  for (ScopedVector<ZygoteForkDelegate>::iterator i = helpers_.begin();
+       i != helpers_.end();
+       ++i) {
+    if ((*i)->CanHelp(process_type, uma_name, uma_sample, uma_boundary_value)) {
+      helper = *i;
+      break;
+    }
   }
 
-  if (use_helper) {
-    std::vector<int> fds;
+  base::ScopedFD read_pipe, write_pipe;
+  base::ProcessId pid = 0;
+  if (helper) {
     int ipc_channel_fd = LookUpFd(fd_mapping, kPrimaryIPCChannel);
     if (ipc_channel_fd < 0) {
       DLOG(ERROR) << "Failed to find kPrimaryIPCChannel in FD mapping";
-      goto error;
+      return -1;
     }
+    std::vector<int> fds;
     fds.push_back(ipc_channel_fd);  // kBrowserFDIndex
-    fds.push_back(dummy_fd);  // kDummyFDIndex
-    fds.push_back(pipe_fds[0]);  // kParentFDIndex
-    pid = helper_->Fork(fds);
+    fds.push_back(pid_oracle.get());  // kPIDOracleFDIndex
+    pid = helper->Fork(process_type, fds, channel_id);
+
+    // Helpers should never return in the child process.
+    CHECK_NE(pid, 0);
   } else {
+    CreatePipe(&read_pipe, &write_pipe);
     pid = fork();
   }
-  if (pid < 0) {
-    goto error;
-  } else if (pid == 0) {
+
+  if (pid == 0) {
     // In the child process.
-    close(pipe_fds[1]);
+    write_pipe.reset();
+
+    // Ping the PID oracle socket so the browser can find our PID.
+    CHECK(SendZygoteChildPing(pid_oracle.get()));
+
+    // Now read back our real PID from the zygote.
     base::ProcessId real_pid;
-    // Wait until the parent process has discovered our PID.  We
-    // should not fork any child processes (which the seccomp
-    // sandbox does) until then, because that can interfere with the
-    // parent's discovery of our PID.
-    if (!file_util::ReadFromFD(pipe_fds[0],
-                               reinterpret_cast<char*>(&real_pid),
-                               sizeof(real_pid))) {
+    if (!base::ReadFromFD(read_pipe.get(),
+                          reinterpret_cast<char*>(&real_pid),
+                          sizeof(real_pid))) {
       LOG(FATAL) << "Failed to synchronise with parent zygote process";
     }
     if (real_pid <= 0) {
@@ -351,87 +402,69 @@ int Zygote::ForkWithRealPid(const std::string& process_type,
     base::debug::TraceLog::GetInstance()->SetProcessID(
         static_cast<int>(real_pid));
 #endif
-    close(pipe_fds[0]);
-    close(dummy_fd);
     return 0;
-  } else {
-    // In the parent process.
-    close(dummy_fd);
-    dummy_fd = -1;
-    close(pipe_fds[0]);
-    pipe_fds[0] = -1;
-    base::ProcessId real_pid;
-    if (UsingSUIDSandbox()) {
-      uint8_t reply_buf[512];
-      Pickle request;
-      request.WriteInt(LinuxSandbox::METHOD_GET_CHILD_WITH_INODE);
-      request.WriteUInt64(dummy_inode);
-
-      const ssize_t r = UnixDomainSocket::SendRecvMsg(
-          GetSandboxFD(), reply_buf, sizeof(reply_buf), NULL,
-          request);
-      if (r == -1) {
-        LOG(ERROR) << "Failed to get child process's real PID";
-        goto error;
-      }
-
-      Pickle reply(reinterpret_cast<char*>(reply_buf), r);
-      PickleIterator iter(reply);
-      if (!reply.ReadInt(&iter, &real_pid))
-        goto error;
-      if (real_pid <= 0) {
-        // METHOD_GET_CHILD_WITH_INODE failed. Did the child die already?
-        LOG(ERROR) << "METHOD_GET_CHILD_WITH_INODE failed";
-        goto error;
-      }
-    } else {
-      // If no SUID sandbox is involved then no pid translation is
-      // necessary.
-      real_pid = pid;
-    }
+  }
 
-    // Now set-up this process to be tracked by the Zygote.
-    if (process_info_map_.find(real_pid) != process_info_map_.end()) {
-      LOG(ERROR) << "Already tracking PID " << real_pid;
-      NOTREACHED();
-    }
-    process_info_map_[real_pid].internal_pid = pid;
-    process_info_map_[real_pid].started_from_helper = use_helper;
-
-    if (use_helper) {
-      if (!helper_->AckChild(pipe_fds[1], channel_switch)) {
-        LOG(ERROR) << "Failed to synchronise with zygote fork helper";
-        goto error;
-      }
-    } else {
-      int written =
-          HANDLE_EINTR(write(pipe_fds[1], &real_pid, sizeof(real_pid)));
-      if (written != sizeof(real_pid)) {
-        LOG(ERROR) << "Failed to synchronise with child process";
-        goto error;
-      }
+  // In the parent process.
+  read_pipe.reset();
+  pid_oracle.reset();
+
+  // Always receive a real PID from the zygote host, though it might
+  // be invalid (see below).
+  base::ProcessId real_pid;
+  {
+    ScopedVector<base::ScopedFD> recv_fds;
+    char buf[kZygoteMaxMessageLength];
+    const ssize_t len = UnixDomainSocket::RecvMsg(
+        kZygoteSocketPairFd, buf, sizeof(buf), &recv_fds);
+    CHECK_GT(len, 0);
+    CHECK(recv_fds.empty());
+
+    Pickle pickle(buf, len);
+    PickleIterator iter(pickle);
+
+    int kind;
+    CHECK(pickle.ReadInt(&iter, &kind));
+    CHECK(kind == kZygoteCommandForkRealPID);
+    CHECK(pickle.ReadInt(&iter, &real_pid));
+  }
+
+  // Fork failed.
+  if (pid < 0) {
+    return -1;
+  }
+
+  // If we successfully forked a child, but it crashed without sending
+  // a message to the browser, the browser won't have found its PID.
+  if (real_pid < 0) {
+    KillAndReap(pid, helper);
+    return -1;
+  }
+
+  // If we're not using a helper, send the PID back to the child process.
+  if (!helper) {
+    ssize_t written =
+        HANDLE_EINTR(write(write_pipe.get(), &real_pid, sizeof(real_pid)));
+    if (written != sizeof(real_pid)) {
+      KillAndReap(pid, helper);
+      return -1;
     }
-    close(pipe_fds[1]);
-    return real_pid;
   }
 
- error:
-  if (pid > 0) {
-    if (waitpid(pid, NULL, WNOHANG) == -1)
-      LOG(ERROR) << "Failed to wait for process";
+  // Now set-up this process to be tracked by the Zygote.
+  if (process_info_map_.find(real_pid) != process_info_map_.end()) {
+    LOG(ERROR) << "Already tracking PID " << real_pid;
+    NOTREACHED();
   }
-  if (dummy_fd >= 0)
-    close(dummy_fd);
-  if (pipe_fds[0] >= 0)
-    close(pipe_fds[0]);
-  if (pipe_fds[1] >= 0)
-    close(pipe_fds[1]);
-  return -1;
+  process_info_map_[real_pid].internal_pid = pid;
+  process_info_map_[real_pid].started_from_helper = helper;
+
+  return real_pid;
 }
 
 base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
                                         PickleIterator iter,
-                                        std::vector<int>& fds,
+                                        ScopedVector<base::ScopedFD> fds,
                                         std::string* uma_name,
                                         int* uma_sample,
                                         int* uma_boundary_value) {
@@ -455,7 +488,7 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
       return -1;
     args.push_back(arg);
     if (arg.compare(0, channel_id_prefix.length(), channel_id_prefix) == 0)
-      channel_id = arg;
+      channel_id = arg.substr(channel_id_prefix.length());
   }
 
   if (!pickle.ReadInt(&iter, &numfds))
@@ -463,26 +496,40 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
   if (numfds != static_cast<int>(fds.size()))
     return -1;
 
-  for (int i = 0; i < numfds; ++i) {
+  // First FD is the PID oracle socket.
+  if (fds.size() < 1)
+    return -1;
+  base::ScopedFD pid_oracle(fds[0]->Pass());
+
+  // Remaining FDs are for the global descriptor mapping.
+  for (int i = 1; i < numfds; ++i) {
     base::GlobalDescriptors::Key key;
     if (!pickle.ReadUInt32(&iter, &key))
       return -1;
-    mapping.push_back(std::make_pair(key, fds[i]));
+    mapping.push_back(std::make_pair(key, fds[i]->get()));
   }
 
   mapping.push_back(std::make_pair(
       static_cast<uint32_t>(kSandboxIPCChannel), GetSandboxFD()));
 
   // Returns twice, once per process.
-  base::ProcessId child_pid = ForkWithRealPid(process_type, mapping, channel_id,
-                                              uma_name, uma_sample,
+  base::ProcessId child_pid = ForkWithRealPid(process_type,
+                                              mapping,
+                                              channel_id,
+                                              pid_oracle.Pass(),
+                                              uma_name,
+                                              uma_sample,
                                               uma_boundary_value);
   if (!child_pid) {
     // This is the child process.
 
-    close(kZygoteSocketPairFd);  // Our socket from the browser.
-    if (UsingSUIDSandbox())
-      close(kZygoteIdFd);  // Another socket from the browser.
+    // Our socket from the browser.
+    PCHECK(0 == IGNORE_EINTR(close(kZygoteSocketPairFd)));
+
+    // Pass ownership of file descriptors from fds to GlobalDescriptors.
+    for (ScopedVector<base::ScopedFD>::iterator i = fds.begin(); i != fds.end();
+         ++i)
+      ignore_result((*i)->release());
     base::GlobalDescriptors::GetInstance()->Reset(mapping);
 
     // Reset the process-wide command line to our new command line.
@@ -504,26 +551,19 @@ base::ProcessId Zygote::ReadArgsAndFork(const Pickle& pickle,
 bool Zygote::HandleForkRequest(int fd,
                                const Pickle& pickle,
                                PickleIterator iter,
-                               std::vector<int>& fds) {
+                               ScopedVector<base::ScopedFD> fds) {
   std::string uma_name;
   int uma_sample;
   int uma_boundary_value;
-  base::ProcessId child_pid = ReadArgsAndFork(pickle, iter, fds,
-                                              &uma_name, &uma_sample,
-                                              &uma_boundary_value);
+  base::ProcessId child_pid = ReadArgsAndFork(
+      pickle, iter, fds.Pass(), &uma_name, &uma_sample, &uma_boundary_value);
   if (child_pid == 0)
     return true;
-  for (std::vector<int>::const_iterator
-       i = fds.begin(); i != fds.end(); ++i)
-    close(*i);
-  if (uma_name.empty()) {
-    // There is no UMA report from this particular fork.
-    // Use the initial UMA report if any, and clear that record for next time.
-    // Note the swap method here is the efficient way to do this, since
-    // we know uma_name is empty.
-    uma_name.swap(initial_uma_name_);
-    uma_sample = initial_uma_sample_;
-    uma_boundary_value = initial_uma_boundary_value_;
+  // If there's no UMA report for this particular fork, then check if any
+  // helpers have an initial UMA report for us to send instead.
+  while (uma_name.empty() && initial_uma_index_ < helpers_.size()) {
+    helpers_[initial_uma_index_++]->InitialUMA(
+        &uma_name, &uma_sample, &uma_boundary_value);
   }
   // Must always send reply, as ZygoteHost blocks while waiting for it.
   Pickle reply_pickle;