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:
Tom Schuster
2024-10-29 13:05:25 +00:00
parent d1ff2d8901
commit 89f6452c39
13 changed files with 239 additions and 70 deletions

View File

@@ -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.

View File

@@ -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");

View File

@@ -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,

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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) {

View File

@@ -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&);

View File

@@ -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.

View File

@@ -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";

View File

@@ -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)