Bug 1485305 - browser/ Ensure loadURI always passes a triggeringPrincipal() r=Mossop
☠☠ backed out by 95a55456863e ☠ ☠
authorJonathan Kingston <jkt@mozilla.com>
Wed, 29 Aug 2018 15:43:27 +0100
changeset 437049 bca973d90accd3ad02d6598fdcf483199cc39339
parent 437048 4eb33bf25d6f7d4dab6b4bfdd84c4ae2b58f7d5e
child 437050 61aa1cfb0b01ef6bfa03ace99d4aa43bdd19a5bf
push id107977
push userjkingston@mozilla.com
push dateTue, 18 Sep 2018 15:39:24 +0000
treeherdermozilla-inbound@138b8596a9cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs1485305
milestone64.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 1485305 - browser/ Ensure loadURI always passes a triggeringPrincipal() r=Mossop Differential Revision: https://phabricator.services.mozilla.com/D4551
browser/actors/BlockedSiteChild.jsm
browser/base/content/browser.js
browser/base/content/tabbrowser.js
browser/components/extensions/parent/ext-browser.js
browser/components/newtab/lib/SnippetsFeed.jsm
browser/components/preferences/in-content/subdialogs.js
browser/components/preferences/in-content/sync.js
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/content/aboutSessionRestore.js
browser/components/shell/HeadlessShell.jsm
browser/components/uitour/UITour.jsm
browser/modules/ContentSearch.jsm
--- a/browser/actors/BlockedSiteChild.jsm
+++ b/browser/actors/BlockedSiteChild.jsm
@@ -3,16 +3,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var EXPORTED_SYMBOLS = ["BlockedSiteChild"];
 
 ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
+ChromeUtils.defineModuleGetter(this, "Utils",
+  "resource://gre/modules/sessionstore/Utils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "SafeBrowsing",
                                "resource://gre/modules/SafeBrowsing.jsm");
 
 function getSiteBlockedErrorDetails(docShell) {
   let blockedInfo = {};
   if (docShell.failedChannel) {
     let classifiedChannel = docShell.failedChannel.
@@ -24,17 +26,19 @@ function getSiteBlockedErrorDetails(docS
 
       // Remove the query to avoid leaking sensitive data
       if (reportUri instanceof Ci.nsIURL) {
         reportUri = reportUri.mutate()
                              .setQuery("")
                              .finalize();
       }
 
+      let triggeringPrincipal = docShell.failedChannel.loadInfo ? Utils.serializePrincipal(docShell.failedChannel.loadInfo.triggeringPrincipal) : null;
       blockedInfo = { list: classifiedChannel.matchedList,
+                      triggeringPrincipal,
                       provider: classifiedChannel.matchedProvider,
                       uri: reportUri.asciiSpec };
     }
   }
   return blockedInfo;
 }
 
 class BlockedSiteChild extends ActorChild {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1004,16 +1004,28 @@ function handleUriInChrome(aBrowser, aUr
     } catch (e) {
       return false;
     }
   }
 
   return false;
 }
 
+/* Creates a null principal using the userContextId
+   from the current selected tab or a passed in tab argument */
+function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) {
+  let userContextId;
+  if (tab.hasAttribute("usercontextid")) {
+    userContextId = tab.getAttribute("usercontextid");
+  }
+  return Services.scriptSecurityManager.createNullPrincipal({
+    userContextId,
+  });
+}
+
 // A shared function used by both remote and non-remote browser XBL bindings to
 // load a URI or redirect it to the correct process.
 function _loadURI(browser, uri, params = {}) {
   let tab = gBrowser.getTabForBrowser(browser);
   // Preloaded browsers don't have tabs, so we ignore those.
   if (tab) {
     maybeRecordAbandonmentTelemetry(tab, "newURI");
   }
@@ -1026,16 +1038,20 @@ function _loadURI(browser, uri, params =
     flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
     referrerURI,
     referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
     triggeringPrincipal,
     postData,
     userContextId,
   } = params || {};
 
+  if (!triggeringPrincipal) {
+    throw new Error("Must load with a triggering Principal");
+  }
+
   let {
     uriObject,
     requiredRemoteType,
     mustChangeProcess,
     newFrameloader,
   } = E10SUtils.shouldLoadURIInBrowser(browser, uri, gMultiProcessBrowser,
                                        flags);
   if (uriObject && handleUriInChrome(browser, uriObject)) {
@@ -2394,16 +2410,20 @@ function BrowserCloseTabOrWindow(event) 
 function BrowserTryToCloseWindow() {
   if (WindowIsClosing())
     window.close(); // WindowIsClosing does all the necessary checks
 }
 
 function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
                  userContextId, originPrincipal, forceAboutBlankViewerInCurrent,
                  triggeringPrincipal, allowInheritPrincipal = false) {
+  if (!triggeringPrincipal) {
+    throw new Error("Must load with a triggering Principal");
+  }
+
   try {
     openLinkIn(uri, "current",
                { referrerURI: referrer,
                  referrerPolicy,
                  postData,
                  allowThirdPartyFixup,
                  userContextId,
                  originPrincipal,
@@ -3135,20 +3155,22 @@ var BrowserOnClick = {
           }
           this.ignoreWarningLink(reason, blockedInfo);
         }
         break;
     }
   },
 
   ignoreWarningLink(reason, blockedInfo) {
+    let triggeringPrincipal = Utils.deserializePrincipal(blockedInfo.triggeringPrincipal) || _createNullPrincipalFromTabUserContextId();
     // Allow users to override and continue through to the site,
     // but add a notify bar as a reminder, so that they don't lose
     // track after, e.g., tab switching.
     gBrowser.loadURI(gBrowser.currentURI.spec, {
+      triggeringPrincipal,
       flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
     });
 
     Services.perms.add(gBrowser.currentURI, "safe-browsing",
                        Ci.nsIPermissionManager.ALLOW_ACTION,
                        Ci.nsIPermissionManager.EXPIRE_SESSION);
 
     let buttons = [{
@@ -3203,33 +3225,37 @@ var BrowserOnClick = {
 /**
  * Re-direct the browser to a known-safe page.  This function is
  * used when, for example, the user browses to a known malware page
  * and is presented with about:blocked.  The "Get me out of here!"
  * button should take the user to the default start page so that even
  * when their own homepage is infected, we can get them somewhere safe.
  */
 function getMeOutOfHere() {
-  gBrowser.loadURI(getDefaultHomePage());
+  gBrowser.loadURI(getDefaultHomePage(), {
+    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage
+  });
 }
 
 /**
  * Re-direct the browser to the previous page or a known-safe page if no
  * previous page is found in history.  This function is used when the user
  * browses to a secure page with certificate issues and is presented with
  * about:certerror.  The "Go Back" button should take the user to the previous
  * or a default start page so that even when their own homepage is on a server
  * that has certificate errors, we can get them somewhere safe.
  */
 function goBackFromErrorPage() {
   let state = JSON.parse(SessionStore.getTabState(gBrowser.selectedTab));
   if (state.index == 1) {
     // If the unsafe page is the first or the only one in history, go to the
     // start page.
-    gBrowser.loadURI(getDefaultHomePage());
+    gBrowser.loadURI(getDefaultHomePage(), {
+      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    });
   } else {
     BrowserBack();
   }
 }
 
 /**
  * Return the default start page for the cases when the user's own homepage is
  * infected, so we can get them somewhere safe.
@@ -3255,17 +3281,20 @@ function getWebNavigation() {
 }
 
 function BrowserReloadWithFlags(reloadFlags) {
   let url = gBrowser.currentURI.spec;
   if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
     // If the remoteness has changed, the new browser doesn't have any
     // information of what was loaded before, so we need to load the previous
     // URL again.
-    gBrowser.loadURI(url, { flags: reloadFlags });
+    gBrowser.loadURI(url, {
+      flags: reloadFlags,
+      triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
+    });
     return;
   }
 
   // Do this after the above case where we might flip remoteness.
   // Unfortunately, we'll count the remoteness flip case as a
   // "newURL" load, since we're using loadURI, but hopefully
   // that's rare enough to not matter.
   maybeRecordAbandonmentTelemetry(gBrowser.selectedTab, "reload");
@@ -7661,17 +7690,19 @@ function switchToTabHavingURI(aURI, aOpe
             window.gBrowser.tabContainer.selectedIndex + 1,
             /* aSelectTab = */ true
           );
         } else {
           aWindow.focus();
         }
 
         if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
-          browser.loadURI(aURI.spec);
+          browser.loadURI(aURI.spec, {
+            triggeringPrincipal: aOpenParams.triggeringPrincipal || _createNullPrincipalFromTabUserContextId(),
+          });
         }
 
         if (!doAdopt) {
           aWindow.gBrowser.tabContainer.selectedIndex = i;
         }
 
         return true;
       }
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1787,17 +1787,19 @@ window._gBrowser = {
 
     if (remoteType != E10SUtils.NOT_REMOTE) {
       // For remote browsers, we need to make sure that the webProgress is
       // instantiated, otherwise the parent won't get informed about the state
       // of the preloaded browser until it gets attached to a tab.
       browser.webProgress;
     }
 
-    browser.loadURI(BROWSER_NEW_TAB_URL);
+    browser.loadURI(BROWSER_NEW_TAB_URL, {
+      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    });
     browser.docShellIsActive = false;
     browser._urlbarFocused = true;
 
     // Make sure the preloaded browser is loaded with desired zoom level
     let tabURI = Services.io.newURI(BROWSER_NEW_TAB_URL);
     FullZoom.onLocationChange(tabURI, false, browser);
   },
 
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -119,16 +119,17 @@ global.waitForTabLoaded = (tab, url) => 
     });
   });
 };
 
 global.replaceUrlInTab = (gBrowser, tab, url) => {
   let loaded = waitForTabLoaded(tab, url);
   gBrowser.loadURI(url, {
     flags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
+    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // This is safe from this functions usage however it would be preferred not to dot his.
   });
   return loaded;
 };
 
 /**
  * Manages tab-specific and window-specific context data, and dispatches
  * tab select events across all windows.
  */
--- a/browser/components/newtab/lib/SnippetsFeed.jsm
+++ b/browser/components/newtab/lib/SnippetsFeed.jsm
@@ -185,17 +185,19 @@ this.SnippetsFeed = class SnippetsFeed {
     Services.prefs.removeObserver(FXA_USERNAME_PREF, this._refresh);
     Services.obs.removeObserver(this, SEARCH_ENGINE_OBSERVER_TOPIC);
     this.store.dispatch(ac.BroadcastToContent({type: at.SNIPPETS_RESET}));
   }
 
   async showFirefoxAccounts(browser) {
     const url = await FxAccounts.config.promiseSignUpURI("snippets");
     // We want to replace the current tab.
-    browser.loadURI(url);
+    browser.loadURI(url, {
+      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+    });
   }
 
   async onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
         break;
       case at.UNINIT:
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -154,17 +154,19 @@ SubDialog.prototype = {
           this._frame.removeEventListener("load", onBlankLoad);
           // We're now officially done closing, so update the state to reflect that.
           this._openedURL = null;
           this._isClosing = false;
           this._resolveClosePromise();
         }
       };
       this._frame.addEventListener("load", onBlankLoad);
-      this._frame.loadURI("about:blank");
+      this._frame.loadURI("about:blank", {
+        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+      });
     }, 0);
   },
 
   handleEvent(aEvent) {
     switch (aEvent.type) {
       case "click":
         // Close the dialog if the user clicked the overlay background, just
         // like when the user presses the ESC key (case "command" below).
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -353,17 +353,19 @@ var gSyncPane = {
     win.switchToTabHavingURI(url, true, options);
   },
 
   // Replace the current tab with the specified URL.
   replaceTabWithUrl(url) {
     // Get the <browser> element hosting us.
     let browser = window.docShell.chromeEventHandler;
     // And tell it to load our URL.
-    browser.loadURI(url);
+    browser.loadURI(url, {
+      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+    });
   },
 
   async signIn() {
     const url = await FxAccounts.config.promiseSignInURI(this._getEntryPoint());
     this.replaceTabWithUrl(url);
   },
 
   async reSignIn() {
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2955,17 +2955,21 @@ var SessionStoreInternal = {
                       "Somehow a crashed browser is still remote.");
     }
 
     // We put the browser at about:blank in case the user is
     // restoring tabs on demand. This way, the user won't see
     // a flash of the about:tabcrashed page after selecting
     // the revived tab.
     aTab.removeAttribute("crashed");
-    browser.loadURI("about:blank");
+    browser.loadURI("about:blank", {
+      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
+        userContextId: aTab.userContextId,
+      }),
+    });
 
     let data = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
     this.restoreTab(aTab, data, {
       forceOnDemand: true,
     });
   },
 
   /**
--- a/browser/components/sessionstore/content/aboutSessionRestore.js
+++ b/browser/components/sessionstore/content/aboutSessionRestore.js
@@ -175,17 +175,19 @@ function restoreSession() {
     var tabbrowser = top.gBrowser;
     var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
     tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
   }, "browser-delayed-startup-finished");
 }
 
 function startNewSession() {
   if (Services.prefs.getIntPref("browser.startup.page") == 0)
-    getBrowserWindow().gBrowser.loadURI("about:blank");
+    getBrowserWindow().gBrowser.loadURI("about:blank", {
+      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+    });
   else
     getBrowserWindow().BrowserHome();
 }
 
 function onListClick(aEvent) {
   // don't react to right-clicks
   if (aEvent.button == 2)
     return;
--- a/browser/components/shell/HeadlessShell.jsm
+++ b/browser/components/shell/HeadlessShell.jsm
@@ -8,19 +8,19 @@ var EXPORTED_SYMBOLS = ["HeadlessShell"]
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 // Refrences to the progress listeners to keep them from being gc'ed
 // before they are called.
 const progressListeners = new Map();
 
-function loadContentWindow(webNavigation, uri) {
+function loadContentWindow(webNavigation, uri, principal) {
   return new Promise((resolve, reject) => {
-    webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+    webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null, principal);
     let docShell = webNavigation.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDocShell);
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     let progressListener = {
       onLocationChange(progress, request, location, flags) {
         // Ignore inner-frame events
         if (progress != webProgress) {
@@ -45,17 +45,17 @@ function loadContentWindow(webNavigation
                                     Ci.nsIWebProgress.NOTIFY_LOCATION);
   });
 }
 
 async function takeScreenshot(fullWidth, fullHeight, contentWidth, contentHeight, path, url) {
   try {
     var windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
     // nsIWindowlessBrowser inherits from nsIWebNavigation.
-    let contentWindow = await loadContentWindow(windowlessBrowser, url);
+    let contentWindow = await loadContentWindow(windowlessBrowser, url, Services.scriptSecurityManager.getSystemPrincipal());
     contentWindow.resizeTo(contentWidth, contentHeight);
 
     let canvas = contentWindow.document.createElementNS("http://www.w3.org/1999/xhtml", "html:canvas");
     let context = canvas.getContext("2d");
     let width = fullWidth ? contentWindow.innerWidth + contentWindow.scrollMaxX - contentWindow.scrollMinX
                           : contentWindow.innerWidth;
     let height = fullHeight ? contentWindow.innerHeight + contentWindow.scrollMaxY - contentWindow.scrollMinY
                             : contentWindow.innerHeight;
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -463,32 +463,36 @@ var UITour = {
           const url = new URL(uri);
           // Call our helper to validate extraURLCampaignParams and populate URLSearchParams
           if (!this._populateCampaignParams(url, data.extraURLCampaignParams)) {
             log.warn("showFirefoxAccounts: invalid campaign args specified");
             return;
           }
 
           // We want to replace the current tab.
-          browser.loadURI(url.href);
+          browser.loadURI(url.href, {
+            triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+          });
         });
         break;
       }
 
       case "showConnectAnotherDevice": {
         FxAccounts.config.promiseConnectDeviceURI("uitour").then(uri => {
           const url = new URL(uri);
           // Call our helper to validate extraURLCampaignParams and populate URLSearchParams
           if (!this._populateCampaignParams(url, data.extraURLCampaignParams)) {
             log.warn("showConnectAnotherDevice: invalid campaign args specified");
             return;
           }
 
           // We want to replace the current tab.
-          browser.loadURI(url.href);
+          browser.loadURI(url.href, {
+            triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+          });
         });
         break;
       }
 
       case "resetFirefox": {
         // Open a reset profile dialog window.
         if (ResetProfile.resetSupported()) {
           ResetProfile.openConfirmationDialog(window);
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -242,16 +242,19 @@ var ContentSearch = {
     // where === "current"), openUILinkIn will not work because that tab is no
     // longer the current one. For this case we manually load the URI.
     if (where === "current") {
       // Since we're going to load the search in the same browser, blur the search
       // UI to prevent further interaction before we start loading.
       this._reply(msg, "Blur");
       browser.loadURI(submission.uri.spec, {
         postData: submission.postData,
+        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
+          userContextId: win.gBrowser.selectedBrowser.getAttribute("userContextId"),
+        }),
       });
     } else {
       let params = {
         postData: submission.postData,
         inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
       };
       win.openTrustedLinkIn(submission.uri.spec, where, params);
     }