Bug 1909110 - Apply CSP to media that would be loaded as a MediaDocument. r=necko-reviewers,nika,freddyb
Differential Revision: https://phabricator.services.mozilla.com/D221326
This commit is contained in:
@@ -38,6 +38,7 @@ unwantedBlocked=The site at %S has been reported as serving unwanted software an
|
||||
deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
|
||||
cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
|
||||
xfoBlocked=This page has an X-Frame-Options policy that prevents it from being loaded in this context.
|
||||
cspMediaBlocked=This page has a content security policy that prevents it from being displayed.
|
||||
corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
|
||||
## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
|
||||
sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
|
||||
|
||||
@@ -3374,6 +3374,10 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
||||
// CSP error
|
||||
cssClass.AssignLiteral("neterror");
|
||||
error = "cspBlocked";
|
||||
} else if (NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT == aError) {
|
||||
// CSP MediaDocument error
|
||||
cssClass.AssignLiteral("neterror");
|
||||
error = "cspMediaBlocked";
|
||||
} else if (NS_ERROR_XFO_VIOLATION == aError) {
|
||||
// XFO error
|
||||
cssClass.AssignLiteral("neterror");
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsCharsetSource.h" // kCharsetFrom* macro definition
|
||||
#include "nsNodeInfoManager.h"
|
||||
#include "nsContentSecurityUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDocElementCreatedNotificationRunner.h"
|
||||
#include "mozilla/Encoding.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/Components.h"
|
||||
#include "mozilla/Encoding.h"
|
||||
#include "mozilla/NullPrincipal.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsIMultiPartChannel.h"
|
||||
@@ -144,6 +146,22 @@ nsresult MediaDocument::StartDocumentLoad(
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Bug 1909110: Block MediaDocument loads, if they are triggered by a page
|
||||
// that would block those loads coming from a normal e.g. <img> element.
|
||||
// (This is also checked by DocumentLoadListener::OnStartRequest)
|
||||
nsContentDLF::DocumentKind kind =
|
||||
MediaDocumentKind() == MediaDocumentKind::Image
|
||||
? nsContentDLF::DocumentKind::Image
|
||||
: nsContentDLF::DocumentKind::Video;
|
||||
if (!nsContentSecurityUtils::CheckCSPMediaDocumentLoad(aChannel, kind)) {
|
||||
aChannel->Cancel(NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT);
|
||||
// Ensure the document can't leak to the opener, similar to the CSP/XFO
|
||||
// check in Document::StartDocumentLoad.
|
||||
RefPtr<NullPrincipal> nullPrincipal =
|
||||
NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
|
||||
SetPrincipals(nullPrincipal, nullPrincipal);
|
||||
}
|
||||
|
||||
// We try to set the charset of the current document to that of the
|
||||
// 'genuine' (as opposed to an intervening 'chrome') parent document
|
||||
// that may be in a different window/tab. Even if we fail here,
|
||||
|
||||
@@ -31,6 +31,7 @@ unwantedBlocked=The site at %S has been reported as serving unwanted software an
|
||||
deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
|
||||
cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
|
||||
xfoBlocked=This page has an X-Frame-Options policy that prevents it from being loaded in this context.
|
||||
cspMediaBlocked=This page has a content security policy that prevents it from being displayed.
|
||||
corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
|
||||
sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
|
||||
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.
|
||||
|
||||
@@ -172,7 +172,7 @@ CanPlayStatus DecoderTraits::CanHandleContainerType(
|
||||
|
||||
/* static */
|
||||
bool DecoderTraits::ShouldHandleMediaType(
|
||||
const char* aMIMEType, DecoderDoctorDiagnostics* aDiagnostics) {
|
||||
const nsACString& aMIMEType, DecoderDoctorDiagnostics* aDiagnostics) {
|
||||
Maybe<MediaContainerType> containerType = MakeMediaContainerType(aMIMEType);
|
||||
if (!containerType) {
|
||||
return false;
|
||||
|
||||
@@ -34,7 +34,7 @@ class DecoderTraits {
|
||||
// as an <object> or as a toplevel page. If, in practice, our support
|
||||
// for the type is more limited than appears in the wild, we should return
|
||||
// false here even if CanHandleMediaType would return true.
|
||||
static bool ShouldHandleMediaType(const char* aMIMEType,
|
||||
static bool ShouldHandleMediaType(const nsACString& aMIMEType,
|
||||
DecoderDoctorDiagnostics* aDiagnostics);
|
||||
|
||||
// Create a demuxer for the given MIME type aType. Returns null if we
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsContentDLF.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
@@ -1139,6 +1140,54 @@ bool nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(nsIChannel* aChannel) {
|
||||
isFrameOptionsIgnored);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsContentSecurityUtils::CheckCSPMediaDocumentLoad(
|
||||
nsIChannel* aChannel, nsContentDLF::DocumentKind aKind) {
|
||||
using DocumentKind = nsContentDLF::DocumentKind;
|
||||
MOZ_ASSERT(aKind == DocumentKind::Image || aKind == DocumentKind::Video);
|
||||
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
||||
if (loadInfo->GetExternalContentPolicyType() !=
|
||||
ExtContentPolicy::TYPE_DOCUMENT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp = loadInfo->GetCspToInherit();
|
||||
if (!csp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal();
|
||||
if (!loadingPrincipal) {
|
||||
loadingPrincipal = loadInfo->TriggeringPrincipal();
|
||||
}
|
||||
|
||||
nsContentPolicyType contentPolicyType =
|
||||
aKind == DocumentKind::Image ? nsIContentPolicy::TYPE_IMAGE
|
||||
: nsIContentPolicy::TYPE_INTERNAL_VIDEO;
|
||||
|
||||
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
|
||||
loadingPrincipal, loadInfo->TriggeringPrincipal(), nullptr,
|
||||
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
rv = csp->ShouldLoad(secCheckLoadInfo->InternalContentPolicyType(),
|
||||
nullptr /* aCSPEventListener */, secCheckLoadInfo, uri,
|
||||
nullptr /* aOriginalURIIfRedirect */,
|
||||
loadInfo->GetSendCSPViolationEvents(), &shouldLoad);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
if (NS_CP_REJECTED(shouldLoad)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-csp/#is-element-nonceable
|
||||
/* static */
|
||||
nsString nsContentSecurityUtils::GetIsElementNonceableNonce(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <utility>
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsContentDLF.h"
|
||||
#include "nsStringFwd.h"
|
||||
|
||||
struct JSContext;
|
||||
@@ -66,6 +67,12 @@ class nsContentSecurityUtils {
|
||||
// 2. x-frame-options
|
||||
static bool CheckCSPFrameAncestorAndXFO(nsIChannel* aChannel);
|
||||
|
||||
// Returns false if a MediaDocument load should be blocked, because the
|
||||
// inherited CSP would block it as a regular <audio>/<video>/<img> load as
|
||||
// well.
|
||||
static bool CheckCSPMediaDocumentLoad(nsIChannel* aChannel,
|
||||
nsContentDLF::DocumentKind aKind);
|
||||
|
||||
// Implements https://w3c.github.io/webappsec-csp/#is-element-nonceable.
|
||||
//
|
||||
// Returns an empty nonce for elements without a nonce OR when a potential
|
||||
|
||||
@@ -75,6 +75,39 @@ nsContentDLF::~nsContentDLF() = default;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsContentDLF, nsIDocumentLoaderFactory)
|
||||
|
||||
/* static */
|
||||
nsContentDLF::DocumentKind nsContentDLF::DocumentKindForContentType(
|
||||
const nsACString& aContentType) {
|
||||
// Try html or plaintext; both use the same document CID
|
||||
if (IsTypeInList(aContentType, gHTMLTypes) ||
|
||||
nsContentUtils::IsPlainTextType(aContentType)) {
|
||||
return DocumentKind::HTML;
|
||||
}
|
||||
|
||||
// Try XML
|
||||
if (IsTypeInList(aContentType, gXMLTypes)) {
|
||||
return DocumentKind::XML;
|
||||
}
|
||||
|
||||
// Try SVG
|
||||
if (IsTypeInList(aContentType, gSVGTypes)) {
|
||||
return DocumentKind::SVG;
|
||||
}
|
||||
|
||||
if (mozilla::DecoderTraits::ShouldHandleMediaType(
|
||||
aContentType,
|
||||
/* DecoderDoctorDiagnostics* */ nullptr)) {
|
||||
return DocumentKind::Video;
|
||||
}
|
||||
|
||||
// Try image types
|
||||
if (IsImageContentType(aContentType)) {
|
||||
return DocumentKind::Image;
|
||||
}
|
||||
|
||||
return DocumentKind::None;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsContentDLF::CreateInstance(const char* aCommand, nsIChannel* aChannel,
|
||||
nsILoadGroup* aLoadGroup,
|
||||
@@ -118,72 +151,76 @@ nsContentDLF::CreateInstance(const char* aCommand, nsIChannel* aChannel,
|
||||
|
||||
nsresult rv;
|
||||
bool imageDocument = false;
|
||||
// Try html or plaintext; both use the same document CID
|
||||
if (IsTypeInList(contentType, gHTMLTypes) ||
|
||||
nsContentUtils::IsPlainTextType(contentType)) {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewHTMLDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
} // Try XML
|
||||
else if (IsTypeInList(contentType, gXMLTypes)) {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
} // Try SVG
|
||||
else if (IsTypeInList(contentType, gSVGTypes)) {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewSVGDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
} else if (mozilla::DecoderTraits::ShouldHandleMediaType(
|
||||
contentType.get(),
|
||||
/* DecoderDoctorDiagnostics* */ nullptr)) {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewVideoDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
} // Try image types
|
||||
else if (IsImageContentType(contentType)) {
|
||||
imageDocument = true;
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewImageDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
} else {
|
||||
// If we get here, then we weren't able to create anything. Sorry!
|
||||
return NS_ERROR_FAILURE;
|
||||
switch (DocumentKindForContentType(contentType)) {
|
||||
case DocumentKind::HTML: {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewHTMLDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
break;
|
||||
}
|
||||
case DocumentKind::XML: {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewXMLDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
break;
|
||||
}
|
||||
case DocumentKind::SVG: {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewSVGDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
break;
|
||||
}
|
||||
case DocumentKind::Video: {
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewVideoDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
break;
|
||||
}
|
||||
case DocumentKind::Image: {
|
||||
imageDocument = true;
|
||||
rv = CreateDocument(
|
||||
aCommand, aChannel, aLoadGroup, aContainer,
|
||||
[]() -> already_AddRefed<Document> {
|
||||
RefPtr<Document> doc;
|
||||
nsresult rv =
|
||||
NS_NewImageDocument(getter_AddRefs(doc), nullptr, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
return doc.forget();
|
||||
},
|
||||
aDocListener, aDocViewer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// If we get here, then we weren't able to create anything. Sorry!
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && !imageDocument) {
|
||||
|
||||
@@ -48,6 +48,18 @@ class nsContentDLF final : public nsIDocumentLoaderFactory {
|
||||
nsILoadGroup* aLoadGroup, nsIPrincipal* aPrincipal,
|
||||
nsIPrincipal* aPartitionedPrincipal, nsDocShell* aContainer);
|
||||
|
||||
enum class DocumentKind {
|
||||
HTML,
|
||||
XML,
|
||||
SVG,
|
||||
Video,
|
||||
Image,
|
||||
None,
|
||||
};
|
||||
|
||||
static DocumentKind DocumentKindForContentType(
|
||||
const nsACString& aContentType);
|
||||
|
||||
private:
|
||||
static nsresult EnsureUAStyleSheet();
|
||||
static bool IsImageContentType(const nsACString&);
|
||||
|
||||
@@ -2564,6 +2564,34 @@ DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Bug 1909110: Block top-level MediaDocument loads, if they are triggered by
|
||||
// a page that would block those loads coming from a normal e.g. <img>
|
||||
// element.
|
||||
{
|
||||
nsAutoCString contentType;
|
||||
mChannel->GetContentType(contentType);
|
||||
|
||||
using DocumentKind = nsContentDLF::DocumentKind;
|
||||
DocumentKind kind = nsContentDLF::DocumentKindForContentType(contentType);
|
||||
if (kind == DocumentKind::Image || kind == DocumentKind::Video) {
|
||||
if (!nsContentSecurityUtils::CheckCSPMediaDocumentLoad(mChannel, kind)) {
|
||||
mChannel->Cancel(NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT);
|
||||
if (loadingContext) {
|
||||
RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
|
||||
new MaybeCloseWindowHelper(loadingContext);
|
||||
// If a new window was opened specifically for this request, close it
|
||||
// after blocking the navigation.
|
||||
maybeCloseWindowHelper->SetShouldCloseWindow(
|
||||
IsFirstLoadInWindow(mChannel));
|
||||
Unused << maybeCloseWindowHelper->MaybeCloseWindow();
|
||||
}
|
||||
DisconnectListeners(NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT,
|
||||
NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generally we want to switch to a real channel even if the request failed,
|
||||
// since the listener might want to access protocol-specific data (like http
|
||||
// response headers) in its error handling.
|
||||
|
||||
@@ -354,6 +354,17 @@ function initPage() {
|
||||
tryAgain.hidden = true;
|
||||
break;
|
||||
|
||||
case "cspMediaBlocked":
|
||||
bodyTitleId = "csp-xfo-error-title";
|
||||
|
||||
// Remove the "Try again" button for XFO and CSP violations,
|
||||
// since it's almost certainly useless. (Bug 553180)
|
||||
tryAgain.hidden = true;
|
||||
|
||||
// Does not contain anything useful.
|
||||
longDesc = null;
|
||||
|
||||
break;
|
||||
case "cspBlocked":
|
||||
case "xfoBlocked": {
|
||||
bodyTitleId = "csp-xfo-error-title";
|
||||
|
||||
@@ -909,6 +909,7 @@ with modules["SECURITY"]:
|
||||
# Error code for CSP
|
||||
errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(97)
|
||||
errors["NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION"] = FAILURE(98)
|
||||
errors["NS_ERROR_CSP_BLOCKED_MEDIA_DOCUMENT"] = FAILURE(99)
|
||||
|
||||
# Error code for Sub-Resource Integrity
|
||||
errors["NS_ERROR_SRI_CORRUPT"] = FAILURE(200)
|
||||
|
||||
Reference in New Issue
Block a user