|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/common/child_process_host_impl.h" | 
|  |  | 
|  | #include <limits> | 
|  | #include <tuple> | 
|  |  | 
|  | #include "base/atomic_sequence_num.h" | 
|  | #include "base/clang_profiling_buildflags.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/files/file.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/hash/hash.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/numerics/safe_math.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/process/process_metrics.h" | 
|  | #include "base/rand_util.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "build/build_config.h" | 
|  | #include "content/common/content_constants_internal.h" | 
|  | #include "content/common/pseudonymization_salt.h" | 
|  | #include "content/public/common/child_process_host_delegate.h" | 
|  | #include "content/public/common/content_client.h" | 
|  | #include "content/public/common/content_paths.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "ipc/ipc.mojom.h" | 
|  | #include "ipc/ipc_channel.h" | 
|  | #include "ipc/ipc_channel_mojo.h" | 
|  | #include "ipc/ipc_logging.h" | 
|  | #include "ipc/message_filter.h" | 
|  | #include "mojo/public/cpp/bindings/lib/message_quota_checker.h" | 
|  | #include "services/resource_coordinator/public/mojom/memory_instrumentation/constants.mojom.h" | 
|  | #include "services/service_manager/public/cpp/interface_provider.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
|  | #include "base/linux_util.h" | 
|  | #elif BUILDFLAG(IS_MAC) | 
|  | #include "base/mac/foundation_util.h" | 
|  | #include "content/common/mac_helpers.h" | 
|  | #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Global atomic to generate child process unique IDs. | 
|  | base::AtomicSequenceNumber g_unique_id; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | ChildProcessHost::~ChildProcessHost() = default; | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<ChildProcessHost> ChildProcessHost::Create( | 
|  | ChildProcessHostDelegate* delegate, | 
|  | IpcMode ipc_mode) { | 
|  | return base::WrapUnique(new ChildProcessHostImpl(delegate, ipc_mode)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | base::FilePath ChildProcessHost::GetChildPath(int flags) { | 
|  | base::FilePath child_path; | 
|  |  | 
|  | child_path = base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( | 
|  | switches::kBrowserSubprocessPath); | 
|  |  | 
|  | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
|  | // Use /proc/self/exe rather than our known binary path so updates | 
|  | // can't swap out the binary from underneath us. | 
|  | if (child_path.empty() && flags & CHILD_ALLOW_SELF) | 
|  | child_path = base::FilePath(base::kProcSelfExe); | 
|  | #endif | 
|  |  | 
|  | // On most platforms, the child executable is the same as the current | 
|  | // executable. | 
|  | if (child_path.empty()) | 
|  | base::PathService::Get(CHILD_PROCESS_EXE, &child_path); | 
|  |  | 
|  | #if BUILDFLAG(IS_MAC) | 
|  | std::string child_base_name = child_path.BaseName().value(); | 
|  |  | 
|  | if (flags != CHILD_NORMAL && base::mac::AmIBundled()) { | 
|  | // This is a specialized helper, with the |child_path| at | 
|  | // ../Framework.framework/Versions/X/Helpers/Chromium Helper.app/Contents/ | 
|  | // MacOS/Chromium Helper. Go back up to the "Helpers" directory to select | 
|  | // a different variant. | 
|  | child_path = child_path.DirName().DirName().DirName().DirName(); | 
|  |  | 
|  | if (flags == CHILD_RENDERER) { | 
|  | child_base_name += kMacHelperSuffix_renderer; | 
|  | } else if (flags == CHILD_GPU) { | 
|  | child_base_name += kMacHelperSuffix_gpu; | 
|  | } else if (flags == CHILD_PLUGIN) { | 
|  | child_base_name += kMacHelperSuffix_plugin; | 
|  | } else if (flags > CHILD_EMBEDDER_FIRST) { | 
|  | return GetContentClient()->GetChildProcessPath(flags, child_path); | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | child_path = child_path.Append(child_base_name + ".app") | 
|  | .Append("Contents") | 
|  | .Append("MacOS") | 
|  | .Append(child_base_name); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_MAC) | 
|  |  | 
|  | return child_path; | 
|  | } | 
|  |  | 
|  | ChildProcessHostImpl::ChildProcessHostImpl(ChildProcessHostDelegate* delegate, | 
|  | IpcMode ipc_mode) | 
|  | : ipc_mode_(ipc_mode), delegate_(delegate), opening_channel_(false) { | 
|  | if (ipc_mode_ == IpcMode::kLegacy) { | 
|  | // In legacy mode, we only have an IPC Channel. Bind ChildProcess to a | 
|  | // disconnected pipe so it quietly discards messages. | 
|  | std::ignore = child_process_.BindNewPipeAndPassReceiver(); | 
|  | channel_ = IPC::ChannelMojo::Create( | 
|  | mojo_invitation_->AttachMessagePipe( | 
|  | kChildProcessReceiverAttachmentName), | 
|  | IPC::Channel::MODE_SERVER, this, base::ThreadTaskRunnerHandle::Get(), | 
|  | base::ThreadTaskRunnerHandle::Get(), | 
|  | mojo::internal::MessageQuotaChecker::MaybeCreate()); | 
|  | } else if (ipc_mode_ == IpcMode::kNormal) { | 
|  | child_process_.Bind(mojo::PendingRemote<mojom::ChildProcess>( | 
|  | mojo_invitation_->AttachMessagePipe( | 
|  | kChildProcessReceiverAttachmentName), | 
|  | /*version=*/0)); | 
|  | receiver_.Bind(mojo::PendingReceiver<mojom::ChildProcessHost>( | 
|  | mojo_invitation_->AttachMessagePipe( | 
|  | kChildProcessHostRemoteAttachmentName))); | 
|  | receiver_.set_disconnect_handler( | 
|  | base::BindOnce(&ChildProcessHostImpl::OnDisconnectedFromChildProcess, | 
|  | base::Unretained(this))); | 
|  | } | 
|  | } | 
|  |  | 
|  | ChildProcessHostImpl::~ChildProcessHostImpl() { | 
|  | // If a channel was never created than it wasn't registered and the filters | 
|  | // weren't notified. For the sake of symmetry don't call the matching teardown | 
|  | // functions. This is analogous to how RenderProcessHostImpl handles things. | 
|  | if (!channel_) | 
|  | return; | 
|  |  | 
|  | for (auto& filter : filters_) { | 
|  | filter->OnChannelClosing(); | 
|  | filter->OnFilterRemoved(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::AddFilter(IPC::MessageFilter* filter) { | 
|  | filters_.push_back(filter); | 
|  |  | 
|  | if (channel_) | 
|  | filter->OnFilterAdded(channel_.get()); | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::BindReceiver(mojo::GenericPendingReceiver receiver) { | 
|  | child_process_->BindReceiver(std::move(receiver)); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | void ChildProcessHostImpl::ReinitializeLogging( | 
|  | uint32_t logging_dest, | 
|  | base::ScopedFD log_file_descriptor) { | 
|  | auto logging_settings = mojom::LoggingSettings::New(); | 
|  | logging_settings->logging_dest = logging_dest; | 
|  | logging_settings->log_file_descriptor = | 
|  | mojo::PlatformHandle(std::move(log_file_descriptor)); | 
|  | child_process()->ReinitializeLogging(std::move(logging_settings)); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS_ASH) | 
|  |  | 
|  | base::Process& ChildProcessHostImpl::GetPeerProcess() { | 
|  | if (!peer_process_.IsValid()) { | 
|  | const base::Process& process = delegate_->GetProcess(); | 
|  | if (process.IsValid()) { | 
|  | peer_process_ = base::Process::OpenWithExtraPrivileges(process.Pid()); | 
|  | if (!peer_process_.IsValid()) | 
|  | peer_process_ = process.Duplicate(); | 
|  | DCHECK(peer_process_.IsValid()); | 
|  | } | 
|  | } | 
|  |  | 
|  | return peer_process_; | 
|  | } | 
|  |  | 
|  | // TODO(crbug.com/1328879): Remove this method when fixing the bug. | 
|  | #if BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_CAST_ANDROID) | 
|  | void ChildProcessHostImpl::RunServiceDeprecated( | 
|  | const std::string& service_name, | 
|  | mojo::ScopedMessagePipeHandle service_pipe) { | 
|  | child_process_->RunServiceDeprecated(service_name, std::move(service_pipe)); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void ChildProcessHostImpl::ForceShutdown() { | 
|  | child_process_->ProcessShutdown(); | 
|  | } | 
|  |  | 
|  | absl::optional<mojo::OutgoingInvitation>& | 
|  | ChildProcessHostImpl::GetMojoInvitation() { | 
|  | return mojo_invitation_; | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::CreateChannelMojo() { | 
|  | // If in legacy mode, |channel_| is already initialized by the constructor | 
|  | // not bound through the ChildProcess API. | 
|  | if (ipc_mode_ != IpcMode::kLegacy) { | 
|  | DCHECK(!channel_); | 
|  | DCHECK_EQ(ipc_mode_, IpcMode::kNormal); | 
|  | DCHECK(child_process_); | 
|  |  | 
|  | mojo::ScopedMessagePipeHandle bootstrap = | 
|  | mojo_invitation_->AttachMessagePipe(kLegacyIpcBootstrapAttachmentName); | 
|  | channel_ = IPC::ChannelMojo::Create( | 
|  | std::move(bootstrap), IPC::Channel::MODE_SERVER, this, | 
|  | base::ThreadTaskRunnerHandle::Get(), | 
|  | base::ThreadTaskRunnerHandle::Get(), | 
|  | mojo::internal::MessageQuotaChecker::MaybeCreate()); | 
|  | } | 
|  | DCHECK(channel_); | 
|  |  | 
|  | // Since we're initializing a legacy IPC Channel, we will use its connection | 
|  | // status to monitor child process lifetime instead of using the status of the | 
|  | // `receiver_` endpoint. | 
|  | if (receiver_.is_bound()) | 
|  | receiver_.set_disconnect_handler(base::NullCallback()); | 
|  |  | 
|  | bool initialized = InitChannel(); | 
|  | DCHECK(initialized); | 
|  | } | 
|  |  | 
|  | bool ChildProcessHostImpl::InitChannel() { | 
|  | if (!channel_->Connect()) | 
|  | return false; | 
|  |  | 
|  | for (auto& filter : filters_) | 
|  | filter->OnFilterAdded(channel_.get()); | 
|  |  | 
|  | delegate_->OnChannelInitialized(channel_.get()); | 
|  |  | 
|  | // Make sure these messages get sent first. | 
|  | #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) | 
|  | bool enabled = IPC::Logging::GetInstance()->Enabled(); | 
|  | child_process_->SetIPCLoggingEnabled(enabled); | 
|  | #endif | 
|  |  | 
|  | opening_channel_ = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::OnDisconnectedFromChildProcess() { | 
|  | if (channel_) { | 
|  | opening_channel_ = false; | 
|  | delegate_->OnChannelError(); | 
|  | for (auto& filter : filters_) | 
|  | filter->OnChannelError(); | 
|  | } | 
|  |  | 
|  | // This will delete host_, which will also destroy this! | 
|  | delegate_->OnChildDisconnected(); | 
|  | } | 
|  |  | 
|  | bool ChildProcessHostImpl::IsChannelOpening() { | 
|  | return opening_channel_; | 
|  | } | 
|  |  | 
|  | bool ChildProcessHostImpl::Send(IPC::Message* message) { | 
|  | if (!channel_) { | 
|  | delete message; | 
|  | return false; | 
|  | } | 
|  | return channel_->Send(message); | 
|  | } | 
|  |  | 
|  | int ChildProcessHostImpl::GenerateChildProcessUniqueId() { | 
|  | // This function must be threadsafe. | 
|  | // | 
|  | // Historically, this function returned ids started with 1, so in several | 
|  | // places in the code a value of 0 (rather than kInvalidUniqueID) was used as | 
|  | // an invalid value. So we retain those semantics. | 
|  | int id = g_unique_id.GetNext() + 1; | 
|  |  | 
|  | CHECK_NE(0, id); | 
|  | CHECK_NE(kInvalidUniqueID, id); | 
|  |  | 
|  | return id; | 
|  | } | 
|  |  | 
|  | uint64_t ChildProcessHostImpl::ChildProcessUniqueIdToTracingProcessId( | 
|  | int child_process_id) { | 
|  | // In single process mode, all the children are hosted in the same process, | 
|  | // therefore the generated memory dump guids should not be conditioned by the | 
|  | // child process id. The clients need not be aware of SPM and the conversion | 
|  | // takes care of the SPM special case while translating child process ids to | 
|  | // tracing process ids. | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kSingleProcess)) | 
|  | return memory_instrumentation::mojom::kServiceTracingProcessId; | 
|  |  | 
|  | // The hash value is incremented so that the tracing id is never equal to | 
|  | // MemoryDumpManager::kInvalidTracingProcessId. | 
|  | return static_cast<uint64_t>(base::PersistentHash( | 
|  | base::as_bytes(base::make_span(&child_process_id, 1)))) + | 
|  | 1; | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::Ping(PingCallback callback) { | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::BindHostReceiver( | 
|  | mojo::GenericPendingReceiver receiver) { | 
|  | delegate_->BindHostReceiver(std::move(receiver)); | 
|  | } | 
|  |  | 
|  | bool ChildProcessHostImpl::OnMessageReceived(const IPC::Message& msg) { | 
|  | #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) | 
|  | IPC::Logging* logger = IPC::Logging::GetInstance(); | 
|  | if (msg.type() == IPC_LOGGING_ID) { | 
|  | logger->OnReceivedLoggingMessage(msg); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (logger->Enabled()) | 
|  | logger->OnPreDispatchMessage(msg); | 
|  | #endif | 
|  |  | 
|  | bool handled = false; | 
|  | for (auto& filter : filters_) { | 
|  | if (filter->OnMessageReceived(msg)) { | 
|  | handled = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!handled) { | 
|  | handled = delegate_->OnMessageReceived(msg); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED) | 
|  | if (logger->Enabled()) | 
|  | logger->OnPostDispatchMessage(msg); | 
|  | #endif | 
|  | return handled; | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::OnChannelConnected(int32_t peer_pid) { | 
|  | // Propagate the pseudonymization salt to all the child processes. | 
|  | // | 
|  | // TODO(dullweber, lukasza): Figure out if it is possible to reset the salt | 
|  | // at a regular interval (on the order of hours?).  The browser would need | 
|  | // to be responsible for 1) deciding when the refresh happens and 2) pushing | 
|  | // the updated salt to all the child processes. | 
|  | child_process_->SetPseudonymizationSalt(GetPseudonymizationSalt()); | 
|  |  | 
|  | // We ignore the `peer_pid` argument, which ultimately comes over IPC from the | 
|  | // remote process, in favor of the PID already known by the browser after | 
|  | // launching the process. This is partly because IPC Channel is being phased | 
|  | // out and some process types no longer use it, but also because there's | 
|  | // really no need to get this information from the child process when we | 
|  | // already have it. | 
|  | // | 
|  | // TODO(crbug.com/616980): Remove the peer_pid argument altogether from | 
|  | // IPC::Listener::OnChannelConnected. | 
|  | const base::Process& peer_process = GetPeerProcess(); | 
|  | base::ProcessId pid = | 
|  | peer_process.IsValid() ? peer_process.Pid() : base::GetCurrentProcId(); | 
|  | opening_channel_ = false; | 
|  | delegate_->OnChannelConnected(pid); | 
|  | for (auto& filter : filters_) | 
|  | filter->OnChannelConnected(pid); | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::OnChannelError() { | 
|  | OnDisconnectedFromChildProcess(); | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::OnBadMessageReceived(const IPC::Message& message) { | 
|  | delegate_->OnBadMessageReceived(message); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX) | 
|  | void ChildProcessHostImpl::DumpProfilingData(base::OnceClosure callback) { | 
|  | child_process_->WriteClangProfilingProfile(std::move(callback)); | 
|  | } | 
|  |  | 
|  | void ChildProcessHostImpl::SetProfilingFile(base::File file) { | 
|  | child_process_->SetProfilingFile(std::move(file)); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | }  // namespace content |