bug 1287125 Lock down mozAddonManager.install() r=rhelmer
☠☠ backed out by 0fbdcd21fad7 ☠ ☠
authorAndrew Swan <aswan@mozilla.com>
Fri, 15 Jul 2016 11:40:45 -0700
changeset 305188 fbf164ef9e70c56d898f1c56d316cf4b88f2efac
parent 305187 1a50ebf3ebab7a0e7cf7b28e3107d6e4c8cb0ffb
child 305189 eadff0851c406013f211f660201abd4433620ee3
push id79518
push usercbook@mozilla.com
push dateSun, 17 Jul 2016 08:09:59 +0000
treeherdermozilla-inbound@711963e8daa3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhelmer
bugs1287125
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1287125 Lock down mozAddonManager.install() r=rhelmer MozReview-Commit-ID: 7wLqVme2Yzi
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
--- 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,32 @@ 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) => {
+      return new Promise((resolve, reject) => {
+        try {
+          let host = Services.io.newURI(options.url, null, null).host;
+          if (WEBAPI_INSTALL_HOSTS.includes(host)) {
+            // good
+          } else if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING)
+                     && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) {
+            // good
+          } else {
+            throw new Error(`Install from ${host} not permitted`);
+          }
+        } 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
@@ -235,8 +235,27 @@ 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_badhost() {
+  return BrowserTestUtils.withNewTab(TESTPAGE, function*(browser) {
+    let result = yield ContentTask.spawn(browser, null, function () {
+      return new Promise(resolve => {
+        const url = "https://addons.not-really-mozilla.org/impostor.xpi";
+        content.navigator.mozAddonManager.createInstall({url})
+          .then(() => {
+            resolve({success: false, message: "createInstall should not have succeeded"});
+          }, err => {
+            if (!err.message.match(/not permitted/)) {
+              resolve({success: false, message: "Wrong error message for invalid download url"});
+            }
+            resolve({success: true});
+          });
+      });
+    });
+    is(result.success, true, result.message || "Trying to download an invalid URL resulted in an error");
+  });
+});