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.

Depends on D46879

Differential Revision: https://phabricator.services.mozilla.com/D46880

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Thinker Li 2019-12-05 00:04:19 +00:00
parent e9554bb05d
commit 3b1f4faef8
3 changed files with 184 additions and 0 deletions

View File

@ -43,6 +43,17 @@
#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
@ -165,6 +176,46 @@ typedef mozilla::UniquePtr<char*[], FreeEnvVarsArray> EnvironmentArray;
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&,

View File

@ -11,10 +11,24 @@
#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.
@ -27,6 +41,119 @@ static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
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]);

View File

@ -49,6 +49,12 @@ class FileDescriptorShuffle {
// 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;