mirror of
https://github.com/zen-browser/desktop.git
synced 2026-01-19 11:27:16 +00:00
test: Fix default app dir for XDG and import sandbox tests, b=bug #11917, c=tests
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
"safebrowsing/browser.toml",
|
||||
"sandbox/browser.toml",
|
||||
"shell/browser.toml",
|
||||
"tooltiptext/browser.toml",
|
||||
]
|
||||
37
src/zen/tests/mochitests/sandbox/browser.toml
Normal file
37
src/zen/tests/mochitests/sandbox/browser.toml
Normal 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",
|
||||
]
|
||||
203
src/zen/tests/mochitests/sandbox/browser_bug1393259.js
Normal file
203
src/zen/tests/mochitests/sandbox/browser_bug1393259.js
Normal 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);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -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'",
|
||||
]
|
||||
@@ -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'",
|
||||
]
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/zen/tests/mochitests/sandbox/browser_profiler.toml
Normal file
22
src/zen/tests/mochitests/sandbox/browser_profiler.toml
Normal 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'",
|
||||
]
|
||||
153
src/zen/tests/mochitests/sandbox/browser_sandbox_profiler.js
Normal file
153
src/zen/tests/mochitests/sandbox/browser_sandbox_profiler.js
Normal 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();
|
||||
});
|
||||
78
src/zen/tests/mochitests/sandbox/browser_sandbox_test.js
Normal file
78
src/zen/tests/mochitests/sandbox/browser_sandbox_test.js
Normal 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);
|
||||
}
|
||||
20
src/zen/tests/mochitests/sandbox/browser_snap.toml
Normal file
20
src/zen/tests/mochitests/sandbox/browser_snap.toml
Normal 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'",
|
||||
]
|
||||
22
src/zen/tests/mochitests/sandbox/browser_xdg_default.toml
Normal file
22
src/zen/tests/mochitests/sandbox/browser_xdg_default.toml
Normal 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'"]
|
||||
@@ -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'"]
|
||||
@@ -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'",
|
||||
]
|
||||
19
src/zen/tests/mochitests/sandbox/bug1393259.html
Normal file
19
src/zen/tests/mochitests/sandbox/bug1393259.html
Normal 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>
|
||||
85
src/zen/tests/mochitests/sandbox/mac_register_font.py
Executable file
85
src/zen/tests/mochitests/sandbox/mac_register_font.py
Executable 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()
|
||||
Reference in New Issue
Block a user