bug 1287125 Lock down mozAddonManager.install() r?rhelmer
MozReview-Commit-ID: 7wLqVme2Yzi
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -41,16 +41,17 @@ const PREF_EM_HOTFIX_LASTVERSION =
const PREF_EM_HOTFIX_URL = "extensions.hotfix.url";
const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes";
const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs.";
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
const PREF_SELECTED_LOCALE = "general.useragent.locale";
const UNKNOWN_XPCOM_ABI = "unknownABI";
const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion";
+const PREF_WEBAPI_TESTING = "extensions.webapi.testing";
const UPDATE_REQUEST_VERSION = 2;
const CATEGORY_UPDATE_PARAMS = "extension-update-params";
const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
const KEY_PROFILEDIR = "ProfD";
const KEY_APPDIR = "XCurProcD";
@@ -61,16 +62,23 @@ const PREF_EM_CHECK_COMPATIBILITY_BASE =
var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ?
PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" :
undefined;
const TOOLKIT_ID = "toolkit@mozilla.org";
const VALID_TYPES_REGEXP = /^[\w\-]+$/;
+const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "addons.cdn.mozilla.net"];
+const WEBAPI_TEST_INSTALL_HOSTS = [
+ "addons.allizom.org", "addons-stage-cdn.allizom.org",
+ "addons-dev.allizom.org", "addons-dev-cdn-allizom.org",
+ "example.com",
+];
+
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
@@ -2891,17 +2899,39 @@ var AddonManagerInternal = {
if (!info) {
throw new Error(`forgetInstall cannot find ${id}`);
}
info.install.removeListener(info.listener);
this.installs.delete(id);
},
createInstall(target, options) {
- return new Promise((resolve) => {
+ // Throw an appropriate error if the given URL is not valid
+ // as an installation source. Return silently if it is okay.
+ function checkInstallUrl(url) {
+ let host = Services.io.newURI(options.url, null, null).host;
+ if (WEBAPI_INSTALL_HOSTS.includes(host)) {
+ return;
+ }
+ if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING)
+ && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) {
+ return;
+ }
+
+ throw new Error(`Install from ${host} not permitted`);
+ }
+
+ return new Promise((resolve, reject) => {
+ try {
+ checkInstallUrl(options.url);
+ } catch (err) {
+ reject({message: err.message});
+ return;
+ }
+
let newInstall = install => {
let id = this.nextInstall++;
let listener = this.makeListener(id, target);
install.addListener(listener);
this.installs.set(id, {install, target, listener});
let result = {id};
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -243,17 +243,19 @@ amManager.prototype = {
onInstalled: (addon) => handler("onInstalled", addon.id, false),
onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart),
onUninstalled: (addon) => handler("onUninstalled", addon.id, false),
onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false),
};
}
AddonManager.addAddonListener(this.addonListener);
} else {
- AddonManager.removeAddonListener(this.addonListener);
+ if (this.addonListener) {
+ AddonManager.removeAddonListener(this.addonListener);
+ }
}
}
}
return undefined;
},
sendEvent(target, data) {
target.sendAsyncMessage(MSG_INSTALL_EVENT, data);
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -1,38 +1,39 @@
const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
const XPI_URL = `${SECURE_TESTROOT}addons/browser_webapi_install.xpi`;
const ID = "webapi_install@tests.mozilla.org";
// eh, would be good to just stat the real file instead of this...
const XPI_LEN = 4782;
-Services.prefs.setBoolPref("extensions.webapi.testing", true);
-Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
-registerCleanupFunction(() => {
- Services.prefs.clearUserPref("extensions.webapi.testing");
- Services.prefs.clearUserPref("extensions.install.requireBuiltInCerts");
-});
-
function waitForClear() {
const MSG = "WebAPICleanup";
return new Promise(resolve => {
let listener = {
receiveMessage: function(msg) {
if (msg.name == MSG) {
Services.ppmm.removeMessageListener(MSG, listener);
resolve();
}
}
};
Services.ppmm.addMessageListener(MSG, listener);
});
}
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["extensions.webapi.testing", true],
+ ["extensions.install.requireBuiltInCerts", false]],
+ });
+ info("added preferences");
+});
+
// Wrapper around a common task to run in the content process to test
// the mozAddonManager API. Takes a URL for the XPI to install and an
// array of steps, each of which can either be an action to take
// (i.e., start or cancel the install) or an install event to wait for.
// Steps that look for a specific event may also include a "props" property
// with properties that the AddonInstall object is expected to have when
// that event is triggered.
function* testInstall(browser, url, steps, description) {
@@ -235,8 +236,35 @@ add_task(makeInstallTest(function* (brow
yield testInstall(browser, XPI_URL + "bogus", steps, "install of a bad url fails");
let addons = yield promiseAddonsByIDs([ID]);
is(addons[0], null, "The addon was not installed");
ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
}));
+add_task(function* test_permissions() {
+ function testBadUrl(url, pattern, successMessage) {
+ return BrowserTestUtils.withNewTab(TESTPAGE, function* (browser) {
+ let result = yield ContentTask.spawn(browser, {url, pattern}, function (opts) {
+ return new Promise(resolve => {
+ content.navigator.mozAddonManager.createInstall({url: opts.url})
+ .then(() => {
+ resolve({success: false, message: "createInstall should not have succeeded"});
+ }, err => {
+ if (err.message.match(new RegExp(opts.pattern))) {
+ resolve({success: true});
+ }
+ resolve({success: false, message: `Wrong error message: ${err.message}`});
+ });
+ });
+ });
+ is(result.success, true, result.message || successMessage);
+ });
+ }
+
+ yield testBadUrl("i am not a url", "NS_ERROR_MALFORMED_URI",
+ "Installing from an unparseable URL fails");
+
+ yield testBadUrl("https://addons.not-really-mozilla.org/impostor.xpi",
+ "not permitted",
+ "Installing from non-approved URL fails");
+});