Bug 1470591 - Part 5: ForkServer to create new processes. r=gsvelto draft
authorThinker <thinker.li@gmail.com>
Fri, 22 Nov 2019 21:15:36 -0800
changeset 2497053 d55d96679a3be5109ad8105ed2a05c196f91cad1
parent 2497052 4c8e93ae0668df294da6dc78bb8c30ae79077d08
child 2497054 009278cc097194bcb39ee3bec08c68992641e104
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 5: ForkServer to create new processes. r=gsvelto Class ForkServer and class ForkServiceChild are implemented. The chrome process can ask the fork server process to create content processes. The requests are sent by MiniTransceiver over a socket. The fork server replys with the process IDs/handles of created processes. LaunchOptions::use_forkserver is a boolean. With use_forkserver being true, the chrome process sends a request to the fork server instead of forking directly.
ipc/chromium/src/base/process_util.h
ipc/chromium/src/base/process_util_linux.cc
ipc/glue/FileDescriptor.cpp
ipc/glue/ForkServer.cpp
ipc/glue/ForkServer.h
ipc/glue/ForkServiceChild.cpp
ipc/glue/ForkServiceChild.h
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/IPDLParamTraits.h
ipc/glue/moz.build
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -40,16 +40,17 @@
 #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/Tuple.h"
 #include "mozilla/ipc/FileDescriptorShuffle.h"
 
 namespace mozilla {
 namespace ipc {
 class FileDescriptor;
 }
 }
 #endif
@@ -121,16 +122,20 @@ struct LaunchOptions {
 #if defined(OS_POSIX)
   environment_map env_map;
 
   // A mapping of (src fd -> dest fd) to propagate into the child
   // process.  All other fds will be closed, except std{in,out,err}.
   file_handle_mapping_vector fds_to_remap;
 #endif
 
+#if defined(MOZ_ENABLE_FORKSERVER)
+  bool use_forkserver = false;
+#endif
+
 #if defined(OS_LINUX)
   struct ForkDelegate {
     virtual ~ForkDelegate() {}
     virtual pid_t Fork() = 0;
   };
 
   // If non-null, the fork delegate will be called instead of fork().
   // It is not required to call pthread_atfork hooks.
@@ -208,16 +213,25 @@ public:
 
 private:
   void ReplaceArguments(int *argcp, char*** argvp);
 
   mozilla::ipc::FileDescriptorShuffle shuffle_;
   std::vector<std::string> argv_;
 };
 
+void InitForkServerProcess();
+
+/**
+ * Make a FD not being closed when create a new content process.
+ *
+ * AppProcessBuilder would close most unrelated FDs for new content
+ * processes.  You may want to reserve some of FDs to keep using them
+ * in content processes.
+ */
 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);
 
@@ -271,16 +285,21 @@ class EnvironmentLog {
   }
 
  private:
   std::string fname_;
 
   DISALLOW_EVIL_CONSTRUCTORS(EnvironmentLog);
 };
 
+#if defined(MOZ_ENABLE_FORKSERVER)
+typedef Tuple<nsCString, nsCString> EnvVar;
+typedef Tuple<mozilla::ipc::FileDescriptor, int> FdMapping;
+#endif
+
 }  // namespace mozilla
 
 #if defined(OS_WIN)
 // Undo the windows.h damage
 #  undef GetMessage
 #  undef CreateEvent
 #  undef GetClassName
 #  undef GetBinaryType
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -11,26 +11,30 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "algorithm"
 
 #if defined(MOZ_ENABLE_FORKSERVER)
 #include <stdlib.h>
 #include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 #  if defined(DEBUG)
 #include "base/message_loop.h"
 #  endif
 #include "mozilla/DebugOnly.h"
+#include "mozilla/ipc/ForkServiceChild.h"
 
 using namespace mozilla::ipc;
 #endif
 
 #include "base/eintr_wrapper.h"
 #include "base/logging.h"
+#include "mozilla/ipc/FileDescriptor.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 {
@@ -147,20 +151,66 @@ AppProcessBuilder::InitAppProcess(int *a
   });
   // 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);
 }
+
+static void
+handle_sigchld(int s) {
+  waitpid(-1, nullptr, WNOHANG);
+}
+
+void
+InitForkServerProcess() {
+  // Since content processes are not children of the chrome process
+  // any more, the fork server process has to handle SIGCHLD, or
+  // content process would remain zombie after dead.
+  signal(SIGCHLD, handle_sigchld);
+}
+
+static bool
+LaunchAppWithForkServer(const std::vector<std::string>& argv,
+                      const LaunchOptions& options,
+                      ProcessHandle* process_handle) {
+  MOZ_ASSERT(ForkServiceChild::Get());
+
+  nsTArray<nsCString> _argv(argv.size());
+  nsTArray<mozilla::EnvVar> env(options.env_map.size());
+  nsTArray<mozilla::FdMapping> fdsremap(options.fds_to_remap.size());
+
+  for (auto& arg : argv) {
+    _argv.AppendElement(arg.c_str());
+  }
+  for (auto& vv : options.env_map) {
+    env.AppendElement(mozilla::EnvVar(nsCString(vv.first.c_str()),
+                                      nsCString(vv.second.c_str())));
+  }
+  for (auto& fdmapping : options.fds_to_remap) {
+    fdsremap.AppendElement(mozilla::FdMapping(mozilla::ipc::FileDescriptor(fdmapping.first),
+                                              fdmapping.second));
+  }
+
+  return ForkServiceChild::Get()->SendForkNewSubprocess(_argv, env,
+                                                       fdsremap,
+                                                       process_handle);
+}
 #endif  // MOZ_ENABLE_FORKSERVER
 
 bool LaunchApp(const std::vector<std::string>& argv,
                const LaunchOptions& options, ProcessHandle* process_handle) {
+#if defined(MOZ_ENABLE_FORKSERVER)
+  if (options.use_forkserver) {
+    return LaunchAppWithForkServer(argv, options, process_handle);
+  }
+#endif
+
   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;
   }
 
--- a/ipc/glue/FileDescriptor.cpp
+++ b/ipc/glue/FileDescriptor.cpp
@@ -124,18 +124,27 @@ FileDescriptor::UniquePlatformHandle Fil
 #endif
   NS_WARNING("Failed to duplicate file handle for current process!");
   return UniqueFileHandle();
 }
 
 void IPDLParamTraits<FileDescriptor>::Write(IPC::Message* aMsg,
                                             IProtocol* aActor,
                                             const FileDescriptor& aParam) {
+#ifdef XP_WIN
   FileDescriptor::PickleType pfd =
       aParam.ShareTo(FileDescriptor::IPDLPrivate(), aActor->OtherPid());
+#else
+  // The pid returned by OtherPID() is only required for Windows to
+  // send file descriptors.  For the use case of the fork server,
+  // aActor is always null.  Since it is only for the special case of
+  // Windows, here we skip it for other platforms.
+  FileDescriptor::PickleType pfd =
+      aParam.ShareTo(FileDescriptor::IPDLPrivate(), 0);
+#endif
   WriteIPDLParam(aMsg, aActor, pfd);
 }
 
 bool IPDLParamTraits<FileDescriptor>::Read(const IPC::Message* aMsg,
                                            PickleIterator* aIter,
                                            IProtocol* aActor,
                                            FileDescriptor* aResult) {
   FileDescriptor::PickleType pfd;
new file mode 100644
--- /dev/null
+++ b/ipc/glue/ForkServer.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/ipc/ForkServer.h"
+#include "mozilla/Logging.h"
+#include "chrome/common/chrome_switches.h"
+#include "mozilla/BlockingResourceBase.h"
+#include "nsTraceRefcnt.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+#include "mozilla/SandboxLaunch.h"
+#endif
+
+#include <algorithm>
+
+
+namespace mozilla {
+namespace ipc {
+
+static const int sClientFd = 3;
+
+LazyLogModule gForkServiceLog("ForkService");
+
+ForkServer::ForkServer() {
+}
+
+/**
+ * Prepare an environment for running a fork server.
+ */
+void
+ForkServer::InitProcess(int* aArgc, char*** aArgv) {
+    base::InitForkServerProcess();
+
+    int fd = sClientFd;
+    int fd_flags = fcntl(sClientFd, F_GETFL, 0);
+    fcntl(fd, F_SETFL, fd_flags & ~O_NONBLOCK);
+    mTcver = MakeUnique<MiniTransceiver>(fd, DataBufferClear::AfterReceiving);
+}
+
+/**
+ * Start providing the service at the IPC channel.
+ */
+bool
+ForkServer::HandleMessages() {
+    // |sClientFd| is created by an instance of |IPC::Channel|.
+    // It sends a HELLO automatically.
+    IPC::Message hello;
+    mTcver->RecvInfallible(hello,
+                           "Expect to receive a HELLO message from the parent process!");
+    MOZ_ASSERT(hello.type() == kHELLO_MESSAGE_TYPE);
+
+    // Send it back
+    mTcver->SendInfallible(hello, "Fail to ack the received HELLO!");
+
+    while (true) {
+        IPC::Message msg;
+        if (!mTcver->Recv(msg)) {
+            break;
+        }
+
+        OnMessageReceived(std::move(msg));
+
+        if (mAppProcBuilder) {
+            // New process - child
+            return false;
+        }
+    }
+    // Stop the server
+    return true;
+}
+
+inline void
+CleanCString(nsCString& str) {
+    char* data;
+    int sz = str.GetMutableData(&data);
+
+    memset(data, ' ', sz);
+}
+
+inline void
+CleanString(std::string& str) {
+  const char deadbeef[] =
+      "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"
+      "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef";
+  int pos = 0;
+  unsigned long sz = str.size();
+  while (sz > 0) {
+    int toclean = std::min(sz, sizeof(deadbeef) - 1);
+    str.replace(pos, toclean, deadbeef);
+    sz -= toclean;
+    pos += toclean;
+  }
+}
+
+inline void
+PrepareArguments(std::vector<std::string>& aArgv,
+                 nsTArray<nsCString>& aArgvArray) {
+    for (auto& elt : aArgvArray) {
+        aArgv.push_back(elt.get());
+        CleanCString(elt);
+    }
+}
+
+// Prepare aOptions->env_map
+inline void
+PrepareEnv(base::LaunchOptions* aOptions, nsTArray<EnvVar>& aEnvMap) {
+    for (auto& elt : aEnvMap) {
+        nsCString &var = Get<0>(elt);
+        nsCString &val = Get<1>(elt);
+        aOptions->env_map[var.get()] = val.get();
+        CleanCString(var);
+        CleanCString(val);
+    }
+}
+
+// Prepare aOptions->fds_to_remap
+inline void
+PrepareFdsRemap(base::LaunchOptions* aOptions,
+                nsTArray<FdMapping>& aFdsRemap) {
+    MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("fds mapping:"));
+    for (auto& elt : aFdsRemap) {
+        // FDs are duplicated here.
+        int fd = Get<0>(elt).ClonePlatformHandle().release();
+        std::pair<int, int> fdmap(fd, Get<1>(elt));
+        aOptions->fds_to_remap.push_back(fdmap);
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+                ("\t%d => %d", fdmap.first, fdmap.second));
+    }
+}
+
+/**
+ * Parse a Message to get a list of arguments and fill a LaunchOptions.
+ */
+inline bool
+ParseForkNewSubprocess(IPC::Message& aMsg,
+                       std::vector<std::string>& aArgv,
+                       base::LaunchOptions* aOptions) {
+    if (aMsg.type() != Msg_ForkNewSubprocess__ID) {
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+                ("unknown message type %d\n", aMsg.type()));
+        return false;
+    }
+
+    PickleIterator iter(aMsg);
+    nsTArray<nsCString> argv_array;
+    nsTArray<EnvVar> env_map;
+    nsTArray<FdMapping> fds_remap;
+
+    ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &argv_array,
+                            "Error deserializing 'nsCString[]'");
+    ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &env_map,
+                            "Error deserializing 'EnvVar[]'");
+    ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &fds_remap,
+                            "Error deserializing 'FdMapping[]'");
+    aMsg.EndRead(iter, aMsg.type());
+
+    PrepareArguments(aArgv, argv_array);
+    PrepareEnv(aOptions, env_map);
+    PrepareFdsRemap(aOptions, fds_remap);
+
+    return true;
+}
+
+inline void
+SanitizeBuffers(IPC::Message& aMsg,
+                std::vector<std::string>& aArgv,
+                base::LaunchOptions& aOptions) {
+    // Clean all buffers in the message to make sure content processes
+    // not peeking others.
+    auto& blist = aMsg.Buffers();
+    for (auto itr = blist.Iter();
+         !itr.Done();
+         itr.Advance(blist, itr.RemainingInSegment())) {
+        memset(itr.Data(), 0, itr.RemainingInSegment());
+    }
+    
+    // clean all data string made from the message.
+    for (auto& var : aOptions.env_map) {
+        // Do it anyway since it is not going to be used anymore.
+        CleanString(*const_cast<std::string*>(&var.first));
+        CleanString(var.second);
+    }
+    for (auto& arg : aArgv) {
+        CleanString(arg);
+    }
+}
+
+/**
+ * Extract parameters from the |Message| to create a
+ * |base::AppProcessBuilder| as |mAppProcBuilder|.
+ *
+ * It will return in both the fork server process and the new content
+ * process.  |mAppProcBuilder| is null for the fork server.
+ */
+void
+ForkServer::OnMessageReceived(IPC::Message&& message) {
+    IPC::Message msg(std::move(message));
+
+    std::vector<std::string> argv;
+    base::LaunchOptions options;
+    if (!ParseForkNewSubprocess(msg, argv, &options)) {
+        return;
+    }
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+    mozilla::SandboxLaunchForkServerPrepare(argv, options);
+#endif
+
+    base::ProcessHandle child_pid = -1;
+    mAppProcBuilder = MakeUnique<base::AppProcessBuilder>();
+    if (!mAppProcBuilder->ForkProcess(argv, options, &child_pid)) {
+        MOZ_CRASH("fail to fork");
+    }
+    MOZ_ASSERT(child_pid >= 0);
+
+    if (child_pid == 0) {
+        // Content process
+        return;
+    }
+
+    // Fork server process
+
+    mAppProcBuilder = nullptr;
+
+    IPC::Message reply(MSG_ROUTING_CONTROL, Reply_ForkNewSubprocess__ID);
+    WriteIPDLParam(&reply, nullptr, child_pid);
+    mTcver->SendInfallible(reply,
+                           "failed to send a reply message");
+
+    // Without this, the content processes that is forked later are
+    // able to read the content of buffers even the buffers have been
+    // released.
+    SanitizeBuffers(msg, argv, options);
+}
+
+/**
+ * Setup and run a fork server at the main thread.
+ *
+ * This function returns for two reasons:
+ *  - the fork server is stopped normally, or
+ *  - a new process is forked from the fork server and this function
+ *    returned in the child, the new process.
+ *
+ * For the later case, aArgc and aArgv are modified to pass the
+ * arguments from the chrome process.
+ */
+bool
+ForkServer::RunForkServer(int* aArgc, char*** aArgv) {
+#ifdef DEBUG
+    if (getenv("MOZ_FORKSERVER_WAIT_GDB")) {
+        printf("Waiting for 30 seconds."
+               "  Attach the fork server with gdb %s %d\n",
+               (*aArgv)[0], base::GetCurrentProcId());
+        sleep(30);
+    }
+    bool sleep_newproc = !!getenv("MOZ_FORKSERVER_WAIT_GDB_NEWPROC");
+#endif
+
+    XRE_SetProcessType("forkserver");
+    NS_LogInit();
+    mozilla::LogModule::Init(0, nullptr);
+    MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Start a fork server"));
+    ForkServer forkserver;
+    {
+        forkserver.InitProcess(aArgc, aArgv);
+
+        DebugOnly<base::ProcessHandle> forkserver_pid = base::GetCurrentProcId();
+        if (forkserver.HandleMessages()) {
+            // In the fork server process
+            // The server has stopped.
+            MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Terminate the fork server"));
+            return true;
+        }
+        // Now, we are running in a content process just forked from
+        // the fork server process.
+        MOZ_ASSERT(base::GetCurrentProcId() != forkserver_pid);
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Fork a new content process"));
+    }
+#ifdef DEBUG
+    if (sleep_newproc) {
+        printf("Waiting for 30 seconds."
+               "  Attach the new process with gdb %s %d\n",
+               (*aArgv)[0], base::GetCurrentProcId());
+        sleep(30);
+    }
+#endif
+    NS_LogTerm();
+
+    MOZ_ASSERT(forkserver.mAppProcBuilder);
+    // |messageloop| has been destroyed.  So, we can intialized the
+    // process safely.  Message loops may allocates some file
+    // descriptors.  If it is destroyed later, it may mess up this
+    // content process by closing wrong file descriptors.
+    forkserver.mAppProcBuilder->InitAppProcess(aArgc, aArgv);
+    forkserver.mAppProcBuilder.reset();
+
+    return false;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/ipc/glue/ForkServer.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __FORKSERVER_H_
+#define __FORKSERVER_H_
+
+#include "mozilla/UniquePtr.h"
+#include "base/process_util.h"
+#include "mozilla/ipc/MiniTransceiver.h"
+
+namespace mozilla {
+namespace ipc {
+
+class ForkServer {
+public:
+    static const int kHELLO_MESSAGE_TYPE = 65535;
+
+    ForkServer();
+    ~ForkServer() {};
+
+    void InitProcess(int* aArgc, char*** aArgv);
+    bool HandleMessages();
+
+    // Called when a message is received.
+    void OnMessageReceived(IPC::Message&& message);
+
+    static bool RunForkServer(int* aArgc, char*** aArgv);
+
+private:
+    UniquePtr<MiniTransceiver> mTcver;
+    UniquePtr<base::AppProcessBuilder> mAppProcBuilder;
+};
+
+enum {
+    Msg_ForkNewSubprocess__ID = 0x7f0, // a random picked number
+    Reply_ForkNewSubprocess__ID,
+};
+
+}
+}
+
+#endif // __FORKSERVER_H_
new file mode 100644
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "ForkServiceChild.h"
+#include "ForkServer.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace mozilla {
+namespace ipc {
+
+extern LazyLogModule gForkServiceLog;
+
+mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild;
+
+void
+ForkServiceChild::StartForkServer() {
+    std::vector<std::string> extraArgs;
+
+    GeckoChildProcessHost *subprocess =
+        new GeckoChildProcessHost(GeckoProcessType_ForkServer, false);
+    subprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
+
+    int fd = subprocess->GetChannel()->GetFileDescriptor();
+    fd = dup(fd);               // Dup it because the channel will close it.
+    int fs_flags = fcntl(fd, F_GETFL, 0);
+    fcntl(fd, F_SETFL, fs_flags & ~O_NONBLOCK);
+    int fd_flags = fcntl(fd, F_GETFD, 0);
+    fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);
+
+    sForkServiceChild = mozilla::MakeUnique<ForkServiceChild>(fd, subprocess);
+
+    // Without doing this, IO thread may intercept messages since the
+    // IPC::Channel created by it is still open.
+    subprocess->GetChannel()->Close();
+}
+
+void
+ForkServiceChild::StopForkServer() {
+    sForkServiceChild = nullptr;
+}
+
+ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess)
+    : mWaitForHello(true)
+    , mProcess(aProcess) {
+    mTcver = MakeUnique<MiniTransceiver>(aFd);
+}
+
+ForkServiceChild::~ForkServiceChild() {
+    mProcess->Destroy();
+    close(mTcver->GetFD());
+}
+
+bool
+ForkServiceChild::SendForkNewSubprocess(const nsTArray<nsCString>& aArgv,
+                                        const nsTArray<EnvVar>& aEnvMap,
+                                        const nsTArray<FdMapping>& aFdsRemap,
+                                        pid_t* aPid) {
+    if (mWaitForHello) {
+        // IPC::Channel created by the GeckoChildProcessHost has
+        // already send a HELLO.  It is expected to receive a hello
+        // message from the fork server too.
+        IPC::Message hello;
+        mTcver->RecvInfallible(hello, "Fail to receive HELLO message");
+        MOZ_ASSERT(hello.type() == ForkServer::kHELLO_MESSAGE_TYPE);
+        mWaitForHello = false;
+    }
+
+    mRecvPid = -1;
+    IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID);
+
+    WriteIPDLParam(&msg, nullptr, aArgv);
+    WriteIPDLParam(&msg, nullptr, aEnvMap);
+    WriteIPDLParam(&msg, nullptr, aFdsRemap);
+    if (!mTcver->Send(msg)) {
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+                ("the pipe to the fork server is closed or having errors"));
+        return false;
+    }
+
+    IPC::Message reply;
+    if (!mTcver->Recv(reply)) {
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+                ("the pipe to the fork server is closed or having errors"));
+        return false;
+    }
+    OnMessageReceived(std::move(reply));
+
+    MOZ_ASSERT(mRecvPid != -1);
+    *aPid = mRecvPid;
+    return true;
+}
+
+void
+ForkServiceChild::OnMessageReceived(IPC::Message&& message) {
+    if (message.type() != Reply_ForkNewSubprocess__ID) {
+        MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+                ("unknown reply type %d", message.type()));
+        return;
+    }
+    PickleIterator iter__(message);
+
+    if (!ReadIPDLParam(&message, &iter__, nullptr, &mRecvPid)) {
+        MOZ_CRASH("Error deserializing 'pid_t'");
+    }
+    message.EndRead(iter__, message.type());
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __FORKSERVICE_CHILD_H_
+#define __FORKSERVICE_CHILD_H_
+
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "mozilla/ipc/MiniTransceiver.h"
+
+#include <sys/types.h>
+#include <poll.h>
+
+namespace mozilla {
+namespace ipc {
+
+class GeckoChildProcessHost;
+
+/**
+ * This is the interface to the fork server.
+ *
+ * When the chrome process calls |ForkServiceChild| to create a new
+ * process, this class send a message to the fork server through a
+ * pipe and get the PID of the new process from the reply.
+ */
+class ForkServiceChild {
+public:
+    ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess);
+    virtual ~ForkServiceChild();
+
+    /**
+     * Ask the fork server to create a new process with given parameters.
+     *
+     * The fork server uses |base::LaunchApp()| to create a new
+     * content process with the following parameters.
+     *
+     * \param aArgv assigns |argv| of the content process.
+     * \param aEnvMap sets |LaunchOptions::env_map|.
+     * \param aFdsRemap sets |LaunchOptions::fd_to_remap|.
+     * \param aPid returns the PID of the content process created.
+     * \return true if success.
+     */
+    bool SendForkNewSubprocess(
+        const nsTArray<nsCString>& aArgv,
+        const nsTArray<EnvVar>& aEnvMap,
+        const nsTArray<FdMapping>& aFdsRemap,
+        pid_t* aPid);
+
+    /**
+     * Create a fork server process and the singleton of this class.
+     *
+     * This function uses |GeckoChildProcessHost| to launch the fork
+     * server, getting the fd of a pipe/socket to the fork server from
+     * it's |IPC::Channel|.
+     */
+    static void StartForkServer();
+    static void StopForkServer();
+    /**
+     * Return the singleton.
+     */
+    static ForkServiceChild* Get() { return sForkServiceChild.get(); }
+
+private:
+    // Called when a message is received.
+    void OnMessageReceived(IPC::Message&& message);
+
+    UniquePtr<MiniTransceiver> mTcver;
+    static UniquePtr<ForkServiceChild> sForkServiceChild;
+    pid_t mRecvPid;
+    bool mWaitForHello;
+    GeckoChildProcessHost* mProcess;
+};
+
+}
+}
+
+#endif /* __FORKSERVICE_CHILD_H_ */
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -97,16 +97,20 @@ using mozilla::ScopedPRFileDesc;
 
 #ifdef MOZ_WIDGET_ANDROID
 #  include "AndroidBridge.h"
 #  include "GeneratedJNIWrappers.h"
 #  include "mozilla/jni/Refs.h"
 #  include "mozilla/jni/Utils.h"
 #endif
 
+#ifdef MOZ_ENABLE_FORKSERVER
+#  include "mozilla/ipc/ForkServiceChild.h"
+#endif
+
 static bool ShouldHaveDirectoryService() {
   return GeckoProcessType_Default == XRE_GetProcessType();
 }
 
 namespace mozilla {
 namespace ipc {
 
 static Atomic<int32_t> gChildCounter;
@@ -352,16 +356,21 @@ GeckoChildProcessHost::GeckoChildProcess
     nsCOMPtr<nsIFile> contentTempDir;
     nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
                                          getter_AddRefs(contentTempDir));
     if (NS_SUCCEEDED(rv)) {
       contentTempDir->GetNativePath(mTmpDirName);
     }
   }
 #endif
+#if defined(MOZ_ENABLE_FORKSERVER)
+  if (aProcessType == GeckoProcessType_Content) {
+    mLaunchOptions->use_forkserver = true;
+  }
+#endif
 }
 
 GeckoChildProcessHost::~GeckoChildProcessHost()
 
 {
   AssertIOThread();
   MOZ_RELEASE_ASSERT(mDestroying);
 
@@ -814,17 +823,20 @@ void BaseProcessLauncher::GetChildLogNam
 }
 
 // Windows needs a single dedicated thread for process launching,
 // because of thread-safety restrictions/assertions in the sandbox
 // code.
 //
 // Android also needs a single dedicated thread to simplify thread
 // safety in java.
-#if defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID)
+//
+// Fork server needs a dedicated thread for accessing
+// |ForkServiceChild|.
+#if defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ENABLE_FORKSERVER)
 
 static mozilla::StaticMutex gIPCLaunchThreadMutex;
 static mozilla::StaticRefPtr<nsIThread> gIPCLaunchThread;
 
 class IPCLaunchThreadObserver final : public nsIObserver {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
@@ -867,28 +879,28 @@ nsCOMPtr<nsIEventTarget> GetIPCLauncher(
     }
   }
 
   nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get();
   MOZ_DIAGNOSTIC_ASSERT(thread);
   return thread;
 }
 
-#else  // defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID)
+#else  // defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ENABLE_FORKSERVER)
 
 // Other platforms use an on-demand thread pool.
 
 nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
   nsCOMPtr<nsIEventTarget> pool =
       mozilla::SharedThreadPool::Get(NS_LITERAL_CSTRING("IPC Launch"));
   MOZ_DIAGNOSTIC_ASSERT(pool);
   return pool;
 }
 
-#endif  // XP_WIN
+#endif  // XP_WIN || MOZ_WIDGET_ANDROID || MOZ_ENABLE_FORKSERVER
 
 void
 #if defined(XP_WIN)
 AddAppDirToCommandLine(CommandLine& aCmdLine, nsIFile* aAppDir)
 #else
 AddAppDirToCommandLine(std::vector<std::string>& aCmdLine, nsIFile* aAppDir,
     nsIFile* aProfileDir)
 #endif
--- a/ipc/glue/IPDLParamTraits.h
+++ b/ipc/glue/IPDLParamTraits.h
@@ -63,16 +63,28 @@ static MOZ_NEVER_INLINE void WriteIPDLPa
 
 template <typename P>
 static MOZ_NEVER_INLINE bool ReadIPDLParam(const IPC::Message* aMsg,
                                            PickleIterator* aIter,
                                            IProtocol* aActor, P* aResult) {
   return IPDLParamTraits<P>::Read(aMsg, aIter, aActor, aResult);
 }
 
+template <typename P>
+static MOZ_NEVER_INLINE bool ReadIPDLParamInfallible(const IPC::Message* aMsg,
+                                                     PickleIterator* aIter,
+                                                     IProtocol* aActor, P* aResult,
+                                                     const char* aCrashMessage) {
+  bool ok = ReadIPDLParam(aMsg, aIter, aActor, aResult);
+  if (!ok) {
+    MOZ_CRASH_UNSAFE(aCrashMessage);
+  }
+  return ok;
+}
+
 constexpr void WriteIPDLParamList(IPC::Message*, IProtocol*) {}
 
 template <typename P, typename... Ps>
 static void WriteIPDLParamList(IPC::Message* aMsg, IProtocol* aActor,
                                P&& aParam, Ps&&... aParams) {
   WriteIPDLParam(aMsg, aActor, std::forward<P>(aParam));
   WriteIPDLParamList(aMsg, aActor, std::forward<Ps>(aParams)...);
 }
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -214,16 +214,28 @@ IPDL_SOURCES = [
     'PFileDescriptorSet.ipdl',
     'PIdleScheduler.ipdl',
     'PInProcess.ipdl',
     'PParentToChildStream.ipdl',
     'ProtocolTypes.ipdlh',
     'URIParams.ipdlh',
 ]
 
+if CONFIG['MOZ_ENABLE_FORKSERVER']:
+    EXPORTS.mozilla.ipc += [
+        'ForkServer.h',
+        'ForkServiceChild.h',
+        'MiniTransceiver.h',
+    ]
+    UNIFIED_SOURCES += [
+        'ForkServer.cpp',
+        'ForkServiceChild.cpp',
+        'MiniTransceiver.cpp',
+    ]
+
 LOCAL_INCLUDES += [
     '/dom/ipc',
     '/toolkit/crashreporter',
     '/toolkit/xre',
     '/xpcom/base',
     '/xpcom/threads',
 ]