Bug 1208629 - Properly support data: and blob: URIs with an integrity atribute. r=ckerschb

This commit is contained in:
Francois Marier 2015-10-07 11:27:19 -07:00
parent 97f026f94b
commit 7e25502ebc
10 changed files with 251 additions and 54 deletions

View File

@ -1425,16 +1425,9 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
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);

View File

@ -16,6 +16,8 @@
#include "nsIProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIStreamLoader.h"
#include "nsIUnicharStreamLoader.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsWhitespaceTokenizer.h"
@ -45,9 +47,13 @@ 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"));
@ -236,38 +242,14 @@ SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
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
@ -306,5 +288,62 @@ SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
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

View File

@ -11,11 +11,9 @@
#include "nsCOMPtr.h"
#include "SRIMetadata.h"
class nsIChannel;
class nsIDocument;
class nsIScriptSecurityManager;
class nsIStreamLoader;
class nsIURI;
class nsIUnicharStreamLoader;
namespace mozilla {
namespace dom {
@ -39,7 +37,7 @@ public:
* 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);
@ -49,7 +47,7 @@ public:
* 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,

View File

@ -58,6 +58,19 @@
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()
}
@ -99,6 +112,19 @@
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");

View File

@ -102,7 +102,21 @@
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 -->
@ -200,6 +214,32 @@
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>

View File

@ -6,12 +6,28 @@
<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.");
}
@ -26,22 +42,55 @@
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>

View File

@ -8,11 +8,17 @@
<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();
@ -42,6 +48,19 @@
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 -->
@ -63,8 +82,39 @@
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>

View File

@ -23,6 +23,7 @@ support-files =
script_401.js
script_401.js^headers^
style1.css
style1.css^headers^
style2.css
style3.css
style_301.css

View File

@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888

View File

@ -965,7 +965,7 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
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"));