#include "sandbox/policy/linux/sandbox_linux.h"
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/set_process_title.h"
#include "base/strings/string_number_conversions.h"
#include "base/system/sys_info.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_id_name_manager.h"
#include "build/build_config.h"
#include "sandbox/constants.h"
#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/libc_interceptor.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "sandbox/linux/services/proc_util.h"
#include "sandbox/linux/services/resource_limits.h"
#include "sandbox/linux/services/syscall_wrappers.h"
#include "sandbox/linux/services/thread_helpers.h"
#include "sandbox/linux/services/yama.h"
#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
#include "sandbox/linux/syscall_broker/broker_client.h"
#include "sandbox/linux/syscall_broker/broker_command.h"
#include "sandbox/linux/syscall_broker/broker_process.h"
#include "sandbox/linux/system_headers/linux_landlock.h"
#include "sandbox/linux/system_headers/linux_stat.h"
#include "sandbox/policy/features.h"
#include "sandbox/policy/linux/bpf_broker_policy_linux.h"
#include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "sandbox/sandbox_buildflags.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#if BUILDFLAG(USING_SANITIZER)
#include <sanitizer/common_interface_defs.h>
#endif
namespace sandbox {
namespace policy {
namespace {
void LogSandboxStarted(const std::string& sandbox_name) {
const std::string process_type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kProcessType);
const std::string activated_sandbox =
"Activated " + sandbox_name +
" sandbox for process type: " + process_type + ".";
VLOG(1) << activated_sandbox;
}
bool IsRunningTSAN() {
#if defined(THREAD_SANITIZER)
return true;
#else
return false;
#endif
}
bool ShouldAllowThreadsDuringSandboxInit(const std::string& process_type,
sandbox::mojom::Sandbox sandbox_type) {
if (process_type == switches::kGpuProcess) {
return true;
}
if (process_type == switches::kUtilityProcess &&
sandbox_type == sandbox::mojom::Sandbox::kOnDeviceModelExecution) {
return true;
}
return false;
}
base::ScopedFD OpenProc(int proc_fd) {
int ret_proc_fd = -1;
if (proc_fd >= 0) {
ret_proc_fd =
HANDLE_EINTR(openat(proc_fd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
} else {
ret_proc_fd = HANDLE_EINTR(
openat(AT_FDCWD, "/proc/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
}
DCHECK_LE(0, ret_proc_fd);
return base::ScopedFD(ret_proc_fd);
}
bool UpdateProcessTypeAndEnableSandbox(
SandboxLinux::Options options,
const syscall_broker::BrokerSandboxConfig& policy) {
base::CommandLine::StringVector exec =
base::CommandLine::ForCurrentProcess()->GetArgs();
base::CommandLine::Reset();
base::CommandLine::Init(0, nullptr);
base::CommandLine::ForCurrentProcess()->InitFromArgv(exec);
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
std::string new_process_type =
command_line->GetSwitchValueASCII(switches::kProcessType);
if (!new_process_type.empty()) {
new_process_type.append("-broker");
} else {
new_process_type = "broker";
}
VLOG(3) << "UpdateProcessTypeAndEnableSandbox: Updating process type to "
<< new_process_type;
command_line->AppendSwitchASCII(switches::kProcessType, new_process_type);
base::SetProcessTitleFromCommandLine(nullptr);
return SandboxSeccompBPF::StartSandboxWithExternalPolicy(
std::make_unique<BrokerProcessPolicy>(policy.allowed_command_set),
base::ScopedFD());
}
}
SandboxLinux::SandboxLinux()
: proc_fd_(-1),
seccomp_bpf_started_(false),
sandbox_status_flags_(kInvalid),
pre_initialized_(false),
seccomp_bpf_supported_(false),
seccomp_bpf_with_tsync_supported_(false),
yama_is_enforcing_(false),
initialize_sandbox_ran_(false),
setuid_sandbox_client_(SetuidSandboxClient::Create()),
broker_process_(nullptr) {
if (!setuid_sandbox_client_) {
LOG(FATAL) << "Failed to instantiate the setuid sandbox client.";
}
#if BUILDFLAG(USING_SANITIZER)
sanitizer_args_ = std::make_unique<__sanitizer_sandbox_arguments>();
*sanitizer_args_ = {0};
#endif
}
SandboxLinux::~SandboxLinux() {
if (pre_initialized_) {
CHECK(initialize_sandbox_ran_);
}
}
SandboxLinux* SandboxLinux::GetInstance() {
SandboxLinux* instance = base::Singleton<SandboxLinux>::get();
CHECK(instance);
return instance;
}
void SandboxLinux::PreinitializeSandbox() {
CHECK(!pre_initialized_);
seccomp_bpf_supported_ = false;
#if BUILDFLAG(USING_SANITIZER)
__sanitizer_sandbox_on_notify(sanitizer_args());
sanitizer_args_.reset();
#endif
proc_fd_ = HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC));
CHECK_GE(proc_fd_, 0);
if (SandboxSeccompBPF::IsSeccompBPFDesired()) {
if (!SandboxSeccompBPF::SupportsSandbox()) {
VLOG(1) << "Lacking support for seccomp-bpf sandbox.";
} else {
seccomp_bpf_supported_ = true;
}
if (SandboxSeccompBPF::SupportsSandboxWithTsync()) {
seccomp_bpf_with_tsync_supported_ = true;
}
}
const int yama_status = Yama::GetStatus();
yama_is_enforcing_ = (yama_status & Yama::STATUS_PRESENT) &&
(yama_status & Yama::STATUS_ENFORCING);
pre_initialized_ = true;
}
void SandboxLinux::EngageNamespaceSandbox(bool from_zygote) {
CHECK(EngageNamespaceSandboxInternal(from_zygote));
}
bool SandboxLinux::EngageNamespaceSandboxIfPossible() {
return EngageNamespaceSandboxInternal(false );
}
std::vector<int> SandboxLinux::GetFileDescriptorsToClose() {
std::vector<int> fds;
if (proc_fd_ >= 0) {
fds.push_back(proc_fd_);
}
return fds;
}
int SandboxLinux::GetStatus() {
if (!pre_initialized_) {
return 0;
}
if (sandbox_status_flags_ == kInvalid) {
sandbox_status_flags_ = 0;
if (setuid_sandbox_client_->IsSandboxed()) {
sandbox_status_flags_ |= kSUID;
if (setuid_sandbox_client_->IsInNewPIDNamespace())
sandbox_status_flags_ |= kPIDNS;
if (setuid_sandbox_client_->IsInNewNETNamespace())
sandbox_status_flags_ |= kNetNS;
} else if (NamespaceSandbox::InNewUserNamespace()) {
sandbox_status_flags_ |= kUserNS;
if (NamespaceSandbox::InNewPidNamespace())
sandbox_status_flags_ |= kPIDNS;
if (NamespaceSandbox::InNewNetNamespace())
sandbox_status_flags_ |= kNetNS;
}
if (seccomp_bpf_supported()) {
sandbox_status_flags_ |= kSeccompBPF;
}
if (seccomp_bpf_with_tsync_supported()) {
sandbox_status_flags_ |= kSeccompTSYNC;
}
if (yama_is_enforcing_) {
sandbox_status_flags_ |= kYama;
}
}
return sandbox_status_flags_;
}
bool SandboxLinux::IsSingleThreaded() const {
base::ScopedFD proc_fd(OpenProc(proc_fd_));
CHECK(proc_fd.is_valid()) << "Could not count threads, the sandbox was not "
<< "pre-initialized properly.";
const bool is_single_threaded =
ThreadHelpers::IsSingleThreaded(proc_fd.get());
return is_single_threaded;
}
bool SandboxLinux::seccomp_bpf_started() const {
return seccomp_bpf_started_;
}
SetuidSandboxClient* SandboxLinux::setuid_sandbox_client() const {
return setuid_sandbox_client_.get();
}
bool SandboxLinux::StartSeccompBPF(sandbox::mojom::Sandbox sandbox_type,
PreSandboxHook hook,
const Options& options) {
CHECK(!seccomp_bpf_started_);
CHECK(pre_initialized_);
#if BUILDFLAG(USE_SECCOMP_BPF)
if (!seccomp_bpf_supported())
return false;
if (IsUnsandboxedSandboxType(sandbox_type) ||
!SandboxSeccompBPF::IsSeccompBPFDesired() ||
!SandboxSeccompBPF::SupportsSandbox()) {
return true;
}
if (hook)
CHECK(std::move(hook).Run(options));
SandboxBPF::SeccompLevel seccomp_level =
options.allow_threads_during_sandbox_init && !IsSingleThreaded()
? SandboxBPF::SeccompLevel::MULTI_THREADED
: SandboxBPF::SeccompLevel::SINGLE_THREADED;
std::unique_ptr<BPFBasePolicy> policy =
SandboxSeccompBPF::PolicyForSandboxType(sandbox_type, options);
SandboxSeccompBPF::StartSandboxWithExternalPolicy(
std::move(policy), OpenProc(proc_fd_), seccomp_level);
SandboxSeccompBPF::RunSandboxSanityChecks(sandbox_type, options);
seccomp_bpf_started_ = true;
LogSandboxStarted("seccomp-bpf");
return true;
#else
return false;
#endif
}
bool SandboxLinux::InitializeSandbox(sandbox::mojom::Sandbox sandbox_type,
SandboxLinux::PreSandboxHook hook,
const Options& options) {
DCHECK(!initialize_sandbox_ran_);
initialize_sandbox_ran_ = true;
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
const std::string process_type =
command_line->GetSwitchValueASCII(switches::kProcessType);
absl::Cleanup sandbox_sealer = [this] { SealSandbox(); };
absl::Cleanup sandbox_promise_keeper = [this, sandbox_type] {
CheckForBrokenPromises(sandbox_type);
};
const bool has_threads = !IsSingleThreaded();
#if !BUILDFLAG(IS_OHOS)
DCHECK(process_type == switches::kGpuProcess ||
!options.allow_threads_during_sandbox_init);
#endif
if (has_threads && !options.allow_threads_during_sandbox_init) {
std::string error_message =
"InitializeSandbox() called with multiple threads in process " +
process_type + ".";
if (IsRunningTSAN())
return false;
bool sandbox_failure_fatal =
!ShouldAllowThreadsDuringSandboxInit(process_type, sandbox_type);
if (process_type == switches::kGpuProcess &&
command_line->HasSwitch(switches::kGpuSandboxFailuresFatal)) {
const std::string switch_value =
command_line->GetSwitchValueASCII(switches::kGpuSandboxFailuresFatal);
sandbox_failure_fatal = switch_value != "no";
}
if (sandbox_failure_fatal && !IsUnsandboxedSandboxType(sandbox_type)) {
error_message += " Try waiting for /proc to be updated.";
LOG(ERROR) << error_message;
for (const auto& id :
base::ThreadIdNameManager::GetInstance()->GetIds()) {
LOG(ERROR) << "ThreadId=" << id << " name:"
<< base::ThreadIdNameManager::GetInstance()->GetName(id);
}
ThreadHelpers::AssertSingleThreaded();
} else {
LOG(WARNING) << error_message;
return false;
}
}
if (!pre_initialized_)
PreinitializeSandbox();
if (options.engage_namespace_sandbox)
EngageNamespaceSandbox(false );
CHECK(!options.check_for_open_directories || !HasOpenDirectories())
<< "InitializeSandbox() called after unexpected directories have been "
<< "opened. This breaks the security of the setuid sandbox.";
InitLibcLocaltimeFunctions();
#if !BUILDFLAG(IS_CHROMEOS)
if (!IsUnsandboxedSandboxType(sandbox_type)) {
DiscourageGetaddrinfo();
}
#endif
int error = 0;
const bool limited_as = LimitAddressSpace(&error);
if (error) {
errno = error;
PCHECK(limited_as);
}
return StartSeccompBPF(sandbox_type, std::move(hook), options);
}
void SandboxLinux::StopThread(base::Thread* thread) {
DCHECK(thread);
StopThreadAndEnsureNotCounted(thread);
}
bool SandboxLinux::seccomp_bpf_supported() const {
CHECK(pre_initialized_);
return seccomp_bpf_supported_;
}
bool SandboxLinux::seccomp_bpf_with_tsync_supported() const {
CHECK(pre_initialized_);
return seccomp_bpf_with_tsync_supported_;
}
rlim_t GetProcessDataSizeLimit(sandbox::mojom::Sandbox sandbox_type) {
#if defined(ARCH_CPU_64_BITS)
if (sandbox_type == sandbox::mojom::Sandbox::kGpu ||
sandbox_type == sandbox::mojom::Sandbox::kOnDeviceModelExecution ||
sandbox_type == sandbox::mojom::Sandbox::kRenderer) {
constexpr rlim_t GB = 1024 * 1024 * 1024;
const rlim_t physical_memory =
base::SysInfo::AmountOfPhysicalMemory().InBytes();
rlim_t limit;
if ((sandbox_type == sandbox::mojom::Sandbox::kGpu ||
sandbox_type == sandbox::mojom::Sandbox::kOnDeviceModelExecution) &&
physical_memory > 64 * GB) {
limit = 64 * GB;
} else if (physical_memory > 32 * GB) {
limit = 32 * GB;
} else if (physical_memory > 16 * GB) {
limit = 16 * GB;
} else {
limit = 8 * GB;
}
if (sandbox_type == sandbox::mojom::Sandbox::kRenderer &&
base::FeatureList::IsEnabled(
sandbox::policy::features::kHigherRendererMemoryLimit)) {
limit *= 2;
}
return limit;
}
#endif
return static_cast<rlim_t>(kDataSizeLimit);
}
bool SandboxLinux::LimitAddressSpace(int* error) {
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
!defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
sandbox::mojom::Sandbox sandbox_type =
SandboxTypeFromCommandLine(*command_line);
if (sandbox_type == sandbox::mojom::Sandbox::kNoSandbox) {
return false;
}
rlim_t process_data_size_limit = GetProcessDataSizeLimit(sandbox_type);
*error = ResourceLimits::Lower(RLIMIT_DATA, process_data_size_limit);
base::SysInfo::AmountOfVirtualMemory();
return *error == 0;
#else
base::SysInfo::AmountOfVirtualMemory();
return false;
#endif
}
void SandboxLinux::StartBrokerProcess(
const syscall_broker::BrokerCommandSet& allowed_command_set,
std::vector<syscall_broker::BrokerFilePermission> permissions,
const Options& options) {
auto policy = std::make_optional<syscall_broker::BrokerSandboxConfig>(
allowed_command_set, std::move(permissions), EACCES);
broker_process_ = new syscall_broker::BrokerProcess(
std::move(policy),
syscall_broker::BrokerProcess::BrokerType::SIGNAL_BASED);
CHECK(broker_process_->Fork(
base::BindOnce(&UpdateProcessTypeAndEnableSandbox, options)));
}
bool SandboxLinux::ShouldBrokerHandleSyscall(int sysno) const {
return broker_process_->IsSyscallAllowed(sysno);
}
bpf_dsl::ResultExpr SandboxLinux::HandleViaBroker(int sysno) const {
const bpf_dsl::ResultExpr handle_via_broker =
bpf_dsl::Trap(syscall_broker::BrokerClient::SIGSYS_Handler,
broker_process_->GetBrokerClientSignalBased());
if (sysno == __NR_fstatat_default) {
const bpf_dsl::Arg<int> flags(3);
return bpf_dsl::If((flags & AT_EMPTY_PATH) == AT_EMPTY_PATH,
RewriteFstatatSIGSYS(BPFBasePolicy::GetFSDeniedErrno()))
.Else(handle_via_broker);
} else {
return handle_via_broker;
}
}
bool SandboxLinux::HasOpenDirectories() const {
return ProcUtil::HasOpenDirectory(proc_fd_);
}
void SandboxLinux::SealSandbox() {
if (proc_fd_ >= 0) {
int ret = IGNORE_EINTR(close(proc_fd_));
CHECK_EQ(0, ret);
proc_fd_ = -1;
}
}
void SandboxLinux::CheckForBrokenPromises(
sandbox::mojom::Sandbox sandbox_type) {
if (sandbox_type != sandbox::mojom::Sandbox::kRenderer) {
return;
}
bool promised_seccomp_bpf_would_start =
(sandbox_status_flags_ != kInvalid) && (GetStatus() & kSeccompBPF);
CHECK(!promised_seccomp_bpf_would_start || seccomp_bpf_started_);
}
void SandboxLinux::StopThreadAndEnsureNotCounted(base::Thread* thread) const {
DCHECK(thread);
base::ScopedFD proc_fd(OpenProc(proc_fd_));
PCHECK(proc_fd.is_valid());
CHECK(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.get(), thread));
}
bool SandboxLinux::EngageNamespaceSandboxInternal(bool from_zygote) {
CHECK(pre_initialized_);
CHECK(IsSingleThreaded())
<< "The process cannot have multiple threads when engaging the namespace "
"sandbox, because the thread engaging the sandbox cannot ensure that "
"other threads close all their open directories.";
if (from_zygote) {
CHECK(NamespaceSandbox::InNewPidNamespace());
const pid_t pid = getpid();
CHECK_EQ(1, pid);
}
if (!Credentials::MoveToNewUserNS()) {
return false;
}
CHECK(Credentials::DropFileSystemAccess(proc_fd_));
std::vector<Credentials::Capability> caps;
if (from_zygote) {
caps.push_back(Credentials::Capability::SYS_ADMIN);
}
CHECK(Credentials::SetCapabilities(proc_fd_, caps));
return true;
}
}
}