// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sandbox/policy/linux/sandbox_linux.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/feature_list.h" #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/singleton.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #include "base/system/sys_info.h" #include "base/time/time.h" #include "build/build_config.h" #include "sandbox/constants.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/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/policy/linux/bpf_broker_policy_linux.h" #include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h" #include "sandbox/policy/sandbox.h" #include "sandbox/policy/sandbox_type.h" #include "sandbox/policy/switches.h" #include "sandbox/sandbox_buildflags.h" #if BUILDFLAG(USING_SANITIZER) #include #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 } // Get a file descriptor to /proc. Either duplicate |proc_fd| or try to open // it by using the filesystem directly. // TODO(jln): get rid of this ugly interface. base::ScopedFD OpenProc(int proc_fd) { int ret_proc_fd = -1; if (proc_fd >= 0) { // If a handle to /proc is available, use it. This allows to bypass file // system restrictions. ret_proc_fd = HANDLE_EINTR(openat(proc_fd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC)); } else { // Otherwise, make an attempt to access the file system directly. 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::PreSandboxHook broker_side_hook, SandboxLinux::Options options, syscall_broker::BrokerCommandSet allowed_command_set) { 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); if (broker_side_hook) CHECK(std::move(broker_side_hook).Run(options)); return SandboxSeccompBPF::StartSandboxWithExternalPolicy( std::make_unique(allowed_command_set), base::ScopedFD()); } } // namespace 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::get(); CHECK(instance); return instance; } bool SandboxLinux::PreinitializeSandbox() { CHECK(!pre_initialized_); seccomp_bpf_supported_ = false; #if BUILDFLAG(USING_SANITIZER) // Sanitizers need to open some resources before the sandbox is enabled. // This should not fork, not launch threads, not open a directory. __sanitizer_sandbox_on_notify(sanitizer_args()); sanitizer_args_.reset(); #endif // Open proc_fd_. It would break the security of the setuid sandbox if it was // not closed. // If SandboxLinux::PreinitializeSandbox() runs, InitializeSandbox() must run // as well. proc_fd_ = HANDLE_EINTR(open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); CHECK_GE(proc_fd_, 0); // We "pre-warm" the code that detects supports for seccomp BPF. 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; } } // Yama is a "global", system-level status. We assume it will not regress // after startup. const int yama_status = Yama::GetStatus(); yama_is_enforcing_ = (yama_status & Yama::STATUS_PRESENT) && (yama_status & Yama::STATUS_ENFORCING); pre_initialized_ = true; return seccomp_bpf_supported_; } void SandboxLinux::EngageNamespaceSandbox(bool from_zygote) { CHECK(EngageNamespaceSandboxInternal(from_zygote)); } bool SandboxLinux::EngageNamespaceSandboxIfPossible() { return EngageNamespaceSandboxInternal(false /* from_zygote */); } std::vector SandboxLinux::GetFileDescriptorsToClose() { std::vector 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) { // Initialize sandbox_status_flags_. 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; } // We report whether the sandbox will be activated when renderers, workers // and PPAPI plugins go through sandbox initialization. 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_; } // Threads are counted via /proc/self/task. This is a little hairy because of // PID namespaces and existing sandboxes, so "self" must really be used instead // of using the pid. 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(); } // For seccomp-bpf, we use the SandboxSeccompBPF class. bool SandboxLinux::StartSeccompBPF(SandboxType 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)); // If we allow threads *and* have multiple threads, try to use TSYNC. SandboxBPF::SeccompLevel seccomp_level = options.allow_threads_during_sandbox_init && !IsSingleThreaded() ? SandboxBPF::SeccompLevel::MULTI_THREADED : SandboxBPF::SeccompLevel::SINGLE_THREADED; // If the kernel supports the sandbox, and if the command line says we // should enable it, enable it or die. std::unique_ptr 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(SandboxType 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); // We need to make absolutely sure that our sandbox is "sealed" before // returning. // Unretained() since the current object is a Singleton. base::ScopedClosureRunner sandbox_sealer( base::BindOnce(&SandboxLinux::SealSandbox, base::Unretained(this))); // Make sure that this function enables sandboxes as promised by GetStatus(). // Unretained() since the current object is a Singleton. base::ScopedClosureRunner sandbox_promise_keeper( base::BindOnce(&SandboxLinux::CheckForBrokenPromises, base::Unretained(this), sandbox_type)); const bool has_threads = !IsSingleThreaded(); // For now, restrict the |options.allow_threads_during_sandbox_init| option to // the GPU process DCHECK(process_type == switches::kGpuProcess || !options.allow_threads_during_sandbox_init); if (has_threads && !options.allow_threads_during_sandbox_init) { std::string error_message = "InitializeSandbox() called with multiple threads in process " + process_type + "."; // TSAN starts a helper thread, so we don't start the sandbox and don't // even report an error about it. if (IsRunningTSAN()) return false; // The GPU process is allowed to call InitializeSandbox() with threads. bool sandbox_failure_fatal = process_type != switches::kGpuProcess; // This can be disabled with the '--gpu-sandbox-failures-fatal' flag. // Setting the flag with no value or any value different than 'yes' or 'no' // is equal to setting '--gpu-sandbox-failures-fatal=yes'. 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) { error_message += " Try waiting for /proc to be updated."; LOG(ERROR) << error_message; // This will return if /proc/self eventually reports this process is // single-threaded, or crash if it does not after a number of retries. ThreadHelpers::AssertSingleThreaded(); } else { LOG(ERROR) << error_message; return false; } } // At this point we are either single threaded, or we won't be engaging the // semantic layer of the sandbox and we won't care if there are file // descriptors left open. // Pre-initialize if not already done. if (!pre_initialized_) PreinitializeSandbox(); // Turn on the namespace sandbox if our caller wants it (and the zygote hasn't // done so already). if (options.engage_namespace_sandbox) EngageNamespaceSandbox(false /* from_zygote */); // Check for open directories, which can break the semantic sandbox layer. In // some cases the caller doesn't want to enable the semantic sandbox layer, // and this CHECK should be skipped. In this case, the caller should unset // |options.check_for_open_directories|. CHECK(!options.check_for_open_directories || !HasOpenDirectories()) << "InitializeSandbox() called after unexpected directories have been " << "opened. This breaks the security of the setuid sandbox."; InitLibcLocaltimeFunctions(); // Attempt to limit the future size of the address space of the process. // Fine to call with multiple threads as we don't use RLIMIT_STACK. int error = 0; const bool limited_as = LimitAddressSpace(&error); if (error) { // Restore errno. Internally to |LimitAddressSpace|, the errno due to // setrlimit may be lost. 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(SandboxType sandbox_type) { #if defined(ARCH_CPU_64_BITS) if (sandbox_type == SandboxType::kGpu || sandbox_type == SandboxType::kRenderer) { // Allow the GPU/RENDERER process's sandbox to access more physical memory // if it's available on the system. // // Renderer processes are allowed to access 16 GB; the GPU process, up // to 64 GB. constexpr rlim_t GB = 1024 * 1024 * 1024; const rlim_t physical_memory = base::SysInfo::AmountOfPhysicalMemory(); if (sandbox_type == SandboxType::kGpu && physical_memory > 64 * GB) { return 64 * GB; } else if (sandbox_type == SandboxType::kGpu && physical_memory > 32 * GB) { return 32 * GB; } else if (physical_memory > 16 * GB) { return 16 * GB; } else { return 8 * GB; } } #endif return static_cast(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(); SandboxType sandbox_type = SandboxTypeFromCommandLine(*command_line); if (sandbox_type == SandboxType::kNoSandbox) { return false; } // Unfortunately, it does not appear possible to set RLIMIT_AS such that it // will both (a) be high enough to support V8's and WebAssembly's address // space requirements while also (b) being low enough to mitigate exploits // using integer overflows that require large allocations, heap spray, or // other memory-hungry attack modes. rlim_t process_data_size_limit = GetProcessDataSizeLimit(sandbox_type); // Fine to call with multiple threads as we don't use RLIMIT_STACK. *error = ResourceLimits::Lower(RLIMIT_DATA, process_data_size_limit); // Cache the resource limit before turning on the sandbox. base::SysInfo::AmountOfVirtualMemory(); return *error == 0; #else base::SysInfo::AmountOfVirtualMemory(); return false; #endif // !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && // !defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER) } void SandboxLinux::StartBrokerProcess( const syscall_broker::BrokerCommandSet& allowed_command_set, std::vector permissions, PreSandboxHook broker_side_hook, const Options& options) { // Leaked at shutdown, so use bare |new|. // Use EACCES as the policy's default error number to remain consistent with // other LSMs like AppArmor and Landlock. Some userspace code, such as // glibc's |dlopen|, expect to see EACCES rather than EPERM. See // crbug.com/1233028 for an example. broker_process_ = new syscall_broker::BrokerProcess( EACCES, allowed_command_set, permissions, syscall_broker::BrokerProcess::BrokerType::SIGNAL_BASED); // The initialization callback will perform generic initialization and then // call broker_sandboxer_callback. CHECK(broker_process_->Init(base::BindOnce(&UpdateProcessTypeAndEnableSandbox, std::move(broker_side_hook), options, allowed_command_set))); } bool SandboxLinux::ShouldBrokerHandleSyscall(int sysno) const { return broker_process_->IsSyscallAllowed(sysno); } sandbox::bpf_dsl::ResultExpr SandboxLinux::HandleViaBroker() const { return sandbox::bpf_dsl::Trap( sandbox::syscall_broker::BrokerClient::SIGSYS_Handler, broker_process_->GetBrokerClientSignalBased()); } 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(SandboxType sandbox_type) { if (sandbox_type != SandboxType::kRenderer && sandbox_type != SandboxType::kPpapi) { return; } // Make sure that any promise made with GetStatus() wasn't broken. 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 being in a new PID namespace created by the namespace sandbox and // being the init process. CHECK(NamespaceSandbox::InNewPidNamespace()); const pid_t pid = getpid(); CHECK_EQ(1, pid); } // After we successfully move to a new user ns, we don't allow this function // to fail. if (!Credentials::MoveToNewUserNS()) { return false; } // Note: this requires SealSandbox() to be called later in this process to be // safe, as this class is keeping a file descriptor to /proc/. CHECK(Credentials::DropFileSystemAccess(proc_fd_)); // Now we drop all capabilities that we can. In the zygote process, we need // to keep CAP_SYS_ADMIN, to place each child in its own PID namespace // later on. std::vector caps; if (from_zygote) { caps.push_back(Credentials::Capability::SYS_ADMIN); } CHECK(Credentials::SetCapabilities(proc_fd_, caps)); return true; } } // namespace policy } // namespace sandbox