Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "IntegrityPolicyService.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/IntegrityPolicy.h"
#include "mozilla/dom/IntegrityViolationReportBody.h"
#include "mozilla/dom/PolicyContainer.h"
#include "mozilla/dom/ReportingUtils.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/net/SFVService.h"
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsILoadInfo.h"
#include "nsString.h"
using namespace mozilla;
static LazyLogModule sIntegrityPolicyServiceLogModule("IntegrityPolicy");
#define LOG(fmt, ...) \
MOZ_LOG_FMT(sIntegrityPolicyServiceLogModule, LogLevel::Debug, fmt, \
##__VA_ARGS__)
namespace mozilla::dom {
IntegrityPolicyService::~IntegrityPolicyService() = default;
/* nsIContentPolicy implementation */
NS_IMETHODIMP
IntegrityPolicyService::ShouldLoad(nsIURI* aContentLocation,
nsILoadInfo* aLoadInfo, int16_t* aDecision) {
LOG("ShouldLoad: [{}] Entered ShouldLoad", static_cast<void*>(aLoadInfo));
*aDecision = nsIContentPolicy::ACCEPT;
if (!StaticPrefs::security_integrity_policy_enabled()) {
LOG("ShouldLoad: [{}] Integrity policy is disabled",
static_cast<void*>(aLoadInfo));
return NS_OK;
}
if (!aContentLocation) {
LOG("ShouldLoad: [{}] No content location", static_cast<void*>(aLoadInfo));
return NS_ERROR_FAILURE;
}
bool block = ShouldRequestBeBlocked(aContentLocation, aLoadInfo);
*aDecision =
block ? nsIContentPolicy::REJECT_SERVER : nsIContentPolicy::ACCEPT;
return NS_OK;
}
NS_IMETHODIMP IntegrityPolicyService::ShouldProcess(nsIURI* aContentLocation,
nsILoadInfo* aLoadInfo,
int16_t* aDecision) {
*aDecision = nsIContentPolicy::ACCEPT;
return NS_OK;
}
bool IntegrityPolicyService::ShouldRequestBeBlocked(nsIURI* aContentLocation,
nsILoadInfo* aLoadInfo) {
// Efficiency check: if we don't care about this type, we can skip.
auto destination = IntegrityPolicy::ContentTypeToDestinationType(
aLoadInfo->InternalContentPolicyType());
if (destination.isNothing()) {
LOG("ShouldLoad: [{}] Integrity policy doesn't handle this type={}",
static_cast<void*>(aLoadInfo),
static_cast<uint8_t>(aLoadInfo->InternalContentPolicyType()));
return false;
}
// Exempt addons from integrity policy checks.
// Top level document loads have null LoadingPrincipal, but we don't apply
// integrity policy to top level document loads right now.
if (BasePrincipal::Cast(aLoadInfo->TriggeringPrincipal())
->OverridesCSP(aLoadInfo->GetLoadingPrincipal())) {
LOG("ShouldLoad: [{}] Got a request from an addon, allowing it.",
static_cast<void*>(aLoadInfo));
return false;
}
// 2. Let parsedMetadata be the result of calling parse metadata with
// request’s integrity metadata.
// In our case, parsedMetadata is in loadInfo.
Maybe<RequestMode> maybeRequestMode;
aLoadInfo->GetRequestMode(&maybeRequestMode);
if (maybeRequestMode.isNothing()) {
// We don't have a request mode set explicitly, get it from the secFlags.
// Just make sure that we aren't trying to get it from a
// nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK loadInfo. In those
// cases, we have to set the requestMode explicitly.
MOZ_ASSERT(aLoadInfo->GetSecurityFlags() !=
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK);
maybeRequestMode = Some(nsContentSecurityManager::SecurityModeToRequestMode(
aLoadInfo->GetSecurityMode()));
}
RequestMode requestMode = *maybeRequestMode;
if (MOZ_LOG_TEST(sIntegrityPolicyServiceLogModule, LogLevel::Debug)) {
nsAutoString integrityMetadata;
aLoadInfo->GetIntegrityMetadata(integrityMetadata);
LOG("ShouldLoad: [{}] uri={} destination={} "
"requestMode={} integrityMetadata={}",
static_cast<void*>(aLoadInfo), aContentLocation->GetSpecOrDefault(),
static_cast<uint8_t>(*destination), static_cast<uint8_t>(requestMode),
NS_ConvertUTF16toUTF8(integrityMetadata).get());
}
// 3. If parsedMetadata is not the empty set and request’s mode is either
// "cors" or "same-origin", return "Allowed".
if (requestMode == RequestMode::Cors ||
requestMode == RequestMode::Same_origin) {
nsAutoString integrityMetadata;
aLoadInfo->GetIntegrityMetadata(integrityMetadata);
SRIMetadata outMetadata;
dom::SRICheck::IntegrityMetadata(integrityMetadata,
aContentLocation->GetSpecOrDefault(),
nullptr, &outMetadata);
if (outMetadata.IsValid()) {
LOG("ShouldLoad: [{}] Allowed because we have valid a integrity.",
static_cast<void*>(aLoadInfo));
return false;
}
}
// 4. If request's url is local, return "Allowed".
if (aContentLocation->SchemeIs("data") ||
aContentLocation->SchemeIs("blob") ||
aContentLocation->SchemeIs("about")) {
LOG("ShouldLoad: [{}] Allowed because we have data or blob.",
static_cast<void*>(aLoadInfo));
return false;
}
// We only support integrity policy for documents so far.
nsCOMPtr<nsIPolicyContainer> policyContainer =
aLoadInfo->GetPolicyContainer();
if (!policyContainer) {
LOG("ShouldLoad: [{}] No policy container", static_cast<void*>(aLoadInfo));
return false;
}
// 5. Let policy be policyContainer’s integrity policy.
// 6. Let reportPolicy be policyContainer’s report only integrity policy.
// Our IntegrityPolicy struct contains both the enforcement and
// report-only policies.
RefPtr<IntegrityPolicy> policy = IntegrityPolicy::Cast(
PolicyContainer::Cast(policyContainer)->GetIntegrityPolicy());
if (!policy) {
// 7. If both policy and reportPolicy are empty integrity policy structs,
// return "Allowed".
LOG("ShouldLoad: [{}] No integrity policy", static_cast<void*>(aLoadInfo));
return false;
}
// TODO: 8. Let global be request’s client’s global object.
// TODO: 9. If global is not a Window nor a WorkerGlobalScope, return
// "Allowed".
// Steps 10-13 in policy->PolicyContains(...)
bool contains = false;
bool roContains = false;
policy->PolicyContains(*destination, &contains, &roContains);
// 14. If block is true or reportBlock is true, then report violation
// with request, block, reportBlock, policy and reportPolicy.
if (contains || roContains) {
ReportToConsole(aContentLocation, aLoadInfo, *destination, contains,
roContains);
ReportViolation(aContentLocation, aLoadInfo, *destination, policy, contains,
roContains);
}
// 15. If block is true, then return "Blocked"; otherwise "Allowed".
return contains;
}
const char* GetReportMessageKey(bool aEnforcing,
IntegrityPolicy::DestinationType aDestination) {
// If we are not enforcing, we are reporting only.
switch (aDestination) {
case IntegrityPolicy::DestinationType::Script:
return aEnforcing ? "IntegrityPolicyEnforceBlockedScript"
: "IntegrityPolicyReportOnlyBlockedScript";
case IntegrityPolicy::DestinationType::Style:
return aEnforcing ? "IntegrityPolicyEnforceBlockedStylesheet"
: "IntegrityPolicyReportOnlyBlockedStylesheet";
default:
MOZ_ASSERT_UNREACHABLE("Unhandled destination type");
return nullptr;
}
}
void IntegrityPolicyService::ReportToConsole(
nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
IntegrityPolicy::DestinationType aDestination, bool aEnforce,
bool aReportOnly) const {
if (nsContentUtils::IsPreloadType(aLoadInfo->InternalContentPolicyType())) {
return; // Don't report for preloads.
}
const char* messageKey = GetReportMessageKey(aEnforce, aDestination);
NS_ENSURE_TRUE_VOID(messageKey);
// We just report to the console for now. We should use the reporting API
// in the future.
AutoTArray<nsString, 1> params = {
NS_ConvertUTF8toUTF16(aContentLocation->GetSpecOrDefault())};
nsAutoString localizedMsg;
nsresult rv = nsContentUtils::FormatLocalizedString(
PropertiesFile::SECURITY_PROPERTIES, messageKey, params, localizedMsg);
NS_ENSURE_SUCCESS_VOID(rv);
uint64_t windowID = aLoadInfo->GetInnerWindowID();
nsContentUtils::ReportToConsoleByWindowID(
localizedMsg,
aEnforce ? nsIScriptError::errorFlag : nsIScriptError::warningFlag,
"Security"_ns, windowID);
}
void dom::IntegrityPolicyService::ReportViolation(
nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
IntegrityPolicy::DestinationType aDestination,
const IntegrityPolicy* aPolicy, bool aEnforce, bool aReportOnly) const {
// 1. Assert: request’s client is not null.
// 2. Let settingsObject be request’s client.
// 3. Let global be settingsObject’s global object.
nsCOMPtr<nsISupports> loadingContext = aLoadInfo->GetLoadingContext();
RefPtr<Document> doc;
if (nsCOMPtr<nsINode> node = do_QueryInterface(loadingContext)) {
doc = node->OwnerDoc();
} else if (nsCOMPtr<nsPIDOMWindowOuter> window =
do_QueryInterface(loadingContext)) {
doc = window->GetDoc();
}
if (NS_WARN_IF(!doc)) {
return;
}
nsPIDOMWindowInner* window = doc->GetInnerWindow();
if (NS_WARN_IF(!window)) {
return;
}
nsCOMPtr<nsIGlobalObject> global = window->AsGlobal();
// 4. Assert: global is a Window or a WorkerGlobalScope.
// 5. Let url be null.
// 6. If global is a Window, set url to global’s associated Document’s URL.
nsCOMPtr<nsIURI> uri = doc->GetDocumentURI(); // XXX Maybe not the right URL?
// 7. If global is a WorkerGlobalScope, set url to global’s URL.
// TODO(bug 1969279): Worker support.
// 8. Assert: url is a URL.
if (NS_WARN_IF(!uri)) {
return;
}
// 9. Let documentURL be the result of strip URL for use in reports on url.
nsAutoCString documentURL;
ReportingUtils::StripURL(uri, documentURL);
NS_ConvertUTF8toUTF16 documentURLUTF16(documentURL);
// 10. Let blockedURL be the result of strip URL for use in reports on
// request’s URL.
nsAutoCString blockedURL;
ReportingUtils::StripURL(aContentLocation, blockedURL);
nsAutoCString destination;
switch (aDestination) {
case IntegrityPolicy::DestinationType::Script:
destination = "script"_ns;
break;
case IntegrityPolicy::DestinationType::Style:
destination = "style"_ns;
break;
case IntegrityPolicy::DestinationType::Image:
destination = "image"_ns;
break;
}
nsTArray<nsCString> enforcementEndpoints;
nsTArray<nsCString> reportOnlyEndpoints;
aPolicy->Endpoints(enforcementEndpoints, reportOnlyEndpoints);
// 11. If block is true, for each endpoint in policy’s endpoints:
if (aEnforce) {
for (const nsCString& endpoint : enforcementEndpoints) {
// 11.1. Let body be a new IntegrityViolationReportBody, initialized as
// follows:
//
// documentURL
// documentURL
// blockedURL
// blockedURL
// destination
// request’s destination
// reportOnly
// false
RefPtr<IntegrityViolationReportBody> body =
new IntegrityViolationReportBody(global, documentURL, blockedURL,
destination, false);
// 11.2. Generate and queue a report with the following arguments:
//
// context
// settingsObject
// type
// "integrity-violation"
// destination
// endpoint
// data
// body
ReportingUtils::Report(global, nsGkAtoms::integrity_violation,
NS_ConvertUTF8toUTF16(endpoint), documentURLUTF16,
body);
}
}
// 11. If reportBlock is true, for each endpoint in reportPolicy’s endpoints:
if (aReportOnly) {
for (const nsCString& endpoint : reportOnlyEndpoints) {
// 11.1. Let reportBody be a new IntegrityViolationReportBody, initialized
// as follows:
//
// documentURL
// documentURL
// blockedURL
// blockedURL
// destination
// request’s destination
// reportOnly
// true
RefPtr<IntegrityViolationReportBody> reportBody =
new IntegrityViolationReportBody(global, documentURL, blockedURL,
destination, true);
// 11.2. Generate and queue a report with the following arguments:
//
// context
// settingsObject
// type
// "integrity-violation"
// destination
// endpoint
// data
// reportBody
ReportingUtils::Report(global, nsGkAtoms::integrity_violation,
NS_ConvertUTF8toUTF16(endpoint), documentURLUTF16,
reportBody);
}
}
}
NS_IMPL_ISUPPORTS(IntegrityPolicyService, nsIContentPolicy)
} // namespace mozilla::dom
#undef LOG