1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
3 /* import-globals-from browser_content_sandbox_utils.js */
6 // Test if the content process can create in $HOME, this should fail
7 async function createFileInHome() {
8 let browser = gBrowser.selectedBrowser;
9 let homeFile = fileInHomeDir();
10 let path = homeFile.path;
11 let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
12 ok(!fileCreated.ok, "creating a file in home dir is not permitted");
14 // content process successfully created the file, now remove it
15 homeFile.remove(false);
19 // Test if the content process can create a temp file, this is disallowed on
20 // macOS and Windows but allowed everywhere else. Also test that the content
21 // process cannot create symlinks on macOS or delete files.
22 async function createTempFile() {
23 // On Windows we allow access to the temp dir for DEBUG builds, because of
24 // logging that uses that dir.
25 let isOptWin = isWin() && !SpecialPowers.isDebugBuild;
27 let browser = gBrowser.selectedBrowser;
28 let path = fileInTempDir().path;
29 let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
30 if (isMac() || isOptWin) {
31 ok(!fileCreated.ok, "creating a file in temp is not permitted");
33 ok(!!fileCreated.ok, "creating a file in temp is permitted");
35 // now delete the file
36 let fileDeleted = await SpecialPowers.spawn(browser, [path], deleteFile);
37 if (isMac() || isOptWin) {
38 // On macOS we do not allow file deletion - it is not needed by the content
39 // process itself, and macOS uses a different permission to control access
40 // so revoking it is easy.
41 ok(!fileDeleted.ok, "deleting a file in temp is not permitted");
43 ok(!!fileDeleted.ok, "deleting a file in temp is permitted");
46 // Test that symlink creation is not allowed on macOS.
48 let path = fileInTempDir().path;
49 let symlinkCreated = await SpecialPowers.spawn(
54 ok(!symlinkCreated.ok, "created a symlink in temp is not permitted");
58 // Test reading files and dirs from web and file content processes.
59 async function testFileAccessAllPlatforms() {
60 let webBrowser = GetWebBrowser();
61 let fileContentProcessEnabled = isFileContentProcessEnabled();
62 let fileBrowser = GetFileBrowser();
64 // Directories/files to test accessing from content processes.
65 // For directories, we test whether a directory listing is allowed
66 // or blocked. For files, we test if we can read from the file.
67 // Each entry in the array represents a test file or directory
68 // that will be read from either a web or file process.
71 let profileDir = GetProfileDir();
73 desc: "profile dir", // description
74 ok: false, // expected to succeed?
75 browser: webBrowser, // browser to run test in
76 file: profileDir, // nsIFile object
77 minLevel: minProfileReadSandboxLevel(), // min level to enable test
80 if (fileContentProcessEnabled) {
91 let homeDir = GetHomeDir();
97 minLevel: minHomeReadSandboxLevel(),
100 if (fileContentProcessEnabled) {
104 browser: fileBrowser,
111 let extensionsDir = GetProfileEntry("extensions");
112 if (extensionsDir.exists() && extensionsDir.isDirectory()) {
114 desc: "extensions dir",
122 ok(false, `${extensionsDir.path} is a valid dir`);
125 let chromeDir = GetProfileEntry("chrome");
126 if (chromeDir.exists() && chromeDir.isDirectory()) {
136 ok(false, `${chromeDir.path} is valid dir`);
139 let cookiesFile = GetProfileEntry("cookies.sqlite");
140 if (cookiesFile.exists() && !cookiesFile.isDirectory()) {
142 desc: "cookies file",
146 minLevel: minProfileReadSandboxLevel(),
149 if (fileContentProcessEnabled) {
151 desc: "cookies file",
153 browser: fileBrowser,
160 ok(false, `${cookiesFile.path} is a valid file`);
163 if (isMac() || isLinux()) {
164 let varDir = GetDir("/var");
167 // Mac sandbox rules use /private/var because /var is a symlink
168 // to /private/var on OS X. Make sure that hasn't changed.
171 varDir.path === "/private/var",
172 "/var resolves to /private/var"
181 minLevel: minHomeReadSandboxLevel(),
184 if (fileContentProcessEnabled) {
188 browser: fileBrowser,
196 await runTestsList(tests);
199 async function testFileAccessMacOnly() {
204 let webBrowser = GetWebBrowser();
205 let fileContentProcessEnabled = isFileContentProcessEnabled();
206 let fileBrowser = GetFileBrowser();
207 let level = GetSandboxLevel();
211 // If ~/Library/Caches/TemporaryItems exists, when level <= 2 we
212 // make sure it's readable. For level 3, we make sure it isn't.
213 let homeTempDir = GetHomeDir();
214 homeTempDir.appendRelativePath("Library/Caches/TemporaryItems");
215 if (homeTempDir.exists()) {
216 let shouldBeReadable, minLevel;
217 if (level >= minHomeReadSandboxLevel()) {
218 shouldBeReadable = false;
219 minLevel = minHomeReadSandboxLevel();
221 shouldBeReadable = true;
225 desc: "home library cache temp dir",
226 ok: shouldBeReadable,
234 // Test if we can read from $TMPDIR because we expect it
235 // to be within /private/var. Reading from it should be
236 // prevented in a 'web' process.
237 let macTempDir = GetDirFromEnvVariable("TMPDIR");
239 macTempDir.normalize();
241 macTempDir.path.startsWith("/private/var"),
242 "$TMPDIR is in /private/var"
246 desc: `$TMPDIR (${macTempDir.path})`,
250 minLevel: minHomeReadSandboxLevel(),
253 if (fileContentProcessEnabled) {
255 desc: `$TMPDIR (${macTempDir.path})`,
257 browser: fileBrowser,
264 // The font registry directory is in the Darwin user cache dir which is
265 // accessible with the getconf(1) library call using DARWIN_USER_CACHE_DIR.
266 // For this test, assume the cache dir is located at $TMPDIR/../C and use
267 // the $TMPDIR to derive the path to the registry.
268 let fontRegistryDir = macTempDir.parent.clone();
269 fontRegistryDir.appendRelativePath("C/com.apple.FontRegistry");
270 if (fontRegistryDir.exists()) {
272 desc: `FontRegistry (${fontRegistryDir.path})`,
275 file: fontRegistryDir,
276 minLevel: minHomeReadSandboxLevel(),
279 // Check that we can read the file named `font` which typically
280 // exists in the the font registry directory.
281 let fontFile = fontRegistryDir.clone();
282 fontFile.appendRelativePath("font");
283 if (fontFile.exists()) {
285 desc: `FontRegistry file (${fontFile.path})`,
289 minLevel: minHomeReadSandboxLevel(),
295 // Test that we cannot read from /Volumes at level 3
296 let volumes = GetDir("/Volumes");
302 minLevel: minHomeReadSandboxLevel(),
306 // /Network is not present on macOS 10.15 (xnu 19). Don't
307 // test this directory on 10.15 and later.
308 if (AppConstants.isPlatformAndVersionAtMost("macosx", 18)) {
309 // Test that we cannot read from /Network at level 3
310 let network = GetDir("/Network");
316 minLevel: minHomeReadSandboxLevel(),
320 // Test that we cannot read from /Users at level 3
321 let users = GetDir("/Users");
327 minLevel: minHomeReadSandboxLevel(),
331 // Test that we can stat /Users at level 3
337 minLevel: minHomeReadSandboxLevel(),
341 // Test that we can stat /Library at level 3, but can't get a
342 // directory listing of /Library. This test uses "/Library"
343 // because it's a path that is expected to always be present.
344 let libraryDir = GetDir("/Library");
350 minLevel: minHomeReadSandboxLevel(),
358 minLevel: minHomeReadSandboxLevel(),
362 // Similarly, test that we can stat /private, but not /private/etc.
363 let privateDir = GetDir("/private");
369 minLevel: minHomeReadSandboxLevel(),
373 await runTestsList(tests);
376 async function testFileAccessLinuxOnly() {
381 let webBrowser = GetWebBrowser();
382 let fileContentProcessEnabled = isFileContentProcessEnabled();
383 let fileBrowser = GetFileBrowser();
387 // Test /proc/self/fd, because that can be used to unfreeze
388 // frozen shared memory.
389 let selfFdDir = GetDir("/proc/self/fd");
391 desc: "/proc/self/fd",
395 minLevel: isContentFileIOSandboxed(),
399 let cacheFontConfigDir = GetHomeSubdir(".cache/fontconfig/");
401 desc: `$HOME/.cache/fontconfig/ (${cacheFontConfigDir.path})`,
404 file: cacheFontConfigDir,
405 minLevel: minHomeReadSandboxLevel(),
409 // allows to handle both $HOME/.config/ or $XDG_CONFIG_HOME
410 let configDir = GetHomeSubdir(".config");
412 const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
414 if (xdgConfigHome.length > 1) {
415 configDir = GetDir(xdgConfigHome);
416 configDir.normalize();
419 desc: `$XDG_CONFIG_HOME (${configDir.path})`,
423 minLevel: minHomeReadSandboxLevel(),
428 // $HOME/.config/ or $XDG_CONFIG_HOME/ should have rdonly access
430 desc: `${configDir.path} dir`,
434 minLevel: minHomeReadSandboxLevel(),
437 if (fileContentProcessEnabled) {
439 desc: `${configDir.path} dir`,
441 browser: fileBrowser,
448 if (xdgConfigHome.length > 1) {
449 // When XDG_CONFIG_HOME is set, dont allow $HOME/.config
450 const homeConfigDir = GetHomeSubdir(".config");
452 desc: `${homeConfigDir.path} dir`,
456 minLevel: minHomeReadSandboxLevel(),
459 if (fileContentProcessEnabled) {
461 desc: `${homeConfigDir.path} dir`,
463 browser: fileBrowser,
470 // WWhen XDG_CONFIG_HOME is not set, verify we do not allow $HOME/.configlol
471 // (i.e., check allow the dir and not the prefix)
473 // Checking $HOME/.config is already done above.
474 const homeConfigPrefix = GetHomeSubdir(".configlol");
476 desc: `${homeConfigPrefix.path} dir`,
479 file: homeConfigPrefix,
480 minLevel: minHomeReadSandboxLevel(),
483 if (fileContentProcessEnabled) {
485 desc: `${homeConfigPrefix.path} dir`,
487 browser: fileBrowser,
488 file: homeConfigPrefix,
495 // Create a file under $HOME/.config/ or $XDG_CONFIG_HOME and ensure we can
497 let fileUnderConfig = GetSubdirFile(configDir);
498 await IOUtils.writeUTF8(fileUnderConfig.path, "TEST FILE DUMMY DATA");
500 await IOUtils.exists(fileUnderConfig.path),
501 `File ${fileUnderConfig.path} was properly created`
505 desc: `${configDir.path}/xxx is readable (${fileUnderConfig.path})`,
508 file: fileUnderConfig,
509 minLevel: minHomeReadSandboxLevel(),
511 cleanup: aPath => IOUtils.remove(aPath),
514 let configFile = GetSubdirFile(configDir);
516 desc: `${configDir.path} file write`,
520 minLevel: minHomeReadSandboxLevel(),
523 if (fileContentProcessEnabled) {
525 desc: `${configDir.path} file write`,
527 browser: fileBrowser,
534 // Create a $HOME/.config/mozilla/ or $XDG_CONFIG_HOME/mozilla/ if none
535 // exists and assert content process cannot access it
536 let configMozilla = GetSubdir(configDir, "mozilla");
537 const emptyFileName = ".test_run_browser_sandbox.tmp";
538 let emptyFile = configMozilla.clone();
539 emptyFile.appendRelativePath(emptyFileName);
541 let populateFakeConfigMozilla = async aPath => {
542 // called with configMozilla
543 await IOUtils.makeDirectory(aPath, { permissions: 0o700 });
544 await IOUtils.writeUTF8(emptyFile.path, "");
546 await IOUtils.exists(emptyFile.path),
547 `Temp file ${emptyFile.path} was created`
551 let unpopulateFakeConfigMozilla = async aPath => {
552 // called with emptyFile
553 await IOUtils.remove(aPath);
554 ok(!(await IOUtils.exists(aPath)), `Temp file ${aPath} was removed`);
555 const parentDir = PathUtils.parent(aPath);
557 await IOUtils.remove(parentDir, { recursive: false });
560 !DOMException.isInstance(ex) ||
561 ex.name !== "OperationError" ||
562 /Could not remove the non-empty directory/.test(ex.message)
564 // If we get here it means the directory was not empty and since we assert
565 // earlier we removed the temp file we created it means we should not
566 // worrying about removing this directory ...
572 await populateFakeConfigMozilla(configMozilla.path);
575 desc: `stat ${configDir.path}/mozilla (${configMozilla.path})`,
579 minLevel: minHomeReadSandboxLevel(),
584 desc: `read ${configDir.path}/mozilla (${configMozilla.path})`,
588 minLevel: minHomeReadSandboxLevel(),
593 desc: `stat ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
597 minLevel: minHomeReadSandboxLevel(),
602 desc: `read ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
606 minLevel: minHomeReadSandboxLevel(),
608 cleanup: unpopulateFakeConfigMozilla,
611 // Only needed to perform cleanup
612 if (xdgConfigHome.length > 1) {
614 desc: `$XDG_CONFIG_HOME (${configDir.path}) cleanup`,
618 minLevel: minHomeReadSandboxLevel(),
623 await runTestsList(tests);
626 async function testFileAccessLinuxSnap() {
627 let webBrowser = GetWebBrowser();
631 // Assert that if we run with SNAP= env, then we allow access to it in the
633 let snap = Services.env.get("SNAP");
634 let snapExpectedResult = false;
635 if (snap.length > 1) {
636 snapExpectedResult = true;
638 snap = "/tmp/.snap_firefox_current/";
641 let snapDir = GetDir(snap);
644 let snapFile = GetSubdirFile(snapDir);
645 await createFile(snapFile.path);
646 ok(await IOUtils.exists(snapFile.path), `SNAP ${snapFile.path} was created`);
647 info(`SNAP (file) ${snapFile.path} was created`);
650 desc: `$SNAP (${snapDir.path} => ${snapFile.path})`,
651 ok: snapExpectedResult,
654 minLevel: minHomeReadSandboxLevel(),
658 await runTestsList(tests);
661 async function testFileAccessWindowsOnly() {
666 let webBrowser = GetWebBrowser();
670 let extDir = GetPerUserExtensionDir();
672 desc: "per-user extensions dir",
676 minLevel: minHomeReadSandboxLevel(),
680 await runTestsList(tests);
683 function cleanupBrowserTabs() {
684 let fileBrowser = GetFileBrowser();
685 if (fileBrowser.selectedTab) {
686 gBrowser.removeTab(fileBrowser.selectedTab);
689 let webBrowser = GetWebBrowser();
690 if (webBrowser.selectedTab) {
691 gBrowser.removeTab(webBrowser.selectedTab);
694 let tab1 = gBrowser.tabs[1];
696 gBrowser.removeTab(tab1);