Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / zygote / zygote_linux.cc
index 2f0bd20..5f922a4 100644 (file)
@@ -15,6 +15,7 @@
 #include "base/file_util.h"
 #include "base/linux_util.h"
 #include "base/logging.h"
+#include "base/macros.h"
 #include "base/pickle.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/posix/global_descriptors.h"
@@ -49,6 +50,30 @@ int LookUpFd(const base::GlobalDescriptors::Mapping& fd_mapping, uint32_t key) {
   return -1;
 }
 
+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]);
+}
+
+void KillAndReap(pid_t pid, bool use_helper) {
+  if (use_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,
@@ -83,10 +108,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,7 +147,7 @@ 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);
 
@@ -145,7 +170,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 +185,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 +199,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 +221,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.
@@ -283,7 +325,8 @@ 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) {
@@ -291,50 +334,38 @@ int Zygote::ForkWithRealPid(const std::string& 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;
-  }
 
+  base::ScopedFD read_pipe, write_pipe;
+  base::ProcessId pid = 0;
   if (use_helper) {
-    std::vector<int> fds;
     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 (!base::ReadFromFD(pipe_fds[0], reinterpret_cast<char*>(&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";
     }
@@ -350,87 +381,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, use_helper);
+    return -1;
+  }
+
+  // If we're not using a helper, send the PID back to the child process.
+  if (!use_helper) {
+    ssize_t written =
+        HANDLE_EINTR(write(write_pipe.get(), &real_pid, sizeof(real_pid)));
+    if (written != sizeof(real_pid)) {
+      KillAndReap(pid, use_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 = use_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) {
@@ -454,7 +467,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))
@@ -462,26 +475,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.
@@ -503,18 +530,14 @@ 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.