Bug 1428473 Support X-Content-Type-Options: nosniff when navigating r=ckerschb,dragana,alchen

***
Apply Requested Revision

Differential Revision: https://phabricator.services.mozilla.com/D33959

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Sebastian Streich 2019-07-31 16:59:53 +00:00
Родитель 33327053e3
Коммит 6917b697b8
15 изменённых файлов: 279 добавлений и 6 удалений

Просмотреть файл

@ -0,0 +1,32 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","*/*"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

Просмотреть файл

@ -0,0 +1,33 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case 'js':
return `<script> alert("This shouldt not be executed"); </script>`
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","garbage/garbage"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

Просмотреть файл

@ -0,0 +1,33 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case 'js':
return `<script> alert("This shouldt not be executed"); </script>`
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","picture/png"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

Просмотреть файл

@ -2,6 +2,9 @@
support-files =
file_contentpolicytype_targeted_link_iframe.sjs
file_nosniff_testserver.sjs
file_nosniff_navigation.sjs
file_nosniff_navigation_mismatch.sjs
file_nosniff_navigation_garbage.sjs
file_block_script_wrong_mime_server.sjs
file_block_toplevel_data_navigation.html
file_block_toplevel_data_navigation2.html
@ -24,6 +27,7 @@ support-files =
[test_contentpolicytype_targeted_link_iframe.html]
[test_nosniff.html]
[test_nosniff_navigation.html]
[test_block_script_wrong_mime.html]
[test_block_toplevel_data_navigation.html]
skip-if = toolkit == 'android' # intermittent failure

Просмотреть файл

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1428473 Support X-Content-Type-Options: nosniff when navigating</title>
<!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
iframe{
border: 1px solid orange;
}
</style>
<iframe src="file_nosniff_navigation.sjs?xml"> </iframe>
<iframe src="file_nosniff_navigation.sjs?html"></iframe>
<iframe src="file_nosniff_navigation.sjs?css" ></iframe>
<iframe src="file_nosniff_navigation.sjs?json"></iframe>
<iframe src="file_nosniff_navigation.sjs?img"></iframe>
<hr>
<iframe src="file_nosniff_navigation_mismatch.sjs?html"></iframe>
<iframe src="file_nosniff_navigation_mismatch.sjs?xml"></iframe>
<iframe src="file_nosniff_navigation_mismatch.sjs"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?xml"> </iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?html"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?css" ></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?json"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?img"></iframe>
</head>
<body>
<!-- add the two script tests -->
<script id="scriptCorrectType"></script>
<script id="scriptWrongType"></script>
<script class="testbody" type="text/javascript">
/* Description of the test:
* We're testing if Firefox respects the nosniff Header for Top-Level
* Navigations.
* If Firefox cant Display the Page, it will prompt a download
* and the URL of the Page will be about:blank.
* So we will try to open different content send with
* no-mime, mismatched-mime and garbage-mime types.
*
*/
SimpleTest.waitForExplicitFinish();
window.addEventListener("load", ()=>{
let iframes = Array.from(document.querySelectorAll("iframe"));
iframes.forEach( frame => {
let result = frame.contentWindow.document.URL == "about:blank";
let sniffTarget = (new URL(frame.src)).search;
ok(result, `${sniffTarget} - was not Sniffed`);
});
SimpleTest.finish();
});
</script>
</body>
</html>

Просмотреть файл

@ -584,6 +584,7 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo,
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetDocumentHasLoaded(), cspNonce,
aLoadInfo->GetSkipContentSniffing(),
aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieSettingsArgs,
aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo));
@ -746,7 +747,7 @@ nsresult LoadInfoArgsToLoadInfo(
loadInfoArgs.serviceWorkerTaintingSynthesized(),
loadInfoArgs.documentHasUserInteracted(),
loadInfoArgs.documentHasLoaded(), loadInfoArgs.cspNonce(),
loadInfoArgs.requestBlockingReason());
loadInfoArgs.skipContentSniffing(), loadInfoArgs.requestBlockingReason());
if (loadInfoArgs.isFromProcessingFrameAttributes()) {
loadInfo->SetIsFromProcessingFrameAttributes();
@ -761,6 +762,7 @@ void LoadInfoToParentLoadInfoForwarder(
if (!aLoadInfo) {
*aForwarderArgsOut = ParentLoadInfoForwarderArgs(
false, false, Nothing(), nsILoadInfo::TAINTING_BASIC,
false, // SkipContentSniffing
false, // serviceWorkerTaintingSynthesized
false, // documentHasUserInteracted
false, // documentHasLoaded
@ -792,6 +794,7 @@ void LoadInfoToParentLoadInfoForwarder(
*aForwarderArgsOut = ParentLoadInfoForwarderArgs(
aLoadInfo->GetAllowInsecureRedirectToDataURI(),
aLoadInfo->GetBypassCORSChecks(), ipcController, tainting,
aLoadInfo->GetSkipContentSniffing(),
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetDocumentHasLoaded(), cookieSettingsArgs,
@ -826,6 +829,9 @@ nsresult MergeParentLoadInfoForwarder(
aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting());
}
rv = aLoadInfo->SetSkipContentSniffing(aForwarderArgs.skipContentSniffing());
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
aForwarderArgs.documentHasUserInteracted()));
MOZ_ALWAYS_SUCCEEDS(

Просмотреть файл

@ -97,6 +97,7 @@ LoadInfo::LoadInfo(
mServiceWorkerTaintingSynthesized(false),
mDocumentHasUserInteracted(false),
mDocumentHasLoaded(false),
mSkipContentSniffing(false),
mIsFromProcessingFrameAttributes(false) {
MOZ_ASSERT(mLoadingPrincipal);
MOZ_ASSERT(mTriggeringPrincipal);
@ -358,6 +359,7 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
mServiceWorkerTaintingSynthesized(false),
mDocumentHasUserInteracted(false),
mDocumentHasLoaded(false),
mSkipContentSniffing(false),
mIsFromProcessingFrameAttributes(false) {
// Top-level loads are never third-party
// Grab the information we can out of the window.
@ -475,6 +477,7 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
mDocumentHasLoaded(rhs.mDocumentHasLoaded),
mCspNonce(rhs.mCspNonce),
mSkipContentSniffing(rhs.mSkipContentSniffing),
mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes) {}
LoadInfo::LoadInfo(
@ -508,7 +511,7 @@ LoadInfo::LoadInfo(
bool aIsPreflight, bool aLoadTriggeredFromExternal,
bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
bool aDocumentHasLoaded, const nsAString& aCspNonce,
uint32_t aRequestBlockingReason)
bool aSkipContentSniffing, uint32_t aRequestBlockingReason)
: mLoadingPrincipal(aLoadingPrincipal),
mTriggeringPrincipal(aTriggeringPrincipal),
mPrincipalToInherit(aPrincipalToInherit),
@ -559,6 +562,7 @@ LoadInfo::LoadInfo(
mDocumentHasUserInteracted(aDocumentHasUserInteracted),
mDocumentHasLoaded(aDocumentHasLoaded),
mCspNonce(aCspNonce),
mSkipContentSniffing(aSkipContentSniffing),
mIsFromProcessingFrameAttributes(false) {
// Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
MOZ_ASSERT(mLoadingPrincipal ||
@ -1321,6 +1325,18 @@ LoadInfo::SetCspNonce(const nsAString& aCspNonce) {
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
*aSkipContentSniffing = mSkipContentSniffing;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
mSkipContentSniffing = aSkipContentSniffing;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetIsTopLevelLoad(bool* aResult) {
*aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID

Просмотреть файл

@ -154,7 +154,8 @@ class LoadInfo final : public nsILoadInfo {
bool aIsPreflight, bool aLoadTriggeredFromExternal,
bool aServiceWorkerTaintingSynthesized,
bool aDocumentHasUserInteracted, bool aDocumentHasLoaded,
const nsAString& aCspNonce, uint32_t aRequestBlockingReason);
const nsAString& aCspNonce, bool aSkipContentSniffing,
uint32_t aRequestBlockingReason);
LoadInfo(const LoadInfo& rhs);
NS_IMETHOD GetRedirects(JSContext* aCx,
@ -246,6 +247,7 @@ class LoadInfo final : public nsILoadInfo {
bool mDocumentHasUserInteracted;
bool mDocumentHasLoaded;
nsString mCspNonce;
bool mSkipContentSniffing;
// Is true if this load was triggered by processing the attributes of the
// browsing context container.

Просмотреть файл

@ -413,6 +413,15 @@ interface nsILoadInfo : nsISupports
*/
[infallible] readonly attribute unsigned long securityMode;
/**
* This flag is used for any browsing context where we should not sniff
* the content type. E.g if an iframe has the XCTO nosniff header, then
* that flag is set to true so we skip content sniffing for that browsing
* context.
*/
[infallible] attribute boolean skipContentSniffing;
/**
* True if this request is embedded in a context that can't be third-party
* (i.e. an iframe embedded in a cross-origin parent window). If this is

Просмотреть файл

@ -2703,6 +2703,15 @@ nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
const uint8_t* aData, uint32_t aLength,
nsACString& aSniffedType) {
// In case XCTO nosniff was present, we could just skip sniffing here
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if (channel) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
aSniffedType.Truncate();
return;
}
}
typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
extern ContentSnifferCache* gNetSniffers;
extern ContentSnifferCache* gDataSniffers;

Просмотреть файл

@ -139,6 +139,7 @@ struct LoadInfoArgs
bool documentHasUserInteracted;
bool documentHasLoaded;
nsString cspNonce;
bool skipContentSniffing;
bool isFromProcessingFrameAttributes;
CookieSettingsArgs cookieSettings;
uint32_t requestBlockingReason;
@ -172,6 +173,12 @@ struct ParentLoadInfoForwarderArgs
// tainting value.
uint32_t tainting;
// This flag is used for any browsing context where we should not sniff
// the content type. E.g if an iframe has the XCTO nosniff header, then
// that flag is set to true so we skip content sniffing for that browsing
bool skipContentSniffing;
// We must also note that the tainting value was explicitly set
// by the service worker.
bool serviceWorkerTaintingSynthesized;

Просмотреть файл

@ -17,6 +17,7 @@
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.h"
#include "nsString.h"
#include "nsIApplicationCacheService.h"
#include "nsIApplicationCacheContainer.h"
#include "nsICacheStorageService.h"
@ -1449,6 +1450,16 @@ nsresult ProcessXCTO(nsHttpChannel* aChannel, nsIURI* aURI,
Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
auto policyType = aLoadInfo->GetExternalContentPolicyType();
if (policyType == nsIContentPolicy::TYPE_DOCUMENT ||
policyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
// If the header XCTO nosniff is set for any browsing context, then
// we set the skipContentSniffing flag on the Loadinfo. Within
// NS_SniffContent we then bail early and do not do any sniffing.
aLoadInfo->SetSkipContentSniffing(true);
return NS_OK;
}
return NS_OK;
}

Просмотреть файл

@ -326,6 +326,11 @@ nsUnknownDecoder::GetMIMETypeFromContent(nsIRequest* aRequest,
nsACString& type) {
// This is only used by sniffer, therefore we do not need to lock anything
// here.
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return NS_OK;
}
mBuffer = const_cast<char*>(reinterpret_cast<const char*>(aData));
mBufferLen = aLength;
@ -355,6 +360,11 @@ bool nsUnknownDecoder::AllowSniffing(nsIRequest* aRequest) {
return false;
}
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return false;
}
bool isLocalFile = false;
if (NS_FAILED(uri->SchemeIs("file", &isLocalFile)) || isLocalFile) {
return false;
@ -401,10 +411,17 @@ void nsUnknownDecoder::DetermineContentType(nsIRequest* aRequest) {
if (!mContentType.IsEmpty()) return;
}
nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(aRequest));
if (channel) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return;
}
}
const char* testData = mBuffer;
uint32_t testDataLen = mBufferLen;
// Check if data are compressed.
nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(aRequest));
nsAutoCString decodedData;
if (channel) {
@ -575,6 +592,11 @@ bool nsUnknownDecoder::SniffForXML(nsIRequest* aRequest) {
}
bool nsUnknownDecoder::SniffURI(nsIRequest* aRequest) {
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return false;
}
nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1"));
if (mimeService) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
@ -606,6 +628,12 @@ bool nsUnknownDecoder::LastDitchSniff(nsIRequest* aRequest) {
// All we can do now is try to guess whether this is text/plain or
// application/octet-stream
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return false;
}
MutexAutoLock lock(mMutex);
const char* testData;
@ -827,6 +855,10 @@ void nsBinaryDetector::DetermineContentType(nsIRequest* aRequest) {
return;
}
nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
if (loadInfo->GetSkipContentSniffing()) {
return;
}
// It's an HTTP channel. Check for the text/plain mess
nsAutoCString contentTypeHdr;
Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),

Просмотреть файл

@ -190,7 +190,8 @@ nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
mFlushTimerMutex("nsHtml5StreamParser mFlushTimerMutex"),
mFlushTimerArmed(false),
mFlushTimerEverFired(false),
mMode(aMode) {
mMode(aMode),
mSkipContentSniffing(false) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
#ifdef DEBUG
mAtomTable.SetPermittedLookupEventTarget(mEventTarget);
@ -631,7 +632,7 @@ nsresult nsHtml5StreamParser::FinalizeSniffing(Span<const uint8_t> aFromSegment,
}
// meta scan failed.
if (mCharsetSource < kCharsetFromMetaPrescan) {
if (!mSkipContentSniffing && mCharsetSource < kCharsetFromMetaPrescan) {
// Check for BOMless UTF-16 with Basic
// Latin content for compat with IE. See bug 631751.
SniffBOMlessUTF16BasicLatin(aFromSegment.To(aCountToSniffingLimit));
@ -662,6 +663,7 @@ nsresult nsHtml5StreamParser::SniffStreamBytes(
Span<const uint8_t> aFromSegment) {
NS_ASSERTION(IsParserThread(), "Wrong thread!");
nsresult rv = NS_OK;
// mEncoding and mCharsetSource potentially have come from channel or higher
// by now. If we find a BOM, SetupDecodingFromBom() will overwrite them.
// If we don't find a BOM, the previously set values of mEncoding and
@ -957,6 +959,13 @@ nsresult nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest) {
mObserver->OnStartRequest(aRequest);
}
mRequest = aRequest;
nsCOMPtr<nsIChannel> myChannel(do_QueryInterface(aRequest));
nsCOMPtr<nsILoadInfo> loadInfo = myChannel->LoadInfo();
mSkipContentSniffing = loadInfo->GetSkipContentSniffing();
if (mSkipContentSniffing) {
mFeedChardet = false;
}
mStreamState = STREAM_BEING_READ;

Просмотреть файл

@ -594,6 +594,11 @@ class nsHtml5StreamParser final : public nsICharsetDetectionObserver {
* Whether the parser is doing a normal parse, view source or plain text.
*/
eParserMode mMode;
/**
* Whether the parser should not sniff the content type.
*/
bool mSkipContentSniffing;
};
#endif // nsHtml5StreamParser_h