Bug 1833110 - Cache ldconfig to limit main thread io r=jld,Gijs
[gecko.git] / security / sandbox / linux / broker / SandboxBrokerPolicyFactory.cpp
blob83c642855dae07791c4a1ce67b9ed3965e1a12f0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SandboxBrokerPolicyFactory.h"
8 #include "SandboxInfo.h"
9 #include "SandboxLogging.h"
11 #include "base/shared_memory.h"
12 #include "mozilla/Array.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/Omnijar.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/SandboxLaunch.h"
17 #include "mozilla/SandboxSettings.h"
18 #include "mozilla/StaticPrefs_security.h"
19 #include "mozilla/StaticMutex.h"
20 #include "mozilla/UniquePtr.h"
21 #include "mozilla/UniquePtrExtensions.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsPrintfCString.h"
24 #include "nsString.h"
25 #include "nsThreadUtils.h"
26 #include "nsXULAppAPI.h"
27 #include "nsDirectoryServiceDefs.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "SpecialSystemDirectory.h"
30 #include "nsReadableUtils.h"
31 #include "nsIFileStreams.h"
32 #include "nsILineInputStream.h"
33 #include "nsIFile.h"
35 #include "nsNetCID.h"
36 #include "prenv.h"
38 #ifdef ANDROID
39 # include "cutils/properties.h"
40 #endif
42 #ifdef MOZ_WIDGET_GTK
43 # include "mozilla/WidgetUtilsGtk.h"
44 # include <glib.h>
45 #endif
47 #include <dirent.h>
48 #include <sys/stat.h>
49 #include <sys/sysmacros.h>
50 #include <sys/types.h>
51 #ifndef ANDROID
52 # include <glob.h>
53 #endif
55 namespace mozilla {
57 namespace {
58 static const int rdonly = SandboxBroker::MAY_READ;
59 static const int wronly = SandboxBroker::MAY_WRITE;
60 static const int rdwr = rdonly | wronly;
61 static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
62 static const int access = SandboxBroker::MAY_ACCESS;
63 static const int deny = SandboxBroker::FORCE_DENY;
64 } // namespace
66 using CacheE = std::pair<nsCString, int>;
67 using FileCacheT = nsTArray<CacheE>;
69 static void AddDriPaths(SandboxBroker::Policy* aPolicy) {
70 // Bug 1401666: Mesa driver loader part 2: Mesa <= 12 using libudev
71 // Used by libdrm, which is used by Mesa, and
72 // Intel(R) Media Driver for VAAPI.
73 if (auto dir = opendir("/dev/dri")) {
74 while (auto entry = readdir(dir)) {
75 if (entry->d_name[0] != '.') {
76 nsPrintfCString devPath("/dev/dri/%s", entry->d_name);
77 struct stat sb;
78 if (stat(devPath.get(), &sb) == 0 && S_ISCHR(sb.st_mode)) {
79 // For both the DRI node and its parent (the physical
80 // device), allow reading the "uevent" file.
81 static const Array<nsCString, 2> kSuffixes = {""_ns, "/device"_ns};
82 nsPrintfCString prefix("/sys/dev/char/%u:%u", major(sb.st_rdev),
83 minor(sb.st_rdev));
84 for (const auto& suffix : kSuffixes) {
85 nsCString sysPath(prefix + suffix);
87 // libudev will expand the symlink but not do full
88 // canonicalization, so it will leave in ".." path
89 // components that will be realpath()ed in the
90 // broker. To match this, allow the canonical paths.
91 UniqueFreePtr<char[]> realSysPath(realpath(sysPath.get(), nullptr));
92 if (realSysPath) {
93 // https://gitlab.freedesktop.org/mesa/drm/-/commit/3988580e4c0f4b3647a0c6af138a3825453fe6e0
94 // > term = strrchr(real_path, '/');
95 // > if (term && strncmp(term, "/virtio", 7) == 0)
96 // > *term = 0;
97 char* term = strrchr(realSysPath.get(), '/');
98 if (term && strncmp(term, "/virtio", 7) == 0) {
99 *term = 0;
102 aPolicy->AddFilePrefix(rdonly, realSysPath.get(), "");
103 // Allowing stat-ing and readlink-ing the parent dirs
104 nsPrintfCString basePath("%s/", realSysPath.get());
105 aPolicy->AddAncestors(basePath.get(), rdonly);
109 // https://gitlab.freedesktop.org/mesa/drm/-/commit/a02900133b32dd4a7d6da4966f455ab337e80dfc
110 // > strncpy(path, device_path, PATH_MAX);
111 // > strncat(path, "/subsystem", PATH_MAX);
112 // >
113 // > if (readlink(path, link, PATH_MAX) < 0)
114 // > return -errno;
115 nsCString subsystemPath(prefix + "/device/subsystem"_ns);
116 aPolicy->AddPath(rdonly, subsystemPath.get());
117 aPolicy->AddAncestors(subsystemPath.get(), rdonly);
121 closedir(dir);
124 // https://gitlab.freedesktop.org/mesa/mesa/-/commit/04bdbbcab3c4862bf3f54ce60fcc1d2007776f80
125 aPolicy->AddPath(rdonly, "/usr/share/drirc.d");
127 // https://dri.freedesktop.org/wiki/ConfigurationInfrastructure/
128 aPolicy->AddPath(rdonly, "/etc/drirc");
130 nsCOMPtr<nsIFile> drirc;
131 nsresult rv =
132 GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(drirc));
133 if (NS_SUCCEEDED(rv)) {
134 rv = drirc->AppendNative(".drirc"_ns);
135 if (NS_SUCCEEDED(rv)) {
136 nsAutoCString tmpPath;
137 rv = drirc->GetNativePath(tmpPath);
138 if (NS_SUCCEEDED(rv)) {
139 aPolicy->AddPath(rdonly, tmpPath.get());
145 static void JoinPathIfRelative(const nsACString& aCwd, const nsACString& inPath,
146 nsACString& outPath) {
147 if (inPath.Length() < 1) {
148 outPath.Assign(aCwd);
149 SANDBOX_LOG("Unjoinable path: %s", PromiseFlatCString(aCwd).get());
150 return;
152 const char* startChar = inPath.BeginReading();
153 if (*startChar != '/') {
154 // Relative path, copy basepath in front
155 outPath.Assign(aCwd);
156 outPath.Append("/");
157 outPath.Append(inPath);
158 } else {
159 // Absolute path, it's ok like this
160 outPath.Assign(inPath);
164 static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath);
166 static void CachePathsFromFileInternal(FileCacheT& aCache,
167 const nsACString& aCwd,
168 const nsACString& aPath) {
169 nsresult rv;
170 nsCOMPtr<nsIFile> ldconfig(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
171 if (NS_FAILED(rv)) {
172 return;
174 rv = ldconfig->InitWithNativePath(aPath);
175 if (NS_WARN_IF(NS_FAILED(rv))) {
176 return;
178 nsCOMPtr<nsIFileInputStream> fileStream(
179 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
180 if (NS_WARN_IF(NS_FAILED(rv))) {
181 return;
183 rv = fileStream->Init(ldconfig, -1, -1, 0);
184 if (NS_WARN_IF(NS_FAILED(rv))) {
185 return;
187 nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
188 if (NS_WARN_IF(NS_FAILED(rv))) {
189 return;
192 nsAutoCString line;
193 bool more = true;
194 do {
195 rv = lineStream->ReadLine(line, &more);
196 if (NS_FAILED(rv)) {
197 break;
199 // Cut off any comments at the end of the line, also catches lines
200 // that are entirely a comment
201 int32_t hash = line.FindChar('#');
202 if (hash >= 0) {
203 line = Substring(line, 0, hash);
205 // Simplify our following parsing by trimming whitespace
206 line.CompressWhitespace(true, true);
207 if (line.IsEmpty()) {
208 // Skip comment lines
209 continue;
211 // Check for any included files and recursively process
212 nsACString::const_iterator start, end, token_end;
214 line.BeginReading(start);
215 line.EndReading(end);
216 token_end = end;
218 if (FindInReadable("include "_ns, start, token_end)) {
219 nsAutoCString includes(Substring(token_end, end));
220 for (const nsACString& includeGlob : includes.Split(' ')) {
221 // Glob path might be relative, so add cwd if so.
222 nsAutoCString includeFile;
223 JoinPathIfRelative(aCwd, includeGlob, includeFile);
224 glob_t globbuf;
225 if (!glob(PromiseFlatCString(includeFile).get(), GLOB_NOSORT, nullptr,
226 &globbuf)) {
227 for (size_t fileIdx = 0; fileIdx < globbuf.gl_pathc; fileIdx++) {
228 nsAutoCString filePath(globbuf.gl_pathv[fileIdx]);
229 CachePathsFromFile(aCache, filePath);
231 globfree(&globbuf);
236 // Cut off anything behind an = sign, used by dirname=TYPE directives
237 int32_t equals = line.FindChar('=');
238 if (equals >= 0) {
239 line = Substring(line, 0, equals);
241 char* resolvedPath = realpath(line.get(), nullptr);
242 if (resolvedPath) {
243 aCache.AppendElement(std::make_pair(nsCString(resolvedPath), rdonly));
244 free(resolvedPath);
246 } while (more);
249 static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath) {
250 // Find the new base path where that file sits in.
251 nsresult rv;
252 nsCOMPtr<nsIFile> includeFile(
253 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
254 if (NS_FAILED(rv)) {
255 return;
257 rv = includeFile->InitWithNativePath(aPath);
258 if (NS_WARN_IF(NS_FAILED(rv))) {
259 return;
261 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
262 SANDBOX_LOG("Adding paths from %s to policy.",
263 PromiseFlatCString(aPath).get());
266 // Find the parent dir where this file sits in.
267 nsCOMPtr<nsIFile> parentDir;
268 rv = includeFile->GetParent(getter_AddRefs(parentDir));
269 if (NS_WARN_IF(NS_FAILED(rv))) {
270 return;
272 nsAutoCString parentPath;
273 rv = parentDir->GetNativePath(parentPath);
274 if (NS_WARN_IF(NS_FAILED(rv))) {
275 return;
277 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
278 SANDBOX_LOG("Parent path is %s", PromiseFlatCString(parentPath).get());
280 CachePathsFromFileInternal(aCache, parentPath, aPath);
283 static void AddLdconfigPaths(SandboxBroker::Policy* aPolicy) {
284 static StaticMutex sMutex;
285 StaticMutexAutoLock lock(sMutex);
287 static FileCacheT ldConfigCache{};
288 static bool ldConfigCachePopulated = false;
289 if (!ldConfigCachePopulated) {
290 CachePathsFromFile(ldConfigCache, "/etc/ld.so.conf"_ns);
291 ldConfigCachePopulated = true;
292 RunOnShutdown([&] {
293 ldConfigCache.Clear();
294 MOZ_ASSERT(ldConfigCache.IsEmpty(), "ldconfig cache should be empty");
297 for (const CacheE& e : ldConfigCache) {
298 aPolicy->AddDir(e.second, e.first.get());
302 static void AddLdLibraryEnvPaths(SandboxBroker::Policy* aPolicy) {
303 nsAutoCString LdLibraryEnv(PR_GetEnv("LD_LIBRARY_PATH"));
304 // The items in LD_LIBRARY_PATH can be separated by either colons or
305 // semicolons, according to the ld.so(8) man page, and empirically it
306 // seems to be allowed to mix them (i.e., a:b;c is a list with 3 elements).
307 // There is no support for escaping the delimiters, fortunately (for us).
308 LdLibraryEnv.ReplaceChar(';', ':');
309 for (const nsACString& libPath : LdLibraryEnv.Split(':')) {
310 char* resolvedPath = realpath(PromiseFlatCString(libPath).get(), nullptr);
311 if (resolvedPath) {
312 aPolicy->AddDir(rdonly, resolvedPath);
313 free(resolvedPath);
318 static void AddSharedMemoryPaths(SandboxBroker::Policy* aPolicy, pid_t aPid) {
319 std::string shmPath("/dev/shm");
320 if (base::SharedMemory::AppendPosixShmPrefix(&shmPath, aPid)) {
321 aPolicy->AddPrefix(rdwrcr, shmPath.c_str());
325 static void AddMemoryReporting(SandboxBroker::Policy* aPolicy, pid_t aPid) {
326 // Bug 1198552: memory reporting.
327 // Bug 1647957: memory reporting.
328 aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
329 aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
332 static void AddDynamicPathList(SandboxBroker::Policy* policy,
333 const char* aPathListPref, int perms) {
334 nsAutoCString pathList;
335 nsresult rv = Preferences::GetCString(aPathListPref, pathList);
336 if (NS_SUCCEEDED(rv)) {
337 for (const nsACString& path : pathList.Split(',')) {
338 nsCString trimPath(path);
339 trimPath.Trim(" ", true, true);
340 policy->AddDynamic(perms, trimPath.get());
345 static void AddX11Dependencies(SandboxBroker::Policy* policy) {
346 // Allow Primus to contact the Bumblebee daemon to manage GPU
347 // switching on NVIDIA Optimus systems.
348 const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET");
349 if (bumblebeeSocket == nullptr) {
350 bumblebeeSocket = "/var/run/bumblebee.socket";
352 policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
354 #if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
355 // Allow local X11 connections, for several purposes:
357 // * for content processes to use WebGL when the browser is in headless
358 // mode, by opening the X display if/when needed
360 // * if Primus or VirtualGL is used, to contact the secondary X server
361 static const bool kIsX11 =
362 !mozilla::widget::GdkIsWaylandDisplay() && PR_GetEnv("DISPLAY");
363 if (kIsX11) {
364 policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
365 if (auto* const xauth = PR_GetEnv("XAUTHORITY")) {
366 policy->AddPath(rdonly, xauth);
367 } else if (auto* const home = PR_GetEnv("HOME")) {
368 // This follows the logic in libXau: append "/.Xauthority",
369 // even if $HOME ends in a slash, except in the special case
370 // where HOME=/ because POSIX allows implementations to treat
371 // an initial double slash specially.
372 nsAutoCString xauth(home);
373 if (xauth != "/"_ns) {
374 xauth.Append('/');
376 xauth.AppendLiteral(".Xauthority");
377 policy->AddPath(rdonly, xauth.get());
380 #endif
383 static void AddGLDependencies(SandboxBroker::Policy* policy) {
384 // Devices
385 policy->AddDir(rdwr, "/dev/dri");
386 policy->AddFilePrefix(rdwr, "/dev", "nvidia");
388 // Hardware info
389 AddDriPaths(policy);
391 // /etc and /usr/share (glvnd, libdrm, drirc, ...?)
392 policy->AddDir(rdonly, "/etc");
393 policy->AddDir(rdonly, "/usr/share");
394 policy->AddDir(rdonly, "/usr/local/share");
396 // Snap puts the usual /usr/share things in a different place, and
397 // we'll fail to load the library if we don't have (at least) the
398 // glvnd config:
399 if (const char* snapDesktopDir = PR_GetEnv("SNAP_DESKTOP_RUNTIME")) {
400 nsAutoCString snapDesktopShare(snapDesktopDir);
401 snapDesktopShare.AppendLiteral("/usr/share");
402 policy->AddDir(rdonly, snapDesktopShare.get());
405 // Note: This function doesn't do anything about Mesa's shader
406 // cache, because the details can vary by process type, including
407 // whether caching is enabled.
409 // This also doesn't include permissions for connecting to a display
410 // server, because headless GL (e.g., Mesa GBM) may not need it.
413 void SandboxBrokerPolicyFactory::InitContentPolicy() {
414 const bool headless =
415 StaticPrefs::security_sandbox_content_headless_AtStartup();
417 // Policy entries that are the same in every process go here, and
418 // are cached over the lifetime of the factory.
419 SandboxBroker::Policy* policy = new SandboxBroker::Policy;
420 // Write permssions
422 // Bug 1575985: WASM library sandbox needs RW access to /dev/null
423 policy->AddPath(rdwr, "/dev/null");
425 if (!headless) {
426 AddGLDependencies(policy);
427 AddX11Dependencies(policy);
430 // Read permissions
431 policy->AddPath(rdonly, "/dev/urandom");
432 policy->AddPath(rdonly, "/dev/random");
433 policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
434 policy->AddPath(rdonly, "/proc/cpuinfo");
435 policy->AddPath(rdonly, "/proc/meminfo");
436 policy->AddDir(rdonly, "/sys/devices/cpu");
437 policy->AddDir(rdonly, "/sys/devices/system/cpu");
438 policy->AddDir(rdonly, "/lib");
439 policy->AddDir(rdonly, "/lib64");
440 policy->AddDir(rdonly, "/usr/lib");
441 policy->AddDir(rdonly, "/usr/lib32");
442 policy->AddDir(rdonly, "/usr/lib64");
443 policy->AddDir(rdonly, "/etc");
444 policy->AddDir(rdonly, "/usr/share");
445 policy->AddDir(rdonly, "/usr/local/share");
446 // Various places where fonts reside
447 policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts");
448 policy->AddDir(rdonly, "/nix/store");
449 // https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/e434e680d22260f277f4a30ec4660ed32b591d16/files/fontconfig-flatpak.conf
450 policy->AddDir(rdonly, "/run/host/fonts");
451 policy->AddDir(rdonly, "/run/host/user-fonts");
452 policy->AddDir(rdonly, "/run/host/local-fonts");
453 policy->AddDir(rdonly, "/var/cache/fontconfig");
455 AddLdconfigPaths(policy);
456 AddLdLibraryEnvPaths(policy);
458 if (!headless) {
459 // Bug 1385715: NVIDIA PRIME support
460 policy->AddPath(rdonly, "/proc/modules");
463 // XDG directories might be non existent according to specs:
464 // https://specifications.freedesktop.org/basedir-spec/0.8/ar01s04.html
466 // > If, when attempting to write a file, the destination directory is
467 // > non-existent an attempt should be made to create it with permission 0700.
469 // For that we use AddPath(, SandboxBroker::Policy::AddCondition::AddAlways).
471 // Allow access to XDG_CONFIG_HOME and XDG_CONFIG_DIRS
472 nsAutoCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME"));
473 if (!xdgConfigHome.IsEmpty()) { // AddPath will fail on empty strings
474 policy->AddFutureDir(rdonly, xdgConfigHome.get());
477 nsAutoCString xdgConfigDirs(PR_GetEnv("XDG_CONFIG_DIRS"));
478 for (const auto& path : xdgConfigDirs.Split(':')) {
479 if (!path.IsEmpty()) { // AddPath will fail on empty strings
480 policy->AddFutureDir(rdonly, PromiseFlatCString(path).get());
484 // Allow fonts subdir in XDG_DATA_HOME
485 nsAutoCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME"));
486 if (!xdgDataHome.IsEmpty()) {
487 nsAutoCString fontPath(xdgDataHome);
488 fontPath.Append("/fonts");
489 policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
492 // Any font subdirs in XDG_DATA_DIRS
493 nsAutoCString xdgDataDirs(PR_GetEnv("XDG_DATA_DIRS"));
494 for (const auto& path : xdgDataDirs.Split(':')) {
495 nsAutoCString fontPath(path);
496 fontPath.Append("/fonts");
497 policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
500 // Extra configuration/cache dirs in the homedir that we want to allow read
501 // access to.
502 std::vector<const char*> extraConfDirsAllow = {
503 ".themes",
504 ".fonts",
505 ".cache/fontconfig",
508 // Fallback if XDG_CONFIG_HOME isn't set
509 if (xdgConfigHome.IsEmpty()) {
510 extraConfDirsAllow.emplace_back(".config");
513 nsCOMPtr<nsIFile> homeDir;
514 nsresult rv =
515 GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
516 if (NS_SUCCEEDED(rv)) {
517 nsCOMPtr<nsIFile> confDir;
519 for (const auto& dir : extraConfDirsAllow) {
520 rv = homeDir->Clone(getter_AddRefs(confDir));
521 if (NS_SUCCEEDED(rv)) {
522 rv = confDir->AppendRelativeNativePath(nsDependentCString(dir));
523 if (NS_SUCCEEDED(rv)) {
524 nsAutoCString tmpPath;
525 rv = confDir->GetNativePath(tmpPath);
526 if (NS_SUCCEEDED(rv)) {
527 policy->AddDir(rdonly, tmpPath.get());
533 // ~/.config/mozilla/ needs to be manually blocked, because the previous
534 // loop will allow for ~/.config/ access.
536 // If $XDG_CONFIG_HOME is set, we need to account for it.
537 // FIXME: Bug 1722272: Maybe this should just be handled with
538 // GetSpecialSystemDirectory(Unix_XDG_ConfigHome) ?
539 nsCOMPtr<nsIFile> confDirOrXDGConfigHomeDir;
540 if (!xdgConfigHome.IsEmpty()) {
541 rv = NS_NewNativeLocalFile(xdgConfigHome, true,
542 getter_AddRefs(confDirOrXDGConfigHomeDir));
543 // confDirOrXDGConfigHomeDir = nsIFile($XDG_CONFIG_HOME)
544 } else {
545 rv = homeDir->Clone(getter_AddRefs(confDirOrXDGConfigHomeDir));
546 if (NS_SUCCEEDED(rv)) {
547 // since we will use that later, we dont need to care about trailing
548 // slash
549 rv = confDirOrXDGConfigHomeDir->AppendNative(".config"_ns);
550 // confDirOrXDGConfigHomeDir = nsIFile($HOME/.config/)
554 if (NS_SUCCEEDED(rv)) {
555 rv = confDirOrXDGConfigHomeDir->AppendNative("mozilla"_ns);
556 if (NS_SUCCEEDED(rv)) {
557 nsAutoCString tmpPath;
558 rv = confDirOrXDGConfigHomeDir->GetNativePath(tmpPath);
559 if (NS_SUCCEEDED(rv)) {
560 policy->AddFutureDir(deny, tmpPath.get());
566 // ~/.local/share (for themes)
567 rv = homeDir->Clone(getter_AddRefs(confDir));
568 if (NS_SUCCEEDED(rv)) {
569 rv = confDir->AppendNative(".local"_ns);
570 if (NS_SUCCEEDED(rv)) {
571 rv = confDir->AppendNative("share"_ns);
573 if (NS_SUCCEEDED(rv)) {
574 nsAutoCString tmpPath;
575 rv = confDir->GetNativePath(tmpPath);
576 if (NS_SUCCEEDED(rv)) {
577 policy->AddDir(rdonly, tmpPath.get());
582 // ~/.fonts.conf (Fontconfig)
583 rv = homeDir->Clone(getter_AddRefs(confDir));
584 if (NS_SUCCEEDED(rv)) {
585 rv = confDir->AppendNative(".fonts.conf"_ns);
586 if (NS_SUCCEEDED(rv)) {
587 nsAutoCString tmpPath;
588 rv = confDir->GetNativePath(tmpPath);
589 if (NS_SUCCEEDED(rv)) {
590 policy->AddPath(rdonly, tmpPath.get());
595 // .pangorc
596 rv = homeDir->Clone(getter_AddRefs(confDir));
597 if (NS_SUCCEEDED(rv)) {
598 rv = confDir->AppendNative(".pangorc"_ns);
599 if (NS_SUCCEEDED(rv)) {
600 nsAutoCString tmpPath;
601 rv = confDir->GetNativePath(tmpPath);
602 if (NS_SUCCEEDED(rv)) {
603 policy->AddPath(rdonly, tmpPath.get());
609 // Firefox binary dir.
610 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
611 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
612 // system, which may not be the case for some tests. For querying for the
613 // location of XPCOM things, we can use it anyway.
614 nsCOMPtr<nsIFile> ffDir;
615 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
616 if (NS_SUCCEEDED(rv)) {
617 nsAutoCString tmpPath;
618 rv = ffDir->GetNativePath(tmpPath);
619 if (NS_SUCCEEDED(rv)) {
620 policy->AddDir(rdonly, tmpPath.get());
624 if (!mozilla::IsPackagedBuild()) {
625 // If this is not a packaged build the resources are likely symlinks to
626 // outside the binary dir. Therefore in non-release builds we allow reads
627 // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
628 const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
629 if (developer_repo_dir) {
630 policy->AddDir(rdonly, developer_repo_dir);
634 #ifdef DEBUG
635 char* bloatLog = PR_GetEnv("XPCOM_MEM_BLOAT_LOG");
636 // XPCOM_MEM_BLOAT_LOG has the format
637 // /tmp/tmpd0YzFZ.mozrunner/runtests_leaks.log
638 // but stores into /tmp/tmpd0YzFZ.mozrunner/runtests_leaks_tab_pid3411.log
639 // So cut the .log part and whitelist the prefix.
640 if (bloatLog != nullptr) {
641 size_t bloatLen = strlen(bloatLog);
642 if (bloatLen >= 4) {
643 nsAutoCString bloatStr(bloatLog);
644 bloatStr.Truncate(bloatLen - 4);
645 policy->AddPrefix(rdwrcr, bloatStr.get());
648 #endif
650 if (!headless) {
651 AddX11Dependencies(policy);
654 // Bug 1732580: when packaged as a strictly confined snap, may need
655 // read-access to configuration files under $SNAP/.
656 const char* snap = PR_GetEnv("SNAP");
657 if (snap) {
658 // When running as a snap, the directory pointed to by $SNAP is guaranteed
659 // to exist before the app is launched, but unit tests need to create it
660 // dynamically, hence the use of AddFutureDir().
661 policy->AddDir(rdonly, snap);
664 // Read any extra paths that will get write permissions,
665 // configured by the user or distro
666 AddDynamicPathList(policy, "security.sandbox.content.write_path_whitelist",
667 rdwr);
669 // Whitelisted for reading by the user/distro
670 AddDynamicPathList(policy, "security.sandbox.content.read_path_whitelist",
671 rdonly);
673 #if defined(MOZ_CONTENT_TEMP_DIR)
674 // Add write permissions on the content process specific temporary dir.
675 nsCOMPtr<nsIFile> tmpDir;
676 rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
677 getter_AddRefs(tmpDir));
678 if (NS_SUCCEEDED(rv)) {
679 nsAutoCString tmpPath;
680 rv = tmpDir->GetNativePath(tmpPath);
681 if (NS_SUCCEEDED(rv)) {
682 policy->AddDir(rdwrcr, tmpPath.get());
685 #endif
687 // userContent.css and the extensions dir sit in the profile, which is
688 // normally blocked.
689 nsCOMPtr<nsIFile> profileDir;
690 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
691 getter_AddRefs(profileDir));
692 if (NS_SUCCEEDED(rv)) {
693 nsCOMPtr<nsIFile> workDir;
694 rv = profileDir->Clone(getter_AddRefs(workDir));
695 if (NS_SUCCEEDED(rv)) {
696 rv = workDir->AppendNative("chrome"_ns);
697 if (NS_SUCCEEDED(rv)) {
698 nsAutoCString tmpPath;
699 rv = workDir->GetNativePath(tmpPath);
700 if (NS_SUCCEEDED(rv)) {
701 policy->AddDir(rdonly, tmpPath.get());
705 rv = profileDir->Clone(getter_AddRefs(workDir));
706 if (NS_SUCCEEDED(rv)) {
707 rv = workDir->AppendNative("extensions"_ns);
708 if (NS_SUCCEEDED(rv)) {
709 nsAutoCString tmpPath;
710 rv = workDir->GetNativePath(tmpPath);
711 if (NS_SUCCEEDED(rv)) {
712 bool exists;
713 rv = workDir->Exists(&exists);
714 if (NS_SUCCEEDED(rv)) {
715 if (!exists) {
716 policy->AddPrefix(rdonly, tmpPath.get());
717 policy->AddPath(rdonly, tmpPath.get());
718 } else {
719 policy->AddDir(rdonly, tmpPath.get());
727 const int level = GetEffectiveContentSandboxLevel();
728 bool allowPulse = false;
729 bool allowAlsa = false;
730 if (level < 4) {
731 #ifdef MOZ_PULSEAUDIO
732 allowPulse = true;
733 #endif
734 #ifdef MOZ_ALSA
735 allowAlsa = true;
736 #endif
739 if (allowAlsa) {
740 // Bug 1309098: ALSA support
741 policy->AddDir(rdwr, "/dev/snd");
744 if (allowPulse) {
745 policy->AddDir(rdwrcr, "/dev/shm");
748 #ifdef MOZ_WIDGET_GTK
749 if (const auto userDir = g_get_user_runtime_dir()) {
750 // Bug 1321134: DConf's single bit of shared memory
751 // The leaf filename is "user" by default, but is configurable.
752 nsPrintfCString shmPath("%s/dconf/", userDir);
753 policy->AddPrefix(rdwrcr, shmPath.get());
754 policy->AddAncestors(shmPath.get());
755 if (allowPulse) {
756 // PulseAudio, if it can't get server info from X11, will break
757 // unless it can open this directory (or create it, but in our use
758 // case we know it already exists). See bug 1335329.
759 nsPrintfCString pulsePath("%s/pulse", userDir);
760 policy->AddPath(rdonly, pulsePath.get());
763 #endif // MOZ_WIDGET_GTK
765 if (allowPulse) {
766 // PulseAudio also needs access to read the $XAUTHORITY file (see
767 // bug 1384986 comment #1), but that's already allowed for hybrid
768 // GPU drivers (see above).
769 policy->AddPath(rdonly, "/var/lib/dbus/machine-id");
772 // Bug 1434711 - AMDGPU-PRO crashes if it can't read it's marketing ids
773 // and various other things
774 if (!headless && HasAtiDrivers()) {
775 policy->AddDir(rdonly, "/opt/amdgpu/share");
776 policy->AddPath(rdonly, "/sys/module/amdgpu");
779 mCommonContentPolicy.reset(policy);
782 UniquePtr<SandboxBroker::Policy> SandboxBrokerPolicyFactory::GetContentPolicy(
783 int aPid, bool aFileProcess) {
784 // Policy entries that vary per-process (because they depend on the
785 // pid or content subtype) are added here.
787 MOZ_ASSERT(NS_IsMainThread());
789 const int level = GetEffectiveContentSandboxLevel();
790 // The file broker is used at level 2 and up.
791 if (level <= 1) {
792 // Level 1 has been removed.
793 MOZ_ASSERT(level == 0);
794 return nullptr;
797 std::call_once(mContentInited, [this] { InitContentPolicy(); });
798 MOZ_ASSERT(mCommonContentPolicy);
799 UniquePtr<SandboxBroker::Policy> policy(
800 new SandboxBroker::Policy(*mCommonContentPolicy));
802 // No read blocking at level 2 and below.
803 // file:// processes also get global read permissions
804 if (level <= 2 || aFileProcess) {
805 policy->AddDir(rdonly, "/");
806 // Any other read-only rules will be removed as redundant by
807 // Policy::FixRecursivePermissions, so there's no need to
808 // early-return here.
811 // Access to /dev/shm is restricted to a per-process prefix to
812 // prevent interfering with other processes or with services outside
813 // the browser (e.g., PulseAudio).
814 AddSharedMemoryPaths(policy.get(), aPid);
816 // Bug 1198550: the profiler's replacement for dl_iterate_phdr
817 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
819 // Bug 1736040: CPU use telemetry
820 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/stat", aPid).get());
822 // Bug 1198552: memory reporting.
823 AddMemoryReporting(policy.get(), aPid);
825 // Bug 1384804, notably comment 15
826 // Used by libnuma, included by x265/ffmpeg, who falls back
827 // to get_mempolicy if this fails
828 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/status", aPid).get());
830 // Finalize the policy.
831 policy->FixRecursivePermissions();
832 return policy;
835 /* static */ UniquePtr<SandboxBroker::Policy>
836 SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) {
837 auto policy = MakeUnique<SandboxBroker::Policy>();
839 AddSharedMemoryPaths(policy.get(), aPid);
841 policy->AddPath(rdonly, "/dev/urandom");
842 // FIXME (bug 1662321): we should fix nsSystemInfo so that every
843 // child process doesn't need to re-read these files to get the info
844 // the parent process already has.
845 policy->AddPath(rdonly, "/proc/cpuinfo");
846 policy->AddPath(rdonly,
847 "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
848 policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index2/size");
849 policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index3/size");
850 policy->AddDir(rdonly, "/sys/devices/cpu");
851 policy->AddDir(rdonly, "/sys/devices/system/cpu");
852 policy->AddDir(rdonly, "/sys/devices/system/node");
853 policy->AddDir(rdonly, "/lib");
854 policy->AddDir(rdonly, "/lib64");
855 policy->AddDir(rdonly, "/usr/lib");
856 policy->AddDir(rdonly, "/usr/lib32");
857 policy->AddDir(rdonly, "/usr/lib64");
858 policy->AddDir(rdonly, "/run/opengl-driver/lib");
859 policy->AddDir(rdonly, "/nix/store");
861 // Bug 1647957: memory reporting.
862 AddMemoryReporting(policy.get(), aPid);
864 // Firefox binary dir.
865 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
866 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
867 // system, which may not be the case for some tests. For querying for the
868 // location of XPCOM things, we can use it anyway.
869 nsCOMPtr<nsIFile> ffDir;
870 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
871 if (NS_SUCCEEDED(rv)) {
872 nsAutoCString tmpPath;
873 rv = ffDir->GetNativePath(tmpPath);
874 if (NS_SUCCEEDED(rv)) {
875 policy->AddDir(rdonly, tmpPath.get());
879 if (!mozilla::IsPackagedBuild()) {
880 // If this is not a packaged build the resources are likely symlinks to
881 // outside the binary dir. Therefore in non-release builds we allow reads
882 // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
883 const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
884 if (developer_repo_dir) {
885 policy->AddDir(rdonly, developer_repo_dir);
889 // VA-API needs GPU access and GL context creation (but not display
890 // server access, as of bug 1769499).
891 AddGLDependencies(policy.get());
893 // FFmpeg and GPU drivers may need general-case library loading
894 AddLdconfigPaths(policy.get());
895 AddLdLibraryEnvPaths(policy.get());
897 if (policy->IsEmpty()) {
898 policy = nullptr;
900 return policy;
903 /* static */ UniquePtr<SandboxBroker::Policy>
904 SandboxBrokerPolicyFactory::GetSocketProcessPolicy(int aPid) {
905 auto policy = MakeUnique<SandboxBroker::Policy>();
907 policy->AddPath(rdonly, "/dev/urandom");
908 policy->AddPath(rdonly, "/dev/random");
909 policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
910 policy->AddPath(rdonly, "/proc/cpuinfo");
911 policy->AddPath(rdonly, "/proc/meminfo");
912 policy->AddDir(rdonly, "/sys/devices/cpu");
913 policy->AddDir(rdonly, "/sys/devices/system/cpu");
914 policy->AddDir(rdonly, "/lib");
915 policy->AddDir(rdonly, "/lib64");
916 policy->AddDir(rdonly, "/usr/lib");
917 policy->AddDir(rdonly, "/usr/lib32");
918 policy->AddDir(rdonly, "/usr/lib64");
919 policy->AddDir(rdonly, "/usr/share");
920 policy->AddDir(rdonly, "/usr/local/share");
921 policy->AddDir(rdonly, "/etc");
923 // glibc will try to stat64("/") while populating nsswitch database
924 // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
925 // denying will make getaddrinfo() return ENONAME
926 policy->AddDir(access, "/");
928 AddLdconfigPaths(policy.get());
930 // Socket process sandbox needs to allow shmem in order to support
931 // profiling. See Bug 1626385.
932 AddSharedMemoryPaths(policy.get(), aPid);
934 // Bug 1647957: memory reporting.
935 AddMemoryReporting(policy.get(), aPid);
937 // Firefox binary dir.
938 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
939 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
940 // system, which may not be the case for some tests. For querying for the
941 // location of XPCOM things, we can use it anyway.
942 nsCOMPtr<nsIFile> ffDir;
943 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
944 if (NS_SUCCEEDED(rv)) {
945 nsAutoCString tmpPath;
946 rv = ffDir->GetNativePath(tmpPath);
947 if (NS_SUCCEEDED(rv)) {
948 policy->AddDir(rdonly, tmpPath.get());
952 if (policy->IsEmpty()) {
953 policy = nullptr;
955 return policy;
958 /* static */ UniquePtr<SandboxBroker::Policy>
959 SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(int aPid) {
960 auto policy = MakeUnique<SandboxBroker::Policy>();
962 policy->AddPath(rdonly, "/dev/urandom");
963 policy->AddPath(rdonly, "/proc/cpuinfo");
964 policy->AddPath(rdonly, "/proc/meminfo");
965 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/exe", aPid).get());
966 policy->AddDir(rdonly, "/sys/devices/cpu");
967 policy->AddDir(rdonly, "/sys/devices/system/cpu");
968 policy->AddDir(rdonly, "/lib");
969 policy->AddDir(rdonly, "/lib64");
970 policy->AddDir(rdonly, "/usr/lib");
971 policy->AddDir(rdonly, "/usr/lib32");
972 policy->AddDir(rdonly, "/usr/lib64");
973 policy->AddDir(rdonly, "/usr/share");
974 policy->AddDir(rdonly, "/usr/local/share");
975 policy->AddDir(rdonly, "/etc");
977 // glibc will try to stat64("/") while populating nsswitch database
978 // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
979 // denying will make getaddrinfo() return ENONAME
980 policy->AddDir(access, "/");
982 AddLdconfigPaths(policy.get());
984 // Utility process sandbox needs to allow shmem in order to support
985 // profiling. See Bug 1626385.
986 AddSharedMemoryPaths(policy.get(), aPid);
988 // Bug 1647957: memory reporting.
989 AddMemoryReporting(policy.get(), aPid);
991 // Firefox binary dir.
992 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
993 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
994 // system, which may not be the case for some tests. For querying for the
995 // location of XPCOM things, we can use it anyway.
996 nsCOMPtr<nsIFile> ffDir;
997 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
998 if (NS_SUCCEEDED(rv)) {
999 nsAutoCString tmpPath;
1000 rv = ffDir->GetNativePath(tmpPath);
1001 if (NS_SUCCEEDED(rv)) {
1002 policy->AddDir(rdonly, tmpPath.get());
1006 if (policy->IsEmpty()) {
1007 policy = nullptr;
1009 return policy;
1012 } // namespace mozilla