1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/PolicyContainer.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/net/SFVService.h"
#include "nsContentSecurityManager.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
// 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);
// TODO: 14. If block is true or reportBlock is true, then report violation
// with request, block, reportBlock, policy and reportPolicy.
MaybeReport(aContentLocation, aLoadInfo, *destination, 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::MaybeReport(
nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
IntegrityPolicy::DestinationType aDestination, bool aEnforce,
bool aReportOnly) {
if (!aEnforce && !aReportOnly) {
return;
}
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(
nsContentUtils::eSECURITY_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);
}
NS_IMPL_ISUPPORTS(IntegrityPolicyService, nsIContentPolicy)
} // namespace mozilla::dom
#undef LOG
|