Bug 950417 - Enable safe direct request add-on installs. r=wesj, r=Mossop
authorEugen Sawin <esawin@mozilla.com>
Wed, 19 Mar 2014 21:24:59 +0100
changeset 174563 cb74aa26cb490b0930804b2d786ffa747800e921
parent 174562 131277cc768d8c9fcceeb018188432808d11a731
child 174564 5ad3cc0fecea5592b035f243af1ee907ad107c6f
push id26455
push userryanvm@gmail.com
push dateThu, 20 Mar 2014 20:48:34 +0000
treeherdermozilla-central@7a603fc0c178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj, Mossop
bugs950417
milestone31.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 950417 - Enable safe direct request add-on installs. r=wesj, r=Mossop
mobile/android/app/mobile.js
mobile/android/base/AndroidManifest.xml.in
mobile/android/chrome/content/browser.js
mobile/android/locales/en-US/chrome/browser.properties
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpinstall/browser.ini
toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js
toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js
toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -165,16 +165,18 @@ pref("browser.formfill.enable", true);
 /* spellcheck */
 pref("layout.spellcheckDefault", 0);
 
 /* new html5 forms */
 pref("dom.experimental_forms", true);
 pref("dom.forms.number", true);
 
 /* extension manager and xpinstall */
+pref("xpinstall.whitelist.directRequest", false);
+pref("xpinstall.whitelist.fileRequest", false);
 pref("xpinstall.whitelist.add", "addons.mozilla.org");
 pref("xpinstall.whitelist.add.180", "marketplace.firefox.com");
 
 pref("extensions.enabledScopes", 1);
 pref("extensions.autoupdate.enabled", true);
 pref("extensions.autoupdate.interval", 86400);
 pref("extensions.update.enabled", false);
 pref("extensions.update.interval", 86400);
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -136,16 +136,35 @@
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
 
             <intent-filter>
                 <action android:name="android.intent.action.SEARCH" />
             </intent-filter>
 
+            <!-- For XPI installs from websites and the download manager. -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="file" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:mimeType="application/x-xpinstall" />
+            </intent-filter>
+
+            <!-- For XPI installs from file: URLs. -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:host="" />
+                <data android:scheme="file" />
+                <data android:pathPattern=".*\\.xpi" />
+            </intent-filter>
+
 #ifdef MOZ_ANDROID_BEAM
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
 #endif
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5690,17 +5690,20 @@ var XPInstallObserver = {
         break;
       case "addon-install-blocked":
         let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
         let win = installInfo.originatingWindow;
         let tab = BrowserApp.getTabForWindow(win.top);
         if (!tab)
           return;
 
-        let host = installInfo.originatingURI.host;
+        let host = null;
+        if (installInfo.originatingURI) {
+          host = installInfo.originatingURI.host;
+        }
 
         let brandShortName = Strings.brand.GetStringFromName("brandShortName");
         let notificationName, buttons, message;
         let strings = Strings.browser;
         let enabled = true;
         try {
           enabled = Services.prefs.getBoolPref("xpinstall.enabled");
         }
@@ -5718,17 +5721,33 @@ var XPInstallObserver = {
               callback: function editPrefs() {
                 Services.prefs.setBoolPref("xpinstall.enabled", true);
                 return false;
               }
             }];
           }
         } else {
           notificationName = "xpinstall";
-          message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
+          if (host) {
+            // We have a host which asked for the install.
+            message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
+          } else {
+            // Without a host we address the add-on as the initiator of the install.
+            let addon = null;
+            if (installInfo.installs.length > 0) {
+              addon = installInfo.installs[0].name;
+            }
+            if (addon) {
+              // We have an addon name, show the regular message.
+              message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
+            } else {
+              // We don't have an addon name, show an alternative message.
+              message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
+            }
+          }
 
           buttons = [{
             label: strings.GetStringFromName("xpinstallPromptAllowButton"),
             callback: function() {
               // Kick off the install
               installInfo.install();
               return false;
             }
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -66,16 +66,18 @@ popup.dontShow=Don't show
 safeBrowsingDoorhanger=This site has been identified as containing malware or a phishing attempt. Be careful.
 
 # LOCALIZATION NOTE (blockPopups.label): Label that will be used in
 # site settings dialog.
 blockPopups.label=Block Popups
 
 # XPInstall
 xpinstallPromptWarning2=%S prevented this site (%S) from asking you to install software on your device.
+xpinstallPromptWarningLocal=%S prevented this add-on (%S) from installing on your device.
+xpinstallPromptWarningDirect=%S prevented an add-on from installing on your device.
 xpinstallPromptAllowButton=Allow
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage2=Software installation is currently disabled. Press Enable and try again.
 xpinstallDisabledButton=Enable
 
 # Site Identity
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -62,16 +62,18 @@ const PREF_EM_UPDATE_URL              = 
 const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_EXTENSION_FORMAT        = "extensions.";
 const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
 const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
 const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
 const PREF_XPI_ENABLED                = "xpinstall.enabled";
 const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
+const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
+const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
 const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
 const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
@@ -3441,31 +3443,55 @@ var XPIProvider = {
    * @return true if installing is enabled
    */
   isInstallEnabled: function XPI_isInstallEnabled() {
     // Default to enabled if the preference does not exist
     return Prefs.getBoolPref(PREF_XPI_ENABLED, true);
   },
 
   /**
+   * Called to test whether installing XPI add-ons by direct URL requests is
+   * whitelisted.
+   *
+   * @return true if installing by direct requests is whitelisted
+   */
+  isDirectRequestWhitelisted: function XPI_isDirectRequestWhitelisted() {
+    // Default to whitelisted if the preference does not exist.
+    return Prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true);
+  },
+
+  /**
+   * Called to test whether installing XPI add-ons from file referrers is
+   * whitelisted.
+   *
+   * @return true if installing from file referrers is whitelisted
+   */
+  isFileRequestWhitelisted: function XPI_isFileRequestWhitelisted() {
+    // Default to whitelisted if the preference does not exist.
+    return Prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true);
+  },
+
+  /**
    * Called to test whether installing XPI add-ons from a URI is allowed.
    *
    * @param  aUri
    *         The URI being installed from
    * @return true if installing is allowed
    */
   isInstallAllowed: function XPI_isInstallAllowed(aUri) {
     if (!this.isInstallEnabled())
       return false;
 
+    // Direct requests without a referrer are either whitelisted or blocked.
     if (!aUri)
-      return true;
-
-    // file: and chrome: don't need whitelisted hosts
-    if (aUri.schemeIs("chrome") || aUri.schemeIs("file"))
+      return this.isDirectRequestWhitelisted();
+
+    // Local referrers can be whitelisted.
+    if (this.isFileRequestWhitelisted() &&
+        (aUri.schemeIs("chrome") || aUri.schemeIs("file")))
       return true;
 
     this.importPermissions();
 
     let permission = Services.perms.testPermission(aUri, XPI_PERMISSION);
     if (permission == Ci.nsIPermissionManager.DENY_ACTION)
       return false;
 
--- a/toolkit/mozapps/extensions/test/xpinstall/browser.ini
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini
@@ -57,16 +57,18 @@ support-files =
 [browser_httphash2.js]
 [browser_httphash3.js]
 [browser_httphash4.js]
 [browser_httphash5.js]
 [browser_httphash6.js]
 [browser_installchrome.js]
 [browser_localfile.js]
 [browser_localfile2.js]
+[browser_localfile3.js]
+[browser_localfile4.js]
 [browser_multipackage.js]
 [browser_navigateaway.js]
 [browser_navigateaway2.js]
 [browser_offline.js]
 [browser_relative.js]
 [browser_signed_multiple.js]
 [browser_signed_naming.js]
 [browser_signed_tampered.js]
@@ -79,8 +81,9 @@ support-files =
 [browser_unsigned_trigger_iframe.js]
 [browser_unsigned_url.js]
 [browser_whitelist.js]
 [browser_whitelist2.js]
 [browser_whitelist3.js]
 [browser_whitelist4.js]
 [browser_whitelist5.js]
 [browser_whitelist6.js]
+[browser_whitelist7.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js
@@ -0,0 +1,37 @@
+// ----------------------------------------------------------------------------
+// Tests installing an add-on from a local file with whitelisting disabled.
+// This should be blocked by the whitelist check.
+function test() {
+  Harness.installBlockedCallback = allow_blocked;
+  Harness.installsCompletedCallback = finish_test;
+  Harness.setup();
+
+  // Disable direct request whitelisting, installing from file should be blocked.
+  Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false);
+
+  var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+                     .getService(Components.interfaces.nsIChromeRegistry);
+
+  var chromeroot = extractChromeRoot(gTestPath);
+  try {
+    var xpipath = cr.convertChromeURL(makeURI(chromeroot + "unsigned.xpi")).spec;
+  } catch (ex) {
+    var xpipath = chromeroot + "unsigned.xpi"; //scenario where we are running from a .jar and already extracted
+  }
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(xpipath);
+}
+
+function allow_blocked(installInfo) {
+  ok(true, "Seen blocked");
+  return false;
+}
+
+function finish_test(count) {
+  is(count, 0, "No add-ons should have been installed");
+
+  Services.prefs.clearUserPref("xpinstall.whitelist.directRequest");
+
+  gBrowser.removeCurrentTab();
+  Harness.finish();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js
@@ -0,0 +1,40 @@
+// ----------------------------------------------------------------------------
+// Tests installing an add-on from a local file with whitelisting disabled.
+// This should be blocked by the whitelist check.
+function test() {
+  Harness.installBlockedCallback = allow_blocked;
+  Harness.installsCompletedCallback = finish_test;
+  Harness.setup();
+
+  // Disable file request whitelisting, installing by file referrer should be blocked.
+  Services.prefs.setBoolPref("xpinstall.whitelist.fileRequest", false);
+
+  var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+                     .getService(Components.interfaces.nsIChromeRegistry);
+
+  var chromeroot = extractChromeRoot(gTestPath);
+  try {
+    var xpipath = cr.convertChromeURL(makeURI(chromeroot)).spec;
+  } catch (ex) {
+    var xpipath = chromeroot; //scenario where we are running from a .jar and already extracted
+  }
+  var triggers = encodeURIComponent(JSON.stringify({
+    "Unsigned XPI": TESTROOT + "unsigned.xpi"
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(xpipath + "installtrigger.html?" + triggers);
+}
+
+function allow_blocked(installInfo) {
+  ok(true, "Seen blocked");
+  return false;
+}
+
+function finish_test(count) {
+  is(count, 0, "No add-ons should have been installed");
+
+  Services.prefs.clearUserPref("xpinstall.whitelist.fileRequest");
+
+  gBrowser.removeCurrentTab();
+  Harness.finish();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_whitelist7.js
@@ -0,0 +1,30 @@
+// ----------------------------------------------------------------------------
+// Tests installing an unsigned add-on through a direct install request from
+// web content. This should be blocked by the whitelist check because we disable
+// direct request whitelisting, even though the target URI is whitelisted.
+function test() {
+  Harness.installBlockedCallback = allow_blocked;
+  Harness.installsCompletedCallback = finish_test;
+  Harness.setup();
+
+  // Disable direct request whitelisting, installing should be blocked.
+  Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false);
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "unsigned.xpi");
+}
+
+function allow_blocked(installInfo) {
+  ok(true, "Seen blocked");
+  return false;
+}
+
+function finish_test(count) {
+  is(count, 0, "No add-ons should have been installed");
+
+  Services.perms.remove("example.org", "install");
+  Services.prefs.clearUserPref("xpinstall.whitelist.directRequest");
+
+  gBrowser.removeCurrentTab();
+  Harness.finish();
+}