// 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 "sandbox/policy/fuchsia/sandbox_policy_fuchsia.h"
#include <lib/fdio/spawn.h>
#include <stdio.h>
#include <zircon/processargs.h>
#include <zircon/syscalls/policy.h>
#include <fuchsia/buildinfo/cpp/fidl.h>
#include <fuchsia/camera3/cpp/fidl.h>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/hwinfo/cpp/fidl.h>
#include <fuchsia/intl/cpp/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <fuchsia/memorypressure/cpp/fidl.h>
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/tracing/perfetto/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <memory>
#include <utility>
#include "base/base_paths.h"
#include "base/clang_profiling_buildflags.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/fuchsia/default_job.h"
#include "base/fuchsia/file_utils.h"
#include "base/fuchsia/filtered_service_directory.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread.h"
#include "printing/buildflags/buildflags.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/switches.h"
namespace sandbox {
namespace policy {
namespace {
enum SandboxFeature {
// Provides access to resources required by Vulkan.
kProvideVulkanResources = 1 << 1,
// Read only access to /config/ssl, which contains root certs info.
kProvideSslConfig = 1 << 2,
};
struct SandboxConfig {
base::span<const char* const> services;
uint32_t features;
};
// Services that are passed to all processes.
// Prevent incorrect indentation due to the preprocessor lines within `({...})`:
// clang-format off
constexpr auto kMinimalServices = base::make_span((const char* const[]){
// TODO(crbug.com/40815933): Remove this and/or intl below if an alternative
// solution does not require access to the service in all processes. For now
// these services are made available everywhere because they are required by
// base::SysInfo.
fuchsia::buildinfo::Provider::Name_,
fuchsia::hwinfo::Product::Name_,
// DebugData service is needed only for profiling.
#if BUILDFLAG(CLANG_PROFILING)
"fuchsia.debugdata.Publisher",
#endif
fuchsia::intl::PropertyProvider::Name_,
fuchsia::logger::LogSink::Name_,
fuchsia::tracing::perfetto::ProducerConnector::Name_,
});
// clang-format on
// For processes that only get kMinimalServices and no other capabilities.
constexpr SandboxConfig kMinimalConfig = {
base::span<const char* const>(),
0,
};
constexpr SandboxConfig kGpuConfig = {
base::make_span((const char* const[]){
// TODO(crbug.com/42050308): Use the fuchsia.scheduler API instead.
fuchsia::media::ProfileProvider::Name_,
fuchsia::mediacodec::CodecFactory::Name_,
fuchsia::sysmem::Allocator::Name_,
fuchsia::sysmem2::Allocator::Name_,
"fuchsia.vulkan.loader.Loader",
fuchsia::tracing::provider::Registry::Name_,
fuchsia::ui::composition::Allocator::Name_,
fuchsia::ui::composition::Flatland::Name_,
}),
kProvideVulkanResources,
};
constexpr SandboxConfig kNetworkConfig = {
base::make_span((const char* const[]){
"fuchsia.device.NameProvider",
"fuchsia.net.name.Lookup",
fuchsia::net::interfaces::State::Name_,
"fuchsia.posix.socket.Provider",
}),
kProvideSslConfig,
};
constexpr SandboxConfig kRendererConfig = {
base::make_span((const char* const[]){
fuchsia::fonts::Provider::Name_,
fuchsia::kernel::VmexResource::Name_,
// TODO(crbug.com/42050308): Use the fuchsia.scheduler API instead.
fuchsia::media::ProfileProvider::Name_,
fuchsia::memorypressure::Provider::Name_,
fuchsia::sysmem::Allocator::Name_,
fuchsia::sysmem2::Allocator::Name_,
fuchsia::ui::composition::Allocator::Name_,
}),
0,
};
constexpr SandboxConfig kVideoCaptureConfig = {
base::make_span((const char* const[]){
fuchsia::camera3::DeviceWatcher::Name_,
fuchsia::sysmem::Allocator::Name_,
fuchsia::sysmem2::Allocator::Name_,
}),
0,
};
constexpr SandboxConfig kServiceWithJitConfig = {
base::make_span(
(const char* const[]){fuchsia::kernel::VmexResource::Name_}),
0,
};
const SandboxConfig* GetConfigForSandboxType(sandbox::mojom::Sandbox type) {
switch (type) {
case sandbox::mojom::Sandbox::kNoSandbox:
return nullptr;
case sandbox::mojom::Sandbox::kGpu:
return &kGpuConfig;
case sandbox::mojom::Sandbox::kNetwork:
return &kNetworkConfig;
case sandbox::mojom::Sandbox::kRenderer:
return &kRendererConfig;
case sandbox::mojom::Sandbox::kVideoCapture:
return &kVideoCaptureConfig;
case sandbox::mojom::Sandbox::kServiceWithJit:
return &kServiceWithJitConfig;
// Remaining types receive no-access-to-anything.
case sandbox::mojom::Sandbox::kAudio:
case sandbox::mojom::Sandbox::kCdm:
case sandbox::mojom::Sandbox::kOnDeviceModelExecution:
#if BUILDFLAG(ENABLE_OOP_PRINTING)
case sandbox::mojom::Sandbox::kPrintBackend:
#endif
case sandbox::mojom::Sandbox::kPrintCompositor:
case sandbox::mojom::Sandbox::kService:
case sandbox::mojom::Sandbox::kSpeechRecognition:
case sandbox::mojom::Sandbox::kUtility:
return &kMinimalConfig;
}
}
scoped_refptr<base::SequencedTaskRunner> GetServiceDirectoryTaskRunner() {
static base::NoDestructor<base::Thread> service_directory_thread(
"svc_directory");
if (!service_directory_thread->IsRunning()) {
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
CHECK(service_directory_thread->StartWithOptions(std::move(options)));
}
return service_directory_thread->task_runner();
}
void AddServiceCallback(const char* service_name, zx_status_t status) {
ZX_CHECK(status == ZX_OK, status)
<< "AddService(" << service_name << ") failed";
}
void ConnectClientCallback(zx_status_t status) {
ZX_CHECK(status == ZX_OK, status) << "ConnectClient failed";
}
} // namespace
SandboxPolicyFuchsia::SandboxPolicyFuchsia(sandbox::mojom::Sandbox type) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox)) {
type_ = sandbox::mojom::Sandbox::kNoSandbox;
} else {
type_ = type;
}
// If we need to pass some services for the given sandbox type then create
// |sandbox_directory_| and initialize it with the corresponding list of
// services. FilteredServiceDirectory must be initialized on a thread that has
// an async_dispatcher.
const SandboxConfig* config = GetConfigForSandboxType(type_);
if (config) {
filtered_service_directory_ =
base::SequenceBound<base::FilteredServiceDirectory>(
GetServiceDirectoryTaskRunner(),
base::ComponentContextForProcess()->svc());
for (const char* service_name : kMinimalServices) {
// |service_name_| points to a compile-time constant in
// |kMinimalServices|. It will remain valid for the duration of the task.
filtered_service_directory_
.AsyncCall(&base::FilteredServiceDirectory::AddService)
.WithArgs(service_name)
.Then(base::BindOnce(&AddServiceCallback, service_name));
}
for (const char* service_name : config->services) {
// |service_name_| comes from |config|, which points to a compile-time
// constant. It will remain valid for the duration of the task.
filtered_service_directory_
.AsyncCall(&base::FilteredServiceDirectory::AddService)
.WithArgs(service_name)
.Then(base::BindOnce(&AddServiceCallback, service_name));
}
filtered_service_directory_
.AsyncCall(&base::FilteredServiceDirectory::ConnectClient)
.WithArgs(service_directory_client_.NewRequest())
.Then(base::BindOnce(&ConnectClientCallback));
}
}
SandboxPolicyFuchsia::~SandboxPolicyFuchsia() = default;
void SandboxPolicyFuchsia::UpdateLaunchOptionsForSandbox(
base::LaunchOptions* options) {
// Always clone stderr to get logs output.
options->fds_to_remap.push_back(std::make_pair(STDERR_FILENO, STDERR_FILENO));
options->fds_to_remap.push_back(std::make_pair(STDOUT_FILENO, STDOUT_FILENO));
if (type_ == sandbox::mojom::Sandbox::kNoSandbox) {
options->spawn_flags = FDIO_SPAWN_CLONE_NAMESPACE | FDIO_SPAWN_CLONE_JOB;
options->clear_environment = false;
return;
}
// Map /pkg (read-only files deployed from the package) into the child's
// namespace.
options->paths_to_clone.push_back(
base::FilePath(base::kPackageRootDirectoryPath));
// If /config/tzdata/icu/ exists then it contains up-to-date timezone
// data which should be provided to all sub-processes, for consistency.
// LINT.IfChange(icu_time_zone_data_path)
const auto kIcuTimezoneDataPath = base::FilePath("/config/tzdata/icu");
// LINT.ThenChange(//base/i18n/icu_util.cc:icu_time_zone_data_path)
static bool icu_timezone_data_exists = base::PathExists(kIcuTimezoneDataPath);
if (icu_timezone_data_exists) {
options->paths_to_clone.push_back(kIcuTimezoneDataPath);
}
// Clear environmental variables to better isolate the child from
// this process.
options->clear_environment = true;
// Don't clone anything by default.
options->spawn_flags = 0;
// Must get a config here as --no-sandbox bails out earlier.
const SandboxConfig* config = GetConfigForSandboxType(type_);
CHECK(config);
if (config->features & kProvideSslConfig) {
options->paths_to_clone.push_back(base::FilePath("/config/ssl"));
}
if (config->features & kProvideVulkanResources) {
static const char* const kPathsToCloneForVulkan[] = {
// Used configure and access the GPU.
"/dev/class/gpu", "/config/vulkan/icd.d",
// Used for Fuchsia Emulator.
"/dev/class/goldfish-address-space", "/dev/class/goldfish-control",
"/dev/class/goldfish-pipe", "/dev/class/goldfish-sync"};
for (const char* path_str : kPathsToCloneForVulkan) {
base::FilePath path(path_str);
// Vulkan paths aren't needed with newer Fuchsia versions, so they may not
// be available.
if (base::PathExists(path)) {
options->paths_to_clone.push_back(path);
}
}
}
// If the process needs access to any services then transfer the
// |service_directory_client_| handle for it to mount at "/svc".
if (service_directory_client_) {
options->paths_to_transfer.push_back(base::PathToTransfer{
base::FilePath("/svc"),
service_directory_client_.TakeChannel().release()});
}
// Isolate the child process from the call by launching it in its own job.
zx_status_t status = zx::job::create(*base::GetDefaultJob(), 0, &job_);
ZX_CHECK(status == ZX_OK, status) << "zx_job_create";
options->job_handle = job_.get();
}
} // namespace policy
} // namespace sandbox