// Copyright (c) 2013 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 "content/common/sandbox_linux/bpf_gpu_policy_linux.h" #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "build/build_config.h" #include "content/common/sandbox_linux/sandbox_bpf_base_policy_linux.h" #include "content/common/sandbox_linux/sandbox_seccomp_bpf_linux.h" #include "content/common/set_process_title.h" #include "content/public/common/content_switches.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" #include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" #include "sandbox/linux/syscall_broker/broker_file_permission.h" #include "sandbox/linux/syscall_broker/broker_process.h" #include "sandbox/linux/system_headers/linux_syscalls.h" using sandbox::arch_seccomp_data; using sandbox::bpf_dsl::Allow; using sandbox::bpf_dsl::ResultExpr; using sandbox::bpf_dsl::Trap; using sandbox::syscall_broker::BrokerFilePermission; using sandbox::syscall_broker::BrokerProcess; using sandbox::SyscallSets; namespace content { namespace { inline bool IsChromeOS() { #if defined(OS_CHROMEOS) return true; #else return false; #endif } inline bool IsArchitectureX86_64() { #if defined(__x86_64__) return true; #else return false; #endif } inline bool IsArchitectureI386() { #if defined(__i386__) return true; #else return false; #endif } inline bool IsArchitectureArm() { #if defined(__arm__) || defined(__aarch64__) return true; #else return false; #endif } inline bool IsOzone() { #if defined(USE_OZONE) return true; #else return false; #endif } inline bool UseLibV4L2() { #if defined(USE_LIBV4L2) return true; #else return false; #endif } bool IsAcceleratedVaapiVideoEncodeEnabled() { bool accelerated_encode_enabled = false; #if defined(OS_CHROMEOS) const base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); accelerated_encode_enabled = !command_line.HasSwitch(switches::kDisableVaapiAcceleratedVideoEncode); #endif return accelerated_encode_enabled; } bool IsAcceleratedVideoDecodeEnabled() { const base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); return !command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode); } intptr_t GpuSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux_broker_process) { RAW_CHECK(aux_broker_process); BrokerProcess* broker_process = static_cast(aux_broker_process); switch (args.nr) { #if !defined(__aarch64__) case __NR_access: return broker_process->Access(reinterpret_cast(args.args[0]), static_cast(args.args[1])); case __NR_open: #if defined(MEMORY_SANITIZER) // http://crbug.com/372840 __msan_unpoison_string(reinterpret_cast(args.args[0])); #endif return broker_process->Open(reinterpret_cast(args.args[0]), static_cast(args.args[1])); #endif // !defined(__aarch64__) case __NR_faccessat: if (static_cast(args.args[0]) == AT_FDCWD) { return broker_process->Access(reinterpret_cast(args.args[1]), static_cast(args.args[2])); } else { return -EPERM; } case __NR_openat: // Allow using openat() as open(). if (static_cast(args.args[0]) == AT_FDCWD) { return broker_process->Open(reinterpret_cast(args.args[1]), static_cast(args.args[2])); } else { return -EPERM; } default: RAW_CHECK(false); return -ENOSYS; } } void AddV4L2GpuWhitelist(std::vector* permissions) { if (IsAcceleratedVideoDecodeEnabled()) { // Device node for V4L2 video decode accelerator drivers. static const char kDevVideoDecPath[] = "/dev/video-dec"; permissions->push_back(BrokerFilePermission::ReadWrite(kDevVideoDecPath)); } // Device node for V4L2 video encode accelerator drivers. static const char kDevVideoEncPath[] = "/dev/video-enc"; permissions->push_back(BrokerFilePermission::ReadWrite(kDevVideoEncPath)); } class GpuBrokerProcessPolicy : public GpuProcessPolicy { public: static sandbox::bpf_dsl::Policy* Create() { return new GpuBrokerProcessPolicy(); } ~GpuBrokerProcessPolicy() override {} ResultExpr EvaluateSyscall(int system_call_number) const override; private: GpuBrokerProcessPolicy() {} DISALLOW_COPY_AND_ASSIGN(GpuBrokerProcessPolicy); }; // x86_64/i386 or desktop ARM. // A GPU broker policy is the same as a GPU policy with access, open, // openat and in the non-Chrome OS case unlink allowed. ResultExpr GpuBrokerProcessPolicy::EvaluateSyscall(int sysno) const { switch (sysno) { #if !defined(__aarch64__) case __NR_access: case __NR_open: #endif // !defined(__aarch64__) case __NR_faccessat: case __NR_openat: #if !defined(OS_CHROMEOS) // The broker process needs to able to unlink the temporary // files that it may create. This is used by DRI3. case __NR_unlink: #endif return Allow(); default: return GpuProcessPolicy::EvaluateSyscall(sysno); } } void UpdateProcessTypeToGpuBroker() { base::CommandLine::StringVector exec = base::CommandLine::ForCurrentProcess()->GetArgs(); base::CommandLine::Reset(); base::CommandLine::Init(0, NULL); base::CommandLine::ForCurrentProcess()->InitFromArgv(exec); base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kProcessType, "gpu-broker"); // Update the process title. The argv was already cached by the call to // SetProcessTitleFromCommandLine in content_main_runner.cc, so we can pass // NULL here (we don't have the original argv at this point). SetProcessTitleFromCommandLine(NULL); } bool UpdateProcessTypeAndEnableSandbox( sandbox::bpf_dsl::Policy* (*broker_sandboxer_allocator)(void)) { DCHECK(broker_sandboxer_allocator); UpdateProcessTypeToGpuBroker(); return SandboxSeccompBPF::StartSandboxWithExternalPolicy( make_scoped_ptr(broker_sandboxer_allocator()), base::ScopedFD()); } } // namespace GpuProcessPolicy::GpuProcessPolicy() : GpuProcessPolicy(false) { } GpuProcessPolicy::GpuProcessPolicy(bool allow_mincore) : broker_process_(NULL), allow_mincore_(allow_mincore) { } GpuProcessPolicy::~GpuProcessPolicy() {} // Main policy for x86_64/i386. Extended by CrosArmGpuProcessPolicy. ResultExpr GpuProcessPolicy::EvaluateSyscall(int sysno) const { switch (sysno) { #if !defined(OS_CHROMEOS) case __NR_ftruncate: #endif case __NR_ioctl: return Allow(); case __NR_mincore: if (allow_mincore_) { return Allow(); } else { return SandboxBPFBasePolicy::EvaluateSyscall(sysno); } #if defined(__i386__) || defined(__x86_64__) || defined(__mips__) // The Nvidia driver uses flags not in the baseline policy // (MAP_LOCKED | MAP_EXECUTABLE | MAP_32BIT) case __NR_mmap: #endif // We also hit this on the linux_chromeos bot but don't yet know what // weird flags were involved. case __NR_mprotect: // TODO(jln): restrict prctl. case __NR_prctl: return Allow(); #if !defined(__aarch64__) case __NR_access: case __NR_open: #endif // !defined(__aarch64__) case __NR_faccessat: case __NR_openat: DCHECK(broker_process_); return Trap(GpuSIGSYS_Handler, broker_process_); case __NR_sched_getaffinity: case __NR_sched_setaffinity: return sandbox::RestrictSchedTarget(GetPolicyPid(), sysno); default: if (SyscallSets::IsEventFd(sysno)) return Allow(); // Default on the baseline policy. return SandboxBPFBasePolicy::EvaluateSyscall(sysno); } } bool GpuProcessPolicy::PreSandboxHook() { // Warm up resources needed by the policy we're about to enable and // eventually start a broker process. const bool chromeos_arm_gpu = IsChromeOS() && IsArchitectureArm(); // This policy is for x86 or Desktop. DCHECK(!chromeos_arm_gpu); DCHECK(!broker_process()); // Create a new broker process. InitGpuBrokerProcess( GpuBrokerProcessPolicy::Create, std::vector()); // No extra files in whitelist. if (IsArchitectureX86_64() || IsArchitectureI386()) { // Accelerated video dlopen()'s some shared objects // inside the sandbox, so preload them now. if (IsAcceleratedVaapiVideoEncodeEnabled() || IsAcceleratedVideoDecodeEnabled()) { const char* I965DrvVideoPath = NULL; if (IsArchitectureX86_64()) { I965DrvVideoPath = "/usr/lib64/va/drivers/i965_drv_video.so"; } else if (IsArchitectureI386()) { I965DrvVideoPath = "/usr/lib/va/drivers/i965_drv_video.so"; } dlopen(I965DrvVideoPath, RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); dlopen("libva.so.1", RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); #if defined(USE_OZONE) dlopen("libva-drm.so.1", RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); #elif defined(USE_X11) dlopen("libva-x11.so.1", RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); #endif } } return true; } void GpuProcessPolicy::InitGpuBrokerProcess( sandbox::bpf_dsl::Policy* (*broker_sandboxer_allocator)(void), const std::vector& permissions_extra) { static const char kDriRcPath[] = "/etc/drirc"; static const char kDriCard0Path[] = "/dev/dri/card0"; static const char kDriRenderNode0Path[] = "/dev/dri/renderD128"; static const char kDevShm[] = "/dev/shm/"; CHECK(broker_process_ == NULL); // All GPU process policies need these files brokered out. std::vector permissions; permissions.push_back(BrokerFilePermission::ReadWrite(kDriCard0Path)); permissions.push_back(BrokerFilePermission::ReadWrite(kDriRenderNode0Path)); permissions.push_back(BrokerFilePermission::ReadOnly(kDriRcPath)); if (!IsChromeOS()) { permissions.push_back( BrokerFilePermission::ReadWriteCreateUnlinkRecursive(kDevShm)); } else if (IsArchitectureArm() || IsOzone()){ AddV4L2GpuWhitelist(&permissions); if (UseLibV4L2()) { dlopen("/usr/lib/libv4l2.so", RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); // This is a device-specific encoder plugin. dlopen("/usr/lib/libv4l/plugins/libv4l-encplugin.so", RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); } } // Add eventual extra files from permissions_extra. for (const auto& perm : permissions_extra) { permissions.push_back(perm); } broker_process_ = new BrokerProcess(GetFSDeniedErrno(), permissions); // The initialization callback will perform generic initialization and then // call broker_sandboxer_callback. CHECK(broker_process_->Init(base::Bind(&UpdateProcessTypeAndEnableSandbox, broker_sandboxer_allocator))); } } // namespace content