Bug 1840371 - Allow /nix/store for loading ffmpeg dependencies r=gcp
[gecko.git] / security / sandbox / linux / broker / SandboxBrokerPolicyFactory.cpp
blobcd8c3042dfdf95dbd932987ef5ca14eeef611599
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 #ifdef MOZ_ENABLE_V4L2
48 # include <linux/videodev2.h>
49 # include <sys/ioctl.h>
50 # include <fcntl.h>
51 #endif // MOZ_ENABLE_V4L2
53 #include <dirent.h>
54 #include <sys/stat.h>
55 #include <sys/sysmacros.h>
56 #include <sys/types.h>
57 #ifndef ANDROID
58 # include <glob.h>
59 #endif
61 namespace mozilla {
63 namespace {
64 static const int rdonly = SandboxBroker::MAY_READ;
65 static const int wronly = SandboxBroker::MAY_WRITE;
66 static const int rdwr = rdonly | wronly;
67 static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
68 static const int access = SandboxBroker::MAY_ACCESS;
69 static const int deny = SandboxBroker::FORCE_DENY;
70 } // namespace
72 using CacheE = std::pair<nsCString, int>;
73 using FileCacheT = nsTArray<CacheE>;
75 static void AddDriPaths(SandboxBroker::Policy* aPolicy) {
76 // Bug 1401666: Mesa driver loader part 2: Mesa <= 12 using libudev
77 // Used by libdrm, which is used by Mesa, and
78 // Intel(R) Media Driver for VAAPI.
79 if (auto dir = opendir("/dev/dri")) {
80 while (auto entry = readdir(dir)) {
81 if (entry->d_name[0] != '.') {
82 nsPrintfCString devPath("/dev/dri/%s", entry->d_name);
83 struct stat sb;
84 if (stat(devPath.get(), &sb) == 0 && S_ISCHR(sb.st_mode)) {
85 // For both the DRI node and its parent (the physical
86 // device), allow reading the "uevent" file.
87 static const Array<nsCString, 2> kSuffixes = {""_ns, "/device"_ns};
88 nsPrintfCString prefix("/sys/dev/char/%u:%u", major(sb.st_rdev),
89 minor(sb.st_rdev));
90 for (const auto& suffix : kSuffixes) {
91 nsCString sysPath(prefix + suffix);
93 // libudev will expand the symlink but not do full
94 // canonicalization, so it will leave in ".." path
95 // components that will be realpath()ed in the
96 // broker. To match this, allow the canonical paths.
97 UniqueFreePtr<char[]> realSysPath(realpath(sysPath.get(), nullptr));
98 if (realSysPath) {
99 // https://gitlab.freedesktop.org/mesa/drm/-/commit/3988580e4c0f4b3647a0c6af138a3825453fe6e0
100 // > term = strrchr(real_path, '/');
101 // > if (term && strncmp(term, "/virtio", 7) == 0)
102 // > *term = 0;
103 char* term = strrchr(realSysPath.get(), '/');
104 if (term && strncmp(term, "/virtio", 7) == 0) {
105 *term = 0;
108 aPolicy->AddFilePrefix(rdonly, realSysPath.get(), "");
109 // Allowing stat-ing and readlink-ing the parent dirs
110 nsPrintfCString basePath("%s/", realSysPath.get());
111 aPolicy->AddAncestors(basePath.get(), rdonly);
115 // https://gitlab.freedesktop.org/mesa/drm/-/commit/a02900133b32dd4a7d6da4966f455ab337e80dfc
116 // > strncpy(path, device_path, PATH_MAX);
117 // > strncat(path, "/subsystem", PATH_MAX);
118 // >
119 // > if (readlink(path, link, PATH_MAX) < 0)
120 // > return -errno;
121 nsCString subsystemPath(prefix + "/device/subsystem"_ns);
122 aPolicy->AddPath(rdonly, subsystemPath.get());
123 aPolicy->AddAncestors(subsystemPath.get(), rdonly);
127 closedir(dir);
130 // https://gitlab.freedesktop.org/mesa/mesa/-/commit/04bdbbcab3c4862bf3f54ce60fcc1d2007776f80
131 aPolicy->AddPath(rdonly, "/usr/share/drirc.d");
133 // https://dri.freedesktop.org/wiki/ConfigurationInfrastructure/
134 aPolicy->AddPath(rdonly, "/etc/drirc");
136 nsCOMPtr<nsIFile> drirc;
137 nsresult rv =
138 GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(drirc));
139 if (NS_SUCCEEDED(rv)) {
140 rv = drirc->AppendNative(".drirc"_ns);
141 if (NS_SUCCEEDED(rv)) {
142 nsAutoCString tmpPath;
143 rv = drirc->GetNativePath(tmpPath);
144 if (NS_SUCCEEDED(rv)) {
145 aPolicy->AddPath(rdonly, tmpPath.get());
151 static void JoinPathIfRelative(const nsACString& aCwd, const nsACString& inPath,
152 nsACString& outPath) {
153 if (inPath.Length() < 1) {
154 outPath.Assign(aCwd);
155 SANDBOX_LOG("Unjoinable path: %s", PromiseFlatCString(aCwd).get());
156 return;
158 const char* startChar = inPath.BeginReading();
159 if (*startChar != '/') {
160 // Relative path, copy basepath in front
161 outPath.Assign(aCwd);
162 outPath.Append("/");
163 outPath.Append(inPath);
164 } else {
165 // Absolute path, it's ok like this
166 outPath.Assign(inPath);
170 static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath);
172 static void CachePathsFromFileInternal(FileCacheT& aCache,
173 const nsACString& aCwd,
174 const nsACString& aPath) {
175 nsresult rv;
176 nsCOMPtr<nsIFile> ldconfig(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
177 if (NS_FAILED(rv)) {
178 return;
180 rv = ldconfig->InitWithNativePath(aPath);
181 if (NS_WARN_IF(NS_FAILED(rv))) {
182 return;
184 nsCOMPtr<nsIFileInputStream> fileStream(
185 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
186 if (NS_WARN_IF(NS_FAILED(rv))) {
187 return;
189 rv = fileStream->Init(ldconfig, -1, -1, 0);
190 if (NS_WARN_IF(NS_FAILED(rv))) {
191 return;
193 nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
194 if (NS_WARN_IF(NS_FAILED(rv))) {
195 return;
198 nsAutoCString line;
199 bool more = true;
200 do {
201 rv = lineStream->ReadLine(line, &more);
202 if (NS_FAILED(rv)) {
203 break;
205 // Cut off any comments at the end of the line, also catches lines
206 // that are entirely a comment
207 int32_t hash = line.FindChar('#');
208 if (hash >= 0) {
209 line = Substring(line, 0, hash);
211 // Simplify our following parsing by trimming whitespace
212 line.CompressWhitespace(true, true);
213 if (line.IsEmpty()) {
214 // Skip comment lines
215 continue;
217 // Check for any included files and recursively process
218 nsACString::const_iterator start, end, token_end;
220 line.BeginReading(start);
221 line.EndReading(end);
222 token_end = end;
224 if (FindInReadable("include "_ns, start, token_end)) {
225 nsAutoCString includes(Substring(token_end, end));
226 for (const nsACString& includeGlob : includes.Split(' ')) {
227 // Glob path might be relative, so add cwd if so.
228 nsAutoCString includeFile;
229 JoinPathIfRelative(aCwd, includeGlob, includeFile);
230 glob_t globbuf;
231 if (!glob(PromiseFlatCString(includeFile).get(), GLOB_NOSORT, nullptr,
232 &globbuf)) {
233 for (size_t fileIdx = 0; fileIdx < globbuf.gl_pathc; fileIdx++) {
234 nsAutoCString filePath(globbuf.gl_pathv[fileIdx]);
235 CachePathsFromFile(aCache, filePath);
237 globfree(&globbuf);
242 // Cut off anything behind an = sign, used by dirname=TYPE directives
243 int32_t equals = line.FindChar('=');
244 if (equals >= 0) {
245 line = Substring(line, 0, equals);
247 char* resolvedPath = realpath(line.get(), nullptr);
248 if (resolvedPath) {
249 aCache.AppendElement(std::make_pair(nsCString(resolvedPath), rdonly));
250 free(resolvedPath);
252 } while (more);
255 static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath) {
256 // Find the new base path where that file sits in.
257 nsresult rv;
258 nsCOMPtr<nsIFile> includeFile(
259 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
260 if (NS_FAILED(rv)) {
261 return;
263 rv = includeFile->InitWithNativePath(aPath);
264 if (NS_WARN_IF(NS_FAILED(rv))) {
265 return;
267 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
268 SANDBOX_LOG("Adding paths from %s to policy.",
269 PromiseFlatCString(aPath).get());
272 // Find the parent dir where this file sits in.
273 nsCOMPtr<nsIFile> parentDir;
274 rv = includeFile->GetParent(getter_AddRefs(parentDir));
275 if (NS_WARN_IF(NS_FAILED(rv))) {
276 return;
278 nsAutoCString parentPath;
279 rv = parentDir->GetNativePath(parentPath);
280 if (NS_WARN_IF(NS_FAILED(rv))) {
281 return;
283 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
284 SANDBOX_LOG("Parent path is %s", PromiseFlatCString(parentPath).get());
286 CachePathsFromFileInternal(aCache, parentPath, aPath);
289 static void AddLdconfigPaths(SandboxBroker::Policy* aPolicy) {
290 static StaticMutex sMutex;
291 StaticMutexAutoLock lock(sMutex);
293 static FileCacheT ldConfigCache{};
294 static bool ldConfigCachePopulated = false;
295 if (!ldConfigCachePopulated) {
296 CachePathsFromFile(ldConfigCache, "/etc/ld.so.conf"_ns);
297 ldConfigCachePopulated = true;
298 RunOnShutdown([&] {
299 ldConfigCache.Clear();
300 MOZ_ASSERT(ldConfigCache.IsEmpty(), "ldconfig cache should be empty");
303 for (const CacheE& e : ldConfigCache) {
304 aPolicy->AddDir(e.second, e.first.get());
308 static void AddLdLibraryEnvPaths(SandboxBroker::Policy* aPolicy) {
309 nsAutoCString LdLibraryEnv(PR_GetEnv("LD_LIBRARY_PATH"));
310 // The items in LD_LIBRARY_PATH can be separated by either colons or
311 // semicolons, according to the ld.so(8) man page, and empirically it
312 // seems to be allowed to mix them (i.e., a:b;c is a list with 3 elements).
313 // There is no support for escaping the delimiters, fortunately (for us).
314 LdLibraryEnv.ReplaceChar(';', ':');
315 for (const nsACString& libPath : LdLibraryEnv.Split(':')) {
316 char* resolvedPath = realpath(PromiseFlatCString(libPath).get(), nullptr);
317 if (resolvedPath) {
318 aPolicy->AddDir(rdonly, resolvedPath);
319 free(resolvedPath);
324 static void AddSharedMemoryPaths(SandboxBroker::Policy* aPolicy, pid_t aPid) {
325 std::string shmPath("/dev/shm");
326 if (base::SharedMemory::AppendPosixShmPrefix(&shmPath, aPid)) {
327 aPolicy->AddPrefix(rdwrcr, shmPath.c_str());
331 static void AddMemoryReporting(SandboxBroker::Policy* aPolicy, pid_t aPid) {
332 // Bug 1198552: memory reporting.
333 // Bug 1647957: memory reporting.
334 aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
335 aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
338 static void AddDynamicPathList(SandboxBroker::Policy* policy,
339 const char* aPathListPref, int perms) {
340 nsAutoCString pathList;
341 nsresult rv = Preferences::GetCString(aPathListPref, pathList);
342 if (NS_SUCCEEDED(rv)) {
343 for (const nsACString& path : pathList.Split(',')) {
344 nsCString trimPath(path);
345 trimPath.Trim(" ", true, true);
346 policy->AddDynamic(perms, trimPath.get());
351 static void AddX11Dependencies(SandboxBroker::Policy* policy) {
352 // Allow Primus to contact the Bumblebee daemon to manage GPU
353 // switching on NVIDIA Optimus systems.
354 const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET");
355 if (bumblebeeSocket == nullptr) {
356 bumblebeeSocket = "/var/run/bumblebee.socket";
358 policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
360 #if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
361 // Allow local X11 connections, for several purposes:
363 // * for content processes to use WebGL when the browser is in headless
364 // mode, by opening the X display if/when needed
366 // * if Primus or VirtualGL is used, to contact the secondary X server
367 static const bool kIsX11 =
368 !mozilla::widget::GdkIsWaylandDisplay() && PR_GetEnv("DISPLAY");
369 if (kIsX11) {
370 policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
371 if (auto* const xauth = PR_GetEnv("XAUTHORITY")) {
372 policy->AddPath(rdonly, xauth);
373 } else if (auto* const home = PR_GetEnv("HOME")) {
374 // This follows the logic in libXau: append "/.Xauthority",
375 // even if $HOME ends in a slash, except in the special case
376 // where HOME=/ because POSIX allows implementations to treat
377 // an initial double slash specially.
378 nsAutoCString xauth(home);
379 if (xauth != "/"_ns) {
380 xauth.Append('/');
382 xauth.AppendLiteral(".Xauthority");
383 policy->AddPath(rdonly, xauth.get());
386 #endif
389 static void AddGLDependencies(SandboxBroker::Policy* policy) {
390 // Devices
391 policy->AddDir(rdwr, "/dev/dri");
392 policy->AddFilePrefix(rdwr, "/dev", "nvidia");
394 // Hardware info
395 AddDriPaths(policy);
397 // /etc and /usr/share (glvnd, libdrm, drirc, ...?)
398 policy->AddDir(rdonly, "/etc");
399 policy->AddDir(rdonly, "/usr/share");
400 policy->AddDir(rdonly, "/usr/local/share");
402 // Snap puts the usual /usr/share things in a different place, and
403 // we'll fail to load the library if we don't have (at least) the
404 // glvnd config:
405 if (const char* snapDesktopDir = PR_GetEnv("SNAP_DESKTOP_RUNTIME")) {
406 nsAutoCString snapDesktopShare(snapDesktopDir);
407 snapDesktopShare.AppendLiteral("/usr/share");
408 policy->AddDir(rdonly, snapDesktopShare.get());
411 // Note: This function doesn't do anything about Mesa's shader
412 // cache, because the details can vary by process type, including
413 // whether caching is enabled.
415 // This also doesn't include permissions for connecting to a display
416 // server, because headless GL (e.g., Mesa GBM) may not need it.
419 void SandboxBrokerPolicyFactory::InitContentPolicy() {
420 const bool headless =
421 StaticPrefs::security_sandbox_content_headless_AtStartup();
423 // Policy entries that are the same in every process go here, and
424 // are cached over the lifetime of the factory.
425 SandboxBroker::Policy* policy = new SandboxBroker::Policy;
426 // Write permssions
428 // Bug 1575985: WASM library sandbox needs RW access to /dev/null
429 policy->AddPath(rdwr, "/dev/null");
431 if (!headless) {
432 AddGLDependencies(policy);
433 AddX11Dependencies(policy);
436 // Read permissions
437 policy->AddPath(rdonly, "/dev/urandom");
438 policy->AddPath(rdonly, "/dev/random");
439 policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
440 policy->AddPath(rdonly, "/proc/cpuinfo");
441 policy->AddPath(rdonly, "/proc/meminfo");
442 policy->AddDir(rdonly, "/sys/devices/cpu");
443 policy->AddDir(rdonly, "/sys/devices/system/cpu");
444 policy->AddDir(rdonly, "/lib");
445 policy->AddDir(rdonly, "/lib64");
446 policy->AddDir(rdonly, "/usr/lib");
447 policy->AddDir(rdonly, "/usr/lib32");
448 policy->AddDir(rdonly, "/usr/lib64");
449 policy->AddDir(rdonly, "/etc");
450 policy->AddDir(rdonly, "/usr/share");
451 policy->AddDir(rdonly, "/usr/local/share");
452 // Various places where fonts reside
453 policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts");
454 policy->AddDir(rdonly, "/nix/store");
455 // https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/e434e680d22260f277f4a30ec4660ed32b591d16/files/fontconfig-flatpak.conf
456 policy->AddDir(rdonly, "/run/host/fonts");
457 policy->AddDir(rdonly, "/run/host/user-fonts");
458 policy->AddDir(rdonly, "/run/host/local-fonts");
459 policy->AddDir(rdonly, "/var/cache/fontconfig");
461 AddLdconfigPaths(policy);
462 AddLdLibraryEnvPaths(policy);
464 if (!headless) {
465 // Bug 1385715: NVIDIA PRIME support
466 policy->AddPath(rdonly, "/proc/modules");
469 // XDG directories might be non existent according to specs:
470 // https://specifications.freedesktop.org/basedir-spec/0.8/ar01s04.html
472 // > If, when attempting to write a file, the destination directory is
473 // > non-existent an attempt should be made to create it with permission 0700.
475 // For that we use AddPath(, SandboxBroker::Policy::AddCondition::AddAlways).
477 // Allow access to XDG_CONFIG_HOME and XDG_CONFIG_DIRS
478 nsAutoCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME"));
479 if (!xdgConfigHome.IsEmpty()) { // AddPath will fail on empty strings
480 policy->AddFutureDir(rdonly, xdgConfigHome.get());
483 nsAutoCString xdgConfigDirs(PR_GetEnv("XDG_CONFIG_DIRS"));
484 for (const auto& path : xdgConfigDirs.Split(':')) {
485 if (!path.IsEmpty()) { // AddPath will fail on empty strings
486 policy->AddFutureDir(rdonly, PromiseFlatCString(path).get());
490 // Allow fonts subdir in XDG_DATA_HOME
491 nsAutoCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME"));
492 if (!xdgDataHome.IsEmpty()) {
493 nsAutoCString fontPath(xdgDataHome);
494 fontPath.Append("/fonts");
495 policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
498 // Any font subdirs in XDG_DATA_DIRS
499 nsAutoCString xdgDataDirs(PR_GetEnv("XDG_DATA_DIRS"));
500 for (const auto& path : xdgDataDirs.Split(':')) {
501 nsAutoCString fontPath(path);
502 fontPath.Append("/fonts");
503 policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
506 // Extra configuration/cache dirs in the homedir that we want to allow read
507 // access to.
508 std::vector<const char*> extraConfDirsAllow = {
509 ".themes",
510 ".fonts",
511 ".cache/fontconfig",
514 // Fallback if XDG_CONFIG_HOME isn't set
515 if (xdgConfigHome.IsEmpty()) {
516 extraConfDirsAllow.emplace_back(".config");
519 nsCOMPtr<nsIFile> homeDir;
520 nsresult rv =
521 GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
522 if (NS_SUCCEEDED(rv)) {
523 nsCOMPtr<nsIFile> confDir;
525 for (const auto& dir : extraConfDirsAllow) {
526 rv = homeDir->Clone(getter_AddRefs(confDir));
527 if (NS_SUCCEEDED(rv)) {
528 rv = confDir->AppendRelativeNativePath(nsDependentCString(dir));
529 if (NS_SUCCEEDED(rv)) {
530 nsAutoCString tmpPath;
531 rv = confDir->GetNativePath(tmpPath);
532 if (NS_SUCCEEDED(rv)) {
533 policy->AddDir(rdonly, tmpPath.get());
539 // ~/.config/mozilla/ needs to be manually blocked, because the previous
540 // loop will allow for ~/.config/ access.
542 // If $XDG_CONFIG_HOME is set, we need to account for it.
543 // FIXME: Bug 1722272: Maybe this should just be handled with
544 // GetSpecialSystemDirectory(Unix_XDG_ConfigHome) ?
545 nsCOMPtr<nsIFile> confDirOrXDGConfigHomeDir;
546 if (!xdgConfigHome.IsEmpty()) {
547 rv = NS_NewNativeLocalFile(xdgConfigHome, true,
548 getter_AddRefs(confDirOrXDGConfigHomeDir));
549 // confDirOrXDGConfigHomeDir = nsIFile($XDG_CONFIG_HOME)
550 } else {
551 rv = homeDir->Clone(getter_AddRefs(confDirOrXDGConfigHomeDir));
552 if (NS_SUCCEEDED(rv)) {
553 // since we will use that later, we dont need to care about trailing
554 // slash
555 rv = confDirOrXDGConfigHomeDir->AppendNative(".config"_ns);
556 // confDirOrXDGConfigHomeDir = nsIFile($HOME/.config/)
560 if (NS_SUCCEEDED(rv)) {
561 rv = confDirOrXDGConfigHomeDir->AppendNative("mozilla"_ns);
562 if (NS_SUCCEEDED(rv)) {
563 nsAutoCString tmpPath;
564 rv = confDirOrXDGConfigHomeDir->GetNativePath(tmpPath);
565 if (NS_SUCCEEDED(rv)) {
566 policy->AddFutureDir(deny, tmpPath.get());
572 // ~/.local/share (for themes)
573 rv = homeDir->Clone(getter_AddRefs(confDir));
574 if (NS_SUCCEEDED(rv)) {
575 rv = confDir->AppendNative(".local"_ns);
576 if (NS_SUCCEEDED(rv)) {
577 rv = confDir->AppendNative("share"_ns);
579 if (NS_SUCCEEDED(rv)) {
580 nsAutoCString tmpPath;
581 rv = confDir->GetNativePath(tmpPath);
582 if (NS_SUCCEEDED(rv)) {
583 policy->AddDir(rdonly, tmpPath.get());
588 // ~/.fonts.conf (Fontconfig)
589 rv = homeDir->Clone(getter_AddRefs(confDir));
590 if (NS_SUCCEEDED(rv)) {
591 rv = confDir->AppendNative(".fonts.conf"_ns);
592 if (NS_SUCCEEDED(rv)) {
593 nsAutoCString tmpPath;
594 rv = confDir->GetNativePath(tmpPath);
595 if (NS_SUCCEEDED(rv)) {
596 policy->AddPath(rdonly, tmpPath.get());
601 // .pangorc
602 rv = homeDir->Clone(getter_AddRefs(confDir));
603 if (NS_SUCCEEDED(rv)) {
604 rv = confDir->AppendNative(".pangorc"_ns);
605 if (NS_SUCCEEDED(rv)) {
606 nsAutoCString tmpPath;
607 rv = confDir->GetNativePath(tmpPath);
608 if (NS_SUCCEEDED(rv)) {
609 policy->AddPath(rdonly, tmpPath.get());
615 // Firefox binary dir.
616 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
617 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
618 // system, which may not be the case for some tests. For querying for the
619 // location of XPCOM things, we can use it anyway.
620 nsCOMPtr<nsIFile> ffDir;
621 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
622 if (NS_SUCCEEDED(rv)) {
623 nsAutoCString tmpPath;
624 rv = ffDir->GetNativePath(tmpPath);
625 if (NS_SUCCEEDED(rv)) {
626 policy->AddDir(rdonly, tmpPath.get());
630 if (!mozilla::IsPackagedBuild()) {
631 // If this is not a packaged build the resources are likely symlinks to
632 // outside the binary dir. Therefore in non-release builds we allow reads
633 // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
634 const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
635 if (developer_repo_dir) {
636 policy->AddDir(rdonly, developer_repo_dir);
640 #ifdef DEBUG
641 char* bloatLog = PR_GetEnv("XPCOM_MEM_BLOAT_LOG");
642 // XPCOM_MEM_BLOAT_LOG has the format
643 // /tmp/tmpd0YzFZ.mozrunner/runtests_leaks.log
644 // but stores into /tmp/tmpd0YzFZ.mozrunner/runtests_leaks_tab_pid3411.log
645 // So cut the .log part and whitelist the prefix.
646 if (bloatLog != nullptr) {
647 size_t bloatLen = strlen(bloatLog);
648 if (bloatLen >= 4) {
649 nsAutoCString bloatStr(bloatLog);
650 bloatStr.Truncate(bloatLen - 4);
651 policy->AddPrefix(rdwrcr, bloatStr.get());
654 #endif
656 if (!headless) {
657 AddX11Dependencies(policy);
660 // Bug 1732580: when packaged as a strictly confined snap, may need
661 // read-access to configuration files under $SNAP/.
662 const char* snap = PR_GetEnv("SNAP");
663 if (snap) {
664 // When running as a snap, the directory pointed to by $SNAP is guaranteed
665 // to exist before the app is launched, but unit tests need to create it
666 // dynamically, hence the use of AddFutureDir().
667 policy->AddDir(rdonly, snap);
670 // Read any extra paths that will get write permissions,
671 // configured by the user or distro
672 AddDynamicPathList(policy, "security.sandbox.content.write_path_whitelist",
673 rdwr);
675 // Whitelisted for reading by the user/distro
676 AddDynamicPathList(policy, "security.sandbox.content.read_path_whitelist",
677 rdonly);
679 #if defined(MOZ_CONTENT_TEMP_DIR)
680 // Add write permissions on the content process specific temporary dir.
681 nsCOMPtr<nsIFile> tmpDir;
682 rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
683 getter_AddRefs(tmpDir));
684 if (NS_SUCCEEDED(rv)) {
685 nsAutoCString tmpPath;
686 rv = tmpDir->GetNativePath(tmpPath);
687 if (NS_SUCCEEDED(rv)) {
688 policy->AddDir(rdwrcr, tmpPath.get());
691 #endif
693 // userContent.css and the extensions dir sit in the profile, which is
694 // normally blocked.
695 nsCOMPtr<nsIFile> profileDir;
696 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
697 getter_AddRefs(profileDir));
698 if (NS_SUCCEEDED(rv)) {
699 nsCOMPtr<nsIFile> workDir;
700 rv = profileDir->Clone(getter_AddRefs(workDir));
701 if (NS_SUCCEEDED(rv)) {
702 rv = workDir->AppendNative("chrome"_ns);
703 if (NS_SUCCEEDED(rv)) {
704 nsAutoCString tmpPath;
705 rv = workDir->GetNativePath(tmpPath);
706 if (NS_SUCCEEDED(rv)) {
707 policy->AddDir(rdonly, tmpPath.get());
711 rv = profileDir->Clone(getter_AddRefs(workDir));
712 if (NS_SUCCEEDED(rv)) {
713 rv = workDir->AppendNative("extensions"_ns);
714 if (NS_SUCCEEDED(rv)) {
715 nsAutoCString tmpPath;
716 rv = workDir->GetNativePath(tmpPath);
717 if (NS_SUCCEEDED(rv)) {
718 bool exists;
719 rv = workDir->Exists(&exists);
720 if (NS_SUCCEEDED(rv)) {
721 if (!exists) {
722 policy->AddPrefix(rdonly, tmpPath.get());
723 policy->AddPath(rdonly, tmpPath.get());
724 } else {
725 policy->AddDir(rdonly, tmpPath.get());
733 const int level = GetEffectiveContentSandboxLevel();
734 bool allowPulse = false;
735 bool allowAlsa = false;
736 if (level < 4) {
737 #ifdef MOZ_PULSEAUDIO
738 allowPulse = true;
739 #endif
740 #ifdef MOZ_ALSA
741 allowAlsa = true;
742 #endif
745 if (allowAlsa) {
746 // Bug 1309098: ALSA support
747 policy->AddDir(rdwr, "/dev/snd");
750 if (allowPulse) {
751 policy->AddDir(rdwrcr, "/dev/shm");
754 #ifdef MOZ_WIDGET_GTK
755 if (const auto userDir = g_get_user_runtime_dir()) {
756 // Bug 1321134: DConf's single bit of shared memory
757 // The leaf filename is "user" by default, but is configurable.
758 nsPrintfCString shmPath("%s/dconf/", userDir);
759 policy->AddPrefix(rdwrcr, shmPath.get());
760 policy->AddAncestors(shmPath.get());
761 if (allowPulse) {
762 // PulseAudio, if it can't get server info from X11, will break
763 // unless it can open this directory (or create it, but in our use
764 // case we know it already exists). See bug 1335329.
765 nsPrintfCString pulsePath("%s/pulse", userDir);
766 policy->AddPath(rdonly, pulsePath.get());
769 #endif // MOZ_WIDGET_GTK
771 if (allowPulse) {
772 // PulseAudio also needs access to read the $XAUTHORITY file (see
773 // bug 1384986 comment #1), but that's already allowed for hybrid
774 // GPU drivers (see above).
775 policy->AddPath(rdonly, "/var/lib/dbus/machine-id");
778 // Bug 1434711 - AMDGPU-PRO crashes if it can't read it's marketing ids
779 // and various other things
780 if (!headless && HasAtiDrivers()) {
781 policy->AddDir(rdonly, "/opt/amdgpu/share");
782 policy->AddPath(rdonly, "/sys/module/amdgpu");
785 mCommonContentPolicy.reset(policy);
788 UniquePtr<SandboxBroker::Policy> SandboxBrokerPolicyFactory::GetContentPolicy(
789 int aPid, bool aFileProcess) {
790 // Policy entries that vary per-process (because they depend on the
791 // pid or content subtype) are added here.
793 MOZ_ASSERT(NS_IsMainThread());
795 const int level = GetEffectiveContentSandboxLevel();
796 // The file broker is used at level 2 and up.
797 if (level <= 1) {
798 // Level 1 has been removed.
799 MOZ_ASSERT(level == 0);
800 return nullptr;
803 std::call_once(mContentInited, [this] { InitContentPolicy(); });
804 MOZ_ASSERT(mCommonContentPolicy);
805 UniquePtr<SandboxBroker::Policy> policy(
806 new SandboxBroker::Policy(*mCommonContentPolicy));
808 // No read blocking at level 2 and below.
809 // file:// processes also get global read permissions
810 if (level <= 2 || aFileProcess) {
811 policy->AddDir(rdonly, "/");
812 // Any other read-only rules will be removed as redundant by
813 // Policy::FixRecursivePermissions, so there's no need to
814 // early-return here.
817 // Access to /dev/shm is restricted to a per-process prefix to
818 // prevent interfering with other processes or with services outside
819 // the browser (e.g., PulseAudio).
820 AddSharedMemoryPaths(policy.get(), aPid);
822 // Bug 1198550: the profiler's replacement for dl_iterate_phdr
823 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
825 // Bug 1736040: CPU use telemetry
826 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/stat", aPid).get());
828 // Bug 1198552: memory reporting.
829 AddMemoryReporting(policy.get(), aPid);
831 // Bug 1384804, notably comment 15
832 // Used by libnuma, included by x265/ffmpeg, who falls back
833 // to get_mempolicy if this fails
834 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/status", aPid).get());
836 // Finalize the policy.
837 policy->FixRecursivePermissions();
838 return policy;
841 #ifdef MOZ_ENABLE_V4L2
842 static void AddV4l2Dependencies(SandboxBroker::Policy* policy) {
843 // For V4L2 hardware-accelerated video decode, RDD needs access to certain
844 // /dev/video* devices but don't want to allow it access to webcams etc.
845 // So we only allow it access to M2M video devices (encoders and decoders).
846 DIR* dir = opendir("/dev");
847 if (!dir) {
848 SANDBOX_LOG("Couldn't list /dev");
849 return;
852 struct dirent* dir_entry;
853 while ((dir_entry = readdir(dir))) {
854 if (strncmp(dir_entry->d_name, "video", 5)) {
855 // Not a /dev/video* device, so ignore it
856 continue;
859 nsCString path = "/dev/"_ns;
860 path += nsDependentCString(dir_entry->d_name);
862 int fd = open(path.get(), O_RDWR | O_NONBLOCK, 0);
863 if (fd < 0) {
864 // Couldn't open this device, so ignore it.
865 SANDBOX_LOG("Couldn't open video device %s", path.get());
866 continue;
869 // Query device capabilities
870 struct v4l2_capability cap;
871 int result = ioctl(fd, VIDIOC_QUERYCAP, &cap);
872 if (result < 0) {
873 // Couldn't query capabilities of this device, so ignore it
874 SANDBOX_LOG("Couldn't query capabilities of video device %s", path.get());
875 close(fd);
876 continue;
879 if ((cap.device_caps & V4L2_CAP_VIDEO_M2M) ||
880 (cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE)) {
881 // This is an M2M device (i.e. not a webcam), so allow access
882 policy->AddPath(rdwr, path.get());
885 close(fd);
887 closedir(dir);
889 // FFmpeg V4L2 needs to list /dev to find V4L2 devices.
890 policy->AddPath(rdonly, "/dev");
892 #endif // MOZ_ENABLE_V4L2
894 /* static */ UniquePtr<SandboxBroker::Policy>
895 SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) {
896 auto policy = MakeUnique<SandboxBroker::Policy>();
898 AddSharedMemoryPaths(policy.get(), aPid);
900 policy->AddPath(rdonly, "/dev/urandom");
901 // FIXME (bug 1662321): we should fix nsSystemInfo so that every
902 // child process doesn't need to re-read these files to get the info
903 // the parent process already has.
904 policy->AddPath(rdonly, "/proc/cpuinfo");
905 policy->AddPath(rdonly,
906 "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
907 policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index2/size");
908 policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index3/size");
909 policy->AddDir(rdonly, "/sys/devices/cpu");
910 policy->AddDir(rdonly, "/sys/devices/system/cpu");
911 policy->AddDir(rdonly, "/sys/devices/system/node");
912 policy->AddDir(rdonly, "/lib");
913 policy->AddDir(rdonly, "/lib64");
914 policy->AddDir(rdonly, "/usr/lib");
915 policy->AddDir(rdonly, "/usr/lib32");
916 policy->AddDir(rdonly, "/usr/lib64");
917 policy->AddDir(rdonly, "/run/opengl-driver/lib");
918 policy->AddDir(rdonly, "/nix/store");
920 // Bug 1647957: memory reporting.
921 AddMemoryReporting(policy.get(), aPid);
923 // Firefox binary dir.
924 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
925 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
926 // system, which may not be the case for some tests. For querying for the
927 // location of XPCOM things, we can use it anyway.
928 nsCOMPtr<nsIFile> ffDir;
929 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
930 if (NS_SUCCEEDED(rv)) {
931 nsAutoCString tmpPath;
932 rv = ffDir->GetNativePath(tmpPath);
933 if (NS_SUCCEEDED(rv)) {
934 policy->AddDir(rdonly, tmpPath.get());
938 if (!mozilla::IsPackagedBuild()) {
939 // If this is not a packaged build the resources are likely symlinks to
940 // outside the binary dir. Therefore in non-release builds we allow reads
941 // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
942 const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
943 if (developer_repo_dir) {
944 policy->AddDir(rdonly, developer_repo_dir);
948 // VA-API needs GPU access and GL context creation (but not display
949 // server access, as of bug 1769499).
950 AddGLDependencies(policy.get());
952 // FFmpeg and GPU drivers may need general-case library loading
953 AddLdconfigPaths(policy.get());
954 AddLdLibraryEnvPaths(policy.get());
956 #ifdef MOZ_ENABLE_V4L2
957 AddV4l2Dependencies(policy.get());
958 #endif // MOZ_ENABLE_V4L2
960 if (policy->IsEmpty()) {
961 policy = nullptr;
963 return policy;
966 /* static */ UniquePtr<SandboxBroker::Policy>
967 SandboxBrokerPolicyFactory::GetSocketProcessPolicy(int aPid) {
968 auto policy = MakeUnique<SandboxBroker::Policy>();
970 policy->AddPath(rdonly, "/dev/urandom");
971 policy->AddPath(rdonly, "/dev/random");
972 policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
973 policy->AddPath(rdonly, "/proc/cpuinfo");
974 policy->AddPath(rdonly, "/proc/meminfo");
975 policy->AddDir(rdonly, "/sys/devices/cpu");
976 policy->AddDir(rdonly, "/sys/devices/system/cpu");
977 policy->AddDir(rdonly, "/lib");
978 policy->AddDir(rdonly, "/lib64");
979 policy->AddDir(rdonly, "/usr/lib");
980 policy->AddDir(rdonly, "/usr/lib32");
981 policy->AddDir(rdonly, "/usr/lib64");
982 policy->AddDir(rdonly, "/usr/share");
983 policy->AddDir(rdonly, "/usr/local/share");
984 policy->AddDir(rdonly, "/etc");
986 // glibc will try to stat64("/") while populating nsswitch database
987 // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
988 // denying will make getaddrinfo() return ENONAME
989 policy->AddDir(access, "/");
991 AddLdconfigPaths(policy.get());
993 // Socket process sandbox needs to allow shmem in order to support
994 // profiling. See Bug 1626385.
995 AddSharedMemoryPaths(policy.get(), aPid);
997 // Bug 1647957: memory reporting.
998 AddMemoryReporting(policy.get(), aPid);
1000 // Firefox binary dir.
1001 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
1002 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
1003 // system, which may not be the case for some tests. For querying for the
1004 // location of XPCOM things, we can use it anyway.
1005 nsCOMPtr<nsIFile> ffDir;
1006 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
1007 if (NS_SUCCEEDED(rv)) {
1008 nsAutoCString tmpPath;
1009 rv = ffDir->GetNativePath(tmpPath);
1010 if (NS_SUCCEEDED(rv)) {
1011 policy->AddDir(rdonly, tmpPath.get());
1015 if (policy->IsEmpty()) {
1016 policy = nullptr;
1018 return policy;
1021 /* static */ UniquePtr<SandboxBroker::Policy>
1022 SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(int aPid) {
1023 auto policy = MakeUnique<SandboxBroker::Policy>();
1025 policy->AddPath(rdonly, "/dev/urandom");
1026 policy->AddPath(rdonly, "/proc/cpuinfo");
1027 policy->AddPath(rdonly, "/proc/meminfo");
1028 policy->AddPath(rdonly, nsPrintfCString("/proc/%d/exe", aPid).get());
1029 policy->AddDir(rdonly, "/sys/devices/cpu");
1030 policy->AddDir(rdonly, "/sys/devices/system/cpu");
1031 policy->AddDir(rdonly, "/lib");
1032 policy->AddDir(rdonly, "/lib64");
1033 policy->AddDir(rdonly, "/usr/lib");
1034 policy->AddDir(rdonly, "/usr/lib32");
1035 policy->AddDir(rdonly, "/usr/lib64");
1036 policy->AddDir(rdonly, "/usr/share");
1037 policy->AddDir(rdonly, "/usr/local/share");
1038 policy->AddDir(rdonly, "/etc");
1039 // Required to make sure ffmpeg loads properly, this is already existing on
1040 // Content and RDD
1041 policy->AddDir(rdonly, "/nix/store");
1043 // glibc will try to stat64("/") while populating nsswitch database
1044 // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
1045 // denying will make getaddrinfo() return ENONAME
1046 policy->AddDir(access, "/");
1048 AddLdconfigPaths(policy.get());
1049 AddLdLibraryEnvPaths(policy.get());
1051 // Utility process sandbox needs to allow shmem in order to support
1052 // profiling. See Bug 1626385.
1053 AddSharedMemoryPaths(policy.get(), aPid);
1055 // Bug 1647957: memory reporting.
1056 AddMemoryReporting(policy.get(), aPid);
1058 // Firefox binary dir.
1059 // Note that unlike the previous cases, we use NS_GetSpecialDirectory
1060 // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
1061 // system, which may not be the case for some tests. For querying for the
1062 // location of XPCOM things, we can use it anyway.
1063 nsCOMPtr<nsIFile> ffDir;
1064 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
1065 if (NS_SUCCEEDED(rv)) {
1066 nsAutoCString tmpPath;
1067 rv = ffDir->GetNativePath(tmpPath);
1068 if (NS_SUCCEEDED(rv)) {
1069 policy->AddDir(rdonly, tmpPath.get());
1073 if (policy->IsEmpty()) {
1074 policy = nullptr;
1076 return policy;
1079 } // namespace mozilla