// Copyright 2017 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/browser/child_process_launcher_helper.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/mac/mach_port_rendezvous.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/posix/global_descriptors.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "content/browser/child_process_launcher.h"
#include "content/browser/child_process_launcher_helper_posix.h"
#include "content/browser/child_process_task_port_provider_mac.h"
#include "content/browser/sandbox_parameters_mac.h"
#include "content/grit/content_resources.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#include "sandbox/mac/sandbox_compiler.h"
#include "sandbox/mac/seatbelt_exec.h"
#include "sandbox/policy/features.h"
#include "sandbox/policy/mac/sandbox_mac.h"
#include "sandbox/policy/sandbox.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
namespace content {
namespace internal {
namespace {
// Class that holds a map of SandboxTypes to compiled policy protos. Only
// certain sandbox types can be cached, depending on the nature of the
// runtime parameters that are bound into the profile.
class SandboxProfileCache {
public:
SandboxProfileCache() = default;
~SandboxProfileCache() = default;
static SandboxProfileCache& Get() {
static base::NoDestructor<SandboxProfileCache> cache;
return *cache;
}
const sandbox::mac::SandboxPolicy* Query(
sandbox::mojom::Sandbox sandbox_type) {
base::AutoLock lock(lock_);
auto it = cache_.find(sandbox_type);
if (it == cache_.end())
return nullptr;
return &it->second;
}
void Insert(sandbox::mojom::Sandbox sandbox_type,
const sandbox::mac::SandboxPolicy& policy) {
DCHECK(sandbox::policy::CanCacheSandboxPolicy(sandbox_type));
base::AutoLock lock(lock_);
cache_.emplace(sandbox_type, policy);
}
private:
base::Lock lock_;
base::flat_map<sandbox::mojom::Sandbox, sandbox::mac::SandboxPolicy> cache_
GUARDED_BY(lock_);
};
} // namespace
std::optional<mojo::NamedPlatformChannel>
ChildProcessLauncherHelper::CreateNamedPlatformChannelOnLauncherThread() {
DCHECK(CurrentlyOnProcessLauncherTaskRunner());
return std::nullopt;
}
void ChildProcessLauncherHelper::BeforeLaunchOnClientThread() {
DCHECK(client_task_runner_->RunsTasksInCurrentSequence());
}
std::unique_ptr<PosixFileDescriptorInfo>
ChildProcessLauncherHelper::GetFilesToMap() {
DCHECK(CurrentlyOnProcessLauncherTaskRunner());
return CreateDefaultPosixFilesToMap(
child_process_id(), mojo_channel_->remote_endpoint(),
/*files_to_preload=*/{}, GetProcessType(), command_line());
}
bool ChildProcessLauncherHelper::IsUsingLaunchOptions() {
return true;
}
bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
FileMappedForLaunch& files_to_register,
base::LaunchOptions* options) {
// Convert FD mapping to FileHandleMappingVector.
options->fds_to_remap = files_to_register.GetMappingWithIDAdjustment(
base::GlobalDescriptors::kBaseDescriptor);
mojo::PlatformHandle endpoint =
mojo_channel_->TakeRemoteEndpoint().TakePlatformHandle();
DCHECK(endpoint.is_valid_mach_receive());
options->mach_ports_for_rendezvous.insert(std::make_pair(
'mojo', base::MachRendezvousPort(endpoint.TakeMachReceiveRight())));
options->environment = delegate_->GetEnvironment();
options->disclaim_responsibility = delegate_->DisclaimResponsibility();
options->enable_cpu_security_mitigations =
delegate_->EnableCpuSecurityMitigations();
auto sandbox_type =
sandbox::policy::SandboxTypeFromCommandLine(*command_line_);
bool no_sandbox =
command_line_->HasSwitch(sandbox::policy::switches::kNoSandbox) ||
sandbox::policy::IsUnsandboxedSandboxType(sandbox_type);
if (!no_sandbox) {
if (!LOG_IS_ON(INFO)) {
// Disable os logging to com.apple.diagnosticd when logging is not
// enabled. The system logging has a measureable performance impact.
options->environment.insert(
std::make_pair("OS_ACTIVITY_MODE", "disable"));
}
const auto* cached_policy = SandboxProfileCache::Get().Query(sandbox_type);
if (cached_policy) {
policy_ = *cached_policy;
} else {
const bool can_cache_policy =
sandbox::policy::CanCacheSandboxPolicy(sandbox_type);
// Generate the sandbox policy profile.
sandbox::SandboxCompiler compiler(
can_cache_policy ? sandbox::SandboxCompiler::Target::kCompiled
: sandbox::SandboxCompiler::Target::kSource);
compiler.SetProfile(sandbox::policy::GetSandboxProfile(sandbox_type));
const bool sandbox_ok =
SetupSandboxParameters(sandbox_type, *command_line_.get(), &compiler);
if (!sandbox_ok) {
LOG(ERROR) << "Sandbox setup failed.";
return false;
}
std::string error;
if (!compiler.CompilePolicyToProto(policy_, error)) {
LOG(ERROR) << "Failed to compile sandbox policy: " << error;
return false;
}
if (can_cache_policy) {
SandboxProfileCache::Get().Insert(sandbox_type, policy_);
}
}
seatbelt_exec_client_ = std::make_unique<sandbox::SeatbeltExecClient>();
int pipe = seatbelt_exec_client_->GetReadFD();
if (pipe < 0) {
LOG(ERROR) << "The file descriptor for the sandboxed child is invalid.";
return false;
}
options->fds_to_remap.push_back(std::make_pair(pipe, pipe));
// Update the command line to enable the V2 sandbox and pass the
// communication FD to the helper executable.
command_line_->AppendArg(
base::StringPrintf("%s%d", sandbox::switches::kSeatbeltClient, pipe));
}
return true;
}
ChildProcessLauncherHelper::Process
ChildProcessLauncherHelper::LaunchProcessOnLauncherThread(
const base::LaunchOptions* options,
std::unique_ptr<PosixFileDescriptorInfo> files_to_register,
bool* is_synchronous_launch,
int* launch_result) {
*is_synchronous_launch = true;
ChildProcessLauncherHelper::Process process;
process.process = base::LaunchProcess(*command_line(), *options);
*launch_result = process.process.IsValid() ? LAUNCH_RESULT_SUCCESS
: LAUNCH_RESULT_FAILURE;
return process;
}
void ChildProcessLauncherHelper::AfterLaunchOnLauncherThread(
const ChildProcessLauncherHelper::Process& process,
const base::LaunchOptions* options) {
// Send the sandbox profile after launch so that the child will exist and be
// waiting for the message on its side of the pipe.
if (process.process.IsValid() && seatbelt_exec_client_.get() != nullptr) {
seatbelt_exec_client_->SendPolicy(policy_);
}
}
ChildProcessTerminationInfo ChildProcessLauncherHelper::GetTerminationInfo(
const ChildProcessLauncherHelper::Process& process,
bool known_dead) {
ChildProcessTerminationInfo info;
info.status = known_dead ? base::GetKnownDeadTerminationStatus(
process.process.Handle(), &info.exit_code)
: base::GetTerminationStatus(
process.process.Handle(), &info.exit_code);
return info;
}
// static
bool ChildProcessLauncherHelper::TerminateProcess(const base::Process& process,
int exit_code) {
// TODO(crbug.com/40565504): Determine whether we should also call
// EnsureProcessTerminated() to make sure of process-exit, and reap it.
return process.Terminate(exit_code, false);
}
// static
void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
ChildProcessLauncherHelper::Process process) {
DCHECK(CurrentlyOnProcessLauncherTaskRunner());
// Client has gone away, so just kill the process. Using exit code 0 means
// that UMA won't treat this as a crash.
process.process.Terminate(RESULT_CODE_NORMAL_EXIT, false);
base::EnsureProcessTerminated(std::move(process.process));
}
void ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread(
base::Process process,
base::Process::Priority priority) {
if (process.CanSetPriority()) {
process.SetPriority(ChildProcessTaskPortProvider::GetInstance(), priority);
}
}
base::File OpenFileToShare(const base::FilePath& path,
base::MemoryMappedFile::Region* region) {
// Not used yet (until required files are described in the service manifest on
// Mac).
NOTREACHED_IN_MIGRATION();
return base::File();
}
} // namespace internal
} // namespace content