Bug 1406278: Part 3 - Use subject principal as triggering principal in <script> "src" attribute. r?bz draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 04 Oct 2017 22:16:32 -0700
changeset 676198 a088266e0fdc6b63d9802f49acf8639de72acae4
parent 676197 cf85aa9c084a1f8721f3d9cd221a468062f74a8e
child 676199 e708771b3b7829fa45e7b85379349336a79afaeb
push id83423
push usermaglione.k@gmail.com
push dateFri, 06 Oct 2017 20:33:12 +0000
reviewersbz
bugs1406278
milestone58.0a1
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& aPrincipal, ErrorResult& rv)
 {
-  rv = SetAttrHelper(nsGkAtoms::src, aSrc);
+  SetHTMLAttr(nsGkAtoms::src, aSrc, aPrincipal, rv);
 }
 
 void
 HTMLScriptElement::SetType(const nsAString& aType, ErrorResult& rv)
 {
   SetHTMLAttr(nsGkAtoms::type, aType, rv);
 }
 
@@ -237,16 +237,20 @@ HTMLScriptElement::AfterSetAttr(int32_t 
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aSubjectPrincipal,
                                 bool aNotify)
 {
   if (nsGkAtoms::async == aName && kNameSpaceID_None == aNamespaceID) {
     mForceAsync = false;
   }
+  if (nsGkAtoms::src == aName && kNameSpaceID_None == aNamespaceID) {
+    mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
+        this, aValue->GetStringValue(), aSubjectPrincipal);
+  }
   return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
                                             aOldValue, aSubjectPrincipal, aNotify);
 }
 
 NS_IMETHODIMP
 HTMLScriptElement::GetInnerHTML(nsAString& aInnerHTML)
 {
   if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -62,17 +62,21 @@ public:
                                 nsIPrincipal* aSubjectPrincipal,
                                 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& aPrincipal, 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";
@@ -3099,16 +3108,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
@@ -430,16 +430,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 = {