// Copyright 2018 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 "services/audio/audio_sandbox_hook_linux.h" #include #include #include #include #include "base/environment.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/syscall_broker/broker_file_permission.h" using sandbox::syscall_broker::BrokerFilePermission; using sandbox::syscall_broker::MakeBrokerCommandSet; namespace audio { namespace { #if defined(USE_ALSA) void AddAlsaFilePermissions(std::vector* permissions) { base::FilePath home_dir; base::PathService::Get(base::DIR_HOME, &home_dir); const base::FilePath asoundrc = home_dir.Append(FILE_PATH_LITERAL(".asoundrc")); const std::string read_only_filenames[]{"/etc/asound.conf", "/proc/cpuinfo", "/etc/group", "/etc/nsswitch.conf", asoundrc.value()}; for (const auto& filename : read_only_filenames) permissions->push_back(BrokerFilePermission::ReadOnly(filename)); permissions->push_back( BrokerFilePermission::ReadOnlyRecursive("/usr/share/alsa/")); permissions->push_back( BrokerFilePermission::ReadWriteCreateRecursive("/dev/snd/")); static const base::FilePath::CharType dev_aload_path[] = FILE_PATH_LITERAL("/dev/aloadC"); for (int i = 0; i <= 31; ++i) { permissions->push_back(BrokerFilePermission::ReadWrite( base::StringPrintf("%s%d", dev_aload_path, i))); } } #endif #if defined(USE_PULSEAUDIO) // Utility function used to grant permissions on paths used by PulseAudio which // are specified through environment variables. |recursive_only| is used to // determine if the path itself should be allowed access or only its content. void AllowAccessToEnvSpecifiedPath( base::StringPiece variable_name, std::vector* permissions, bool recursive_only) { std::unique_ptr env(base::Environment::Create()); std::string path_value; if (!env->GetVar(variable_name, &path_value)) return; const base::FilePath pa_config_path(path_value); if (pa_config_path.empty()) return; if (!recursive_only) { permissions->push_back(BrokerFilePermission::ReadWriteCreate( pa_config_path.StripTrailingSeparators().value())); } permissions->push_back(BrokerFilePermission::ReadWriteCreateRecursive( pa_config_path.AsEndingWithSeparator().value())); } void AddPulseAudioFilePermissions( std::vector* permissions) { base::FilePath home_dir; base::PathService::Get(base::DIR_HOME, &home_dir); const base::FilePath xauthority_path = home_dir.Append(FILE_PATH_LITERAL(".Xauthority")); // Calling read() system call on /proc/self/exe returns broker process' path, // and it's used by pulse audio for creating a new context. const std::string read_only_filenames[]{ "/etc/machine-id", "/proc/self/exe", "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules", xauthority_path.value()}; for (const auto& filename : read_only_filenames) permissions->push_back(BrokerFilePermission::ReadOnly(filename)); // In certain situations, pulse runs stat() on the home directory. permissions->push_back( BrokerFilePermission::StatOnlyWithIntermediateDirs(home_dir.value())); permissions->push_back( BrokerFilePermission::ReadOnlyRecursive("/etc/pulse/")); // At times, Pulse tries to create the directory even if the directory already // exists and fails if the mkdir() operation returns anything other than // "success" or "exists". const char* pulse_home_dirs[] = {".pulse", ".config/pulse"}; for (const char* pulse_home_dir : pulse_home_dirs) { const base::FilePath pulse_home_path = home_dir.Append(pulse_home_dir); permissions->push_back( BrokerFilePermission::ReadWriteCreate(pulse_home_path.value())); permissions->push_back(BrokerFilePermission::ReadWriteCreateRecursive( pulse_home_path.AsEndingWithSeparator().value())); } // Pulse might also need to create directories in tmp of the form // "/tmp/pulse-". permissions->push_back( BrokerFilePermission::ReadWriteCreateRecursive("/tmp/")); const char* env_tmp_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; for (const char* env_tmp_path : env_tmp_paths) { AllowAccessToEnvSpecifiedPath(env_tmp_path, permissions, /*recursive_only=*/true); } // Read up the Pulse paths specified via environment variable and allow for // read/write/create recursively on the directory. const char* env_pulse_paths[] = {"PULSE_CONFIG_PATH", "PULSE_RUNTIME_PATH", "PULSE_STATE_PATH"}; for (const char* env_pulse_path : env_pulse_paths) { AllowAccessToEnvSpecifiedPath(env_pulse_path, permissions, /*recursive_only=*/false); } const char* run_user_paths[] = {"/run/user", "/var/run/user"}; for (const char* run_user_path : run_user_paths) { const std::string path = base::StringPrintf("%s/%d", run_user_path, getuid()); permissions->push_back(BrokerFilePermission::ReadWriteCreate(path)); permissions->push_back( BrokerFilePermission::ReadWriteCreate(path + "/pulse")); permissions->push_back( BrokerFilePermission::ReadWriteCreateRecursive(path + "/pulse/")); } } #endif std::vector GetAudioFilePermissions() { std::vector permissions{ BrokerFilePermission::ReadOnly("/dev/urandom"), BrokerFilePermission::ReadOnly("/sys/devices/system/cpu"), BrokerFilePermission::ReadOnlyRecursive("/usr/share/locale/"), BrokerFilePermission::ReadWriteCreateRecursive("/dev/shm/")}; #if defined(USE_PULSEAUDIO) AddPulseAudioFilePermissions(&permissions); #endif #if defined(USE_ALSA) AddAlsaFilePermissions(&permissions); #endif return permissions; } void LoadAudioLibraries() { const std::string libraries[]{"libasound.so.2", "libpulse.so.0", "libnss_files.so.2", "libnss_compat.so.2"}; for (const auto& library_name : libraries) { if (nullptr == dlopen(library_name.c_str(), RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE)) { LOG(WARNING) << "dlopen: failed to open " << library_name << " with error: " << dlerror(); } } } } // namespace bool AudioPreSandboxHook(sandbox::policy::SandboxLinux::Options options) { LoadAudioLibraries(); auto* instance = sandbox::policy::SandboxLinux::GetInstance(); instance->StartBrokerProcess(MakeBrokerCommandSet({ sandbox::syscall_broker::COMMAND_ACCESS, #if defined(USE_PULSEAUDIO) sandbox::syscall_broker::COMMAND_MKDIR, #endif sandbox::syscall_broker::COMMAND_OPEN, sandbox::syscall_broker::COMMAND_READLINK, sandbox::syscall_broker::COMMAND_STAT, sandbox::syscall_broker::COMMAND_UNLINK, }), GetAudioFilePermissions(), sandbox::policy::SandboxLinux::PreSandboxHook(), options); // TODO(https://crbug.com/850878) enable namespace sandbox. Currently, if // enabled, connect() on pulse native socket fails with ENOENT (called from // pa_context_connect). return true; } } // namespace audio