Bug 1406278: Part 3 - Use subject principal as triggering principal in <script> "src" attribute. r=bz
authorKris Maglione <maglione.k@gmail.com>
Wed, 04 Oct 2017 22:16:32 -0700
changeset 436033 cf383eff981f365975f777158e5e5cf6e388d32c
parent 436032 cf763570260af394cd7375a287954bb2c091236f
child 436034 8e7305ab6283ee4bd9ebf2a352c276b72c93b269
push id8114
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 16:33:21 +0000
treeherdermozilla-beta@73e0d89a540f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1406278
milestone58.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 1406278: Part 3 - Use subject principal as triggering principal in <script> "src" attribute. r=bz MozReview-Commit-ID: KwGIE4t7KUx
dom/html/HTMLScriptElement.cpp
dom/html/HTMLScriptElement.h
dom/script/ScriptLoadRequest.h
dom/script/ScriptLoader.cpp
dom/script/nsIScriptElement.h
dom/webidl/HTMLScriptElement.webidl
toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -164,19 +164,19 @@ HTMLScriptElement::SetDefer(bool aDefer,
 
 bool
 HTMLScriptElement::Defer()
 {
   return GetBoolAttr(nsGkAtoms::defer);
 }
 
 void
-HTMLScriptElement::SetSrc(const nsAString& aSrc, ErrorResult& rv)
+HTMLScriptElement::SetSrc(const nsAString& aSrc, nsIPrincipal& aTriggeringPrincipal, ErrorResult& rv)
 {
-  rv = SetAttrHelper(nsGkAtoms::src, aSrc);
+  SetHTMLAttr(nsGkAtoms::src, aSrc, aTriggeringPrincipal, rv);
 }
 
 void
 HTMLScriptElement::SetType(const nsAString& aType, ErrorResult& rv)
 {
   SetHTMLAttr(nsGkAtoms::type, aType, rv);
 }
 
@@ -237,16 +237,21 @@ HTMLScriptElement::AfterSetAttr(int32_t 
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 bool aNotify)
 {
   if (nsGkAtoms::async == aName && kNameSpaceID_None == aNamespaceID) {
     mForceAsync = false;
   }
+  if (nsGkAtoms::src == aName && kNameSpaceID_None == aNamespaceID) {
+    mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
+        this, aValue ? aValue->GetStringValue() : EmptyString(),
+        aMaybeScriptedPrincipal);
+  }
   return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName,
                                             aValue, aOldValue,
                                             aMaybeScriptedPrincipal,
                                             aNotify);
 }
 
 NS_IMETHODIMP
 HTMLScriptElement::GetInnerHTML(nsAString& aInnerHTML)
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -62,17 +62,21 @@ public:
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 bool aNotify) override;
 
   // WebIDL
   void SetText(const nsAString& aValue, ErrorResult& rv);
   void SetCharset(const nsAString& aCharset, ErrorResult& rv);
   void SetDefer(bool aDefer, ErrorResult& rv);
   bool Defer();
-  void SetSrc(const nsAString& aSrc, ErrorResult& rv);
+  void SetSrc(const nsAString& aSrc, nsIPrincipal& aTriggeringPrincipal, ErrorResult& rv);
+  void GetSrc(nsString& aSrc, nsIPrincipal&)
+  {
+    GetSrc(aSrc);
+  };
   void SetType(const nsAString& aType, ErrorResult& rv);
   void SetHtmlFor(const nsAString& aHtmlFor, ErrorResult& rv);
   void SetEvent(const nsAString& aEvent, ErrorResult& rv);
   void GetCrossOrigin(nsAString& aResult)
   {
     // Null for both missing and invalid defaults is ok, since we
     // always parse to an enum value, so we don't need an invalid
     // default, and we _want_ the missing default to be null.
--- a/dom/script/ScriptLoadRequest.h
+++ b/dom/script/ScriptLoadRequest.h
@@ -175,16 +175,17 @@ public:
 
   // Holds the SRI serialized hash and the script bytecode for non-inline
   // scripts.
   mozilla::Vector<uint8_t> mScriptBytecode;
   uint32_t mBytecodeOffset; // Offset of the bytecode in mScriptBytecode
 
   uint32_t mJSVersion;
   nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal> mOriginPrincipal;
   nsAutoCString mURL;     // Keep the URI's filename alive during off thread parsing.
   int32_t mLineNo;
   const mozilla::CORSMode mCORSMode;
   const mozilla::dom::SRIMetadata mIntegrity;
   mozilla::net::ReferrerPolicy mReferrerPolicy;
 
   // Holds the Cache information, which is used to register the bytecode
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -746,16 +746,17 @@ ScriptLoader::StartFetchingModuleAndDepe
   MOZ_ASSERT(aURI);
 
   RefPtr<ModuleLoadRequest> childRequest =
     new ModuleLoadRequest(aRequest->mElement, aRequest->mJSVersion,
                             aRequest->mCORSMode, aRequest->mIntegrity, this);
 
   childRequest->mIsTopLevel = false;
   childRequest->mURI = aURI;
+  childRequest->mTriggeringPrincipal = aRequest->mTriggeringPrincipal;
   childRequest->mIsInline = false;
   childRequest->mReferrerPolicy = aRequest->mReferrerPolicy;
   childRequest->mParent = aRequest;
   aRequest->mImports.AppendElement(childRequest);
 
   if (LOG_ENABLED()) {
     nsAutoCString url1;
     aRequest->mURI->GetAsciiSpec(url1);
@@ -1014,25 +1015,27 @@ ScriptLoader::StartLoad(ScriptLoadReques
       securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
     } else if (aRequest->mCORSMode == CORS_USE_CREDENTIALS) {
       securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
     }
   }
   securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
 
   nsCOMPtr<nsIChannel> channel;
-  nsresult rv = NS_NewChannel(getter_AddRefs(channel),
-                              aRequest->mURI,
-                              context,
-                              securityFlags,
-                              contentPolicyType,
-                              loadGroup,
-                              prompter,
-                              nsIRequest::LOAD_NORMAL |
-                              nsIChannel::LOAD_CLASSIFY_URI);
+  nsresult rv = NS_NewChannelWithTriggeringPrincipal(
+      getter_AddRefs(channel),
+      aRequest->mURI,
+      context,
+      aRequest->mTriggeringPrincipal,
+      securityFlags,
+      contentPolicyType,
+      loadGroup,
+      prompter,
+      nsIRequest::LOAD_NORMAL |
+      nsIChannel::LOAD_CLASSIFY_URI);
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // To avoid decoding issues, the JSVersion is explicitly guarded here, and the
   // build-id is part of the JSBytecodeMimeType constant.
   aRequest->mCacheInfo = nullptr;
   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
   if (cic && nsContentUtils::IsBytecodeCacheEnabled() &&
@@ -1367,19 +1370,25 @@ ScriptLoader::ProcessScriptElement(nsISc
           if (mDocument->GetDocumentURI()) {
             mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
           }
           SRICheck::IntegrityMetadata(integrity, sourceUri, mReporter,
                                       &sriMetadata);
         }
       }
 
+      nsCOMPtr<nsIPrincipal> principal = aElement->GetScriptURITriggeringPrincipal();
+      if (!principal) {
+        principal = scriptContent->NodePrincipal();
+      }
+
       request = CreateLoadRequest(scriptKind, aElement, version, ourCORSMode,
                                   sriMetadata);
       request->mURI = scriptURI;
+      request->mTriggeringPrincipal = Move(principal);
       request->mIsInline = false;
       request->mReferrerPolicy = ourRefPolicy;
       // keep request->mScriptFromHead to false so we don't treat non preloaded
       // scripts as blockers for full page load. See bug 792438.
 
       rv = StartLoad(request);
       if (NS_FAILED(rv)) {
         const char* message = "ScriptSourceLoadFailed";
@@ -1514,16 +1523,17 @@ ScriptLoader::ProcessScriptElement(nsISc
   }
 
   // Inline scripts ignore ther CORS mode and are always CORS_NONE
   request = CreateLoadRequest(scriptKind, aElement, version, CORS_NONE,
                               SRIMetadata()); // SRI doesn't apply
   request->mJSVersion = version;
   request->mIsInline = true;
   request->mURI = mDocument->GetDocumentURI();
+  request->mTriggeringPrincipal = mDocument->NodePrincipal();
   request->mLineNo = aElement->GetScriptLineNumber();
   request->mProgress = ScriptLoadRequest::Progress::Loading_Source;
   request->mDataType = ScriptLoadRequest::DataType::Source;
   TRACE_FOR_TEST_BOOL(request->mElement, "scriptloader_load_source");
   CollectScriptTelemetry(nullptr, request);
 
   LOG(("ScriptLoadRequest (%p): Created request for inline script",
        request.get()));
@@ -3099,16 +3109,17 @@ ScriptLoader::PreloadURI(nsIURI* aURI, c
     }
     SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
   }
 
   RefPtr<ScriptLoadRequest> request =
     CreateLoadRequest(ScriptKind::Classic, nullptr, 0,
                       Element::StringToCORSMode(aCrossOrigin), sriMetadata);
   request->mURI = aURI;
+  request->mTriggeringPrincipal = mDocument->NodePrincipal();
   request->mIsInline = false;
   request->mReferrerPolicy = aReferrerPolicy;
   request->mScriptFromHead = aScriptFromHead;
   request->mPreloadAsAsync = aAsync;
   request->mPreloadAsDefer = aDefer;
 
   nsresult rv = StartLoad(request);
   if (NS_FAILED(rv)) {
--- a/dom/script/nsIScriptElement.h
+++ b/dom/script/nsIScriptElement.h
@@ -61,16 +61,22 @@ public:
    * this is assumed to be an inline script element.
    */
   nsIURI* GetScriptURI()
   {
     NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
     return mUri;
   }
 
+  nsIPrincipal* GetScriptURITriggeringPrincipal()
+  {
+    NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+    return mSrcTriggeringPrincipal;
+  }
+
   /**
    * Script source text for inline script elements.
    */
   virtual void GetScriptText(nsAString& text) = 0;
 
   virtual void GetScriptCharset(nsAString& charset) = 0;
 
   /**
@@ -317,16 +323,21 @@ protected:
   mozilla::dom::FromParser mParserCreated;
 
   /**
    * The effective src (or null if no src).
    */
   nsCOMPtr<nsIURI> mUri;
 
   /**
+   * The triggering principal for the src URL.
+   */
+  nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
+
+  /**
    * The creator parser of a non-defer, non-async parser-inserted script.
    */
   nsWeakPtr mCreatorParser;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID)
 
 #endif // nsIScriptElement_h___
--- a/dom/webidl/HTMLScriptElement.webidl
+++ b/dom/webidl/HTMLScriptElement.webidl
@@ -5,17 +5,17 @@
  *
  * The origin of this IDL file is
  * http://www.whatwg.org/specs/web-apps/current-work/#the-script-element
  * http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
  */
 
 [HTMLConstructor]
 interface HTMLScriptElement : HTMLElement {
-  [CEReactions, SetterThrows]
+  [CEReactions, NeedsSubjectPrincipal, SetterThrows]
   attribute DOMString src;
   [CEReactions, SetterThrows]
   attribute DOMString type;
   [CEReactions, SetterThrows, Pref="dom.moduleScripts.enabled"]
   attribute boolean noModule;
   [CEReactions, SetterThrows]
   attribute DOMString charset;
   [CEReactions, SetterThrows]
--- a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
@@ -202,17 +202,19 @@ add_task(async function test_web_accessi
     window.addEventListener("message", function rcv(event) {
       browser.runtime.sendMessage("script-ran");
       window.removeEventListener("message", rcv);
     });
 
     testImageLoading(browser.extension.getURL("image.png"), "loaded");
 
     let testScriptElement = document.createElement("script");
-    testScriptElement.setAttribute("src", browser.extension.getURL("test_script.js"));
+    // Set the src via wrappedJSObject so the load is triggered with the
+    // content page's principal rather than ours.
+    testScriptElement.wrappedJSObject.setAttribute("src", browser.extension.getURL("test_script.js"));
     document.head.appendChild(testScriptElement);
     browser.runtime.sendMessage("script-loaded");
   }
 
   function testScript() {
     window.postMessage("test-script-loaded", "*");
   }
 
@@ -294,17 +296,19 @@ add_task(async function test_web_accessi
     browser.test.sendMessage("background-ready");
   }
 
   function content() {
     testImageLoading("http://example.com/tests/toolkit/components/extensions/test/mochitest/file_image_bad.png", "blocked");
     testImageLoading(browser.extension.getURL("image.png"), "loaded");
 
     let testScriptElement = document.createElement("script");
-    testScriptElement.setAttribute("src", browser.extension.getURL("test_script.js"));
+    // Set the src via wrappedJSObject so the load is triggered with the
+    // content page's principal rather than ours.
+    testScriptElement.wrappedJSObject.setAttribute("src", browser.extension.getURL("test_script.js"));
     document.head.appendChild(testScriptElement);
 
     window.addEventListener("message", event => {
       browser.runtime.sendMessage(event.data);
     });
   }
 
   function testScript() {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
@@ -437,16 +437,21 @@ add_task(async function test_contentscri
       element: ["img", {}],
       src: "img.png",
     },
     {
       element: ["img", {}],
       src: "imgset.png",
       srcAttr: "srcset",
     },
+    {
+      element: ["script", {}],
+      src: "script.js",
+      liveSrc: false,
+    },
   ];
 
   /**
    * A set of sources for which each of the above tests is expected to
    * generate one request, if each of the properties in the value object
    * matches the value of the same property in the test object.
    */
   const SOURCES = {