Bug 1850991 - Update brotli to version 1.1.0. r=jfkthame
[gecko.git] / security / sandbox / test / browser_content_sandbox_fs_tests.js
blob12678ddbe0288a70623aa79b2ecb957a6710265f
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 */
4 "use strict";
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");
13   if (fileCreated.ok) {
14     // content process successfully created the file, now remove it
15     homeFile.remove(false);
16   }
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");
32   } else {
33     ok(!!fileCreated.ok, "creating a file in temp is permitted");
34   }
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");
42   } else {
43     ok(!!fileDeleted.ok, "deleting a file in temp is permitted");
44   }
46   // Test that symlink creation is not allowed on macOS.
47   if (isMac()) {
48     let path = fileInTempDir().path;
49     let symlinkCreated = await SpecialPowers.spawn(
50       browser,
51       [path],
52       createSymlink
53     );
54     ok(!symlinkCreated.ok, "created a symlink in temp is not permitted");
55   }
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.
69   let tests = [];
71   let profileDir = GetProfileDir();
72   tests.push({
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
78     func: readDir,
79   });
80   if (fileContentProcessEnabled) {
81     tests.push({
82       desc: "profile dir",
83       ok: true,
84       browser: fileBrowser,
85       file: profileDir,
86       minLevel: 0,
87       func: readDir,
88     });
89   }
91   let homeDir = GetHomeDir();
92   tests.push({
93     desc: "home dir",
94     ok: false,
95     browser: webBrowser,
96     file: homeDir,
97     minLevel: minHomeReadSandboxLevel(),
98     func: readDir,
99   });
100   if (fileContentProcessEnabled) {
101     tests.push({
102       desc: "home dir",
103       ok: true,
104       browser: fileBrowser,
105       file: homeDir,
106       minLevel: 0,
107       func: readDir,
108     });
109   }
111   let extensionsDir = GetProfileEntry("extensions");
112   if (extensionsDir.exists() && extensionsDir.isDirectory()) {
113     tests.push({
114       desc: "extensions dir",
115       ok: true,
116       browser: webBrowser,
117       file: extensionsDir,
118       minLevel: 0,
119       func: readDir,
120     });
121   } else {
122     ok(false, `${extensionsDir.path} is a valid dir`);
123   }
125   let chromeDir = GetProfileEntry("chrome");
126   if (chromeDir.exists() && chromeDir.isDirectory()) {
127     tests.push({
128       desc: "chrome dir",
129       ok: true,
130       browser: webBrowser,
131       file: chromeDir,
132       minLevel: 0,
133       func: readDir,
134     });
135   } else {
136     ok(false, `${chromeDir.path} is valid dir`);
137   }
139   let cookiesFile = GetProfileEntry("cookies.sqlite");
140   if (cookiesFile.exists() && !cookiesFile.isDirectory()) {
141     tests.push({
142       desc: "cookies file",
143       ok: false,
144       browser: webBrowser,
145       file: cookiesFile,
146       minLevel: minProfileReadSandboxLevel(),
147       func: readFile,
148     });
149     if (fileContentProcessEnabled) {
150       tests.push({
151         desc: "cookies file",
152         ok: true,
153         browser: fileBrowser,
154         file: cookiesFile,
155         minLevel: 0,
156         func: readFile,
157       });
158     }
159   } else {
160     ok(false, `${cookiesFile.path} is a valid file`);
161   }
163   if (isMac() || isLinux()) {
164     let varDir = GetDir("/var");
166     if (isMac()) {
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.
169       varDir.normalize();
170       Assert.ok(
171         varDir.path === "/private/var",
172         "/var resolves to /private/var"
173       );
174     }
176     tests.push({
177       desc: "/var",
178       ok: false,
179       browser: webBrowser,
180       file: varDir,
181       minLevel: minHomeReadSandboxLevel(),
182       func: readDir,
183     });
184     if (fileContentProcessEnabled) {
185       tests.push({
186         desc: "/var",
187         ok: true,
188         browser: fileBrowser,
189         file: varDir,
190         minLevel: 0,
191         func: readDir,
192       });
193     }
194   }
196   await runTestsList(tests);
199 async function testFileAccessMacOnly() {
200   if (!isMac()) {
201     return;
202   }
204   let webBrowser = GetWebBrowser();
205   let fileContentProcessEnabled = isFileContentProcessEnabled();
206   let fileBrowser = GetFileBrowser();
207   let level = GetSandboxLevel();
209   let tests = [];
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();
220     } else {
221       shouldBeReadable = true;
222       minLevel = 0;
223     }
224     tests.push({
225       desc: "home library cache temp dir",
226       ok: shouldBeReadable,
227       browser: webBrowser,
228       file: homeTempDir,
229       minLevel,
230       func: readDir,
231     });
232   }
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();
240   Assert.ok(
241     macTempDir.path.startsWith("/private/var"),
242     "$TMPDIR is in /private/var"
243   );
245   tests.push({
246     desc: `$TMPDIR (${macTempDir.path})`,
247     ok: false,
248     browser: webBrowser,
249     file: macTempDir,
250     minLevel: minHomeReadSandboxLevel(),
251     func: readDir,
252   });
253   if (fileContentProcessEnabled) {
254     tests.push({
255       desc: `$TMPDIR (${macTempDir.path})`,
256       ok: true,
257       browser: fileBrowser,
258       file: macTempDir,
259       minLevel: 0,
260       func: readDir,
261     });
262   }
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()) {
271     tests.push({
272       desc: `FontRegistry (${fontRegistryDir.path})`,
273       ok: true,
274       browser: webBrowser,
275       file: fontRegistryDir,
276       minLevel: minHomeReadSandboxLevel(),
277       func: readDir,
278     });
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()) {
284       tests.push({
285         desc: `FontRegistry file (${fontFile.path})`,
286         ok: true,
287         browser: webBrowser,
288         file: fontFile,
289         minLevel: minHomeReadSandboxLevel(),
290         func: readFile,
291       });
292     }
293   }
295   // Test that we cannot read from /Volumes at level 3
296   let volumes = GetDir("/Volumes");
297   tests.push({
298     desc: "/Volumes",
299     ok: false,
300     browser: webBrowser,
301     file: volumes,
302     minLevel: minHomeReadSandboxLevel(),
303     func: readDir,
304   });
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");
311     tests.push({
312       desc: "/Network",
313       ok: false,
314       browser: webBrowser,
315       file: network,
316       minLevel: minHomeReadSandboxLevel(),
317       func: readDir,
318     });
319   }
320   // Test that we cannot read from /Users at level 3
321   let users = GetDir("/Users");
322   tests.push({
323     desc: "/Users",
324     ok: false,
325     browser: webBrowser,
326     file: users,
327     minLevel: minHomeReadSandboxLevel(),
328     func: readDir,
329   });
331   // Test that we can stat /Users at level 3
332   tests.push({
333     desc: "/Users",
334     ok: true,
335     browser: webBrowser,
336     file: users,
337     minLevel: minHomeReadSandboxLevel(),
338     func: statPath,
339   });
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");
345   tests.push({
346     desc: "/Library",
347     ok: true,
348     browser: webBrowser,
349     file: libraryDir,
350     minLevel: minHomeReadSandboxLevel(),
351     func: statPath,
352   });
353   tests.push({
354     desc: "/Library",
355     ok: false,
356     browser: webBrowser,
357     file: libraryDir,
358     minLevel: minHomeReadSandboxLevel(),
359     func: readDir,
360   });
362   // Similarly, test that we can stat /private, but not /private/etc.
363   let privateDir = GetDir("/private");
364   tests.push({
365     desc: "/private",
366     ok: true,
367     browser: webBrowser,
368     file: privateDir,
369     minLevel: minHomeReadSandboxLevel(),
370     func: statPath,
371   });
373   await runTestsList(tests);
376 async function testFileAccessLinuxOnly() {
377   if (!isLinux()) {
378     return;
379   }
381   let webBrowser = GetWebBrowser();
382   let fileContentProcessEnabled = isFileContentProcessEnabled();
383   let fileBrowser = GetFileBrowser();
385   let tests = [];
387   // Test /proc/self/fd, because that can be used to unfreeze
388   // frozen shared memory.
389   let selfFdDir = GetDir("/proc/self/fd");
390   tests.push({
391     desc: "/proc/self/fd",
392     ok: false,
393     browser: webBrowser,
394     file: selfFdDir,
395     minLevel: isContentFileIOSandboxed(),
396     func: readDir,
397   });
399   let cacheFontConfigDir = GetHomeSubdir(".cache/fontconfig/");
400   tests.push({
401     desc: `$HOME/.cache/fontconfig/ (${cacheFontConfigDir.path})`,
402     ok: true,
403     browser: webBrowser,
404     file: cacheFontConfigDir,
405     minLevel: minHomeReadSandboxLevel(),
406     func: readDir,
407   });
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();
418     tests.push({
419       desc: `$XDG_CONFIG_HOME (${configDir.path})`,
420       ok: true,
421       browser: webBrowser,
422       file: configDir,
423       minLevel: minHomeReadSandboxLevel(),
424       func: readDir,
425     });
426   }
428   // $HOME/.config/ or $XDG_CONFIG_HOME/ should have rdonly access
429   tests.push({
430     desc: `${configDir.path} dir`,
431     ok: true,
432     browser: webBrowser,
433     file: configDir,
434     minLevel: minHomeReadSandboxLevel(),
435     func: readDir,
436   });
437   if (fileContentProcessEnabled) {
438     tests.push({
439       desc: `${configDir.path} dir`,
440       ok: true,
441       browser: fileBrowser,
442       file: configDir,
443       minLevel: 0,
444       func: readDir,
445     });
446   }
448   if (xdgConfigHome.length > 1) {
449     // When XDG_CONFIG_HOME is set, dont allow $HOME/.config
450     const homeConfigDir = GetHomeSubdir(".config");
451     tests.push({
452       desc: `${homeConfigDir.path} dir`,
453       ok: false,
454       browser: webBrowser,
455       file: homeConfigDir,
456       minLevel: minHomeReadSandboxLevel(),
457       func: readDir,
458     });
459     if (fileContentProcessEnabled) {
460       tests.push({
461         desc: `${homeConfigDir.path} dir`,
462         ok: true,
463         browser: fileBrowser,
464         file: homeConfigDir,
465         minLevel: 0,
466         func: readDir,
467       });
468     }
469   } else {
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)
472     //
473     // Checking $HOME/.config is already done above.
474     const homeConfigPrefix = GetHomeSubdir(".configlol");
475     tests.push({
476       desc: `${homeConfigPrefix.path} dir`,
477       ok: false,
478       browser: webBrowser,
479       file: homeConfigPrefix,
480       minLevel: minHomeReadSandboxLevel(),
481       func: readDir,
482     });
483     if (fileContentProcessEnabled) {
484       tests.push({
485         desc: `${homeConfigPrefix.path} dir`,
486         ok: false,
487         browser: fileBrowser,
488         file: homeConfigPrefix,
489         minLevel: 0,
490         func: readDir,
491       });
492     }
493   }
495   // Create a file under $HOME/.config/ or $XDG_CONFIG_HOME and ensure we can
496   // read it
497   let fileUnderConfig = GetSubdirFile(configDir);
498   await IOUtils.writeUTF8(fileUnderConfig.path, "TEST FILE DUMMY DATA");
499   ok(
500     await IOUtils.exists(fileUnderConfig.path),
501     `File ${fileUnderConfig.path} was properly created`
502   );
504   tests.push({
505     desc: `${configDir.path}/xxx is readable (${fileUnderConfig.path})`,
506     ok: true,
507     browser: webBrowser,
508     file: fileUnderConfig,
509     minLevel: minHomeReadSandboxLevel(),
510     func: readFile,
511     cleanup: aPath => IOUtils.remove(aPath),
512   });
514   let configFile = GetSubdirFile(configDir);
515   tests.push({
516     desc: `${configDir.path} file write`,
517     ok: false,
518     browser: webBrowser,
519     file: configFile,
520     minLevel: minHomeReadSandboxLevel(),
521     func: createFile,
522   });
523   if (fileContentProcessEnabled) {
524     tests.push({
525       desc: `${configDir.path} file write`,
526       ok: false,
527       browser: fileBrowser,
528       file: configFile,
529       minLevel: 0,
530       func: createFile,
531     });
532   }
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, "");
545     ok(
546       await IOUtils.exists(emptyFile.path),
547       `Temp file ${emptyFile.path} was created`
548     );
549   };
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);
556     try {
557       await IOUtils.remove(parentDir, { recursive: false });
558     } catch (ex) {
559       if (
560         !DOMException.isInstance(ex) ||
561         ex.name !== "OperationError" ||
562         /Could not remove the non-empty directory/.test(ex.message)
563       ) {
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 ...
567         throw ex;
568       }
569     }
570   };
572   await populateFakeConfigMozilla(configMozilla.path);
574   tests.push({
575     desc: `stat ${configDir.path}/mozilla (${configMozilla.path})`,
576     ok: false,
577     browser: webBrowser,
578     file: configMozilla,
579     minLevel: minHomeReadSandboxLevel(),
580     func: statPath,
581   });
583   tests.push({
584     desc: `read ${configDir.path}/mozilla (${configMozilla.path})`,
585     ok: false,
586     browser: webBrowser,
587     file: configMozilla,
588     minLevel: minHomeReadSandboxLevel(),
589     func: readDir,
590   });
592   tests.push({
593     desc: `stat ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
594     ok: false,
595     browser: webBrowser,
596     file: emptyFile,
597     minLevel: minHomeReadSandboxLevel(),
598     func: statPath,
599   });
601   tests.push({
602     desc: `read ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
603     ok: false,
604     browser: webBrowser,
605     file: emptyFile,
606     minLevel: minHomeReadSandboxLevel(),
607     func: readFile,
608     cleanup: unpopulateFakeConfigMozilla,
609   });
611   // Only needed to perform cleanup
612   if (xdgConfigHome.length > 1) {
613     tests.push({
614       desc: `$XDG_CONFIG_HOME (${configDir.path}) cleanup`,
615       ok: true,
616       browser: webBrowser,
617       file: configDir,
618       minLevel: minHomeReadSandboxLevel(),
619       func: readDir,
620     });
621   }
623   await runTestsList(tests);
626 async function testFileAccessLinuxSnap() {
627   let webBrowser = GetWebBrowser();
629   let tests = [];
631   // Assert that if we run with SNAP= env, then we allow access to it in the
632   // content process
633   let snap = Services.env.get("SNAP");
634   let snapExpectedResult = false;
635   if (snap.length > 1) {
636     snapExpectedResult = true;
637   } else {
638     snap = "/tmp/.snap_firefox_current/";
639   }
641   let snapDir = GetDir(snap);
642   snapDir.normalize();
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`);
649   tests.push({
650     desc: `$SNAP (${snapDir.path} => ${snapFile.path})`,
651     ok: snapExpectedResult,
652     browser: webBrowser,
653     file: snapFile,
654     minLevel: minHomeReadSandboxLevel(),
655     func: readFile,
656   });
658   await runTestsList(tests);
661 async function testFileAccessWindowsOnly() {
662   if (!isWin()) {
663     return;
664   }
666   let webBrowser = GetWebBrowser();
668   let tests = [];
670   let extDir = GetPerUserExtensionDir();
671   tests.push({
672     desc: "per-user extensions dir",
673     ok: true,
674     browser: webBrowser,
675     file: extDir,
676     minLevel: minHomeReadSandboxLevel(),
677     func: readDir,
678   });
680   await runTestsList(tests);
683 function cleanupBrowserTabs() {
684   let fileBrowser = GetFileBrowser();
685   if (fileBrowser.selectedTab) {
686     gBrowser.removeTab(fileBrowser.selectedTab);
687   }
689   let webBrowser = GetWebBrowser();
690   if (webBrowser.selectedTab) {
691     gBrowser.removeTab(webBrowser.selectedTab);
692   }
694   let tab1 = gBrowser.tabs[1];
695   if (tab1) {
696     gBrowser.removeTab(tab1);
697   }