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"
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"
39 # include "cutils/properties.h"
43 # include "mozilla/WidgetUtilsGtk.h"
47 #ifdef MOZ_ENABLE_V4L2
48 # include <linux/videodev2.h>
49 # include <sys/ioctl.h>
51 #endif // MOZ_ENABLE_V4L2
55 #include <sys/sysmacros.h>
56 #include <sys/types.h>
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
;
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
);
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
),
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));
99 // https://gitlab.freedesktop.org/mesa/drm/-/commit/3988580e4c0f4b3647a0c6af138a3825453fe6e0
100 // > term = strrchr(real_path, '/');
101 // > if (term && strncmp(term, "/virtio", 7) == 0)
103 char* term
= strrchr(realSysPath
.get(), '/');
104 if (term
&& strncmp(term
, "/virtio", 7) == 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);
119 // > if (readlink(path, link, PATH_MAX) < 0)
121 nsCString
subsystemPath(prefix
+ "/device/subsystem"_ns
);
122 aPolicy
->AddPath(rdonly
, subsystemPath
.get());
123 aPolicy
->AddAncestors(subsystemPath
.get(), rdonly
);
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
;
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());
158 const char* startChar
= inPath
.BeginReading();
159 if (*startChar
!= '/') {
160 // Relative path, copy basepath in front
161 outPath
.Assign(aCwd
);
163 outPath
.Append(inPath
);
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
) {
176 nsCOMPtr
<nsIFile
> ldconfig(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
, &rv
));
180 rv
= ldconfig
->InitWithNativePath(aPath
);
181 if (NS_WARN_IF(NS_FAILED(rv
))) {
184 nsCOMPtr
<nsIFileInputStream
> fileStream(
185 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID
, &rv
));
186 if (NS_WARN_IF(NS_FAILED(rv
))) {
189 rv
= fileStream
->Init(ldconfig
, -1, -1, 0);
190 if (NS_WARN_IF(NS_FAILED(rv
))) {
193 nsCOMPtr
<nsILineInputStream
> lineStream(do_QueryInterface(fileStream
, &rv
));
194 if (NS_WARN_IF(NS_FAILED(rv
))) {
201 rv
= lineStream
->ReadLine(line
, &more
);
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('#');
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
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
);
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
);
231 if (!glob(PromiseFlatCString(includeFile
).get(), GLOB_NOSORT
, nullptr,
233 for (size_t fileIdx
= 0; fileIdx
< globbuf
.gl_pathc
; fileIdx
++) {
234 nsAutoCString
filePath(globbuf
.gl_pathv
[fileIdx
]);
235 CachePathsFromFile(aCache
, filePath
);
242 // Cut off anything behind an = sign, used by dirname=TYPE directives
243 int32_t equals
= line
.FindChar('=');
245 line
= Substring(line
, 0, equals
);
247 char* resolvedPath
= realpath(line
.get(), nullptr);
249 aCache
.AppendElement(std::make_pair(nsCString(resolvedPath
), rdonly
));
255 static void CachePathsFromFile(FileCacheT
& aCache
, const nsACString
& aPath
) {
256 // Find the new base path where that file sits in.
258 nsCOMPtr
<nsIFile
> includeFile(
259 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
, &rv
));
263 rv
= includeFile
->InitWithNativePath(aPath
);
264 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
))) {
278 nsAutoCString parentPath
;
279 rv
= parentDir
->GetNativePath(parentPath
);
280 if (NS_WARN_IF(NS_FAILED(rv
))) {
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;
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);
318 aPolicy
->AddDir(rdonly
, 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");
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
) {
382 xauth
.AppendLiteral(".Xauthority");
383 policy
->AddPath(rdonly
, xauth
.get());
389 static void AddGLDependencies(SandboxBroker::Policy
* policy
) {
391 policy
->AddDir(rdwr
, "/dev/dri");
392 policy
->AddFilePrefix(rdwr
, "/dev", "nvidia");
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
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
;
428 // Bug 1575985: WASM library sandbox needs RW access to /dev/null
429 policy
->AddPath(rdwr
, "/dev/null");
432 AddGLDependencies(policy
);
433 AddX11Dependencies(policy
);
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
);
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
508 std::vector
<const char*> extraConfDirsAllow
= {
514 // Fallback if XDG_CONFIG_HOME isn't set
515 if (xdgConfigHome
.IsEmpty()) {
516 extraConfDirsAllow
.emplace_back(".config");
519 nsCOMPtr
<nsIFile
> homeDir
;
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)
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
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());
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
);
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
);
649 nsAutoCString
bloatStr(bloatLog
);
650 bloatStr
.Truncate(bloatLen
- 4);
651 policy
->AddPrefix(rdwrcr
, bloatStr
.get());
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");
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",
675 // Whitelisted for reading by the user/distro
676 AddDynamicPathList(policy
, "security.sandbox.content.read_path_whitelist",
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());
693 // userContent.css and the extensions dir sit in the profile, which is
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
)) {
719 rv
= workDir
->Exists(&exists
);
720 if (NS_SUCCEEDED(rv
)) {
722 policy
->AddPrefix(rdonly
, tmpPath
.get());
723 policy
->AddPath(rdonly
, tmpPath
.get());
725 policy
->AddDir(rdonly
, tmpPath
.get());
733 const int level
= GetEffectiveContentSandboxLevel();
734 bool allowPulse
= false;
735 bool allowAlsa
= false;
737 #ifdef MOZ_PULSEAUDIO
746 // Bug 1309098: ALSA support
747 policy
->AddDir(rdwr
, "/dev/snd");
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());
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
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.
798 // Level 1 has been removed.
799 MOZ_ASSERT(level
== 0);
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();
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");
848 SANDBOX_LOG("Couldn't list /dev");
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
859 nsCString path
= "/dev/"_ns
;
860 path
+= nsDependentCString(dir_entry
->d_name
);
862 int fd
= open(path
.get(), O_RDWR
| O_NONBLOCK
, 0);
864 // Couldn't open this device, so ignore it.
865 SANDBOX_LOG("Couldn't open video device %s", path
.get());
869 // Query device capabilities
870 struct v4l2_capability cap
;
871 int result
= ioctl(fd
, VIDIOC_QUERYCAP
, &cap
);
873 // Couldn't query capabilities of this device, so ignore it
874 SANDBOX_LOG("Couldn't query capabilities of video device %s", path
.get());
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());
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()) {
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()) {
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
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()) {
1079 } // namespace mozilla