Bug 1357205 - only load system add-ons from a built-in list r?aswan draft
authorRobert Helmer <rhelmer@mozilla.com>
Thu, 14 Sep 2017 00:36:04 -0700
changeset 664973 d9300757ab3f8ac8d7ca5a4d95db24c2617c577c
parent 664972 cf531097158283bcbf1b789feda82fad18f9e575
child 731614 f3ad0f3731b9585514131b01349623e6377b884b
push id79884
push userbmo:rhelmer@mozilla.com
push dateThu, 14 Sep 2017 18:19:17 +0000
reviewersaswan
bugs1357205
milestone57.0a1
Bug 1357205 - only load system add-ons from a built-in list r?aswan MozReview-Commit-ID: A6c5kaLmNPP
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_system_allowed.js
toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -53,17 +53,17 @@ XPCOMUtils.defineLazyServiceGetters(this
   Blocklist: ["@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
   ChromeRegistry: ["@mozilla.org/chrome/chrome-registry;1", "nsIChromeRegistry"],
   ResProtocolHandler: ["@mozilla.org/network/protocol;1?name=resource", "nsIResProtocolHandler"],
   AddonPolicyService: ["@mozilla.org/addons/policy-service;1", "nsIAddonPolicyService"],
   AddonPathService: ["@mozilla.org/addon-path-service;1", "amIAddonPathService"],
   aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
 });
 
-Cu.importGlobalProperties(["URL"]);
+Cu.importGlobalProperties(["URL", "fetch"]);
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
 const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
@@ -97,16 +97,21 @@ const PREF_ALLOW_LEGACY               = 
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_LAST_APP_BUILD_ID       = "extensions.lastAppBuildId";
 
+// Specify a list of valid built-in add-ons to load.
+// Only works in automation; otherwise comes from BUILT_IN_ADDONS_URI.
+const PREF_BUILT_IN_ADDONS            = "extensions.builtInAddons";
+const BUILT_IN_ADDONS_URI             = "resource:///chrome/browser/content/browser/built_in_addons.js";
+
 const OBSOLETE_PREFERENCES = [
   "extensions.bootstrappedAddons",
   "extensions.enabledAddons",
   "extensions.xpiState",
   "extensions.installCache",
 ];
 
 const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
@@ -2034,16 +2039,28 @@ this.XPIProvider = {
         logger.warn("Failed to add directory install location " + aName, e);
         return;
       }
 
       XPIProvider.installLocations.push(location);
       XPIProvider.installLocationsByName[location.name] = location;
     }
 
+    function addBuiltInInstallLocation(name, key, paths, scope) {
+      try {
+        let dir = FileUtils.getDir(key, paths);
+        let location = new BuiltInInstallLocation(name, dir, scope);
+
+        XPIProvider.installLocations.push(location);
+        XPIProvider.installLocationsByName[location.name] = location;
+      } catch(e) {
+        logger.warn(`Failed to add built-in install location ${name}`, e);
+      }
+    }
+
     function addSystemAddonInstallLocation(aName, aKey, aPaths, aScope) {
       try {
         var dir = FileUtils.getDir(aKey, aPaths);
       } catch (e) {
         // Some directories aren't defined on some platforms, ignore them
         logger.debug("Skipping unavailable install location " + aName);
         return;
       }
@@ -2102,18 +2119,18 @@ this.XPIProvider = {
       addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
                                   [DIR_EXTENSIONS],
                                   AddonManager.SCOPE_PROFILE, false);
 
       addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR,
                                     [DIR_SYSTEM_ADDONS],
                                     AddonManager.SCOPE_PROFILE);
 
-      addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_FEATURES,
-                                  [], AddonManager.SCOPE_PROFILE, true);
+      addBuiltInInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_FEATURES,
+                                [], AddonManager.SCOPE_PROFILE);
 
       if (enabledScopes & AddonManager.SCOPE_USER) {
         addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
                                     [Services.appinfo.ID],
                                     AddonManager.SCOPE_USER, true);
         if (hasRegistry) {
           addRegistryInstallLocation("winreg-app-user",
                                      Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
@@ -6485,16 +6502,79 @@ class MutableDirectoryInstallLocation ex
 
     XPIStates.removeAddon(this.name, aId);
 
     delete this._IDToFileMap[aId];
   }
 }
 
 /**
+ * An object which identifies a built-in install location for add-ons.
+ * Generally this is either the omni jar or a directory in a local build.
+ */
+class BuiltInInstallLocation extends DirectoryInstallLocation {
+  /**
+    * @param {string} name - The string identifier of this location.
+    * @param {nsIFile} directory - The directory containing files for this location.
+    * @param {string} scope - The scope of add-ons installed in this location.
+    */
+  constructor(name, directory, scope) {
+    super(name, directory, scope);
+
+    this.locked = true;
+    this._IDToFileMap = {};
+  }
+
+  /**
+   * Read the index and build a mapping between ID and URI for each
+   * installed add-on.
+   */
+  async _readAddons() {
+    let index;
+    try {
+      if (Cu.isInAutomation) {
+        index = JSON.parse(Services.prefs.getStringPref(PREF_BUILT_IN_ADDONS, ""));
+      } else {
+        dump(`rhelmer debug NOT in automation\n`);
+        let url = Services.io.newURI(BUILT_IN_ADDONS_URI);
+        let response = await fetch(url.spec);
+        index = await response.json();
+      }
+    } catch (e) {
+      logger.warn("List of valid built-in add-ons could not be parsed.", e);
+      return;
+    }
+
+    if (!("system" in index)) {
+      logger.warn("No list of valid system add-ons found.");
+      return;
+    }
+
+    for (let id of index.system) {
+      let file = new nsIFile(this._directory.path);
+      file.append(`${id}.xpi`);
+
+      if (!file.exists()) {
+        // if the XPI file can't be loaded, try the directory.
+        file = new nsIFile(this._directory.path);
+        file.append(`${id}`);
+      }
+
+      if (!file.exists()) {
+        logger.warn("Ignoring missing add-on in " + file.path);
+        continue;
+      }
+
+      this._IDToFileMap[id] = file;
+      XPIProvider._addURIMapping(id, file);
+    }
+  }
+}
+
+/**
  * An object which identifies a directory install location for system add-ons
  * updates.
  */
 class SystemAddonInstallLocation extends MutableDirectoryInstallLocation {
   /**
     * The location consists of a directory which contains the add-ons installed.
     *
     * @param  aName
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -19,16 +19,17 @@ const PREF_EM_MIN_COMPAT_APP_VERSION    
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
 const PREF_GETADDONS_BYIDS_PERFORMANCE   = "extensions.getAddons.getWithPerformance.url";
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
+const PREF_BUILT_IN_ADDONS            = "extensions.builtInAddons";
 
 // Forcibly end the test if it runs longer than 15 minutes
 const TIMEOUT_MS = 900000;
 
 // Maximum error in file modification times. Some file systems don't store
 // modification times exactly. As long as we are closer than this then it
 // still passes.
 const MAX_TIME_DIFFERENCE = 3000;
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_allowed.js
@@ -0,0 +1,58 @@
+// Tests that AddonRepository doesn't download results for system add-ons
+
+const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
+
+BootstrapMonitor.init();
+
+// Build the test set
+var distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
+do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system2_1.xpi").copyTo(distroDir, "system2@tests.mozilla.org.xpi");
+do_get_file("data/system_addons/system3_1.xpi").copyTo(distroDir, "system3@tests.mozilla.org.xpi");
+registerDirectory("XREAppFeat", distroDir);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
+
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": ["system1@tests.mozilla.org", "system2@tests.mozilla.org"]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
+function getCachedAddon(id) {
+  return new Promise(resolve => AddonRepository.getCachedAddonByID(id, resolve));
+}
+
+// Ensure that only allowed add-ons are loaded.
+add_task(async function test_allowed_addons() {
+  startupManager();
+
+  // 1 and 2 are allowed, 3 is not.
+  let addon = await AddonManager.getAddonByID("system1@tests.mozilla.org");
+  do_check_neq(addon, null);
+
+  addon = await AddonManager.getAddonByID("system2@tests.mozilla.org");
+  do_check_neq(addon, null);
+
+  addon = await AddonManager.getAddonByID("system3@tests.mozilla.org");
+  do_check_eq(addon, null);
+
+  // 3 is now allowed, 1 and 2 are not.
+  validAddons = { "system": ["system3@tests.mozilla.org"]};
+  Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
+  await promiseRestartManager();
+
+  addon = await AddonManager.getAddonByID("system1@tests.mozilla.org");
+  do_check_eq(addon, null);
+
+  addon = await AddonManager.getAddonByID("system2@tests.mozilla.org");
+  do_check_eq(addon, null);
+
+  addon = await AddonManager.getAddonByID("system3@tests.mozilla.org");
+  do_check_neq(addon, null);
+
+  await promiseShutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
@@ -9,16 +9,23 @@ const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 const IGNORE_ID = "system_delay_ignore@tests.mozilla.org";
 const COMPLETE_ID = "system_delay_complete@tests.mozilla.org";
 const DEFER_ID = "system_delay_defer@tests.mozilla.org";
 const DEFER_ALSO_ID = "system_delay_defer_also@tests.mozilla.org";
 const NORMAL_ID = "system1@tests.mozilla.org";
 
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": [IGNORE_ID, COMPLETE_ID, DEFER_ID, DEFER_ALSO_ID, NORMAL_ID]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
 
 const TEST_IGNORE_PREF = "delaytest.ignore";
 
 const distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
 registerDirectory("XREAppFeat", distroDir);
 
 let testserver = new HttpServer();
 testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_repository.js
@@ -12,16 +12,25 @@ gServer.start(-1);
 var distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
 do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
 do_get_file("data/system_addons/system2_1.xpi").copyTo(distroDir, "system2@tests.mozilla.org.xpi");
 do_get_file("data/system_addons/system3_1.xpi").copyTo(distroDir, "system3@tests.mozilla.org.xpi");
 registerDirectory("XREAppFeat", distroDir);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
 
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": ["system3@tests.mozilla.org", "system2@tests.mozilla.org",
+                               "system1@tests.mozilla.org"]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
 function getCachedAddon(id) {
   return new Promise(resolve => AddonRepository.getCachedAddonByID(id, resolve));
 }
 
 // Test with a missing features directory
 add_task(async function test_app_addons() {
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
   Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, `http://localhost:${gServer.identity.primaryPort}/get?%IDS%`);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -1,13 +1,22 @@
 // Tests that we reset to the default system add-ons correctly when switching
 // application versions
 
 BootstrapMonitor.init();
 
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": ["system3@tests.mozilla.org", "system2@tests.mozilla.org",
+                               "system1@tests.mozilla.org"]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
 const updatesDir = FileUtils.getDir("ProfD", ["features"]);
 
 // Build the test sets
 var dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1"], true);
 do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
 do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
 
 dir = FileUtils.getDir("ProfD", ["sysfeatures", "app2"], true);
@@ -412,9 +421,8 @@ add_task(async function test_updated_bad
   let conditions = [
       { isUpgrade: false, version: "1.0" },
   ];
 
   await check_installed(conditions);
 
   await promiseShutdownManager();
 });
-
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
@@ -9,16 +9,25 @@ createAppInfo("xpcshell@tests.mozilla.or
 var testserver = new HttpServer();
 testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
 testserver.start();
 var root = testserver.identity.primaryScheme + "://" +
            testserver.identity.primaryHost + ":" +
            testserver.identity.primaryPort + "/data/"
 Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
 
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": ["system3@tests.mozilla.org", "system2@tests.mozilla.org",
+                               "system1@tests.mozilla.org"]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
 let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
 registerDirectory("XREAppFeat", distroDir);
 initSystemAddonDirs();
 
 /**
  * Defines the set of initial conditions to run each test against. Each should
  * define the following properties:
  *
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_fail.js
@@ -9,16 +9,25 @@ createAppInfo("xpcshell@tests.mozilla.or
 var testserver = new HttpServer();
 testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
 testserver.start();
 var root = testserver.identity.primaryScheme + "://" +
            testserver.identity.primaryHost + ":" +
            testserver.identity.primaryPort + "/data/"
 Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
 
+// Built-in add-ons can only be overridden via a pref if in automation.
+const PREF_DISABLE_SECURITY = ("security.turn_off_all_security_so_that_" +
+                               "viruses_can_take_over_this_computer");
+Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
+
+let validAddons = { "system": ["system3@tests.mozilla.org", "system2@tests.mozilla.org",
+                               "system1@tests.mozilla.org"]};
+Services.prefs.setCharPref(PREF_BUILT_IN_ADDONS, JSON.stringify(validAddons));
+
 let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
 registerDirectory("XREAppFeat", distroDir);
 initSystemAddonDirs();
 
 /**
  * Defines the set of initial conditions to run each test against. Each should
  * define the following properties:
  *
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -46,10 +46,11 @@ tags = webextensions
 [test_nodisable_hidden.js]
 [test_delay_update_webextension.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
 [test_dependencies.js]
 [test_system_delay_update.js]
 [test_schema_change.js]
 [test_registerchrome.js]
+[test_system_allowed.js]
 
 [include:xpcshell-shared.ini]