test: Fix default app dir for XDG and import sandbox tests, b=bug #11917, c=tests

This commit is contained in:
mr. m
2026-01-17 14:13:06 +01:00
parent 7668c26ce5
commit 56c5f2fb6d
27 changed files with 2623 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae944551f9 100644
index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..ec11f894f18140b8e7ceb65e6ca4a2f27b5ba520 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -398,6 +398,7 @@
@@ -458,10 +458,10 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
+ gZenWorkspaces._initialTab._shouldRemove = true;
+ }
+ }
+ }
}
+ else {
+ gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab;
}
+ }
+ this._hasAlreadyInitializedZenSessionStore = true;
if (tabs.length > 1 || !tabs[0].selected) {
@@ -650,7 +650,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
// If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor.
@@ -5744,13 +5908,13 @@
@@ -5744,15 +5908,22 @@
!excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) {
@@ -662,11 +662,20 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
let remainingTabs = Array.prototype.filter.call(
this.visibleTabs,
- tab => !excludeTabs.has(tab)
+ tab => !excludeTabs.has(tab) && gZenWorkspaces._shouldChangeToTab(tab)
+ tab => !excludeTabs.has(tab) && gZenWorkspaces._shouldChangeToTab(tab) && tab !== aTab
);
+ if (remainingTabs.length > 0 && Services.prefs.getBoolPref("zen.tabs.select-recently-used-on-close")) {
+ let mostRecentTab = remainingTabs.reduce((a, b) =>
+ b.lastAccessed > a.lastAccessed ? b : a
+ );
+ return gZenWorkspaces.findTabToBlur(mostRecentTab);
+ }
+
let tab = this.tabContainer.findNextTab(aTab, {
@@ -5766,7 +5930,7 @@
direction: 1,
filter: _tab => remainingTabs.includes(_tab),
@@ -5766,7 +5937,7 @@
}
if (tab) {
@@ -675,7 +684,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
}
// If no qualifying visible tab was found, see if there is a tab in
@@ -5787,7 +5951,7 @@
@@ -5787,7 +5958,7 @@
});
}
@@ -684,7 +693,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
}
_blurTab(aTab) {
@@ -5798,7 +5962,7 @@
@@ -5798,7 +5969,7 @@
* @returns {boolean}
* False if swapping isn't permitted, true otherwise.
*/
@@ -693,7 +702,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (
@@ -5852,6 +6016,7 @@
@@ -5852,6 +6023,7 @@
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
if (
@@ -701,7 +710,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
!remoteBrowser._beginRemoveTab(aOtherTab, {
adoptedByTab: aOurTab,
closeWindowWithLastTab: true,
@@ -5863,7 +6028,7 @@
@@ -5863,7 +6035,7 @@
// If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid
// about:blank being painted.
@@ -710,7 +719,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
if (closeWindow) {
let win = aOtherTab.ownerGlobal;
win.windowUtils.suppressAnimation(true);
@@ -5987,11 +6152,13 @@
@@ -5987,11 +6159,13 @@
}
// Finish tearing down the tab that's going away.
@@ -724,7 +733,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
this.setTabTitle(aOurTab);
@@ -6193,10 +6360,10 @@
@@ -6193,10 +6367,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
}
@@ -737,7 +746,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
aTab.selected ||
aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -6254,7 +6421,8 @@
@@ -6254,7 +6428,8 @@
*
* @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab
*/
@@ -747,7 +756,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
if (this.tabs.length == 1) {
return null;
}
@@ -6278,12 +6446,14 @@
@@ -6278,12 +6453,14 @@
}
// tell a new window to take the "dropped" tab
@@ -763,7 +772,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
}
/**
@@ -6388,7 +6558,7 @@
@@ -6388,7 +6565,7 @@
* `true` if element is a `<tab-group>`
*/
isTabGroup(element) {
@@ -772,7 +781,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
}
/**
@@ -6473,8 +6643,8 @@
@@ -6473,8 +6650,8 @@
}
// Don't allow mixing pinned and unpinned tabs.
@@ -783,7 +792,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
} else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
}
@@ -6500,10 +6670,16 @@
@@ -6500,10 +6677,16 @@
this.#handleTabMove(
element,
() => {
@@ -802,7 +811,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element);
} else {
@@ -6561,23 +6737,31 @@
@@ -6561,23 +6744,31 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group;
@@ -840,7 +849,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
} else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element
@@ -6590,14 +6774,34 @@
@@ -6590,14 +6781,34 @@
// move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group.
@@ -876,7 +885,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
element.pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -6606,7 +6810,7 @@
@@ -6606,7 +6817,7 @@
element,
() => {
if (moveBefore) {
@@ -885,7 +894,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
} else if (targetElement) {
targetElement.after(element);
} else {
@@ -6676,10 +6880,10 @@
@@ -6676,10 +6887,10 @@
* @param {TabMetricsContext} [metricsContext]
*/
moveTabToExistingGroup(aTab, aGroup, metricsContext) {
@@ -898,7 +907,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
return;
}
if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6751,6 +6955,7 @@
@@ -6751,6 +6962,7 @@
let state = {
tabIndex: tab._tPos,
@@ -906,7 +915,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
};
if (tab.visible) {
state.elementIndex = tab.elementIndex;
@@ -6777,7 +6982,7 @@
@@ -6777,7 +6989,7 @@
let changedTabGroup =
previousTabState.tabGroupId != currentTabState.tabGroupId;
@@ -915,7 +924,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
tab.dispatchEvent(
new CustomEvent("TabMove", {
bubbles: true,
@@ -6818,6 +7023,10 @@
@@ -6818,6 +7030,10 @@
moveActionCallback();
@@ -926,7 +935,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
// Clear tabs cache after moving nodes because the order of tabs may have
// changed.
this.tabContainer._invalidateCachedTabs();
@@ -6869,6 +7078,19 @@
@@ -6869,6 +7085,19 @@
* The new tab in the current window, null if the tab couldn't be adopted.
*/
adoptTab(aTab, { elementIndex, tabIndex, selectTab = false } = {}) {
@@ -946,7 +955,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
// Swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows). We also ensure that the tab we create to swap into has
@@ -6910,6 +7132,8 @@
@@ -6910,6 +7139,8 @@
params.userContextId = aTab.getAttribute("usercontextid");
}
let newTab = this.addWebTab("about:blank", params);
@@ -955,7 +964,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
let newBrowser = this.getBrowserForTab(newTab);
aTab.container.tabDragAndDrop.finishAnimateTabMove();
@@ -7718,7 +7942,7 @@
@@ -7718,7 +7949,7 @@
// preventDefault(). It will still raise the window if appropriate.
break;
}
@@ -964,7 +973,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
window.focus();
aEvent.preventDefault();
break;
@@ -7735,7 +7959,6 @@
@@ -7735,7 +7966,6 @@
}
case "TabGroupCollapse":
aEvent.target.tabs.forEach(tab => {
@@ -972,7 +981,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
});
break;
case "TabGroupCreateByUser":
@@ -7895,7 +8118,9 @@
@@ -7895,7 +8125,9 @@
let filter = this._tabFilters.get(tab);
if (filter) {
@@ -982,7 +991,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
let listener = this._tabListeners.get(tab);
if (listener) {
@@ -8698,6 +8923,7 @@
@@ -8698,6 +8930,7 @@
aWebProgress.isTopLevel
) {
this.mTab.setAttribute("busy", "true");
@@ -990,7 +999,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected;
}
@@ -8778,6 +9004,7 @@
@@ -8778,6 +9011,7 @@
// known defaults. Note we use the original URL since about:newtab
// redirects to a prerendered page.
const shouldRemoveFavicon =
@@ -998,7 +1007,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..5f58cf2009dfe869d05b896b609c7bae
!this.mBrowser.mIconURL &&
!ignoreBlank &&
!(originalLocation.spec in FAVICON_DEFAULTS);
@@ -9803,7 +10030,7 @@ var TabContextMenu = {
@@ -9803,7 +10037,7 @@ var TabContextMenu = {
);
contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected;

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/moz.configure b/toolkit/moz.configure
index 226d0c5a93a9a2404e1974001da4e34b7b670067..18ac234b577eb514d3ef3467e24fceb0eb1ec8f8 100644
index 226d0c5a93a9a2404e1974001da4e34b7b670067..b73277448f7d2706d316df2505e17d232f392d47 100644
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -22,6 +22,7 @@ def check_moz_app_id(moz_app_id, build_project):
@@ -55,3 +55,12 @@ index 226d0c5a93a9a2404e1974001da4e34b7b670067..18ac234b577eb514d3ef3467e24fceb0
option(
@@ -3903,7 +3908,7 @@ with only_when(compile_environment):
return "Mozilla"
elif target.os == "Android":
return ".mozilla"
- return "mozilla"
+ return "zen"
option(
"--with-user-appdir",

View File

@@ -7,6 +7,16 @@
source = "browser/components/safebrowsing/content/test"
is_direct_path = true
[sandbox]
source = "security/sandbox/test"
is_direct_path = true
disable = [
"browser_bug1393259.js",
]
[sandbox.replace-manifest]
"../../../" = "../../../../"
[shell]
source = "browser/components/shell/test"
is_direct_path = true
@@ -17,4 +27,3 @@ disable = [
[tooltiptext]
source = "toolkit/components/tooltiptext"

View File

@@ -8,6 +8,7 @@
BROWSER_CHROME_MANIFESTS += [
"safebrowsing/browser.toml",
"sandbox/browser.toml",
"shell/browser.toml",
"tooltiptext/browser.toml",
]

View File

@@ -0,0 +1,37 @@
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
"mac_register_font.py",
"../../../../layout/reftests/fonts/fira/FiraSans-Regular.otf"
]
["browser_bug1393259.js"]
disabled="Disabled by import_external_tests.py"
support-files = ["bug1393259.html"]
run-if = [
"os == 'mac'", # This is a Mac-specific test
]
skip-if = [
"os == 'mac' && os_version == '14.70' && arch == 'x86_64'", # Bug 1929424
]
tags = "os_integration"
["browser_content_sandbox_fs.js"]
skip-if = [
"os == 'win' && os_version == '11.26100' && arch == 'x86' && debug", # bug 1379635
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && debug", # bug 1379635
]
["browser_content_sandbox_syscalls.js"]
["browser_sandbox_test.js"]
run-if = [
"debug",
]

View File

@@ -0,0 +1,203 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/*
* This test validates that an OTF font installed in a directory not
* accessible to content processes is rendered correctly by checking that
* content displayed never uses the OS fallback font "LastResort". When
* a content process renders a page with the fallback font, that is an
* indication the content process failed to read or load the computed font.
* The test uses a version of the Fira Sans font and depends on the font
* not being already installed and enabled.
*/
const kPageURL =
"http://example.com/browser/security/sandbox/test/bug1393259.html";
// Parameters for running the python script that registers/unregisters fonts.
let kPythonPath = "/usr/bin/python";
if (AppConstants.isPlatformAndVersionAtLeast("macosx", 23.0)) {
kPythonPath = "/usr/local/bin/python3";
}
const kFontInstallerPath = "browser/security/sandbox/test/mac_register_font.py";
const kUninstallFlag = "-u";
const kVerboseFlag = "-v";
// Where to find the font in the test environment.
const kRepoFontPath = "browser/security/sandbox/test/FiraSans-Regular.otf";
// Font name strings to check for.
const kLastResortFontName = "LastResort";
const kTestFontName = "Fira Sans";
// Home-relative path to install a private font. Where a private font is
// a font at a location not readable by content processes.
const kPrivateFontSubPath = "/FiraSans-Regular.otf";
add_task(async function () {
await new Promise(resolve => waitForFocus(resolve, window));
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: kPageURL,
},
async function (aBrowser) {
function runProcess(aCmd, aArgs, blocking = true) {
let cmdFile = Cc["@mozilla.org/file/local;1"].createInstance(
Ci.nsIFile
);
cmdFile.initWithPath(aCmd);
let process = Cc["@mozilla.org/process/util;1"].createInstance(
Ci.nsIProcess
);
process.init(cmdFile);
process.run(blocking, aArgs, aArgs.length);
return process.exitValue;
}
// Register the font at path |fontPath| and wait
// for the browser to detect the change.
async function registerFont(fontPath) {
let fontRegistered = getFontNotificationPromise();
let exitCode = runProcess(kPythonPath, [
kFontInstallerPath,
kVerboseFlag,
fontPath,
]);
Assert.equal(exitCode, 0, "registering font" + fontPath);
if (exitCode == 0) {
// Wait for the font registration to be detected by the browser.
await fontRegistered;
}
}
// Unregister the font at path |fontPath|. If |waitForUnreg| is true,
// don't wait for the browser to detect the change and don't use
// the verbose arg for the unregister command.
async function unregisterFont(fontPath, waitForUnreg = true) {
let args = [kFontInstallerPath, kUninstallFlag];
let fontUnregistered;
if (waitForUnreg) {
args.push(kVerboseFlag);
fontUnregistered = getFontNotificationPromise();
}
let exitCode = runProcess(kPythonPath, args.concat(fontPath));
if (waitForUnreg) {
Assert.equal(exitCode, 0, "unregistering font" + fontPath);
if (exitCode == 0) {
await fontUnregistered;
}
}
}
// Returns a promise that resolves when font info is changed.
let getFontNotificationPromise = () =>
new Promise(resolve => {
const kTopic = "font-info-updated";
function observe() {
Services.obs.removeObserver(observe, kTopic);
resolve();
}
Services.obs.addObserver(observe, kTopic);
});
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
let privateFontPath = homeDir.path + kPrivateFontSubPath;
registerCleanupFunction(function () {
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
runProcess("/bin/rm", [privateFontPath], /* blocking = */ false);
});
// Copy the font file to the private path.
runProcess("/bin/cp", [kRepoFontPath, privateFontPath]);
// Cleanup previous aborted tests.
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
// Get the original width, using the fallback monospaced font
let origWidth = await SpecialPowers.spawn(
aBrowser,
[],
async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
return contentDiv.offsetWidth;
}
);
// Activate the font we want to test at a non-standard path.
await registerFont(privateFontPath);
// Assign the new font to the content.
await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
contentDiv.style.fontFamily = "'Fira Sans', monospace";
});
// Wait until the width has changed, indicating the content process
// has recognized the newly-activated font.
while (true) {
let width = await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
return contentDiv.offsetWidth;
});
if (width != origWidth) {
break;
}
// If the content wasn't ready yet, wait a little before re-checking.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(c => setTimeout(c, 100));
}
// Get a list of fonts now being used to display the web content.
let fontList = await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let range = window.document.createRange();
let contentDiv = window.document.getElementById("content");
range.selectNode(contentDiv);
let fonts = InspectorUtils.getUsedFontFaces(range);
let fontList = [];
for (let i = 0; i < fonts.length; i++) {
fontList.push({ name: fonts[i].name });
}
return fontList;
});
let lastResortFontUsed = false;
let testFontUsed = false;
for (let font of fontList) {
// Did we fall back to the "LastResort" font?
if (!lastResortFontUsed && font.name.includes(kLastResortFontName)) {
lastResortFontUsed = true;
continue;
}
// Did we render using our test font as expected?
if (!testFontUsed && font.name.includes(kTestFontName)) {
testFontUsed = true;
continue;
}
}
Assert.ok(
!lastResortFontUsed,
`The ${kLastResortFontName} fallback font was not used`
);
Assert.ok(testFontUsed, `The test font "${kTestFontName}" was used`);
await unregisterFont(privateFontPath);
}
);
});

View File

@@ -0,0 +1,15 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
environment = "XDG_CONFIG_DIRS=:/opt"
["browser_content_sandbox_bug1717599_XDG-CONFIG-DIRS.js"]
run-if = [
"os == 'linux'",
]

View File

@@ -0,0 +1,13 @@
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
environment = "XDG_CONFIG_HOME="
["browser_content_sandbox_bug1717599_XDG-CONFIG-HOME.js"]
run-if = [
"os == 'linux'",
]

View File

@@ -0,0 +1,11 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
//
// Just test that browser does not die on empty env var
//
add_task(async function () {
ok(true, "Process can run");
});

View File

@@ -0,0 +1,11 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
//
// Just test that browser does not die on empty env var
//
add_task(async function () {
ok(true, "Process can run");
});

View File

@@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
this
);
/*
* This test exercises file I/O from web and file content processes using
* nsIFile etc. methods to validate that calls that are meant to be blocked by
* content sandboxing are blocked.
*/
//
// Checks that sandboxing is enabled and at the appropriate level
// setting before triggering tests that do the file I/O.
//
// Tests attempting to write to a file in the home directory from the
// content process--expected to fail.
//
// Tests attempting to write to a file in the content temp directory
// from the content process--expected to succeed. Uses "ContentTmpD".
//
// Tests reading various files and directories from file and web
// content processes.
//
add_task(async function () {
sanityChecks();
// Test creating a file in the home directory from a web content process
add_task(createFileInHome); // eslint-disable-line no-undef
// Test creating a file content temp from a web content process
add_task(createTempFile); // eslint-disable-line no-undef
// Test reading files/dirs from web and file content processes
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
add_task(testFileAccessMacOnly); // eslint-disable-line no-undef
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
add_task(testFileAccessWindowsOnly); // eslint-disable-line no-undef
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
});

View File

@@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
this
);
add_task(async function () {
// Ensure that SNAP is there
const snap = Services.env.get("SNAP");
Assert.greater(snap.length, 1, "SNAP is defined");
// If it is there, do actual testing
sanityChecks();
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
add_task(testFileAccessLinuxSnap); // eslint-disable-line no-undef
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
});

View File

@@ -0,0 +1,712 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
const lazy = {};
/* getLibcConstants is only present on *nix */
ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
ChromeUtils.getLibcConstants()
);
// Test if the content process can create in $HOME, this should fail
async function createFileInHome() {
let browser = gBrowser.selectedBrowser;
let homeFile = fileInHomeDir();
let path = homeFile.path;
let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
ok(!fileCreated.ok, "creating a file in home dir failed");
is(
fileCreated.code,
Cr.NS_ERROR_FILE_ACCESS_DENIED,
"creating a file in home dir failed with access denied"
);
if (fileCreated.ok) {
// content process successfully created the file, now remove it
homeFile.remove(false);
}
}
// Test if the content process can create a temp file, this is forbidden on all
// platforms. Also test that the content process cannot create symlinks on
// macOS/Linux or delete files.
async function createTempFile() {
// On Windows we allow access to the temp dir for DEBUG builds, because of
// logging that uses that dir.
let isDbgWin = isWin() && SpecialPowers.isDebugBuild;
let browser = gBrowser.selectedBrowser;
let path = fileInTempDir().path;
let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
if (isDbgWin) {
ok(fileCreated.ok, "creating a file in temp suceeded");
} else {
ok(!fileCreated.ok, "creating a file in temp failed");
is(
fileCreated.code,
Cr.NS_ERROR_FILE_ACCESS_DENIED,
"creating a file in temp failed with access denied"
);
}
// now delete the file
let fileDeleted = await SpecialPowers.spawn(browser, [path], deleteFile);
if (isDbgWin) {
ok(fileDeleted.ok, "deleting a file in temp succeeded");
} else {
ok(!fileDeleted.ok, "deleting a file in temp failed");
const expectedError = isLinux()
? Cr.NS_ERROR_FILE_ACCESS_DENIED
: Cr.NS_ERROR_FILE_NOT_FOUND;
is(
fileDeleted.code,
expectedError,
"deleting a file in temp failed with access denied"
);
}
// Test that symlink creation is not allowed on macOS/Linux.
if (isMac() || isLinux()) {
let path = fileInTempDir().path;
let symlinkCreated = await SpecialPowers.spawn(
browser,
[path],
createSymlink
);
ok(!symlinkCreated.ok, "created a symlink in temp failed");
const expectedError = isLinux() ? lazy.LIBC.EACCES : lazy.LIBC.EPERM;
is(
symlinkCreated.code,
expectedError,
"created a symlink in temp failed with access denied"
);
}
}
// Test reading files and dirs from web and file content processes.
async function testFileAccessAllPlatforms() {
let webBrowser = GetWebBrowser();
let fileContentProcessEnabled = isFileContentProcessEnabled();
let fileBrowser = GetFileBrowser();
// Directories/files to test accessing from content processes.
// For directories, we test whether a directory listing is allowed
// or blocked. For files, we test if we can read from the file.
// Each entry in the array represents a test file or directory
// that will be read from either a web or file process.
let tests = [];
let profileDir = GetProfileDir();
tests.push({
desc: "profile dir", // description
ok: false, // expected to succeed?
browser: webBrowser, // browser to run test in
file: profileDir, // nsIFile object
minLevel: minProfileReadSandboxLevel(), // min level to enable test
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: "profile dir",
ok: true,
browser: fileBrowser,
file: profileDir,
minLevel: 0,
func: readDir,
});
}
let homeDir = GetHomeDir();
tests.push({
desc: "home dir",
ok: false,
browser: webBrowser,
file: homeDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: "home dir",
ok: true,
browser: fileBrowser,
file: homeDir,
minLevel: 0,
func: readDir,
});
}
let extensionsDir = GetProfileEntry("extensions");
if (extensionsDir.exists() && extensionsDir.isDirectory()) {
tests.push({
desc: "extensions dir",
ok: true,
browser: webBrowser,
file: extensionsDir,
minLevel: 0,
func: readDir,
});
} else {
ok(false, `${extensionsDir.path} is a valid dir`);
}
let chromeDir = GetProfileEntry("chrome");
if (chromeDir.exists() && chromeDir.isDirectory()) {
tests.push({
desc: "chrome dir",
ok: true,
browser: webBrowser,
file: chromeDir,
minLevel: 0,
func: readDir,
});
} else {
ok(false, `${chromeDir.path} is valid dir`);
}
let cookiesFile = GetProfileEntry("cookies.sqlite");
if (cookiesFile.exists() && !cookiesFile.isDirectory()) {
tests.push({
desc: "cookies file",
ok: false,
browser: webBrowser,
file: cookiesFile,
minLevel: minProfileReadSandboxLevel(),
func: readFile,
});
if (fileContentProcessEnabled) {
tests.push({
desc: "cookies file",
ok: true,
browser: fileBrowser,
file: cookiesFile,
minLevel: 0,
func: readFile,
});
}
} else {
ok(false, `${cookiesFile.path} is a valid file`);
}
if (isMac() || isLinux()) {
let varDir = GetDir("/var");
if (isMac()) {
// Mac sandbox rules use /private/var because /var is a symlink
// to /private/var on OS X. Make sure that hasn't changed.
varDir.normalize();
Assert.strictEqual(
varDir.path,
"/private/var",
"/var resolves to /private/var"
);
}
tests.push({
desc: "/var",
ok: false,
browser: webBrowser,
file: varDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: "/var",
ok: true,
browser: fileBrowser,
file: varDir,
minLevel: 0,
func: readDir,
});
}
}
await runTestsList(tests);
}
async function testFileAccessMacOnly() {
if (!isMac()) {
return;
}
let webBrowser = GetWebBrowser();
let fileContentProcessEnabled = isFileContentProcessEnabled();
let fileBrowser = GetFileBrowser();
let level = GetSandboxLevel();
let tests = [];
// If ~/Library/Caches/TemporaryItems exists, when level <= 2 we
// make sure it's readable. For level 3, we make sure it isn't.
let homeTempDir = GetHomeDir();
homeTempDir.appendRelativePath("Library/Caches/TemporaryItems");
if (homeTempDir.exists()) {
let shouldBeReadable, minLevel;
if (level >= minHomeReadSandboxLevel()) {
shouldBeReadable = false;
minLevel = minHomeReadSandboxLevel();
} else {
shouldBeReadable = true;
minLevel = 0;
}
tests.push({
desc: "home library cache temp dir",
ok: shouldBeReadable,
browser: webBrowser,
file: homeTempDir,
minLevel,
func: readDir,
});
}
// Test if we can read from $TMPDIR because we expect it
// to be within /private/var. Reading from it should be
// prevented in a 'web' process.
let macTempDir = GetDirFromEnvVariable("TMPDIR");
macTempDir.normalize();
Assert.ok(
macTempDir.path.startsWith("/private/var"),
"$TMPDIR is in /private/var"
);
tests.push({
desc: `$TMPDIR (${macTempDir.path})`,
ok: false,
browser: webBrowser,
file: macTempDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: `$TMPDIR (${macTempDir.path})`,
ok: true,
browser: fileBrowser,
file: macTempDir,
minLevel: 0,
func: readDir,
});
}
// The font registry directory is in the Darwin user cache dir which is
// accessible with the getconf(1) library call using DARWIN_USER_CACHE_DIR.
// For this test, assume the cache dir is located at $TMPDIR/../C and use
// the $TMPDIR to derive the path to the registry.
let fontRegistryDir = macTempDir.parent.clone();
fontRegistryDir.appendRelativePath("C/com.apple.FontRegistry");
if (fontRegistryDir.exists()) {
tests.push({
desc: `FontRegistry (${fontRegistryDir.path})`,
ok: true,
browser: webBrowser,
file: fontRegistryDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
// Check that we can read the file named `font` which typically
// exists in the the font registry directory.
let fontFile = fontRegistryDir.clone();
fontFile.appendRelativePath("font");
if (fontFile.exists()) {
tests.push({
desc: `FontRegistry file (${fontFile.path})`,
ok: true,
browser: webBrowser,
file: fontFile,
minLevel: minHomeReadSandboxLevel(),
func: readFile,
});
}
}
// Test that we cannot read from /Volumes at level 3
let volumes = GetDir("/Volumes");
tests.push({
desc: "/Volumes",
ok: false,
browser: webBrowser,
file: volumes,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
// Test that we cannot read from /Users at level 3
let users = GetDir("/Users");
tests.push({
desc: "/Users",
ok: false,
browser: webBrowser,
file: users,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
// Test that we can stat /Users at level 3
tests.push({
desc: "/Users",
ok: true,
browser: webBrowser,
file: users,
minLevel: minHomeReadSandboxLevel(),
func: statPath,
});
// Test that we can stat /Library at level 3, but can't get a
// directory listing of /Library. This test uses "/Library"
// because it's a path that is expected to always be present.
let libraryDir = GetDir("/Library");
tests.push({
desc: "/Library",
ok: true,
browser: webBrowser,
file: libraryDir,
minLevel: minHomeReadSandboxLevel(),
func: statPath,
});
tests.push({
desc: "/Library",
ok: false,
browser: webBrowser,
file: libraryDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
// Similarly, test that we can stat /private, but not /private/etc.
let privateDir = GetDir("/private");
tests.push({
desc: "/private",
ok: true,
browser: webBrowser,
file: privateDir,
minLevel: minHomeReadSandboxLevel(),
func: statPath,
});
await runTestsList(tests);
}
async function testFileAccessLinuxOnly() {
if (!isLinux()) {
return;
}
let webBrowser = GetWebBrowser();
let fileContentProcessEnabled = isFileContentProcessEnabled();
let fileBrowser = GetFileBrowser();
let tests = [];
// Test /proc/self/fd, because that can be used to unfreeze
// frozen shared memory.
let selfFdDir = GetDir("/proc/self/fd");
tests.push({
desc: "/proc/self/fd",
ok: false,
browser: webBrowser,
file: selfFdDir,
minLevel: isContentFileIOSandboxed(),
func: readDir,
});
let cacheFontConfigDir = GetHomeSubdir(".cache/fontconfig/");
tests.push({
desc: `$HOME/.cache/fontconfig/ (${cacheFontConfigDir.path})`,
ok: true,
browser: webBrowser,
file: cacheFontConfigDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
// allows to handle both $HOME/.config/ or $XDG_CONFIG_HOME
let configDir = GetHomeSubdir(".config");
const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
if (xdgConfigHome) {
configDir = GetDir(xdgConfigHome);
configDir.normalize();
}
tests.push({
desc: `$XDG_CONFIG_HOME (${configDir.path})`,
ok: true, // access should not be granted outside of XDG support
browser: webBrowser,
file: configDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
tests.push({
desc: `XDG_CONFIG_HOME=${configDir.path} dir should have rdonly`,
ok: true, // should be allowed only if XDG support is there
browser: webBrowser,
file: configDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: `${configDir.path} dir`,
ok: true, // should be allowed only if XDG support is there
browser: fileBrowser,
file: configDir,
minLevel: 0,
func: readDir,
});
}
if (isXdgEnabled() && xdgConfigHome) {
const homeConfigDir = GetHomeSubdir(".config");
tests.push({
desc: `XDG_CONFIG_HOME=${homeConfigDir.path} dir should deny $HOME/.config`,
ok: false,
browser: webBrowser,
file: homeConfigDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: `${homeConfigDir.path} dir`,
ok: true,
browser: fileBrowser,
file: homeConfigDir,
minLevel: 0,
func: readDir,
});
}
} else {
// WWhen XDG_CONFIG_HOME is not set, verify we do not allow $HOME/.configlol
// (i.e., check allow the dir and not the prefix)
//
// Checking $HOME/.config is already done above.
const homeConfigPrefix = GetHomeSubdir(".configlol");
tests.push({
desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
ok: false,
browser: webBrowser,
file: homeConfigPrefix,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
if (fileContentProcessEnabled) {
tests.push({
desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
ok: false,
browser: fileBrowser,
file: homeConfigPrefix,
minLevel: 0,
func: readDir,
});
}
}
// Create a file under $HOME/.config/ or $XDG_CONFIG_HOME and ensure we can
// read it
let fileUnderConfig = GetSubdirFile(configDir);
await IOUtils.writeUTF8(fileUnderConfig.path, "TEST FILE DUMMY DATA");
ok(
await IOUtils.exists(fileUnderConfig.path),
`File ${fileUnderConfig.path} was properly created`
);
tests.push({
desc: `${configDir.path}/xxx is readable (${fileUnderConfig.path})`,
ok: true,
browser: webBrowser,
file: fileUnderConfig,
minLevel: minHomeReadSandboxLevel(),
func: readFile,
cleanup: aPath => IOUtils.remove(aPath),
});
let configFile = GetSubdirFile(configDir);
tests.push({
desc: `${configDir.path} file write`,
ok: false,
browser: webBrowser,
file: configFile,
minLevel: minHomeReadSandboxLevel(),
func: createFile,
});
if (fileContentProcessEnabled) {
tests.push({
desc: `${configDir.path} file write`,
ok: false,
browser: fileBrowser,
file: configFile,
minLevel: 0,
func: createFile,
});
}
// Create a $HOME/.config/mozilla/ or $XDG_CONFIG_HOME/mozilla/ if none
// exists and assert content process cannot access it
let configMozilla = GetSubdir(configDir, "mozilla");
const emptyFileName = ".test_run_browser_sandbox.tmp";
let emptyFile = configMozilla.clone();
emptyFile.appendRelativePath(emptyFileName);
let populateFakeConfigMozilla = async aPath => {
// called with configMozilla
await IOUtils.makeDirectory(aPath, { permissions: 0o700 });
await IOUtils.writeUTF8(emptyFile.path, "");
ok(
await IOUtils.exists(emptyFile.path),
`Temp file ${emptyFile.path} was created`
);
};
let unpopulateFakeConfigMozilla = async aPath => {
// called with emptyFile
await IOUtils.remove(aPath);
ok(!(await IOUtils.exists(aPath)), `Temp file ${aPath} was removed`);
const parentDir = PathUtils.parent(aPath);
try {
await IOUtils.remove(parentDir, { recursive: false });
} catch (ex) {
if (
!DOMException.isInstance(ex) ||
ex.name !== "OperationError" ||
/Could not remove the non-empty directory/.test(ex.message)
) {
// If we get here it means the directory was not empty and since we assert
// earlier we removed the temp file we created it means we should not
// worrying about removing this directory ...
throw ex;
}
}
};
await populateFakeConfigMozilla(configMozilla.path);
tests.push({
desc: `stat ${configDir.path}/mozilla (${configMozilla.path})`,
ok: false,
browser: webBrowser,
file: configMozilla,
minLevel: minHomeReadSandboxLevel(),
func: statPath,
});
tests.push({
desc: `read ${configDir.path}/mozilla (${configMozilla.path})`,
ok: false,
browser: webBrowser,
file: configMozilla,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
tests.push({
desc: `stat ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
ok: false,
browser: webBrowser,
file: emptyFile,
minLevel: minHomeReadSandboxLevel(),
func: statPath,
});
tests.push({
desc: `read ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
ok: false,
browser: webBrowser,
file: emptyFile,
minLevel: minHomeReadSandboxLevel(),
func: readFile,
cleanup: unpopulateFakeConfigMozilla,
});
// Only needed to perform cleanup
if (isXdgEnabled()) {
tests.push({
desc: `$XDG_CONFIG_HOME (${configDir.path}) cleanup`,
ok: true,
browser: webBrowser,
file: configDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
}
await runTestsList(tests);
}
async function testFileAccessLinuxSnap() {
let webBrowser = GetWebBrowser();
let tests = [];
// Assert that if we run with SNAP= env, then we allow access to it in the
// content process
let snap = Services.env.get("SNAP");
let snapExpectedResult = false;
if (snap.length > 1) {
snapExpectedResult = true;
} else {
snap = "/tmp/.snap_firefox_current/";
}
let snapDir = GetDir(snap);
snapDir.normalize();
let snapFile = GetSubdirFile(snapDir);
await createFile(snapFile.path);
ok(await IOUtils.exists(snapFile.path), `SNAP ${snapFile.path} was created`);
info(`SNAP (file) ${snapFile.path} was created`);
tests.push({
desc: `$SNAP (${snapDir.path} => ${snapFile.path})`,
ok: snapExpectedResult,
browser: webBrowser,
file: snapFile,
minLevel: minHomeReadSandboxLevel(),
func: readFile,
});
await runTestsList(tests);
}
async function testFileAccessWindowsOnly() {
if (!isWin()) {
return;
}
let webBrowser = GetWebBrowser();
let tests = [];
let extDir = GetPerUserExtensionDir();
tests.push({
desc: "per-user extensions dir",
ok: true,
browser: webBrowser,
file: extDir,
minLevel: minHomeReadSandboxLevel(),
func: readDir,
});
await runTestsList(tests);
}
function cleanupBrowserTabs() {
let fileBrowser = GetFileBrowser();
if (fileBrowser.selectedTab) {
gBrowser.removeTab(fileBrowser.selectedTab);
}
let webBrowser = GetWebBrowser();
if (webBrowser.selectedTab) {
gBrowser.removeTab(webBrowser.selectedTab);
}
let tab1 = gBrowser.tabs[1];
if (tab1) {
gBrowser.removeTab(tab1);
}
}

View File

@@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
this
);
SimpleTest.requestCompleteLog();
add_setup(async function setup() {
const xdgConfigHome = Services.env.exists("XDG_CONFIG_HOME");
Assert.equal(xdgConfigHome, false, `XDG_CONFIG_HOME is not set`);
const mozLegacyHome = Services.env.exists("MOZ_LEGACY_HOME");
Assert.equal(mozLegacyHome, false, "MOZ_LEGACY_HOME is not set");
// If it is there, do actual testing
sanityChecks();
});
add_task(async function () {
// Make sure we dont break others.
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
// The linux only tests are the ones that can behave differently based on
// existence of XDG_CONFIG_HOME
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
});

View File

@@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
this
);
SimpleTest.requestCompleteLog();
add_setup(async function setup() {
const xdgConfigHome = Services.env.exists("XDG_CONFIG_HOME");
Assert.equal(xdgConfigHome, true, "XDG_CONFIG_HOME is defined");
if (isXdgEnabled()) {
const mozLegacyHome = Services.env.get("MOZ_LEGACY_HOME");
Assert.equal(mozLegacyHome, 1, "MOZ_LEGACY_HOME is set to 1");
}
// If it is there, do actual testing
sanityChecks();
});
add_task(async function () {
// Make sure we dont break others.
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
// The linux only tests are the ones that can behave differently based on
// existence of XDG_CONFIG_HOME
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
});

View File

@@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
this
);
SimpleTest.requestCompleteLog();
add_setup(async function setup() {
// Ensure that XDG_CONFIG_HOME is there
const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
Assert.greater(xdgConfigHome.length, 1, "XDG_CONFIG_HOME is defined");
// If it is there, do actual testing
sanityChecks();
});
add_task(async function () {
// Make sure we dont break others.
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
// The linux only tests are the ones that can behave differently based on
// existence of XDG_CONFIG_HOME
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
});

View File

@@ -0,0 +1,405 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from browser_content_sandbox_utils.js */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/" +
"security/sandbox/test/browser_content_sandbox_utils.js",
this
);
const lazy = {};
/* getLibcConstants is only present on *nix */
ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
ChromeUtils.getLibcConstants()
);
/*
* This test is for executing system calls in content processes to validate
* that calls that are meant to be blocked by content sandboxing are blocked.
* We use the term system calls loosely so that any OS API call such as
* fopen could be included.
*/
// Calls the native execv library function. Include imports so this can be
// safely serialized and run remotely by ContentTask.spawn.
function callExec(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib, cmd } = args;
let libc = ctypes.open(lib);
let exec = libc.declare(
"execv",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr
);
let rv = exec(cmd);
libc.close();
return rv;
}
// Calls the native fork syscall.
function callFork(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib } = args;
let libc = ctypes.open(lib);
let fork = libc.declare("fork", ctypes.default_abi, ctypes.int);
let rv = fork();
libc.close();
return rv;
}
// Calls the native sysctl syscall.
function callSysctl(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib, name } = args;
let libc = ctypes.open(lib);
let sysctlbyname = libc.declare(
"sysctlbyname",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr,
ctypes.voidptr_t,
ctypes.size_t.ptr,
ctypes.voidptr_t,
ctypes.size_t.ptr
);
let rv = sysctlbyname(name, null, null, null, null);
libc.close();
return rv;
}
function callPrctl(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib, option } = args;
let libc = ctypes.open(lib);
let prctl = libc.declare(
"prctl",
ctypes.default_abi,
ctypes.int,
ctypes.int, // option
ctypes.unsigned_long, // arg2
ctypes.unsigned_long, // arg3
ctypes.unsigned_long, // arg4
ctypes.unsigned_long // arg5
);
let rv = prctl(option, 0, 0, 0, 0);
if (rv == -1) {
rv = ctypes.errno;
}
libc.close();
return rv;
}
// Calls the native open/close syscalls.
function callOpen(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib, path, flags } = args;
let libc = ctypes.open(lib);
let open = libc.declare(
"open",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr,
ctypes.int
);
let close = libc.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
let fd = open(path, flags);
close(fd);
libc.close();
return fd;
}
// Verify faccessat2
function callFaccessat2(args) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let { lib, dirfd, path, mode, flag } = args;
let libc = ctypes.open(lib);
let faccessat = libc.declare(
"faccessat",
ctypes.default_abi,
ctypes.int,
ctypes.int, // dirfd
ctypes.char.ptr, // path
ctypes.int, // mode
ctypes.int // flag
);
let rv = faccessat(dirfd, path, mode, flag);
if (rv == -1) {
rv = ctypes.errno;
}
libc.close();
return rv;
}
// Returns the name of the native library needed for native syscalls
function getOSLib() {
switch (Services.appinfo.OS) {
case "WINNT":
return "kernel32.dll";
case "Darwin":
return "libc.dylib";
case "Linux":
return "libc.so.6";
default:
Assert.ok(false, "Unknown OS");
return 0;
}
}
// Reading a header might be weird, but the alternatives to read a stable
// version number we can easily check against are not much more fun
async function getKernelVersion() {
let header = await IOUtils.readUTF8("/usr/include/linux/version.h");
let hr = header.split("\n");
for (let line in hr) {
let hrs = hr[line].split(" ");
if (hrs[0] === "#define" && hrs[1] === "LINUX_VERSION_CODE") {
return Number(hrs[2]);
}
}
throw Error("No LINUX_VERSION_CODE");
}
// This is how it is done in /usr/include/linux/version.h
function computeKernelVersion(major, minor, dot) {
return (major << 16) + (minor << 8) + dot;
}
function getGlibcVersion() {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
let libc = ctypes.open(getOSLib());
let gnu_get_libc_version = libc.declare(
"gnu_get_libc_version",
ctypes.default_abi,
ctypes.char.ptr
);
let rv = gnu_get_libc_version().readString();
libc.close();
let ar = rv.split(".");
// return a number made of MAJORMINOR
return Number(ar[0] + ar[1]);
}
// Returns a harmless command to execute with execv
function getOSExecCmd() {
Assert.ok(!isWin());
return "/bin/cat";
}
// Returns true if the current content sandbox level, passed in
// the |level| argument, supports syscall sandboxing.
function areContentSyscallsSandboxed(level) {
let syscallsSandboxMinLevel = 0;
// Set syscallsSandboxMinLevel to the lowest level that has
// syscall sandboxing enabled. For now, this varies across
// Windows, Mac, Linux, other.
switch (Services.appinfo.OS) {
case "WINNT":
syscallsSandboxMinLevel = 1;
break;
case "Darwin":
syscallsSandboxMinLevel = 1;
break;
case "Linux":
syscallsSandboxMinLevel = 1;
break;
default:
Assert.ok(false, "Unknown OS");
}
return level >= syscallsSandboxMinLevel;
}
//
// Drive tests for a single content process.
//
// Tests executing OS API calls in the content process. Limited to Mac
// and Linux calls for now.
//
add_task(async function () {
// This test is only relevant in e10s
if (!gMultiProcessBrowser) {
ok(false, "e10s is enabled");
info("e10s is not enabled, exiting");
return;
}
let level = 0;
let prefExists = true;
// Read the security.sandbox.content.level pref.
// If the pref isn't set and we're running on Linux on !isNightly(),
// exit without failing. The Linux content sandbox is only enabled
// on Nightly at this time.
// eslint-disable-next-line mozilla/use-default-preference-values
try {
level = Services.prefs.getIntPref("security.sandbox.content.level");
} catch (e) {
prefExists = false;
}
ok(prefExists, "pref security.sandbox.content.level exists");
if (!prefExists) {
return;
}
info(`security.sandbox.content.level=${level}`);
Assert.greater(level, 0, "content sandbox is enabled.");
let areSyscallsSandboxed = areContentSyscallsSandboxed(level);
// Content sandbox enabled, but level doesn't include syscall sandboxing.
ok(areSyscallsSandboxed, "content syscall sandboxing is enabled.");
if (!areSyscallsSandboxed) {
info("content sandbox level too low for syscall tests, exiting\n");
return;
}
let browser = gBrowser.selectedBrowser;
let lib = getOSLib();
// use execv syscall
// (causes content process to be killed on Linux)
if (isMac()) {
// exec something harmless, this should fail
let cmd = getOSExecCmd();
let rv = await SpecialPowers.spawn(browser, [{ lib, cmd }], callExec);
Assert.equal(rv, -1, `exec(${cmd}) is not permitted`);
}
// use open syscall
if (isLinux() || isMac()) {
// open a file for writing in $HOME, this should fail
let path = fileInHomeDir().path;
let flags = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
let fd = await SpecialPowers.spawn(
browser,
[{ lib, path, flags }],
callOpen
);
Assert.less(fd, 0, "opening a file for writing in home is not permitted");
}
// use open syscall
if (isLinux() || isMac()) {
// open a file for writing in the content temp dir, this should fail on
// macOS and Linux. The open handler in the content process closes the file
// for us
let path = fileInTempDir().path;
let flags = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
let fd = await SpecialPowers.spawn(
browser,
[{ lib, path, flags }],
callOpen
);
Assert.strictEqual(
fd,
-1,
"opening a file for writing in content temp is not permitted"
);
}
// use fork syscall
if (isLinux() || isMac()) {
let rv = await SpecialPowers.spawn(browser, [{ lib }], callFork);
Assert.equal(rv, -1, "calling fork is not permitted");
}
// On macOS before 10.10 the |sysctl-name| predicate didn't exist for
// filtering |sysctl| access. Check the Darwin version before running the
// tests (Darwin 14.0.0 is macOS 10.10). This branch can be removed when we
// remove support for macOS 10.9.
if (isMac() && Services.sysinfo.getProperty("version") >= "14.0.0") {
let rv = await SpecialPowers.spawn(
browser,
[{ lib, name: "kern.boottime" }],
callSysctl
);
Assert.equal(rv, -1, "calling sysctl('kern.boottime') is not permitted");
rv = await SpecialPowers.spawn(
browser,
[{ lib, name: "net.inet.ip.ttl" }],
callSysctl
);
Assert.equal(rv, -1, "calling sysctl('net.inet.ip.ttl') is not permitted");
rv = await SpecialPowers.spawn(
browser,
[{ lib, name: "hw.ncpu" }],
callSysctl
);
Assert.equal(rv, 0, "calling sysctl('hw.ncpu') is permitted");
}
if (isLinux()) {
// These constants are not portable.
// verify we block PR_CAPBSET_READ with EINVAL
let option = lazy.LIBC.PR_CAPBSET_READ;
let rv = await SpecialPowers.spawn(browser, [{ lib, option }], callPrctl);
Assert.strictEqual(
rv,
lazy.LIBC.EINVAL,
"prctl(PR_CAPBSET_READ) is blocked"
);
const kernelVersion = await getKernelVersion();
const glibcVersion = getGlibcVersion();
// faccessat2 is only used with kernel 5.8+ by glibc 2.33+
if (glibcVersion >= 233 && kernelVersion >= computeKernelVersion(5, 8, 0)) {
info("Linux v5.8+, glibc 2.33+, checking faccessat2");
const dirfd = 0;
const path = "/";
const mode = 0;
// the value 0x01 is just one we know should get rejected
let rv = await SpecialPowers.spawn(
browser,
[{ lib, dirfd, path, mode, flag: 0x01 }],
callFaccessat2
);
Assert.strictEqual(
rv,
lazy.LIBC.ENOSYS,
"faccessat2 (flag=0x01) was blocked with ENOSYS"
);
rv = await SpecialPowers.spawn(
browser,
[{ lib, dirfd, path, mode, flag: lazy.LIBC.AT_EACCESS }],
callFaccessat2
);
Assert.strictEqual(
rv,
lazy.LIBC.EACCES,
"faccessat2 (flag=0x200) was allowed, errno=EACCES"
);
} else {
info(
"Unsupported kernel (" +
kernelVersion +
" )/glibc (" +
glibcVersion +
"), skipping faccessat2"
);
}
}
});

View File

@@ -0,0 +1,502 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const uuidGenerator = Services.uuid;
/*
* Utility functions for the browser content sandbox tests.
*/
function sanityChecks() {
// This test is only relevant in e10s
if (!gMultiProcessBrowser) {
ok(false, "e10s is enabled");
info("e10s is not enabled, exiting");
return;
}
let level = 0;
let prefExists = true;
// Read the security.sandbox.content.level pref.
// eslint-disable-next-line mozilla/use-default-preference-values
try {
level = Services.prefs.getIntPref("security.sandbox.content.level");
} catch (e) {
prefExists = false;
}
ok(prefExists, "pref security.sandbox.content.level exists");
if (!prefExists) {
return;
}
info(`security.sandbox.content.level=${level}`);
Assert.greater(level, 0, "content sandbox is enabled.");
let isFileIOSandboxed = isContentFileIOSandboxed(level);
// Content sandbox enabled, but level doesn't include file I/O sandboxing.
ok(isFileIOSandboxed, "content file I/O sandboxing is enabled.");
if (!isFileIOSandboxed) {
info("content sandbox level too low for file I/O tests, exiting\n");
}
}
function isXdgEnabled() {
try {
return Services.prefs.getBoolPref("widget.support-xdg-config");
} catch (ex) {
// if the pref is not there it means we dont have XDG support
if (ex.name === "NS_ERROR_UNEXPECTED") {
return false;
}
throw ex;
}
}
// Creates file at |path| and returns a promise that resolves with an object
// with .ok boolean to indicate true if the file was successfully created,
// otherwise false. Include imports so this can be safely serialized and run
// remotely by ContentTask.spawn.
//
// Report the exception's error code in .code as well.
function createFile(path) {
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
try {
const fstream = Cc[
"@mozilla.org/network/file-output-stream;1"
].createInstance(Ci.nsIFileOutputStream);
fstream.init(
new FileUtils.File(path),
-1, // readonly mode
-1, // default permissions
0
); // behaviour flags
const ostream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
Ci.nsIBinaryOutputStream
);
ostream.setOutputStream(fstream);
const data = "TEST FILE DUMMY DATA";
ostream.writeBytes(data, data.length);
ostream.close();
fstream.close();
} catch (e) {
return { ok: false, code: e.result };
}
return { ok: true };
}
// Creates a symlink at |path| and returns a promise that resolves with an
// object with .ok boolean to indicate true if the symlink was successfully
// created, otherwise false. Include imports so this can be safely serialized
// and run remotely by ContentTask.spawn.
//
// Report the exception's error code in .code as well.
// Report errno in .code if syscall returns -1.
function createSymlink(path) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
try {
// Trying to open "libc.so" on linux will fail with invalid elf header error
// because it would be a linker script. Using libc.so.6 avoids that.
const libc = ctypes.open(
Services.appinfo.OS === "Darwin" ? "libSystem.B.dylib" : "libc.so.6"
);
const symlink = libc.declare(
"symlink",
ctypes.default_abi,
ctypes.int, // return value
ctypes.char.ptr, // target
ctypes.char.ptr //linkpath
);
ctypes.errno = 0;
const rv = symlink("/etc", path);
const _errno = ctypes.errno;
if (rv < 0) {
return { ok: false, code: _errno };
}
} catch (e) {
return { ok: false, code: e.result };
}
return { ok: true };
}
// Deletes file at |path| and returns a promise that resolves with an object
// with .ok boolean to indicate true if the file was successfully deleted,
// otherwise false. Include imports so this can be safely serialized and run
// remotely by ContentTask.spawn.
//
// Report the exception's error code in .code as well.
function deleteFile(path) {
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
try {
const file = new FileUtils.File(path);
file.remove(false);
} catch (e) {
return { ok: false, code: e.result };
}
return { ok: true };
}
// Reads the directory at |path| and returns a promise that resolves when
// iteration over the directory finishes or encounters an error. The promise
// resolves with an object where .ok indicates success or failure and
// .numEntries is the number of directory entries found.
//
// Report the exception's error code in .code as well.
function readDir(path) {
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
let numEntries = 0;
try {
const file = new FileUtils.File(path);
const enumerator = file.directoryEntries;
while (enumerator.hasMoreElements()) {
void enumerator.nextFile;
numEntries++;
}
} catch (e) {
return { ok: false, numEntries, code: e.result };
}
return { ok: true, numEntries };
}
// Reads the file at |path| and returns a promise that resolves when
// reading is completed. Returned object has boolean .ok to indicate
// success or failure.
//
// Report the exception's error code in .code as well.
function readFile(path) {
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
try {
const file = new FileUtils.File(path);
const fstream = Cc[
"@mozilla.org/network/file-input-stream;1"
].createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, -1, 0);
const istream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
Ci.nsIBinaryInputStream
);
istream.setInputStream(fstream);
const available = istream.available();
void istream.readBytes(available);
} catch (e) {
return { ok: false, code: e.result };
}
return { ok: true };
}
// Does a stat of |path| and returns a promise that resolves if the
// stat is successful. Returned object has boolean .ok to indicate
// success or failure.
//
// Report the exception's error code in .code as well.
function statPath(path) {
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
try {
const file = new FileUtils.File(path);
void file.lastModifiedTime;
} catch (e) {
return { ok: false, code: e.result };
}
return { ok: true };
}
// Returns true if the current content sandbox level, passed in
// the |level| argument, supports filesystem sandboxing.
function isContentFileIOSandboxed(level) {
let fileIOSandboxMinLevel = 0;
// Set fileIOSandboxMinLevel to the lowest level that has
// content filesystem sandboxing enabled. For now, this
// varies across Windows, Mac, Linux, other.
switch (Services.appinfo.OS) {
case "WINNT":
fileIOSandboxMinLevel = 1;
break;
case "Darwin":
fileIOSandboxMinLevel = 1;
break;
case "Linux":
fileIOSandboxMinLevel = 2;
break;
default:
Assert.ok(false, "Unknown OS");
}
return level >= fileIOSandboxMinLevel;
}
// Returns the lowest sandbox level where blanket reading of the profile
// directory from the content process should be blocked by the sandbox.
function minProfileReadSandboxLevel() {
switch (Services.appinfo.OS) {
case "WINNT":
return 3;
case "Darwin":
return 2;
case "Linux":
return 3;
default:
Assert.ok(false, "Unknown OS");
return 0;
}
}
// Returns the lowest sandbox level where blanket reading of the home
// directory from the content process should be blocked by the sandbox.
function minHomeReadSandboxLevel() {
switch (Services.appinfo.OS) {
case "WINNT":
return 3;
case "Darwin":
return 3;
case "Linux":
return 3;
default:
Assert.ok(false, "Unknown OS");
return 0;
}
}
function isMac() {
return Services.appinfo.OS == "Darwin";
}
function isWin() {
return Services.appinfo.OS == "WINNT";
}
function isLinux() {
return Services.appinfo.OS == "Linux";
}
function isNightly() {
let version = SpecialPowers.Services.appinfo.version;
return version.endsWith("a1");
}
function uuid() {
return uuidGenerator.generateUUID().toString();
}
// Returns a file object for a new file in the home dir ($HOME/<UUID>).
function fileInHomeDir() {
// get home directory, make sure it exists
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
Assert.ok(homeDir.exists(), "Home dir exists");
Assert.ok(homeDir.isDirectory(), "Home dir is a directory");
// build a file object for a new file named $HOME/<UUID>
let homeFile = homeDir.clone();
homeFile.appendRelativePath(uuid());
Assert.ok(!homeFile.exists(), homeFile.path + " does not exist");
return homeFile;
}
// Returns a file object for a new file in the content temp dir (.../<UUID>).
function fileInTempDir() {
let contentTempKey = "TmpD";
// get the content temp dir, make sure it exists
let ctmp = Services.dirsvc.get(contentTempKey, Ci.nsIFile);
Assert.ok(ctmp.exists(), "Temp dir exists");
Assert.ok(ctmp.isDirectory(), "Temp dir is a directory");
// build a file object for a new file in content temp
let tempFile = ctmp.clone();
tempFile.appendRelativePath(uuid());
Assert.ok(!tempFile.exists(), tempFile.path + " does not exist");
return tempFile;
}
function GetProfileDir() {
// get profile directory
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
return profileDir;
}
function GetHomeDir() {
// get home directory
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
return homeDir;
}
function GetHomeSubdir(subdir) {
return GetSubdir(GetHomeDir(), subdir);
}
function GetHomeSubdirFile(subdir) {
return GetSubdirFile(GetHomeSubdir(subdir));
}
function GetSubdir(dir, subdir) {
let newSubdir = dir.clone();
newSubdir.appendRelativePath(subdir);
return newSubdir;
}
function GetSubdirFile(dir) {
let newFile = dir.clone();
newFile.appendRelativePath(uuid());
return newFile;
}
function GetPerUserExtensionDir() {
return Services.dirsvc.get("XREUSysExt", Ci.nsIFile);
}
// Returns a file object for the file or directory named |name| in the
// profile directory.
function GetProfileEntry(name) {
let entry = GetProfileDir();
entry.append(name);
return entry;
}
function GetDir(path) {
info(`GetDir(${path})`);
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
dir.initWithPath(path);
Assert.ok(dir.isDirectory(), `${path} is a directory`);
return dir;
}
function GetDirFromEnvVariable(varName) {
return GetDir(Services.env.get(varName));
}
function GetFile(path) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(path);
return file;
}
function GetBrowserType(type) {
let browserType = undefined;
if (!GetBrowserType[type]) {
if (type === "web") {
GetBrowserType[type] = gBrowser.selectedBrowser;
} else {
// open a tab in a `type` content process
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
preferredRemoteType: type,
allowInheritPrincipal: true,
});
// get the browser for the `type` process tab
GetBrowserType[type] = gBrowser.getBrowserForTab(gBrowser.selectedTab);
}
}
browserType = GetBrowserType[type];
Assert.strictEqual(
browserType.remoteType,
type,
`GetBrowserType(${type}) returns a ${type} process`
);
return browserType;
}
function GetWebBrowser() {
return GetBrowserType("web");
}
function isFileContentProcessEnabled() {
// Ensure that the file content process is enabled.
let fileContentProcessEnabled = Services.prefs.getBoolPref(
"browser.tabs.remote.separateFileUriProcess"
);
ok(fileContentProcessEnabled, "separate file content process is enabled");
return fileContentProcessEnabled;
}
function GetFileBrowser() {
if (!isFileContentProcessEnabled()) {
return undefined;
}
return GetBrowserType("file");
}
function GetSandboxLevel() {
// Current level
return Services.prefs.getIntPref("security.sandbox.content.level");
}
async function runTestsList(tests) {
let level = GetSandboxLevel();
// remove tests not enabled by the current sandbox level
tests = tests.filter(test => test.minLevel <= level);
for (let test of tests) {
let okString = test.ok ? "allowed" : "blocked";
let processType = test.browser.remoteType;
// ensure the file/dir exists before we ask a content process to stat
// it so we know a failure is not due to a nonexistent file/dir
if (test.func === statPath) {
ok(test.file.exists(), `${test.file.path} exists`);
}
let result = await ContentTask.spawn(
test.browser,
test.file.path,
test.func
);
Assert.equal(
result.ok,
test.ok,
`reading ${test.desc} from a ${processType} process ` +
`is ${okString} (${test.file.path})`
);
// if the directory is not expected to be readable,
// ensure the listing has zero entries
if (test.func === readDir && !test.ok) {
Assert.equal(
result.numEntries,
0,
`directory list is empty (${test.file.path})`
);
}
if (test.cleanup != undefined) {
await test.cleanup(test.file.path);
}
}
}

View File

@@ -0,0 +1,22 @@
[DEFAULT]
skip-if = [
"!gecko_profiler",
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517 for sandbox, bug 1885381 for profiler
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517 for sandbox, bug 1885381 for profiler
]
tags = "contentsandbox"
# This is here to make sure we will not have prelaunched processes, which will
# mess with sandbox profiling interaction: we will miss launch-related markers
# and this makes the test intermittently fail on TV jobs
prefs = [
"dom.ipc.processPrelaunch.fission.number=0"
]
environment = "MOZ_SANDBOX_LOGGING_FOR_TESTS=1"
["browser_sandbox_profiler.js"]
run-if = [
"os == 'linux'",
]

View File

@@ -0,0 +1,153 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { ProfilerTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/ProfilerTestUtils.sys.mjs"
);
async function addTab() {
const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/browser", {
forceNewProcess: true,
});
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
return tab;
}
const sandboxSettingsEnabled = {
entries: 8 * 1024 * 1024, // 8M entries = 64MB
interval: 1, // ms
features: ["stackwalk", "sandbox"],
threads: ["SandboxProfilerEmitter"],
};
const sandboxSettingsDisabled = {
entries: 8 * 1024 * 1024, // 8M entries = 64MB
interval: 1, // ms
features: ["stackwalk"],
threads: ["SandboxProfilerEmitter"],
};
const kNewProcesses = 2;
async function waitForMaybeSandboxProfilerData(
threadName,
name1,
withStacks,
enabled
) {
let tabs = [];
for (let i = 0; i < kNewProcesses; ++i) {
tabs.push(await addTab());
}
let profile;
let intercepted = undefined;
try {
await TestUtils.waitForCondition(
async () => {
profile = await Services.profiler.getProfileDataAsync();
intercepted = profile.processes
.flatMap(ps => {
let sandboxThreads = ps.threads.filter(
th => th.name === threadName
);
return sandboxThreads.flatMap(th => {
let markersData = th.markers.data;
return markersData.flatMap(d => {
let [, , , , , o] = d;
return o;
});
});
})
.filter(x => "name1" in x && name1.includes(x.name1) >= 0);
return !!intercepted.length;
},
`Wait for some samples from ${threadName}`,
/* interval*/ 100,
/* maxTries */ 25
);
Assert.greater(
intercepted.length,
0,
`Should have collected some data from ${threadName}`
);
} catch (ex) {
if (!enabled && ex.includes(`Wait for some samples from ${threadName}`)) {
Assert.equal(
intercepted.length,
0,
`Should have NOT collected data from ${threadName}`
);
} else {
throw ex;
}
}
if (withStacks) {
let stacks = profile.processes.flatMap(ps => {
let sandboxThreads = ps.threads.filter(th => th.name === threadName);
return sandboxThreads.flatMap(th => {
let stackTableData = th.stackTable.data;
return stackTableData.flatMap(d => {
return [d];
});
});
});
if (enabled) {
Assert.greater(stacks.length, 0, "Should have some stack as well");
} else {
Assert.equal(stacks.length, 0, "Should have NO stack as well");
}
}
for (let tab of tabs) {
await BrowserTestUtils.removeTab(tab);
}
}
add_task(async () => {
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
await waitForMaybeSandboxProfilerData(
"SandboxProfilerEmitterSyscalls",
["id", "init"],
/* withStacks */ true,
/* enabled */ true
);
await Services.profiler.StopProfiler();
});
add_task(async () => {
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
await waitForMaybeSandboxProfilerData(
"SandboxProfilerEmitterLogs",
["log"],
/* withStacks */ false,
/* enabled */ true
);
await Services.profiler.StopProfiler();
});
add_task(async () => {
await ProfilerTestUtils.startProfiler(sandboxSettingsDisabled);
await waitForMaybeSandboxProfilerData(
"SandboxProfilerEmitterSyscalls",
["id", "init"],
/* withStacks */ true,
/* enabled */ false
);
await Services.profiler.StopProfiler();
});
add_task(async () => {
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
await waitForMaybeSandboxProfilerData(
"SandboxProfilerEmitterLogs",
["log"],
/* withStacks */ false,
/* enabled */ false
);
await Services.profiler.StopProfiler();
});

View File

@@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
"use strict";
function test() {
waitForExplicitFinish();
// Types of processes to test, taken from GeckoProcessTypes.h
// GPU process might not run depending on the platform, so we need it to be
// the last one of the list to allow the remainingTests logic below to work
// as expected.
//
// For UtilityProcess, allow constructing a string made of the process type
// and the sandbox variant we want to test, e.g.,
// utility:0 for GENERIC_UTILITY
// utility:1 for AppleMedia/WMF on macOS/Windows
var processTypes = ["tab", "socket", "rdd", "gmplugin", "utility:0", "gpu"];
const platform = SpecialPowers.Services.appinfo.OS;
if (platform === "WINNT" || platform === "Darwin") {
processTypes.push("utility:1");
}
// A callback called after each test-result.
let sandboxTestResult = (subject, topic, data) => {
let { testid, passed, message } = JSON.parse(data);
ok(
passed,
"Test " + testid + (passed ? " passed: " : " failed: ") + message
);
};
Services.obs.addObserver(sandboxTestResult, "sandbox-test-result");
var remainingTests = processTypes.length;
// A callback that is notified when a child process is done running tests.
let sandboxTestDone = () => {
remainingTests = remainingTests - 1;
if (remainingTests == 0) {
// Clean up test file
if (homeTestFile.exists()) {
ok(homeTestFile.isFile(), "homeTestFile should be a file");
if (homeTestFile.isFile()) {
homeTestFile.remove(false);
}
}
Services.obs.removeObserver(sandboxTestResult, "sandbox-test-result");
Services.obs.removeObserver(sandboxTestDone, "sandbox-test-done");
// Notify SandboxTest component that it should terminate the connection
// with the child processes.
comp.finishTests();
// Notify mochitest that all process tests are complete.
finish();
}
};
Services.obs.addObserver(sandboxTestDone, "sandbox-test-done");
var comp = Cc["@mozilla.org/sandbox/sandbox-test;1"].getService(
Ci.mozISandboxTest
);
let homeTestFile;
try {
homeTestFile = Services.dirsvc.get("Home", Ci.nsIFile);
homeTestFile.append(".mozilla_gpu_sandbox_read_test");
if (!homeTestFile.exists()) {
homeTestFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
}
} catch (e) {
ok(false, "Failed to create home test file: " + e);
}
comp.startTests(processTypes);
}

View File

@@ -0,0 +1,20 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = "/tmp/.snap_firefox_current_real/"
environment = "SNAP=/tmp/.snap_firefox_current_real/"
["browser_content_sandbox_fs_snap.js"]
run-if = [
"os == 'linux'",
]

View File

@@ -0,0 +1,22 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
# .config needs to exists for the sandbox to properly add it
test-directories = ["/tmp/.xdg_default_test", "/tmp/.xdg_default_test/.config"]
environment = [
"HOME=/tmp/.xdg_default_test",
]
["browser_content_sandbox_fs_xdg_default.js"]
run-if = ["os == 'linux'"]

View File

@@ -0,0 +1,23 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = ["/tmp/.xdg_mozLegacyHome_test/.config", "/tmp/.xdg_config_home_test"]
environment = [
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
"HOME=/tmp/.xdg_mozLegacyHome_test",
"MOZ_LEGACY_HOME=1",
]
["browser_content_sandbox_fs_xdg_mozLegacyHome.js"]
run-if = ["os == 'linux'"]

View File

@@ -0,0 +1,23 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
[DEFAULT]
skip-if = [
"ccov",
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
]
tags = "contentsandbox"
support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = "/tmp/.xdg_config_home_test"
environment = [
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
"MOZ_LEGACY_HOME=0",
]
["browser_content_sandbox_fs_xdg_xdgConfigHome.js"]
run-if = [
"os == 'linux'",
]

View File

@@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
</head>
<style>
#content { display: inline-block; }
.monospace_fallback { font: 3em monospace; }
</style>
<body>
<div id="content" class="monospace_fallback">
abcdefghijklmnopqrstuvwxyz<br>
<b>abcdefghijklmnopqrstuvwxyz</b><br>
<i>abcdefghijklmnopqrstuvwxyz</i>
</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
#!/usr/bin/python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"""
mac_register_font.py
Mac-specific utility command to register a font file with the OS.
"""
import argparse
import sys
import Cocoa
import CoreText
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="print verbose registration failures",
default=False,
)
parser.add_argument(
"file", nargs="*", help="font file to register or unregister", default=[]
)
parser.add_argument(
"-u",
"--unregister",
action="store_true",
help="unregister the provided fonts",
default=False,
)
parser.add_argument(
"-p",
"--persist-user",
action="store_true",
help="permanently register the font",
default=False,
)
args = parser.parse_args()
if args.persist_user:
scope = CoreText.kCTFontManagerScopeUser
scopeDesc = "user"
else:
scope = CoreText.kCTFontManagerScopeSession
scopeDesc = "session"
failureCount = 0
for fontPath in args.file:
fontURL = Cocoa.NSURL.fileURLWithPath_(fontPath)
(result, error) = register_or_unregister_font(fontURL, args.unregister, scope)
if result:
print(
"%sregistered font %s with %s scope"
% (("un" if args.unregister else ""), fontPath, scopeDesc)
)
else:
print(
"Failed to %sregister font %s with %s scope"
% (("un" if args.unregister else ""), fontPath, scopeDesc)
)
if args.verbose:
print(error)
failureCount += 1
sys.exit(failureCount)
def register_or_unregister_font(fontURL, unregister, scope):
return (
CoreText.CTFontManagerUnregisterFontsForURL(fontURL, scope, None)
if unregister
else CoreText.CTFontManagerRegisterFontsForURL(fontURL, scope, None)
)
if __name__ == "__main__":
main()