Bug 1208629 - Properly support data: and blob: URIs with an integrity atribute. r=ckerschb
authorFrancois Marier <francois@mozilla.com>
Wed, 07 Oct 2015 11:27:19 -0700
changeset 266715 fbd07ab0e098ffd9e429ccbc8958126cbe6b8b6a
parent 266714 b268160e8919390c1a9f61a75d3f3b3c79491f77
child 266716 f700d1c3679fb1b0e4fb7b8f45f05d44d437a7bb
push id29497
push usercbook@mozilla.com
push dateThu, 08 Oct 2015 13:27:17 +0000
treeherdermozilla-central@1f4cf75c8948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb
bugs1208629
milestone44.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 1208629 - Properly support data: and blob: URIs with an integrity atribute. r=ckerschb
dom/base/nsScriptLoader.cpp
dom/security/SRICheck.cpp
dom/security/SRICheck.h
dom/security/test/sri/iframe_script_crossdomain.html
dom/security/test/sri/iframe_script_sameorigin.html
dom/security/test/sri/iframe_style_crossdomain.html
dom/security/test/sri/iframe_style_sameorigin.html
dom/security/test/sri/mochitest.ini
dom/security/test/sri/style1.css^headers^
layout/style/Loader.cpp
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -1420,26 +1420,19 @@ nsScriptLoader::OnStreamComplete(nsIStre
                                  nsresult aStatus,
                                  uint32_t aStringLen,
                                  const uint8_t* aString)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
-  nsCOMPtr<nsIHttpChannel> httpChannel;
-  {
-    nsCOMPtr<nsIRequest> req;
-    aLoader->GetRequest(getter_AddRefs(req));
-    httpChannel = do_QueryInterface(req);
-  } // throw away req, we only need the channel
-
   nsresult rv = NS_ERROR_SRI_CORRUPT;
   if (request->mIntegrity.IsEmpty() ||
-      NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity, httpChannel,
+      NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity, aLoader,
                                              request->mCORSMode, aStringLen,
                                              aString, mDocument))) {
     rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
   }
 
   if (NS_FAILED(rv)) {
     /*
      * Handle script not loading error because source was a tracking URL.
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -11,16 +11,18 @@
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsICryptoHash.h"
 #include "nsIDocument.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsIStreamLoader.h"
+#include "nsIUnicharStreamLoader.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsWhitespaceTokenizer.h"
 
 static PRLogModuleInfo*
 GetSriLog()
 {
   static PRLogModuleInfo *gSriPRLog;
@@ -40,19 +42,23 @@ namespace dom {
  * Returns whether or not the sub-resource about to be loaded is eligible
  * for integrity checks. If it's not, the checks will be skipped and the
  * sub-resource will be loaded.
  */
 static nsresult
 IsEligible(nsIChannel* aChannel, const CORSMode aCORSMode,
            const nsIDocument* aDocument)
 {
-  NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aDocument);
 
+  if (!aChannel) {
+    SRILOG(("SRICheck::IsEligible, null channel"));
+    return NS_ERROR_SRI_NOT_ELIGIBLE;
+  }
+
   // Was the sub-resource loaded via CORS?
   if (aCORSMode != CORS_NONE) {
     SRILOG(("SRICheck::IsEligible, CORS mode"));
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> finalURI;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
@@ -231,48 +237,24 @@ SRICheck::IntegrityMetadata(const nsAStr
       SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
     } else {
       SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
     }
   }
   return NS_OK;
 }
 
-/* static */ nsresult
-SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
-                          nsIChannel* aChannel,
-                          const CORSMode aCORSMode,
-                          const nsAString& aString,
-                          const nsIDocument* aDocument)
+static nsresult
+VerifyIntegrityInternal(const SRIMetadata& aMetadata,
+                        nsIChannel* aChannel,
+                        const CORSMode aCORSMode,
+                        uint32_t aStringLen,
+                        const uint8_t* aString,
+                        const nsIDocument* aDocument)
 {
-  NS_ConvertUTF16toUTF8 utf8Hash(aString);
-  return VerifyIntegrity(aMetadata, aChannel, aCORSMode, utf8Hash.Length(),
-                         (uint8_t*)utf8Hash.get(), aDocument);
-}
-
-/* static */ nsresult
-SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
-                          nsIChannel* aChannel,
-                          const CORSMode aCORSMode,
-                          uint32_t aStringLen,
-                          const uint8_t* aString,
-                          const nsIDocument* aDocument)
-{
-  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
-    nsAutoCString requestURL;
-    nsCOMPtr<nsIURI> originalURI;
-    if (NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
-        originalURI) {
-      originalURI->GetAsciiSpec(requestURL);
-      // requestURL will be empty if GetAsciiSpec fails
-    }
-    SRILOG(("SRICheck::VerifyIntegrity, url=%s (length=%u)",
-            requestURL.get(), aStringLen));
-  }
-
   MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
 
   // IntegrityMetadata() checks this and returns "no metadata" if
   // it's disabled so we should never make it this far
   MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
 
   if (NS_FAILED(IsEligible(aChannel, aCORSMode, aDocument))) {
     return NS_ERROR_SRI_NOT_ELIGIBLE;
@@ -301,10 +283,67 @@ SRICheck::VerifyIntegrity(const SRIMetad
                                   NS_LITERAL_CSTRING("Sub-resource Integrity"),
                                   aDocument,
                                   nsContentUtils::eSECURITY_PROPERTIES,
                                   "IntegrityMismatch",
                                   params, ArrayLength(params));
   return NS_ERROR_SRI_CORRUPT;
 }
 
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIUnicharStreamLoader* aLoader,
+                          const CORSMode aCORSMode,
+                          const nsAString& aString,
+                          const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aLoader);
+
+  NS_ConvertUTF16toUTF8 utf8Hash(aString);
+  nsCOMPtr<nsIChannel> channel;
+  aLoader->GetChannel(getter_AddRefs(channel));
+
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString requestURL;
+    nsCOMPtr<nsIURI> originalURI;
+    if (channel &&
+        NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
+        originalURI) {
+      originalURI->GetAsciiSpec(requestURL);
+    }
+    SRILOG(("SRICheck::VerifyIntegrity (unichar stream), url=%s (length=%u)",
+            requestURL.get(), utf8Hash.Length()));
+  }
+
+  return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
+                                 utf8Hash.Length(), (uint8_t*)utf8Hash.get(),
+                                 aDocument);
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIStreamLoader* aLoader,
+                          const CORSMode aCORSMode,
+                          uint32_t aStringLen,
+                          const uint8_t* aString,
+                          const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aLoader);
+
+  nsCOMPtr<nsIRequest> request;
+  aLoader->GetRequest(getter_AddRefs(request));
+  NS_ENSURE_ARG_POINTER(request);
+  nsCOMPtr<nsIChannel> channel;
+  channel = do_QueryInterface(request);
+
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString requestURL;
+    request->GetName(requestURL);
+    SRILOG(("SRICheck::VerifyIntegrity (stream), url=%s (length=%u)",
+            requestURL.get(), aStringLen));
+  }
+
+  return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
+                                 aStringLen, aString, aDocument);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/security/SRICheck.h
+++ b/dom/security/SRICheck.h
@@ -6,21 +6,19 @@
 
 #ifndef mozilla_dom_SRICheck_h
 #define mozilla_dom_SRICheck_h
 
 #include "mozilla/CORSMode.h"
 #include "nsCOMPtr.h"
 #include "SRIMetadata.h"
 
-class nsIChannel;
 class nsIDocument;
-class nsIScriptSecurityManager;
 class nsIStreamLoader;
-class nsIURI;
+class nsIUnicharStreamLoader;
 
 namespace mozilla {
 namespace dom {
 
 class SRICheck final
 {
 public:
   static const uint32_t MAX_METADATA_LENGTH = 24*1024;
@@ -34,27 +32,27 @@ public:
                                     const nsIDocument* aDocument,
                                     SRIMetadata* outMetadata);
 
   /**
    * Process the integrity attribute of the element.  A result of false
    * must prevent the resource from loading.
    */
   static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
-                                  nsIChannel* aChannel,
+                                  nsIUnicharStreamLoader* aLoader,
                                   const CORSMode aCORSMode,
                                   const nsAString& aString,
                                   const nsIDocument* aDocument);
 
   /**
    * Process the integrity attribute of the element.  A result of false
    * must prevent the resource from loading.
    */
   static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
-                                  nsIChannel* aChannel,
+                                  nsIStreamLoader* aLoader,
                                   const CORSMode aCORSMode,
                                   uint32_t aStringLen,
                                   const uint8_t* aString,
                                   const nsIDocument* aDocument);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/security/test/sri/iframe_script_crossdomain.html
+++ b/dom/security/test/sri/iframe_script_crossdomain.html
@@ -53,16 +53,29 @@
 
   function good_correct301Blocked() {
     ok(true, "A non-CORS load with correct hash redirected to a different origin was blocked correctly.");
   }
   function bad_correct301Loaded() {
     ok(false, "Non-CORS loads with correct hashes redirecting to a different origin should be blocked!");
   }
 
+  function good_correctDataBlocked() {
+    ok(true, "A data: URL was blocked correctly.");
+  }
+  function bad_correctDataLoaded() {
+    ok(false, "Since data: URLs are neither same-origin nor CORS, they should be blocked!");
+  }
+  function good_correctDataCORSBlocked() {
+    ok(true, "A data: URL was blocked correctly even though it was a CORS load.");
+  }
+  function bad_correctDataCORSLoaded() {
+    todo(false, "We should not load scripts in data: URIs regardless of CORS mode!");
+  }
+
   window.onload = function() {
     SimpleTest.finish()
   }
 </script>
 
 <!-- cors-enabled. should be loaded -->
 <script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
         crossorigin=""
@@ -94,16 +107,29 @@
         onload="bad_incorrect301Loaded()"></script>
 
 <!-- non-cors that's same-origin initially but redirected to another origin -->
 <script src="script_301.js"
         integrity="sha384-1NpiDI6decClMaTWSCAfUjTdx1BiOffsCPgH4lW5hCLwmHk0VyV/g6B9Sw2kD2K3"
         onerror="good_correct301Blocked()"
         onload="bad_correct301Loaded()"></script>
 
+<!-- data: URLs are not same-origin -->
+<script src="data:,console.log('data:valid');"
+        integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
+        onerror="good_correctDataBlocked()"
+        onload="bad_correctDataLoaded()"></script>
+
+<!-- data: URLs should always be opaque -->
+<script src="data:,console.log('data:valid');"
+        crossorigin="anonymous"
+        integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
+        onerror="good_correctDataCORSBlocked()"
+        onload="bad_correctDataCORSLoaded()"></script>
+
 <script>
   ok(window.hasCORSLoaded, "CORS-enabled resource with a correct hash");
   ok(!window.hasNonCORSLoaded, "Correct hash, but non-CORS, should be blocked");
   ok(!window.onloadCalled, "Failed loads should not call onload when they're cross-domain");
   ok(window.onerrorCalled, "Failed loads should call onerror when they're cross-domain");
 </script>
 </body>
 </html>
--- a/dom/security/test/sri/iframe_script_sameorigin.html
+++ b/dom/security/test/sri/iframe_script_sameorigin.html
@@ -97,17 +97,31 @@
     }
 
     function good_invalid302Blocked() {
       ok(true, "A script was blocked successfully after a 302 response.");
     }
     function bad_invalid302Loaded() {
       ok(false, "We should not load scripts with a 302 response and the wrong hash!");
     }
-  </script>
+
+    function good_validBlobLoaded() {
+      ok(true, "A script was loaded successfully from a blob: URL.");
+    }
+    function bad_validBlobBlocked() {
+      ok(false, "We should load scripts using blob: URLs with the right hash!");
+    }
+
+    function good_invalidBlobBlocked() {
+      ok(true, "A script was blocked successfully from a blob: URL.");
+    }
+    function bad_invalidBlobLoaded() {
+      ok(false, "We should not load scripts using blob: URLs with the wrong hash!");
+    }
+</script>
 </head>
 <body>
   <!-- valid hash. should trigger onload -->
   <!-- the hash value comes from running this command:
        cat script.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
   -->
   <script src="script.js"
           integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
@@ -195,15 +209,41 @@
           onload="good_valid302Loaded()"></script>
 
   <!-- invalid sha256 after a redirection. should trigger onerror -->
   <script src="script_302.js"
           integrity="sha256-JSi74NSN8WQNr9syBGmNg2APJp9PnHUO5ioZo5hmIiQ="
           onerror="good_invalid302Blocked()"
           onload="bad_invalid302Loaded()"></script>
 
+  <!-- valid sha256 for a blob: URL -->
+  <script>
+   var blob = new Blob(["console.log('blob:valid');"],
+                       {type:"application/javascript"});
+   var script = document.createElement('script');
+   script.setAttribute('src', URL.createObjectURL(blob));
+   script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
+   script.onerror = bad_validBlobBlocked;
+   script.onload = good_validBlobLoaded;
+   var head = document.getElementsByTagName('head').item(0);
+   head.appendChild(script);
+  </script>
+
+  <!-- invalid sha256 for a blob: URL -->
+  <script>
+   var blob = new Blob(["console.log('blob:invalid');"],
+                       {type:"application/javascript"});
+   var script = document.createElement('script');
+   script.setAttribute('src', URL.createObjectURL(blob));
+   script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
+   script.onerror = good_invalidBlobBlocked;
+   script.onload = bad_invalidBlobLoaded;
+   var head = document.getElementsByTagName('head').item(0);
+   head.appendChild(script);
+  </script>
+
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 </body>
 </html>
--- a/dom/security/test/sri/iframe_style_crossdomain.html
+++ b/dom/security/test/sri/iframe_style_crossdomain.html
@@ -1,51 +1,100 @@
 <!DOCTYPE HTML>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
 <html>
 <head>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+    function check_styles() {
+      var redText = document.getElementById('red-text');
+      var blackText = document.getElementById('black-text');
+      var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
+      var blackTextColor = window.getComputedStyle(blackText, null).getPropertyValue('color');
+      ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
+      todo(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
+    }
+
     SimpleTest.waitForExplicitFinish();
     window.onload = function() {
+      check_styles();
       SimpleTest.finish();
     }
   </script>
   <script>
+    function good_correctHashCORSLoaded() {
+      ok(true, "A CORS cross-domain stylesheet with correct hash was correctly loaded.");
+    }
+    function bad_correctHashCORSBlocked() {
+      ok(false, "We should load CORS cross-domain stylesheets with hashes that match!");
+    }
     function good_correctHashBlocked() {
       ok(true, "A non-CORS cross-domain stylesheet with correct hash was correctly blocked.");
     }
     function bad_correctHashLoaded() {
       ok(false, "We should block non-CORS cross-domain stylesheets with hashes that match!");
     }
 
     function good_incorrectHashBlocked() {
       ok(true, "A non-CORS cross-domain stylesheet with incorrect hash was correctly blocked.");
     }
     function bad_incorrectHashLoaded() {
       ok(false, "We should load non-CORS cross-domain stylesheets with incorrect hashes!");
     }
 
+    function good_correctDataBlocked() {
+      ok(true, "A stylesheet was correctly blocked, because it came from a data: URI.");
+    }
+    function bad_correctDataLoaded() {
+      ok(false, "We should not load stylesheets in data: URIs!");
+    }
+    function good_correctDataCORSBlocked() {
+      ok(true, "A stylesheet was correctly blocked, because it came from a data: URI even though it was a CORS load.");
+    }
+    function bad_correctDataCORSLoaded() {
+      todo(false, "We should not load stylesheets in data: URIs regardless of CORS mode!");
+    }
   </script>
 
-  <!-- valid non-CORS sha256 hash. should trigger onload -->
+  <!-- valid CORS sha256 hash -->
+  <link rel="stylesheet" href="http://example.com/tests/dom/security/test/sri/style1.css"
+        crossorigin="anonymous"
+        integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+        onerror="bad_correctHashCORSBlocked()"
+        onload="good_correctHashCORSLoaded()">
+
+  <!-- valid non-CORS sha256 hash -->
   <link rel="stylesheet" href="style_301.css"
         integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
         onerror="good_correctHashBlocked()"
         onload="bad_correctHashLoaded()">
 
-  <!-- invalid non-CORS sha256 hash. should trigger onload -->
+  <!-- invalid non-CORS sha256 hash -->
   <link rel="stylesheet" href="style_301.css?again"
         integrity="sha256-bogus"
         onerror="good_incorrectHashBlocked()"
         onload="bad_incorrectHashLoaded()">
+
+  <!-- valid non-CORS sha256 hash in a data: URL -->
+  <link rel="stylesheet" href="data:text/css,.red-text{color:red}"
+        integrity="sha256-ewUcnAs4+XY5k2JpfUQGFdG5YMZkq80/nIKW67kd7vE="
+        onerror="good_correctDataBlocked()"
+        onload="bad_correctDataLoaded()">
+
+  <!-- valid CORS sha256 hash in a data: URL -->
+  <link rel="stylesheet" href="data:text/css,.red-text{color:red}"
+        crossorigin="anonymous"
+        integrity="sha256-ewUcnAs4+XY5k2JpfUQGFdG5YMZkq80/nIKW67kd7vE="
+        onerror="good_correctDataCORSBlocked()"
+        onload="bad_correctDataCORSLoaded()">
 </head>
 <body>
-<p><span id="red-text">This should be red.</span></p>
+<p><span id="red-text">This should be red</span> but
+  <span id="black-text" class="red-text">this should remain black.</span></p>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 </body>
 </html>
--- a/dom/security/test/sri/iframe_style_sameorigin.html
+++ b/dom/security/test/sri/iframe_style_sameorigin.html
@@ -3,21 +3,27 @@
      http://creativecommons.org/publicdomain/zero/1.0/ -->
 <html>
 <head>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
     function check_styles() {
       var redText = document.getElementById('red-text');
-      var blackText = document.getElementById('black-text');
+      var blueText = document.getElementById('blue-text-element');
+      var blackText1 = document.getElementById('black-text');
+      var blackText2 = document.getElementById('black-text-2');
       var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
-      var blackTextColor = window.getComputedStyle(blackText, null).getPropertyValue('color');
+      var blueTextColor = window.getComputedStyle(blueText, null).getPropertyValue('color');
+      var blackTextColor1 = window.getComputedStyle(blackText1, null).getPropertyValue('color');
+      var blackTextColor2 = window.getComputedStyle(blackText2, null).getPropertyValue('color');
       ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
-      ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
+      ok(blueTextColor == 'rgb(0, 0, 255)', "The second part should be blue.");
+      ok(blackTextColor1 == 'rgb(0, 0, 0)', "The second last part should still be black.");
+      ok(blackTextColor2 == 'rgb(0, 0, 0)', "The last part should still be black.");
     }
 
     SimpleTest.waitForExplicitFinish();
     window.onload = function() {
       check_styles();
       SimpleTest.finish();
     }
   </script>
@@ -37,16 +43,29 @@
     }
 
     function good_incorrectHashBlocked() {
       ok(true, "A stylesheet was correctly blocked, because the hash digest was wrong");
     }
     function bad_incorrectHashLoaded() {
       ok(false, "We should not load stylesheets with hashes that do not match the content!");
     }
+
+    function good_validBlobLoaded() {
+      ok(true, "A stylesheet was loaded successfully from a blob: URL with the right hash.");
+    }
+    function bad_validBlobBlocked() {
+      ok(false, "We should load stylesheets using blob: URLs with the right hash!");
+    }
+    function good_invalidBlobBlocked() {
+      ok(true, "A stylesheet was blocked successfully from a blob: URL with an invalid hash.");
+    }
+    function bad_invalidBlobLoaded() {
+      ok(false, "We should not load stylesheets using blob: URLs when they have the wrong hash!");
+    }
   </script>
 
   <!-- valid sha256 hash. should trigger onload -->
   <link rel="stylesheet" href="style1.css"
         integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
         onerror="bad_correctHashBlocked()"
         onload="good_correctHashLoaded()">
 
@@ -58,17 +77,48 @@
 
   <!-- invalid sha256 hash. should trigger onerror -->
   <link rel="stylesheet" href="style3.css"
         integrity="sha256-bogus"
         onerror="good_incorrectHashBlocked()"
         onload="bad_incorrectHashLoaded()">
 </head>
 <body>
+
+<!-- valid sha256 for a blob: URL -->
+<script>
+   var blob = new Blob(['.blue-text{color:blue}'],
+                       {type: 'text/css'});
+   var link = document.createElement('link');
+   link.rel = 'stylesheet';
+   link.href = window.URL.createObjectURL(blob);
+   link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
+   link.onerror = bad_validBlobBlocked;
+   link.onload = good_validBlobLoaded;
+   document.body.appendChild(link);
+</script>
+
+<!-- invalid sha256 for a blob: URL -->
+<script>
+   var blob = new Blob(['.black-text{color:blue}'],
+                       {type: 'text/css'});
+   var link = document.createElement('link');
+   link.rel = 'stylesheet';
+   link.href = window.URL.createObjectURL(blob);
+   link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
+   link.onerror = good_invalidBlobBlocked;
+   link.onload = bad_invalidBlobLoaded;
+   document.body.appendChild(link);
+</script>
+
 <p><span id="red-text">This should be red </span> and
-  <span id="black-text">this should stay black.</p>
+  <span class="blue-text" id="blue-text-element">this should be blue.</span>
+  However, <span id="black-text">this should stay black</span> and
+  <span class="black-text" id="black-text-2">this should also stay black.</span>
+</p>
+
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 </body>
 </html>
--- a/dom/security/test/sri/mochitest.ini
+++ b/dom/security/test/sri/mochitest.ini
@@ -18,16 +18,17 @@ support-files =
   script.js^headers^
   script_301.js
   script_301.js^headers^
   script_302.js
   script_302.js^headers^
   script_401.js
   script_401.js^headers^
   style1.css
+  style1.css^headers^
   style2.css
   style3.css
   style_301.css
   style_301.css^headers^
 
 [test_script_sameorigin.html]
 [test_script_crossdomain.html]
 [test_sri_disabled.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/style1.css^headers^
@@ -0,0 +1,1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -960,17 +960,17 @@ SheetLoadData::OnStreamComplete(nsIUnich
                 contentType.get()));
       mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
   }
 
   SRIMetadata sriMetadata = mSheet->GetIntegrity();
   if (!sriMetadata.IsEmpty() &&
-      NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, httpChannel,
+      NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, aLoader,
                                           mSheet->GetCORSMode(), aBuffer,
                                           mLoader->mDocument))) {
     LOG(("  Load was blocked by SRI"));
     MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
             ("css::Loader::OnStreamComplete, bad metadata"));
     mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
     return NS_OK;
   }