/* -*- 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 "mozilla/ipc/ProtocolMessageUtils.h" #include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "ipc/IPCMessageUtilsSpecializations.h" #include "nsTraceRefcnt.h" #include #include #include #if defined(XP_LINUX) && defined(MOZ_SANDBOX) # include "mozilla/SandboxLaunch.h" #endif #include namespace mozilla { namespace ipc { LazyLogModule gForkServiceLog("ForkService"); ForkServer::ForkServer() {} /** * Prepare an environment for running a fork server. */ void ForkServer::InitProcess(int* aArgc, char*** aArgv) { base::InitForkServerProcess(); mTcver = MakeUnique(kClientPipeFd, DataBufferClear::AfterReceiving); } /** * Start providing the service at the IPC channel. */ bool ForkServer::HandleMessages() { while (true) { UniquePtr 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; size_t 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& aArgv, nsTArray& aArgvArray) { for (auto& elt : aArgvArray) { aArgv.push_back(elt.get()); CleanCString(elt); } } // Prepare aOptions->env_map inline void PrepareEnv(base::LaunchOptions* aOptions, nsTArray& 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& 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 fdmap(fd, Get<1>(elt)); aOptions->fds_to_remap.push_back(fdmap); MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("\t%d => %d", fdmap.first, fdmap.second)); } } template static void ReadParamInfallible(IPC::MessageReader* aReader, P* aResult, const char* aCrashMessage) { if (!IPC::ReadParam(aReader, aResult)) { MOZ_CRASH_UNSAFE(aCrashMessage); } } /** * Parse a Message to get a list of arguments and fill a LaunchOptions. */ inline bool ParseForkNewSubprocess(IPC::Message& aMsg, std::vector& aArgv, base::LaunchOptions* aOptions) { if (aMsg.type() != Msg_ForkNewSubprocess__ID) { MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("unknown message type %d\n", aMsg.type())); return false; } IPC::MessageReader reader(aMsg); nsTArray argv_array; nsTArray env_map; nsTArray fds_remap; ReadParamInfallible(&reader, &argv_array, "Error deserializing 'nsCString[]'"); ReadParamInfallible(&reader, &env_map, "Error deserializing 'EnvVar[]'"); ReadParamInfallible(&reader, &fds_remap, "Error deserializing 'FdMapping[]'"); reader.EndRead(); PrepareArguments(aArgv, argv_array); PrepareEnv(aOptions, env_map); PrepareFdsRemap(aOptions, fds_remap); return true; } inline void SanitizeBuffers(IPC::Message& aMsg, std::vector& 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(&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(UniquePtr message) { std::vector argv; base::LaunchOptions options; if (!ParseForkNewSubprocess(*message, argv, &options)) { return; } #if defined(XP_LINUX) && defined(MOZ_SANDBOX) mozilla::SandboxLaunchForkServerPrepare(argv, options); #endif base::ProcessHandle child_pid = -1; mAppProcBuilder = MakeUnique(); 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); IPC::MessageWriter writer(reply); WriteIPDLParam(&writer, 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(*message, 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 // Do this before NS_LogInit() to avoid log files taking lower // FDs. ForkServer forkserver; forkserver.InitProcess(aArgc, aArgv); XRE_SetProcessType("forkserver"); NS_LogInit(); mozilla::LogModule::Init(0, nullptr); MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Start a fork server")); { DebugOnly 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")); NS_LogTerm(); 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(); MOZ_ASSERT("tab"_ns == (*aArgv)[*aArgc - 1], "Only |tab| is allowed!"); // Open log files again with right names and the new PID. nsTraceRefcnt::ResetLogFiles((*aArgv)[*aArgc - 1]); return false; } } // namespace ipc } // namespace mozilla