summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp')
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp1017
1 files changed, 1017 insertions, 0 deletions
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
new file mode 100644
index 0000000000..fe7ce56c7e
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -0,0 +1,1017 @@
+/* -*- 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 <glib.h>
+#endif
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#ifndef ANDROID
+# include <glob.h>
+#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<nsCString, int>;
+using FileCacheT = nsTArray<CacheE>;
+
+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<nsCString, 2> 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<char[]> 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<nsIFile> 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<nsIFile> 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<nsIFileInputStream> 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<nsILineInputStream> 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<nsIFile> 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<nsIFile> 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<const char*> extraConfDirsAllow = {
+ ".themes",
+ ".fonts",
+ ".cache/fontconfig",
+ };
+
+ // Fallback if XDG_CONFIG_HOME isn't set
+ if (xdgConfigHome.IsEmpty()) {
+ extraConfDirsAllow.emplace_back(".config");
+ }
+
+ nsCOMPtr<nsIFile> homeDir;
+ nsresult rv =
+ GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIFile> profileDir;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> 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<SandboxBroker::Policy> 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<SandboxBroker::Policy> 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;
+}
+
+/* static */ UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ 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<nsIFile> 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());
+
+ if (policy->IsEmpty()) {
+ policy = nullptr;
+ }
+ return policy;
+}
+
+/* static */ UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetSocketProcessPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ 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<nsIFile> 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<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ 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");
+
+ // 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<nsIFile> 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