/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SandboxBrokerPolicyFactory.h" #include "SandboxInfo.h" #include "SandboxLogging.h" #include "base/shared_memory.h" #include "mozilla/Array.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Omnijar.h" #include "mozilla/Preferences.h" #include "mozilla/SandboxLaunch.h" #include "mozilla/SandboxSettings.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/StaticMutex.h" #include "mozilla/UniquePtr.h" #include "mozilla/UniquePtrExtensions.h" #include "nsComponentManagerUtils.h" #include "nsPrintfCString.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "SpecialSystemDirectory.h" #include "nsReadableUtils.h" #include "nsIFileStreams.h" #include "nsILineInputStream.h" #include "nsIFile.h" #include "nsNetCID.h" #include "prenv.h" #ifdef ANDROID # include "cutils/properties.h" #endif #ifdef MOZ_WIDGET_GTK # include "mozilla/WidgetUtilsGtk.h" # include #endif #ifdef MOZ_ENABLE_V4L2 # include # include # include #endif // MOZ_ENABLE_V4L2 #include #include #include #include #ifndef ANDROID # include #endif namespace mozilla { namespace { static const int rdonly = SandboxBroker::MAY_READ; static const int wronly = SandboxBroker::MAY_WRITE; static const int rdwr = rdonly | wronly; static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE; static const int access = SandboxBroker::MAY_ACCESS; static const int deny = SandboxBroker::FORCE_DENY; } // namespace using CacheE = std::pair; using FileCacheT = nsTArray; static void AddDriPaths(SandboxBroker::Policy* aPolicy) { // Bug 1401666: Mesa driver loader part 2: Mesa <= 12 using libudev // Used by libdrm, which is used by Mesa, and // Intel(R) Media Driver for VAAPI. if (auto dir = opendir("/dev/dri")) { while (auto entry = readdir(dir)) { if (entry->d_name[0] != '.') { nsPrintfCString devPath("/dev/dri/%s", entry->d_name); struct stat sb; if (stat(devPath.get(), &sb) == 0 && S_ISCHR(sb.st_mode)) { // For both the DRI node and its parent (the physical // device), allow reading the "uevent" file. static const Array kSuffixes = {""_ns, "/device"_ns}; nsPrintfCString prefix("/sys/dev/char/%u:%u", major(sb.st_rdev), minor(sb.st_rdev)); for (const auto& suffix : kSuffixes) { nsCString sysPath(prefix + suffix); // libudev will expand the symlink but not do full // canonicalization, so it will leave in ".." path // components that will be realpath()ed in the // broker. To match this, allow the canonical paths. UniqueFreePtr realSysPath(realpath(sysPath.get(), nullptr)); if (realSysPath) { // https://gitlab.freedesktop.org/mesa/drm/-/commit/3988580e4c0f4b3647a0c6af138a3825453fe6e0 // > term = strrchr(real_path, '/'); // > if (term && strncmp(term, "/virtio", 7) == 0) // > *term = 0; char* term = strrchr(realSysPath.get(), '/'); if (term && strncmp(term, "/virtio", 7) == 0) { *term = 0; } aPolicy->AddFilePrefix(rdonly, realSysPath.get(), ""); // Allowing stat-ing and readlink-ing the parent dirs nsPrintfCString basePath("%s/", realSysPath.get()); aPolicy->AddAncestors(basePath.get(), rdonly); } } // https://gitlab.freedesktop.org/mesa/drm/-/commit/a02900133b32dd4a7d6da4966f455ab337e80dfc // > strncpy(path, device_path, PATH_MAX); // > strncat(path, "/subsystem", PATH_MAX); // > // > if (readlink(path, link, PATH_MAX) < 0) // > return -errno; nsCString subsystemPath(prefix + "/device/subsystem"_ns); aPolicy->AddPath(rdonly, subsystemPath.get()); aPolicy->AddAncestors(subsystemPath.get(), rdonly); } } } closedir(dir); } // https://gitlab.freedesktop.org/mesa/mesa/-/commit/04bdbbcab3c4862bf3f54ce60fcc1d2007776f80 aPolicy->AddPath(rdonly, "/usr/share/drirc.d"); // https://dri.freedesktop.org/wiki/ConfigurationInfrastructure/ aPolicy->AddPath(rdonly, "/etc/drirc"); nsCOMPtr drirc; nsresult rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(drirc)); if (NS_SUCCEEDED(rv)) { rv = drirc->AppendNative(".drirc"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = drirc->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { aPolicy->AddPath(rdonly, tmpPath.get()); } } } } static void JoinPathIfRelative(const nsACString& aCwd, const nsACString& inPath, nsACString& outPath) { if (inPath.Length() < 1) { outPath.Assign(aCwd); SANDBOX_LOG("Unjoinable path: %s", PromiseFlatCString(aCwd).get()); return; } const char* startChar = inPath.BeginReading(); if (*startChar != '/') { // Relative path, copy basepath in front outPath.Assign(aCwd); outPath.Append("/"); outPath.Append(inPath); } else { // Absolute path, it's ok like this outPath.Assign(inPath); } } static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath); static void CachePathsFromFileInternal(FileCacheT& aCache, const nsACString& aCwd, const nsACString& aPath) { nsresult rv; nsCOMPtr ldconfig(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return; } rv = ldconfig->InitWithNativePath(aPath); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr fileStream( do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = fileStream->Init(ldconfig, -1, -1, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr lineStream(do_QueryInterface(fileStream, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsAutoCString line; bool more = true; do { rv = lineStream->ReadLine(line, &more); if (NS_FAILED(rv)) { break; } // Cut off any comments at the end of the line, also catches lines // that are entirely a comment int32_t hash = line.FindChar('#'); if (hash >= 0) { line = Substring(line, 0, hash); } // Simplify our following parsing by trimming whitespace line.CompressWhitespace(true, true); if (line.IsEmpty()) { // Skip comment lines continue; } // Check for any included files and recursively process nsACString::const_iterator start, end, token_end; line.BeginReading(start); line.EndReading(end); token_end = end; if (FindInReadable("include "_ns, start, token_end)) { nsAutoCString includes(Substring(token_end, end)); for (const nsACString& includeGlob : includes.Split(' ')) { // Glob path might be relative, so add cwd if so. nsAutoCString includeFile; JoinPathIfRelative(aCwd, includeGlob, includeFile); glob_t globbuf; if (!glob(PromiseFlatCString(includeFile).get(), GLOB_NOSORT, nullptr, &globbuf)) { for (size_t fileIdx = 0; fileIdx < globbuf.gl_pathc; fileIdx++) { nsAutoCString filePath(globbuf.gl_pathv[fileIdx]); CachePathsFromFile(aCache, filePath); } globfree(&globbuf); } } } // Cut off anything behind an = sign, used by dirname=TYPE directives int32_t equals = line.FindChar('='); if (equals >= 0) { line = Substring(line, 0, equals); } char* resolvedPath = realpath(line.get(), nullptr); if (resolvedPath) { aCache.AppendElement(std::make_pair(nsCString(resolvedPath), rdonly)); free(resolvedPath); } } while (more); } static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath) { // Find the new base path where that file sits in. nsresult rv; nsCOMPtr includeFile( do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return; } rv = includeFile->InitWithNativePath(aPath); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { SANDBOX_LOG("Adding paths from %s to policy.", PromiseFlatCString(aPath).get()); } // Find the parent dir where this file sits in. nsCOMPtr parentDir; rv = includeFile->GetParent(getter_AddRefs(parentDir)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsAutoCString parentPath; rv = parentDir->GetNativePath(parentPath); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { SANDBOX_LOG("Parent path is %s", PromiseFlatCString(parentPath).get()); } CachePathsFromFileInternal(aCache, parentPath, aPath); } static void AddLdconfigPaths(SandboxBroker::Policy* aPolicy) { static StaticMutex sMutex; StaticMutexAutoLock lock(sMutex); static FileCacheT ldConfigCache{}; static bool ldConfigCachePopulated = false; if (!ldConfigCachePopulated) { CachePathsFromFile(ldConfigCache, "/etc/ld.so.conf"_ns); ldConfigCachePopulated = true; RunOnShutdown([&] { ldConfigCache.Clear(); MOZ_ASSERT(ldConfigCache.IsEmpty(), "ldconfig cache should be empty"); }); } for (const CacheE& e : ldConfigCache) { aPolicy->AddDir(e.second, e.first.get()); } } static void AddLdLibraryEnvPaths(SandboxBroker::Policy* aPolicy) { nsAutoCString LdLibraryEnv(PR_GetEnv("LD_LIBRARY_PATH")); // The items in LD_LIBRARY_PATH can be separated by either colons or // semicolons, according to the ld.so(8) man page, and empirically it // seems to be allowed to mix them (i.e., a:b;c is a list with 3 elements). // There is no support for escaping the delimiters, fortunately (for us). LdLibraryEnv.ReplaceChar(';', ':'); for (const nsACString& libPath : LdLibraryEnv.Split(':')) { char* resolvedPath = realpath(PromiseFlatCString(libPath).get(), nullptr); if (resolvedPath) { aPolicy->AddDir(rdonly, resolvedPath); free(resolvedPath); } } } static void AddSharedMemoryPaths(SandboxBroker::Policy* aPolicy, pid_t aPid) { std::string shmPath("/dev/shm"); if (base::SharedMemory::AppendPosixShmPrefix(&shmPath, aPid)) { aPolicy->AddPrefix(rdwrcr, shmPath.c_str()); } } static void AddMemoryReporting(SandboxBroker::Policy* aPolicy, pid_t aPid) { // Bug 1198552: memory reporting. // Bug 1647957: memory reporting. aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get()); aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get()); } static void AddDynamicPathList(SandboxBroker::Policy* policy, const char* aPathListPref, int perms) { nsAutoCString pathList; nsresult rv = Preferences::GetCString(aPathListPref, pathList); if (NS_SUCCEEDED(rv)) { for (const nsACString& path : pathList.Split(',')) { nsCString trimPath(path); trimPath.Trim(" ", true, true); policy->AddDynamic(perms, trimPath.get()); } } } static void AddX11Dependencies(SandboxBroker::Policy* policy) { // Allow Primus to contact the Bumblebee daemon to manage GPU // switching on NVIDIA Optimus systems. const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET"); if (bumblebeeSocket == nullptr) { bumblebeeSocket = "/var/run/bumblebee.socket"; } policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket); #if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) // Allow local X11 connections, for several purposes: // // * for content processes to use WebGL when the browser is in headless // mode, by opening the X display if/when needed // // * if Primus or VirtualGL is used, to contact the secondary X server static const bool kIsX11 = !mozilla::widget::GdkIsWaylandDisplay() && PR_GetEnv("DISPLAY"); if (kIsX11) { policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X"); if (auto* const xauth = PR_GetEnv("XAUTHORITY")) { policy->AddPath(rdonly, xauth); } else if (auto* const home = PR_GetEnv("HOME")) { // This follows the logic in libXau: append "/.Xauthority", // even if $HOME ends in a slash, except in the special case // where HOME=/ because POSIX allows implementations to treat // an initial double slash specially. nsAutoCString xauth(home); if (xauth != "/"_ns) { xauth.Append('/'); } xauth.AppendLiteral(".Xauthority"); policy->AddPath(rdonly, xauth.get()); } } #endif } static void AddGLDependencies(SandboxBroker::Policy* policy) { // Devices policy->AddDir(rdwr, "/dev/dri"); policy->AddFilePrefix(rdwr, "/dev", "nvidia"); // Hardware info AddDriPaths(policy); // /etc and /usr/share (glvnd, libdrm, drirc, ...?) policy->AddDir(rdonly, "/etc"); policy->AddDir(rdonly, "/usr/share"); policy->AddDir(rdonly, "/usr/local/share"); // Snap puts the usual /usr/share things in a different place, and // we'll fail to load the library if we don't have (at least) the // glvnd config: if (const char* snapDesktopDir = PR_GetEnv("SNAP_DESKTOP_RUNTIME")) { nsAutoCString snapDesktopShare(snapDesktopDir); snapDesktopShare.AppendLiteral("/usr/share"); policy->AddDir(rdonly, snapDesktopShare.get()); } // Note: This function doesn't do anything about Mesa's shader // cache, because the details can vary by process type, including // whether caching is enabled. // This also doesn't include permissions for connecting to a display // server, because headless GL (e.g., Mesa GBM) may not need it. } void SandboxBrokerPolicyFactory::InitContentPolicy() { const bool headless = StaticPrefs::security_sandbox_content_headless_AtStartup(); // Policy entries that are the same in every process go here, and // are cached over the lifetime of the factory. SandboxBroker::Policy* policy = new SandboxBroker::Policy; // Write permssions // Bug 1575985: WASM library sandbox needs RW access to /dev/null policy->AddPath(rdwr, "/dev/null"); if (!headless) { AddGLDependencies(policy); AddX11Dependencies(policy); } // Read permissions policy->AddPath(rdonly, "/dev/urandom"); policy->AddPath(rdonly, "/dev/random"); policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled"); policy->AddPath(rdonly, "/proc/cpuinfo"); policy->AddPath(rdonly, "/proc/meminfo"); policy->AddDir(rdonly, "/sys/devices/cpu"); policy->AddDir(rdonly, "/sys/devices/system/cpu"); policy->AddDir(rdonly, "/lib"); policy->AddDir(rdonly, "/lib64"); policy->AddDir(rdonly, "/usr/lib"); policy->AddDir(rdonly, "/usr/lib32"); policy->AddDir(rdonly, "/usr/lib64"); policy->AddDir(rdonly, "/etc"); policy->AddDir(rdonly, "/usr/share"); policy->AddDir(rdonly, "/usr/local/share"); // Various places where fonts reside policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts"); policy->AddDir(rdonly, "/nix/store"); // https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/e434e680d22260f277f4a30ec4660ed32b591d16/files/fontconfig-flatpak.conf policy->AddDir(rdonly, "/run/host/fonts"); policy->AddDir(rdonly, "/run/host/user-fonts"); policy->AddDir(rdonly, "/run/host/local-fonts"); policy->AddDir(rdonly, "/var/cache/fontconfig"); // Bug 1848615 policy->AddPath(rdonly, "/usr"); policy->AddPath(rdonly, "/nix"); AddLdconfigPaths(policy); AddLdLibraryEnvPaths(policy); if (!headless) { // Bug 1385715: NVIDIA PRIME support policy->AddPath(rdonly, "/proc/modules"); } // XDG directories might be non existent according to specs: // https://specifications.freedesktop.org/basedir-spec/0.8/ar01s04.html // // > If, when attempting to write a file, the destination directory is // > non-existent an attempt should be made to create it with permission 0700. // // For that we use AddPath(, SandboxBroker::Policy::AddCondition::AddAlways). // // Allow access to XDG_CONFIG_HOME and XDG_CONFIG_DIRS nsAutoCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME")); if (!xdgConfigHome.IsEmpty()) { // AddPath will fail on empty strings policy->AddFutureDir(rdonly, xdgConfigHome.get()); } nsAutoCString xdgConfigDirs(PR_GetEnv("XDG_CONFIG_DIRS")); for (const auto& path : xdgConfigDirs.Split(':')) { if (!path.IsEmpty()) { // AddPath will fail on empty strings policy->AddFutureDir(rdonly, PromiseFlatCString(path).get()); } } // Allow fonts subdir in XDG_DATA_HOME nsAutoCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME")); if (!xdgDataHome.IsEmpty()) { nsAutoCString fontPath(xdgDataHome); fontPath.Append("/fonts"); policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get()); } // Any font subdirs in XDG_DATA_DIRS nsAutoCString xdgDataDirs(PR_GetEnv("XDG_DATA_DIRS")); for (const auto& path : xdgDataDirs.Split(':')) { nsAutoCString fontPath(path); fontPath.Append("/fonts"); policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get()); } // Extra configuration/cache dirs in the homedir that we want to allow read // access to. std::vector extraConfDirsAllow = { ".themes", ".fonts", ".cache/fontconfig", }; // Fallback if XDG_CONFIG_HOME isn't set if (xdgConfigHome.IsEmpty()) { extraConfDirsAllow.emplace_back(".config"); } nsCOMPtr homeDir; nsresult rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir)); if (NS_SUCCEEDED(rv)) { nsCOMPtr confDir; for (const auto& dir : extraConfDirsAllow) { rv = homeDir->Clone(getter_AddRefs(confDir)); if (NS_SUCCEEDED(rv)) { rv = confDir->AppendRelativeNativePath(nsDependentCString(dir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = confDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } } } // ~/.config/mozilla/ needs to be manually blocked, because the previous // loop will allow for ~/.config/ access. { // If $XDG_CONFIG_HOME is set, we need to account for it. // FIXME: Bug 1722272: Maybe this should just be handled with // GetSpecialSystemDirectory(Unix_XDG_ConfigHome) ? nsCOMPtr confDirOrXDGConfigHomeDir; if (!xdgConfigHome.IsEmpty()) { rv = NS_NewNativeLocalFile(xdgConfigHome, true, getter_AddRefs(confDirOrXDGConfigHomeDir)); // confDirOrXDGConfigHomeDir = nsIFile($XDG_CONFIG_HOME) } else { rv = homeDir->Clone(getter_AddRefs(confDirOrXDGConfigHomeDir)); if (NS_SUCCEEDED(rv)) { // since we will use that later, we dont need to care about trailing // slash rv = confDirOrXDGConfigHomeDir->AppendNative(".config"_ns); // confDirOrXDGConfigHomeDir = nsIFile($HOME/.config/) } } if (NS_SUCCEEDED(rv)) { rv = confDirOrXDGConfigHomeDir->AppendNative("mozilla"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = confDirOrXDGConfigHomeDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddFutureDir(deny, tmpPath.get()); } } } } // ~/.local/share (for themes) rv = homeDir->Clone(getter_AddRefs(confDir)); if (NS_SUCCEEDED(rv)) { rv = confDir->AppendNative(".local"_ns); if (NS_SUCCEEDED(rv)) { rv = confDir->AppendNative("share"_ns); } if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = confDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } } // ~/.fonts.conf (Fontconfig) rv = homeDir->Clone(getter_AddRefs(confDir)); if (NS_SUCCEEDED(rv)) { rv = confDir->AppendNative(".fonts.conf"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = confDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddPath(rdonly, tmpPath.get()); } } } // .pangorc rv = homeDir->Clone(getter_AddRefs(confDir)); if (NS_SUCCEEDED(rv)) { rv = confDir->AppendNative(".pangorc"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = confDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddPath(rdonly, tmpPath.get()); } } } } // Firefox binary dir. // Note that unlike the previous cases, we use NS_GetSpecialDirectory // instead of GetSpecialSystemDirectory. The former requires a working XPCOM // system, which may not be the case for some tests. For querying for the // location of XPCOM things, we can use it anyway. nsCOMPtr ffDir; rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = ffDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } if (!mozilla::IsPackagedBuild()) { // If this is not a packaged build the resources are likely symlinks to // outside the binary dir. Therefore in non-release builds we allow reads // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run. const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR"); if (developer_repo_dir) { policy->AddDir(rdonly, developer_repo_dir); } } #ifdef DEBUG char* bloatLog = PR_GetEnv("XPCOM_MEM_BLOAT_LOG"); // XPCOM_MEM_BLOAT_LOG has the format // /tmp/tmpd0YzFZ.mozrunner/runtests_leaks.log // but stores into /tmp/tmpd0YzFZ.mozrunner/runtests_leaks_tab_pid3411.log // So cut the .log part and whitelist the prefix. if (bloatLog != nullptr) { size_t bloatLen = strlen(bloatLog); if (bloatLen >= 4) { nsAutoCString bloatStr(bloatLog); bloatStr.Truncate(bloatLen - 4); policy->AddPrefix(rdwrcr, bloatStr.get()); } } #endif if (!headless) { AddX11Dependencies(policy); } // Bug 1732580: when packaged as a strictly confined snap, may need // read-access to configuration files under $SNAP/. const char* snap = PR_GetEnv("SNAP"); if (snap) { // When running as a snap, the directory pointed to by $SNAP is guaranteed // to exist before the app is launched, but unit tests need to create it // dynamically, hence the use of AddFutureDir(). policy->AddDir(rdonly, snap); } // Read any extra paths that will get write permissions, // configured by the user or distro AddDynamicPathList(policy, "security.sandbox.content.write_path_whitelist", rdwr); // Whitelisted for reading by the user/distro AddDynamicPathList(policy, "security.sandbox.content.read_path_whitelist", rdonly); #if defined(MOZ_CONTENT_TEMP_DIR) // Add write permissions on the content process specific temporary dir. nsCOMPtr tmpDir; rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR, getter_AddRefs(tmpDir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = tmpDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdwrcr, tmpPath.get()); } } #endif // userContent.css and the extensions dir sit in the profile, which is // normally blocked. nsCOMPtr profileDir; rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); if (NS_SUCCEEDED(rv)) { nsCOMPtr workDir; rv = profileDir->Clone(getter_AddRefs(workDir)); if (NS_SUCCEEDED(rv)) { rv = workDir->AppendNative("chrome"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = workDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } } rv = profileDir->Clone(getter_AddRefs(workDir)); if (NS_SUCCEEDED(rv)) { rv = workDir->AppendNative("extensions"_ns); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = workDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { bool exists; rv = workDir->Exists(&exists); if (NS_SUCCEEDED(rv)) { if (!exists) { policy->AddPrefix(rdonly, tmpPath.get()); policy->AddPath(rdonly, tmpPath.get()); } else { policy->AddDir(rdonly, tmpPath.get()); } } } } } } const int level = GetEffectiveContentSandboxLevel(); bool allowPulse = false; bool allowAlsa = false; if (level < 4) { #ifdef MOZ_PULSEAUDIO allowPulse = true; #endif #ifdef MOZ_ALSA allowAlsa = true; #endif } if (allowAlsa) { // Bug 1309098: ALSA support policy->AddDir(rdwr, "/dev/snd"); } if (allowPulse) { policy->AddDir(rdwrcr, "/dev/shm"); } #ifdef MOZ_WIDGET_GTK if (const auto userDir = g_get_user_runtime_dir()) { // Bug 1321134: DConf's single bit of shared memory // The leaf filename is "user" by default, but is configurable. nsPrintfCString shmPath("%s/dconf/", userDir); policy->AddPrefix(rdwrcr, shmPath.get()); policy->AddAncestors(shmPath.get()); if (allowPulse) { // PulseAudio, if it can't get server info from X11, will break // unless it can open this directory (or create it, but in our use // case we know it already exists). See bug 1335329. nsPrintfCString pulsePath("%s/pulse", userDir); policy->AddPath(rdonly, pulsePath.get()); } } #endif // MOZ_WIDGET_GTK if (allowPulse) { // PulseAudio also needs access to read the $XAUTHORITY file (see // bug 1384986 comment #1), but that's already allowed for hybrid // GPU drivers (see above). policy->AddPath(rdonly, "/var/lib/dbus/machine-id"); } // Bug 1434711 - AMDGPU-PRO crashes if it can't read it's marketing ids // and various other things if (!headless && HasAtiDrivers()) { policy->AddDir(rdonly, "/opt/amdgpu/share"); policy->AddPath(rdonly, "/sys/module/amdgpu"); } mCommonContentPolicy.reset(policy); } UniquePtr SandboxBrokerPolicyFactory::GetContentPolicy( int aPid, bool aFileProcess) { // Policy entries that vary per-process (because they depend on the // pid or content subtype) are added here. MOZ_ASSERT(NS_IsMainThread()); const int level = GetEffectiveContentSandboxLevel(); // The file broker is used at level 2 and up. if (level <= 1) { // Level 1 has been removed. MOZ_ASSERT(level == 0); return nullptr; } std::call_once(mContentInited, [this] { InitContentPolicy(); }); MOZ_ASSERT(mCommonContentPolicy); UniquePtr policy( new SandboxBroker::Policy(*mCommonContentPolicy)); // No read blocking at level 2 and below. // file:// processes also get global read permissions if (level <= 2 || aFileProcess) { policy->AddDir(rdonly, "/"); // Any other read-only rules will be removed as redundant by // Policy::FixRecursivePermissions, so there's no need to // early-return here. } // Access to /dev/shm is restricted to a per-process prefix to // prevent interfering with other processes or with services outside // the browser (e.g., PulseAudio). AddSharedMemoryPaths(policy.get(), aPid); // Bug 1198550: the profiler's replacement for dl_iterate_phdr policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get()); // Bug 1736040: CPU use telemetry policy->AddPath(rdonly, nsPrintfCString("/proc/%d/stat", aPid).get()); // Bug 1198552: memory reporting. AddMemoryReporting(policy.get(), aPid); // Bug 1384804, notably comment 15 // Used by libnuma, included by x265/ffmpeg, who falls back // to get_mempolicy if this fails policy->AddPath(rdonly, nsPrintfCString("/proc/%d/status", aPid).get()); // Finalize the policy. policy->FixRecursivePermissions(); return policy; } #ifdef MOZ_ENABLE_V4L2 static void AddV4l2Dependencies(SandboxBroker::Policy* policy) { // For V4L2 hardware-accelerated video decode, RDD needs access to certain // /dev/video* devices but don't want to allow it access to webcams etc. // So we only allow it access to M2M video devices (encoders and decoders). DIR* dir = opendir("/dev"); if (!dir) { SANDBOX_LOG("Couldn't list /dev"); return; } struct dirent* dir_entry; while ((dir_entry = readdir(dir))) { if (strncmp(dir_entry->d_name, "video", 5)) { // Not a /dev/video* device, so ignore it continue; } nsCString path = "/dev/"_ns; path += nsDependentCString(dir_entry->d_name); int fd = open(path.get(), O_RDWR | O_NONBLOCK, 0); if (fd < 0) { // Couldn't open this device, so ignore it. SANDBOX_LOG("Couldn't open video device %s", path.get()); continue; } // Query device capabilities struct v4l2_capability cap; int result = ioctl(fd, VIDIOC_QUERYCAP, &cap); if (result < 0) { // Couldn't query capabilities of this device, so ignore it SANDBOX_LOG("Couldn't query capabilities of video device %s", path.get()); close(fd); continue; } if ((cap.device_caps & V4L2_CAP_VIDEO_M2M) || (cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE)) { // This is an M2M device (i.e. not a webcam), so allow access policy->AddPath(rdwr, path.get()); } close(fd); } closedir(dir); // FFmpeg V4L2 needs to list /dev to find V4L2 devices. policy->AddPath(rdonly, "/dev"); } #endif // MOZ_ENABLE_V4L2 /* static */ UniquePtr SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) { auto policy = MakeUnique(); AddSharedMemoryPaths(policy.get(), aPid); policy->AddPath(rdonly, "/dev/urandom"); // FIXME (bug 1662321): we should fix nsSystemInfo so that every // child process doesn't need to re-read these files to get the info // the parent process already has. policy->AddPath(rdonly, "/proc/cpuinfo"); policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"); policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index2/size"); policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index3/size"); policy->AddDir(rdonly, "/sys/devices/cpu"); policy->AddDir(rdonly, "/sys/devices/system/cpu"); policy->AddDir(rdonly, "/sys/devices/system/node"); policy->AddDir(rdonly, "/lib"); policy->AddDir(rdonly, "/lib64"); policy->AddDir(rdonly, "/usr/lib"); policy->AddDir(rdonly, "/usr/lib32"); policy->AddDir(rdonly, "/usr/lib64"); policy->AddDir(rdonly, "/run/opengl-driver/lib"); policy->AddDir(rdonly, "/nix/store"); // Bug 1647957: memory reporting. AddMemoryReporting(policy.get(), aPid); // Firefox binary dir. // Note that unlike the previous cases, we use NS_GetSpecialDirectory // instead of GetSpecialSystemDirectory. The former requires a working XPCOM // system, which may not be the case for some tests. For querying for the // location of XPCOM things, we can use it anyway. nsCOMPtr ffDir; nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = ffDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } if (!mozilla::IsPackagedBuild()) { // If this is not a packaged build the resources are likely symlinks to // outside the binary dir. Therefore in non-release builds we allow reads // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run. const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR"); if (developer_repo_dir) { policy->AddDir(rdonly, developer_repo_dir); } } // VA-API needs GPU access and GL context creation (but not display // server access, as of bug 1769499). AddGLDependencies(policy.get()); // FFmpeg and GPU drivers may need general-case library loading AddLdconfigPaths(policy.get()); AddLdLibraryEnvPaths(policy.get()); #ifdef MOZ_ENABLE_V4L2 AddV4l2Dependencies(policy.get()); #endif // MOZ_ENABLE_V4L2 if (policy->IsEmpty()) { policy = nullptr; } return policy; } /* static */ UniquePtr SandboxBrokerPolicyFactory::GetSocketProcessPolicy(int aPid) { auto policy = MakeUnique(); policy->AddPath(rdonly, "/dev/urandom"); policy->AddPath(rdonly, "/dev/random"); policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled"); policy->AddPath(rdonly, "/proc/cpuinfo"); policy->AddPath(rdonly, "/proc/meminfo"); policy->AddDir(rdonly, "/sys/devices/cpu"); policy->AddDir(rdonly, "/sys/devices/system/cpu"); policy->AddDir(rdonly, "/lib"); policy->AddDir(rdonly, "/lib64"); policy->AddDir(rdonly, "/usr/lib"); policy->AddDir(rdonly, "/usr/lib32"); policy->AddDir(rdonly, "/usr/lib64"); policy->AddDir(rdonly, "/usr/share"); policy->AddDir(rdonly, "/usr/local/share"); policy->AddDir(rdonly, "/etc"); // glibc will try to stat64("/") while populating nsswitch database // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396 // denying will make getaddrinfo() return ENONAME policy->AddDir(access, "/"); AddLdconfigPaths(policy.get()); // Socket process sandbox needs to allow shmem in order to support // profiling. See Bug 1626385. AddSharedMemoryPaths(policy.get(), aPid); // Bug 1647957: memory reporting. AddMemoryReporting(policy.get(), aPid); // Firefox binary dir. // Note that unlike the previous cases, we use NS_GetSpecialDirectory // instead of GetSpecialSystemDirectory. The former requires a working XPCOM // system, which may not be the case for some tests. For querying for the // location of XPCOM things, we can use it anyway. nsCOMPtr ffDir; nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = ffDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } if (policy->IsEmpty()) { policy = nullptr; } return policy; } /* static */ UniquePtr SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(int aPid) { auto policy = MakeUnique(); policy->AddPath(rdonly, "/dev/urandom"); policy->AddPath(rdonly, "/proc/cpuinfo"); policy->AddPath(rdonly, "/proc/meminfo"); policy->AddPath(rdonly, nsPrintfCString("/proc/%d/exe", aPid).get()); policy->AddDir(rdonly, "/sys/devices/cpu"); policy->AddDir(rdonly, "/sys/devices/system/cpu"); policy->AddDir(rdonly, "/lib"); policy->AddDir(rdonly, "/lib64"); policy->AddDir(rdonly, "/usr/lib"); policy->AddDir(rdonly, "/usr/lib32"); policy->AddDir(rdonly, "/usr/lib64"); policy->AddDir(rdonly, "/usr/share"); policy->AddDir(rdonly, "/usr/local/share"); policy->AddDir(rdonly, "/etc"); // Required to make sure ffmpeg loads properly, this is already existing on // Content and RDD policy->AddDir(rdonly, "/nix/store"); // glibc will try to stat64("/") while populating nsswitch database // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396 // denying will make getaddrinfo() return ENONAME policy->AddDir(access, "/"); AddLdconfigPaths(policy.get()); AddLdLibraryEnvPaths(policy.get()); // Utility process sandbox needs to allow shmem in order to support // profiling. See Bug 1626385. AddSharedMemoryPaths(policy.get(), aPid); // Bug 1647957: memory reporting. AddMemoryReporting(policy.get(), aPid); // Firefox binary dir. // Note that unlike the previous cases, we use NS_GetSpecialDirectory // instead of GetSpecialSystemDirectory. The former requires a working XPCOM // system, which may not be the case for some tests. For querying for the // location of XPCOM things, we can use it anyway. nsCOMPtr ffDir; nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir)); if (NS_SUCCEEDED(rv)) { nsAutoCString tmpPath; rv = ffDir->GetNativePath(tmpPath); if (NS_SUCCEEDED(rv)) { policy->AddDir(rdonly, tmpPath.get()); } } if (policy->IsEmpty()) { policy = nullptr; } return policy; } } // namespace mozilla