From 909bcb3a36ee9b82a69019dbba352e897476c5fe Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Fri, 5 Jun 2015 11:46:11 -0700 Subject: [PATCH] Bug 1172028: Sideloaded add-ons without full signing shouldn't ever be loaded. r=dveditz --- layout/tools/reftest/runreftest.py | 3 ++ testing/profiles/prefs_general.js | 1 + .../extensions/internal/XPIProvider.jsm | 28 +++++++++--- .../test/xpcshell/test_signed_inject.js | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/layout/tools/reftest/runreftest.py b/layout/tools/reftest/runreftest.py index 5a1cd3fd89d..d5163b197cb 100644 --- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -221,6 +221,9 @@ class RefTest(object): # And for about:newtab content fetch and pings. prefs['browser.newtabpage.directory.source'] = 'data:application/json,{"reftest":1}' prefs['browser.newtabpage.directory.ping'] = '' + # Only allow add-ons from the profile and app and allow foreign injection + prefs["extensions.enabledScopes"] = 5; + prefs["extensions.autoDisableScopes"] = 0; # Allow unsigned add-ons prefs['xpinstall.signatures.required'] = False diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index 029854cc6b7..8ec90c01762 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -69,6 +69,7 @@ user_pref("experiments.manifest.uri", "http://%(server)s/experiments-dummy/manif // Only load extensions from the application and user profile // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION user_pref("extensions.enabledScopes", 5); +user_pref("extensions.autoDisableScopes", 0); // Disable metadata caching for installed add-ons by default user_pref("extensions.getAddons.cache.enabled", false); // Disable intalling any distribution add-ons diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 511f8a2ffe6..b741b3cad6b 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -657,8 +657,12 @@ function isUsableAddon(aAddon) { if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin) return true; - if (mustSign(aAddon.type) && aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING) - return false; + if (mustSign(aAddon.type)) { + if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING) + return false; + if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED) + return false; + } if (aAddon.blocklistState == Blocklist.STATE_BLOCKED) return false; @@ -2751,9 +2755,12 @@ this.XPIProvider = { let jsonfile = stagingDir.clone(); jsonfile.append(id + ".json"); + // Assume this was a foreign install if there is no cached metadata file + let foreignInstall = !jsonfile.exists(); + let addon; try { - aManifests[aLocation.name][id] = syncLoadManifestFromFile(stageDirEntry); + addon = syncLoadManifestFromFile(stageDirEntry); } catch (e) { logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e); @@ -2763,9 +2770,9 @@ this.XPIProvider = { continue; } - let addon = aManifests[aLocation.name][id]; - - if ((addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) && mustSign(addon.type)) { + if (mustSign(addon.type) && + (addon.signedState <= AddonManager.SIGNEDSTATE_MISSING || + (foreignInstall && addon.signedState < AddonManager.SIGNEDSTATE_SIGNED))) { logger.warn("Refusing to install staged add-on " + id + " with signed state " + addon.signedState); seenFiles.push(stageDirEntry.leafName); seenFiles.push(jsonfile.leafName); @@ -2774,7 +2781,7 @@ this.XPIProvider = { // Check for a cached metadata for this add-on, it may contain updated // compatibility information - if (jsonfile.exists()) { + if (!foreignInstall) { logger.debug("Found updated metadata for " + id + " in " + aLocation.name); let fis = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Ci.nsIFileInputStream); @@ -2785,6 +2792,10 @@ this.XPIProvider = { fis.init(jsonfile, -1, 0, 0); let metadata = json.decodeFromStream(fis, jsonfile.fileSize); addon.importMetadata(metadata); + + // Pass this through to addMetadata so it knows this add-on was + // likely installed through the UI + aManifests[aLocation.name][id] = addon; } catch (e) { // If some data can't be recovered from the cached metadata then it @@ -3380,6 +3391,9 @@ this.XPIProvider = { newAddon.updateDate = aAddonState.mtime; newAddon.foreignInstall = isDetectedInstall; + // appDisabled depends on whether the add-on is a foreignInstall so update + newAddon.appDisabled = !isUsableAddon(newAddon); + if (aMigrateData) { // If there is migration data then apply it. logger.debug("Migrating data from old database"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js index 180661cd255..302cdd233d6 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js @@ -9,6 +9,7 @@ const ADDONS = { unsigned: "unsigned_bootstrap_2.xpi", badid: "signed_bootstrap_badid_2.xpi", signed: "signed_bootstrap_2.xpi", + preliminary: "preliminary_bootstrap_2.xpi", }, nonbootstrap: { unsigned: "unsigned_nonbootstrap_2.xpi", @@ -334,3 +335,45 @@ add_task(function*() { yield promiseShutdownManager(); resetPrefs(); }); + +// Only fully-signed sideloaded add-ons should work +add_task(function*() { + let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.preliminary), profileDir, ID); + + startupManager(); + + // Currently we leave the sideloaded add-on there but just don't run it + let addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + do_check_true(addon.appDisabled); + do_check_false(addon.isActive); + do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_PRELIMINARY); + do_check_eq(getActiveVersion(), -1); + + addon.uninstall(); + yield promiseShutdownManager(); + resetPrefs(); + + do_check_false(file.exists()); + clearCache(file); +}); + +add_task(function*() { + let stage = profileDir.clone(); + stage.append("staged"); + + let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.preliminary), stage, ID); + + startupManager(); + + // Should have refused to install preliminarily signed version + let addon = yield promiseAddonByID(ID); + do_check_eq(addon, null); + do_check_eq(getActiveVersion(), -1); + + do_check_false(file.exists()); + clearCache(file); + + yield promiseShutdownManager(); + resetPrefs(); +});