Files
sousa-gecko/dom/security/IntegrityPolicyService.cpp

377 lines
14 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}
// https://w3c.github.io/webappsec-subresource-integrity/#should-request-be-blocked-by-integrity-policy-section
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
// requests 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 requests 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 policyContainers integrity policy.
// 6. Let reportPolicy be policyContainers 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 requests clients 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);
}
// https://w3c.github.io/webappsec-subresource-integrity/#report-violation
void dom::IntegrityPolicyService::ReportViolation(
nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
IntegrityPolicy::DestinationType aDestination,
const IntegrityPolicy* aPolicy, bool aEnforce, bool aReportOnly) const {
// 1. Assert: requests client is not null.
// 2. Let settingsObject be requests client.
// 3. Let global be settingsObjects 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 globals associated Documents URL.
nsCOMPtr<nsIURI> uri = doc->GetDocumentURI(); // XXX Maybe not the right URL?
// 7. If global is a WorkerGlobalScope, set url to globals 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
// requests 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 policys endpoints:
if (aEnforce) {
for (const nsCString& endpoint : enforcementEndpoints) {
// 11.1. Let body be a new IntegrityViolationReportBody, initialized as
// follows:
//
// documentURL
// documentURL
// blockedURL
// blockedURL
// destination
// requests 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 reportPolicys endpoints:
if (aReportOnly) {
for (const nsCString& endpoint : reportOnlyEndpoints) {
// 11.1. Let reportBody be a new IntegrityViolationReportBody, initialized
// as follows:
//
// documentURL
// documentURL
// blockedURL
// blockedURL
// destination
// requests 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