Bug 1470591 - Part 3: AppForkBuilder to ceate a new content process. r=gsvelto draft
authorThinker <thinker.li@gmail.com>
Mon, 25 Nov 2019 22:43:32 -0800
changeset 2497065 ce460e8da2a2744a7a94d82217e041722c96fbc0
parent 2497064 c26c3b7da49ff583fc441ed934a2fe75fda5ecca
child 2497066 0a70a3407dd1a421d684e8b859954777279bbaf6
push id455563
push userthinker.li@gmail.com
push dateTue, 26 Nov 2019 06:43:52 +0000
treeherdertry@60c1683c97b6 [default view] [failures only]
reviewersgsvelto
bugs1470591
milestone72.0a1
Bug 1470591 - Part 3: AppForkBuilder to ceate a new content process. r=gsvelto An instance of AppForkBuilder creates a new content process from the passed args and LaunchOptions. It bascally does the same thing as LaunchApp() for Linux, but it divides the procedure to two parts, - the 1st part forking a new process, and - the 2nd part initializing FDs, ENV, and message loops. Going two parts gives fork servers a chance to clean new processes before the initialization and running WEB content. For example, to clean sensitive data from memory.
ipc/chromium/src/base/process_util.h
ipc/chromium/src/base/process_util_linux.cc
ipc/glue/FileDescriptorShuffle.h
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -38,16 +38,27 @@
 #endif
 
 #include "base/command_line.h"
 #include "base/process.h"
 
 #include "mozilla/UniquePtr.h"
 #include "mozilla/ipc/EnvironmentMap.h"
 
+#if defined(MOZ_ENABLE_FORKSERVER)
+#include "nsString.h"
+#include "mozilla/ipc/FileDescriptorShuffle.h"
+
+namespace mozilla {
+namespace ipc {
+class FileDescriptor;
+}
+}
+#endif
+
 #if defined(OS_MACOSX)
 struct kinfo_proc;
 #endif
 
 namespace base {
 
 // A minimalistic but hopefully cross-platform set of exit codes.
 // Do not change the enumeration values or you will break third-party
@@ -160,16 +171,56 @@ struct FreeEnvVarsArray {
 
 typedef mozilla::UniquePtr<char*[], FreeEnvVarsArray> EnvironmentArray;
 
 // Merge an environment map with the current environment.
 // Existing variables are overwritten by env_vars_to_set.
 EnvironmentArray BuildEnvironmentArray(const environment_map& env_vars_to_set);
 #endif
 
+#if defined(MOZ_ENABLE_FORKSERVER)
+/**
+ * Create and initialize a new process as a content process.
+ *
+ * This class is used only by the fork server.
+ * To create a new content process, two steps are
+ *  - calling |ForkProcess()| to create a new process, and
+ *  - calling |InitAppProcess()| in the new process, the child
+ *    process, to initialize it for running WEB content later.
+ *
+ * The fork server can clean up it's resources in-between the first
+ * and second step, that is why two steps.
+ */
+class AppProcessBuilder {
+public:
+  AppProcessBuilder();
+  // This function will fork a new process for use as a
+  // content processes.
+  bool ForkProcess(const std::vector<std::string>& argv,
+                   const LaunchOptions& options, ProcessHandle* process_handle);
+  // This function will be called in the child process to initializes
+  // the environment of the content process.  It should be called
+  // after the message loop of the main thread, to make sure the fork
+  // server is destroyed properly in the child process.
+  //
+  // The message loop may allocate resources like file descriptors.
+  // If this function is called before the end of the loop, the
+  // reosurces may be destroyed while the loop is still alive.
+  void InitAppProcess(int *argcp, char*** argvp);
+
+private:
+  void ReplaceArguments(int *argcp, char*** argvp);
+
+  mozilla::ipc::FileDescriptorShuffle shuffle_;
+  std::vector<std::string> argv_;
+};
+
+void RegisterForkServerNoCloseFD(int aFd);
+#endif
+
 // Executes the application specified by cl. This function delegates to one
 // of the above two platform-specific functions.
 bool LaunchApp(const CommandLine& cl, const LaunchOptions&,
                ProcessHandle* process_handle);
 
 // Attempts to kill the process identified by the given process
 // entry structure, giving it the specified exit code. If |wait| is true, wait
 // for the process to be actually terminated before returning.
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -6,32 +6,159 @@
 
 #include "base/process_util.h"
 
 #include <string>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "algorithm"
+
+#if defined(MOZ_ENABLE_FORKSERVER)
+#include <stdlib.h>
+#include <sys/types.h>
+#  if defined(DEBUG)
+#include "base/message_loop.h"
+#  endif
+#include "mozilla/DebugOnly.h"
+
+using namespace mozilla::ipc;
+#endif
+
 #include "base/eintr_wrapper.h"
 #include "base/logging.h"
 #include "mozilla/ipc/FileDescriptorShuffle.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/StaticPtr.h"
 
 // WARNING: despite the name, this file is also used on the BSDs and
 // Solaris (basically, Unixes that aren't Mac OS), not just Linux.
 
 namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
+#if defined(MOZ_ENABLE_FORKSERVER)
+static mozilla::StaticAutoPtr<std::vector<int> > sNoCloseFDs;
+
+void
+RegisterForkServerNoCloseFD(int fd) {
+  if (!sNoCloseFDs) {
+    sNoCloseFDs = new std::vector<int>();
+  }
+  sNoCloseFDs->push_back(fd);
+}
+
+static bool
+IsNoCloseFd(int fd) {
+  if (!sNoCloseFDs) {
+    return false;
+  }
+  return std::any_of(sNoCloseFDs->begin(), sNoCloseFDs->end(),
+                     [fd](int regfd) -> bool { return regfd == fd; });
+}
+
+AppProcessBuilder::AppProcessBuilder() {
+}
+
+static void
+ReplaceEnviroment(const LaunchOptions& options) {
+  for (auto& elt : options.env_map) {
+    setenv(elt.first.c_str(), elt.second.c_str(), 1);
+  }
+}
+
+bool
+AppProcessBuilder::ForkProcess(const std::vector<std::string>& argv,
+                               const LaunchOptions& options, ProcessHandle* process_handle) {
+  argv_ = argv;
+  if (!shuffle_.Init(options.fds_to_remap)) {
+    return false;
+  }
+
+  // Avoid the content of the buffer being sent out by child processes
+  // repeatly.
+  fflush(stdout);
+  fflush(stderr);
+
+#ifdef OS_LINUX
+  pid_t pid = options.fork_delegate ? options.fork_delegate->Fork() : fork();
+  // WARNING: if pid == 0, only async signal safe operations are permitted from
+  // here until exec or _exit.
+  //
+  // Specifically, heap allocation is not safe: the sandbox's fork substitute
+  // won't run the pthread_atfork handlers that fix up the malloc locks.
+#else
+  pid_t pid = fork();
+#endif
+
+  if (pid < 0) {
+    return false;
+  }
+
+  if (pid == 0) {
+    ReplaceEnviroment(options);
+  } else {
+    gProcessLog.print("==> process %d launched child process %d\n",
+                      GetCurrentProcId(), pid);
+    if (options.wait) HANDLE_EINTR(waitpid(pid, 0, 0));
+  }
+
+  if (process_handle) *process_handle = pid;
+
+  return true;
+}
+
+void
+AppProcessBuilder::ReplaceArguments(int *argcp, char*** argvp) {
+  // Change argc & argv of main() with the arguments passing
+  // through IPC.
+  char** argv = new char*[argv_.size() + 1];
+  char** p = argv;
+  for (auto& elt : argv_) {
+    *p++ = strdup(elt.c_str());
+  }
+  *p = nullptr;
+  *argvp = argv;
+  *argcp = argv_.size();
+}
+
+void
+AppProcessBuilder::InitAppProcess(int *argcp, char*** argvp) {
+  MOZ_ASSERT(MessageLoop::current() == nullptr,
+             "The message loop of the main thread should have been destroyed");
+
+  // The fork server handle SIGCHLD to read status of content
+  // processes to handle Zombies.  But, it is not necessary for
+  // content processes.
+  signal(SIGCHLD, SIG_DFL);
+
+  for (const auto& fds : shuffle_.Dup2Sequence()) {
+    int fd = HANDLE_EINTR(dup2(fds.first, fds.second));
+    MOZ_RELEASE_ASSERT(fd == fds.second, "dup2 failed");
+  }
+
+  CloseSuperfluousFds(&shuffle_, [](void* ctx, int fd) {
+    return static_cast<decltype(&shuffle_)>(ctx)->MapsTo(fd) ||
+      IsNoCloseFd(fd);
+  });
+  // Without this, the destructor of |shuffle_| would try to close FDs
+  // created by it, but they have been closed by
+  // |CloseSuperfluousFds()|.
+  shuffle_.Forget();
+
+  ReplaceArguments(argcp, argvp);
+}
+#endif  // MOZ_ENABLE_FORKSERVER
+
 bool LaunchApp(const std::vector<std::string>& argv,
                const LaunchOptions& options, ProcessHandle* process_handle) {
   mozilla::UniquePtr<char*[]> argv_cstr(new char*[argv.size() + 1]);
 
   EnvironmentArray envp = BuildEnvironmentArray(options.env_map);
   mozilla::ipc::FileDescriptorShuffle shuffle;
   if (!shuffle.Init(options.fds_to_remap)) {
     return false;
@@ -48,38 +175,34 @@ bool LaunchApp(const std::vector<std::st
   pid_t pid = fork();
 #endif
 
   if (pid < 0) return false;
 
   if (pid == 0) {
     // In the child:
     for (const auto& fds : shuffle.Dup2Sequence()) {
-      if (HANDLE_EINTR(dup2(fds.first, fds.second)) != fds.second) {
-        // This shouldn't happen, but check for it.  And see below
-        // about logging being unsafe here, so this is debug only.
-        DLOG(ERROR) << "dup2 failed";
-        _exit(127);
-      }
+      int fd = HANDLE_EINTR(dup2(fds.first, fds.second));
+      MOZ_RELEASE_ASSERT(fd == fds.second, "dup2 failed");
     }
 
     CloseSuperfluousFds(&shuffle, [](void* aCtx, int aFd) {
       return static_cast<decltype(&shuffle)>(aCtx)->MapsTo(aFd);
     });
 
     for (size_t i = 0; i < argv.size(); i++)
       argv_cstr[i] = const_cast<char*>(argv[i].c_str());
     argv_cstr[argv.size()] = NULL;
 
     execve(argv_cstr[0], argv_cstr.get(), envp.get());
-    // if we get here, we're in serious trouble and should complain loudly
-    // NOTE: This is async signal unsafe; it could deadlock instead.  (But
-    // only on debug builds; otherwise it's a signal-safe no-op.)
-    DLOG(ERROR) << "FAILED TO exec() CHILD PROCESS, path: " << argv_cstr[0];
-    _exit(127);
+
+    char failed_exec[256];
+    snprintf(failed_exec, sizeof(failed_exec),
+             "FAILED TO exec() CHILD PROCESS, path: %s", argv_cstr[0]);
+    MOZ_CRASH_UNSAFE(failed_exec);
   }
 
   // In the parent:
   gProcessLog.print("==> process %d launched child process %d\n",
                     GetCurrentProcId(), pid);
   if (options.wait) HANDLE_EINTR(waitpid(pid, 0, 0));
 
   if (process_handle) *process_handle = pid;
--- a/ipc/glue/FileDescriptorShuffle.h
+++ b/ipc/glue/FileDescriptorShuffle.h
@@ -44,16 +44,22 @@ class FileDescriptorShuffle {
   // Accessor for the dup2() sequence.  Do not use the returned value
   // or the fds contained in it after this object is destroyed.
   MappingRef Dup2Sequence() const { return mMapping; }
 
   // Tests whether the given fd is used as a destination in this mapping.
   // Can be used to close other fds after performing the dup2()s.
   bool MapsTo(int aFd) const;
 
+  // Forget the information, so that it's destructor will not try to
+  // delete FDs duped by itself.
+  void Forget() {
+    mTempFds.Clear();
+  }
+
  private:
   nsTArray<std::pair<int, int>> mMapping;
   nsTArray<int> mTempFds;
   int mMaxDst;
 
   FileDescriptorShuffle(const FileDescriptorShuffle&) = delete;
   void operator=(const FileDescriptorShuffle&) = delete;
 };