/* -*- 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/. */
// helper function that should not be visible outside this methods scope struct local { staticinline char16_t convertHexDig(char16_t aHexDig) { if (isNumberToken(aHexDig)) { return aHexDig - '0';
} if (aHexDig >= 'A' && aHexDig <= 'F') { return aHexDig - 'A' + 10;
} // must be a lower case character // (aHexDig >= 'a' && aHexDig <= 'f') return aHexDig - 'a' + 10;
}
};
const char16_t *cur, *end, *hexDig1, *hexDig2;
cur = aEncStr.BeginReading();
end = aEncStr.EndReading();
while (cur != end) { // if it's not a percent sign then there is // nothing to do for that character if (*cur != PERCENT_SIGN) {
outDecStr.Append(*cur);
cur++; continue;
}
// get the two hexDigs following the '%'-sign
hexDig1 = cur + 1;
hexDig2 = cur + 2;
// if there are no hexdigs after the '%' then // there is nothing to do for us. if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
!isValidHexDig(*hexDig2)) {
outDecStr.Append(PERCENT_SIGN);
cur++; continue;
}
// decode "% hexDig1 hexDig2" into a character.
char16_t decChar =
(local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
outDecStr.Append(decChar);
// increment 'cur' to after the second hexDig
cur = ++hexDig2;
}
}
// The Content Security Policy should be inherited for // local schemes like: "about", "blob", "data", or "filesystem". // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) { if (!aChannel) { returnfalse;
}
nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp(); if (!csp) {
MOZ_ASSERT(false, "how come there is no CSP"); return;
}
// Make the <meta> policy in browser.xhtml toggleable. if (nsIURI* uri = aDoc.GetDocumentURI();
uri->SchemeIs("chrome") &&
!StaticPrefs::security_browser_xhtml_csp_enabled()) {
nsAutoCString spec;
uri->GetSpec(spec); if (spec.EqualsLiteral("chrome://browser/content/browser.xhtml")) { return;
}
}
// Multiple CSPs (delivered through either header of meta tag) need to // be joined together, see: // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
nsresult rv =
csp->AppendPolicy(policyStr, false, // csp via meta tag can not be report only true); // delivered through the meta tag
NS_ENSURE_SUCCESS_VOID(rv); if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
inner->SetCsp(csp);
}
aDoc.ApplySettingsFromCSP(false);
}
NS_ASSERTION(stringBundleService, "String bundle service must be present!");
stringBundleService->CreateBundle( "chrome://global/locale/security/csp.properties",
getter_AddRefs(keyStringBundle));
NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
// Prepending CSP to the outgoing console message
nsString cspMsg;
CSP_GetLocalizedStr("CSPMessagePrefix",
AutoTArray<nsString, 1>{nsString(aMessage)}, cspMsg);
// Currently 'aSourceLine' is not logged to the console, because similar // information is already included within the source link of the message. // For inline violations however, the line and column number are 0 and // information contained within 'aSourceLine' can be really useful for devs. // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'. // In such cases we append 'aSourceLine' directly to the error message. if (!aSourceLine.IsEmpty() && aLineNumber == 0) {
cspMsg.AppendLiteral(u"\nSource: ");
cspMsg.Append(aSourceLine);
}
// Since we are leveraging csp errors as the category names which // we pass to devtools, we should prepend them with "CSP_" to // allow easy distincution in devtools code. e.g. // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
nsCString category("CSP_");
category.Append(aCategory);
for (uint32_t i = 1; i < numDirs; i++) { if (lowerDir.EqualsASCII(CSPStrDirectives[i])) { returnstatic_cast<CSPDirective>(i);
}
} return nsIContentSecurityPolicy::NO_DIRECTIVE;
}
/* ===== Helpers ============================ */ // This implements // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request. // However the spec doesn't currently cover all request destinations, which // we roughly represent using nsContentPolicyType.
CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) { switch (aType) { case nsIContentPolicy::TYPE_IMAGE: case nsIContentPolicy::TYPE_IMAGESET: case nsIContentPolicy::TYPE_INTERNAL_IMAGE: case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: case nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE: return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
// BLock XSLT as script, see bug 910139 case nsIContentPolicy::TYPE_XSLT: case nsIContentPolicy::TYPE_SCRIPT: case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_MODULE: case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET: case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET: case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT: case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT: // (https://github.com/w3c/webappsec-csp/issues/554) // Some of these types are not explicitly defined in the spec. // // Chrome seems to use script-src-elem for worklet! return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE;
case nsIContentPolicy::TYPE_STYLESHEET: case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
case nsIContentPolicy::TYPE_FONT: case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD: return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_MEDIA: case nsIContentPolicy::TYPE_INTERNAL_AUDIO: case nsIContentPolicy::TYPE_INTERNAL_VIDEO: case nsIContentPolicy::TYPE_INTERNAL_TRACK: return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_WEB_MANIFEST: return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_INTERNAL_WORKER: case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE: case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_SUBDOCUMENT: case nsIContentPolicy::TYPE_INTERNAL_FRAME: case nsIContentPolicy::TYPE_INTERNAL_IFRAME: return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_WEBSOCKET: case nsIContentPolicy::TYPE_XMLHTTPREQUEST: case nsIContentPolicy::TYPE_BEACON: case nsIContentPolicy::TYPE_PING: case nsIContentPolicy::TYPE_FETCH: case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC: case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC: case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE: case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD: case nsIContentPolicy::TYPE_WEB_IDENTITY: case nsIContentPolicy::TYPE_WEB_TRANSPORT: case nsIContentPolicy::TYPE_JSON: case nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD: return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_OBJECT: case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: case nsIContentPolicy::TYPE_INTERNAL_EMBED: case nsIContentPolicy::TYPE_INTERNAL_OBJECT: return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
case nsIContentPolicy::TYPE_DTD: case nsIContentPolicy::TYPE_OTHER: case nsIContentPolicy::TYPE_SPECULATIVE: case nsIContentPolicy::TYPE_INTERNAL_DTD: case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD: return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
// CSP does not apply to webrtc connections case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA: // csp shold not block top level loads, e.g. in case // of a redirect. case nsIContentPolicy::TYPE_DOCUMENT: // CSP can not block csp reports case nsIContentPolicy::TYPE_CSP_REPORT: return nsIContentSecurityPolicy::NO_DIRECTIVE;
case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD: case nsIContentPolicy::TYPE_UA_FONT: return nsIContentSecurityPolicy::NO_DIRECTIVE;
// Fall through to error for all other directives case nsIContentPolicy::TYPE_INVALID: case nsIContentPolicy::TYPE_END:
MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective"); // Do not add default: so that compilers can catch the missing case.
} return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
}
nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) { // Create the host first
nsCString host;
aSelfURI->GetAsciiHost(host);
nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
hostsrc->setGeneratedFromSelfKeyword();
// Add the scheme.
nsCString scheme;
aSelfURI->GetScheme(scheme);
hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
// An empty host (e.g. for data:) indicates it's effectively a unique origin. // Please note that we still need to set the scheme on hostsrc (see above), // because it's used for reporting. if (host.EqualsLiteral("")) {
hostsrc->setIsUniqueOrigin(); // no need to query the port in that case. return hostsrc;
}
int32_t port;
aSelfURI->GetPort(&port); // Only add port if it's not default port. if (port > 0) {
nsAutoString portStr;
portStr.AppendInt(port);
hostsrc->setPort(portStr);
} return hostsrc;
}
nsAutoString keyword; for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) { // skipping the leading ' and trimming the trailing '
keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
keyword.Trim("'", false, true); if (lowerKey.Equals(keyword)) { returntrue;
}
} returnfalse;
}
/* * Checks whether the current directive permits a specific * scheme. This function is called from nsCSPSchemeSrc() and * also nsCSPHostSrc. * @param aEnforcementScheme * The scheme that this directive allows * @param aUri * The uri of the subresource load. * @param aReportOnly * Whether the enforced policy is report only or not. * @param aUpgradeInsecure * Whether the policy makes use of the directive * 'upgrade-insecure-requests'. * @param aFromSelfURI * Whether a scheme was generated from the keyword 'self' * which then allows schemeless sources to match ws and wss.
*/
// no scheme to enforce, let's allow the load (e.g. script-src *) if (aEnforcementScheme.IsEmpty()) { returntrue;
}
// if the scheme matches, all good - allow the load if (aEnforcementScheme.EqualsASCII(scheme.get())) { returntrue;
}
// allow scheme-less sources where the protected resource is http // and the load is https, see: // http://www.w3.org/TR/CSP2/#match-source-expression if (aEnforcementScheme.EqualsASCII("http")) { if (scheme.EqualsASCII("https")) { returntrue;
} if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
aFromSelfURI) { returntrue;
}
} if (aEnforcementScheme.EqualsASCII("https")) { if (scheme.EqualsLiteral("wss") && aFromSelfURI) { returntrue;
}
} if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) { returntrue;
}
// Allow the load when enforcing upgrade-insecure-requests with the // promise the request gets upgraded from http to https and ws to wss. // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note, // the report only policies should not allow the load and report // the error back to the page. return (
(aUpgradeInsecure && !aReportOnly) &&
((scheme.EqualsASCII("http") &&
aEnforcementScheme.EqualsASCII("https")) ||
(scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
}
/* * A helper function for appending a CSP header to an existing CSP * policy. * * @param aCsp the CSP policy * @param aHeaderValue the header * @param aReportOnly is this a report-only header?
*/
// Need to tokenize the header value since multiple headers could be // concatenated into one comma-separated list of policies. // See RFC2616 section 4.2 (last paragraph)
nsresult rv = NS_OK; for (const nsAString& policy :
nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
rv = aCsp->AppendPolicy(policy, aReportOnly, false);
NS_ENSURE_SUCCESS(rv, rv);
{
CSPUTILSLOG(("CSP refined with policy: \"%s\"",
NS_ConvertUTF16toUTF8(policy).get()));
}
} return NS_OK;
}
/* ===== nsCSPSrc ============================ */
nsCSPBaseSrc::nsCSPBaseSrc() {}
nsCSPBaseSrc::~nsCSPBaseSrc() = default;
// ::permits is only called for external load requests, therefore: // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class // implementation which will never allow the load. bool nsCSPBaseSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) {
CSPUTILSLOG(
("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
} returnfalse;
}
// ::allows is only called for inlined loads, therefore: // nsCSPSchemeSrc, nsCSPHostSrc fall back // to this base class implementation which will never allow the load. bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const {
CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
NS_ConvertUTF16toUTF8(aHashOrNonce).get())); returnfalse;
}
/* * Checks whether the current directive permits a specific port. * @param aEnforcementScheme * The scheme that this directive allows * (used to query the default port for that scheme) * @param aEnforcementPort * The port that this directive allows * @param aResourceURI * The uri of the subresource load
*/ bool permitsPort(const nsAString& aEnforcementScheme, const nsAString& aEnforcementPort, nsIURI* aResourceURI) { // If enforcement port is the wildcard, don't block the load. if (aEnforcementPort.EqualsASCII("*")) { returntrue;
}
int32_t resourcePort;
nsresult rv = aResourceURI->GetPort(&resourcePort); if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) { // If we cannot get a Port (e.g. because of an Custom Protocol handler) // We need to check if a default port is associated with the Scheme if (aEnforcementScheme.IsEmpty()) { returnfalse;
} int defaultPortforScheme =
NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
// If there is no default port associated with the Scheme ( // defaultPortforScheme == -1) or it is an externally handled protocol ( // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can // allow not having a port return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
} // Avoid unnecessary string creation/manipulation and don't block the // load if the resource to be loaded uses the default port for that // scheme and there is no port to be enforced. // Note, this optimization relies on scheme checks within permitsScheme(). if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) { returntrue;
}
// By now we know at that either the resourcePort does not use the default // port or there is a port restriction to be enforced. A port value of -1 // corresponds to the protocol's default port (eg. -1 implies port 80 for // http URIs), in such a case we have to query the default port of the // resource to be loaded. if (resourcePort == DEFAULT_PORT) {
nsAutoCString resourceScheme;
rv = aResourceURI->GetScheme(resourceScheme);
NS_ENSURE_SUCCESS(rv, false);
resourcePort = NS_GetDefaultPort(resourceScheme.get());
}
// If there is a port to be enforced and the ports match, then // don't block the load.
nsString resourcePortStr;
resourcePortStr.AppendInt(resourcePort); if (aEnforcementPort.Equals(resourcePortStr)) { returntrue;
}
// If there is no port to be enforced, query the default port for the load.
nsString enforcementPort(aEnforcementPort); if (enforcementPort.IsEmpty()) { // For scheme less sources, our parser always generates a scheme // which is the scheme of the protected resource.
MOZ_ASSERT(!aEnforcementScheme.IsEmpty(), "need a scheme to query default port");
int32_t defaultEnforcementPort =
NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
enforcementPort.Truncate();
enforcementPort.AppendInt(defaultEnforcementPort);
}
// If default ports match, don't block the load if (enforcementPort.Equals(resourcePortStr)) { returntrue;
}
// Additional port matching where the regular URL matching algorithm // treats insecure ports as matching their secure variants. // default port for http is :80 // default port for https is :443 if (enforcementPort.EqualsLiteral("80") &&
resourcePortStr.EqualsLiteral("443")) { returntrue;
}
// ports do not match, block the load. returnfalse;
}
// 4.3) scheme matching: Check if the scheme matches. if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
mGeneratedFromSelfKeyword)) { returnfalse;
}
// The host in nsCSpHostSrc should never be empty. In case we are enforcing // just a specific scheme, the parser should generate a nsCSPSchemeSource.
NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
// Before we can check if the host matches, we have to // extract the host part from aUri.
nsAutoCString uriHost;
nsresult rv = aUri->GetAsciiHost(uriHost);
NS_ENSURE_SUCCESS(rv, false);
// 2) host matching: Enforce a single * if (mHost.EqualsASCII("*")) { // The single ASTERISK character (*) does not match a URI's scheme of a type // designating a globally unique identifier (such as blob:, data:, or // filesystem:) At the moment firefox does not support filesystem; but for // future compatibility we support it in CSP according to the spec, // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of // these schemes would call nsCSPSchemeSrc::permits(). if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
aUri->SchemeIs("filesystem")) { returnfalse;
}
// If no scheme is present there also wont be a port and folder to check // which means we can return early if (mScheme.IsEmpty()) { returntrue;
}
} // 4.5) host matching: Check if the allowed host starts with a wilcard. elseif (mHost.First() == '*') {
NS_ASSERTION(
mHost[1] == '.', "Second character needs to be '.' whenever host starts with '*'");
// Eliminate leading "*", but keeping the FULL STOP (.) thereafter before // checking if the remaining characters match
nsString wildCardHost = mHost;
wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1); if (!StringEndsWith(decodedUriHost, wildCardHost)) { returnfalse;
}
} // 4.6) host matching: Check if hosts match. elseif (!mHost.Equals(decodedUriHost)) { returnfalse;
}
// Port matching: Check if the ports match. if (!permitsPort(mScheme, mPort, aUri)) { returnfalse;
}
// 4.9) Path matching: If there is a path, we have to enforce // path-level matching, unless the channel got redirected, see: // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects if (!aWasRedirected && !mPath.IsEmpty()) { // converting aUri into nsIURL so we can strip query and ref // example.com/test#foo -> example.com/test // example.com/test?val=foo -> example.com/test
nsCOMPtr<nsIURL> url = do_QueryInterface(aUri); if (!url) {
NS_ASSERTION(false, "can't QI into nsIURI"); returnfalse;
}
nsAutoCString uriPath;
rv = url->GetFilePath(uriPath);
NS_ENSURE_SUCCESS(rv, false);
if (mWithinFrameAncstorsDir) { // no path matching for frame-ancestors to not leak any path information. returntrue;
}
// check if the last character of mPath is '/'; if so // we just have to check loading resource is within // the allowed path. if (mPath.Last() == '/') { if (!StringBeginsWith(decodedUriPath, mPath)) { returnfalse;
}
} // otherwise mPath refers to a specific file, and we have to // check if the loading resource matches the file. else { if (!mPath.Equals(decodedUriPath)) { returnfalse;
}
}
}
// At the end: scheme, host, port and path match -> allow the load. returntrue;
}
// If mHost is a single "*", we append the wildcard and return. if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
outStr.Append(mHost); return;
}
nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
: mKeyword(aKeyword) {
NS_ASSERTION((aKeyword != CSP_SELF), "'self' should have been replaced in the parser");
}
nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
: mAlgorithm(aAlgo), mHash(aHash) { // Only the algo should be rewritten to lowercase, the hash must remain the // same.
ToLowerCase(mAlgorithm); // Normalize the base64url encoding to base64 encoding:
char16_t* cur = mHash.BeginWriting();
char16_t* end = mHash.EndWriting();
for (; cur < end; ++cur) { if (char16_t('-') == *cur) {
*cur = char16_t('+');
} if (char16_t('_') == *cur) {
*cur = char16_t('/');
}
}
}
bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
nsCSPSrcVisitor* aVisitor) const {
MOZ_ASSERT_UNREACHABLE( "This method should only be called for other overloads of this method."); returnfalse;
}
bool nsCSPTrustedTypesDirectivePolicyName::visit(
nsCSPSrcVisitor* aVisitor) const {
MOZ_ASSERT_UNREACHABLE( "Should only be called for other overloads of this method."); returnfalse;
}
// Note: For code-reuse we do "request’s cryptographic nonce metadata" here // instead of the caller.
nsAutoString nonce;
MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetCspNonce(nonce));
// Step 2. If nonce is the empty string, return "Does Not Match". if (nonce.IsEmpty()) { returnfalse;
}
// Step 3. For each expression of source list: for (nsCSPBaseSrc* src : aSrcs) { // Step 3.1. If expression matches the nonce-source grammar, and nonce is // identical to expression’s base64-value part, return "Matches". if (src->isNonce()) {
nsAutoString srcNonce; static_cast<nsCSPNonceSrc*>(src)->getNonce(srcNonce); if (srcNonce == nonce) { returntrue;
}
}
}
// Step 4. Return "Does Not Match". returnfalse;
}
// https://www.w3.org/TR/SRI/#parse-metadata // This function is similar to SRICheck::IntegrityMetadata, but also keeps // SRI metadata with weaker hashes. // CSP treats "no metadata" and empty results the same way. static nsTArray<SRIMetadata> ParseSRIMetadata(const nsAString& aMetadata) { // Step 1. Let result be the empty set. // Step 2. Let empty be equal to true.
nsTArray<SRIMetadata> result;
// Step 3. For each token returned by splitting metadata on spaces:
nsCWhitespaceTokenizer tokenizer(metadataList); while (tokenizer.hasMoreTokens()) {
token = tokenizer.nextToken(); // Step 3.1. Set empty to false. // Step 3.3. Parse token per the grammar in integrity metadata.
SRIMetadata metadata(token); // Step 3.2. If token is not a valid metadata, skip the remaining steps, and // proceed to the next token. if (metadata.IsMalformed()) { continue;
}
// Step 3.4. Let algorithm be the alg component of token. // Step 3.5. If algorithm is a hash function recognized by the user agent, // add the // parsed token to result. if (metadata.IsAlgorithmSupported()) {
result.AppendElement(metadata);
}
}
// Step 4. Return no metadata if empty is true, otherwise return result. return result;
}
if (aLoadInfo) { // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request if (aDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) { // Step 3. If the result of executing §6.7.2.3 Does nonce match source // list? on request’s cryptographic nonce metadata and this directive’s // value is "Matches", return "Allowed". if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
CSPUTILSLOG((" Allowed by matching nonce (style)")); returntrue;
}
}
// https://w3c.github.io/webappsec-csp/#script-pre-request // Step 1. If request’s destination is script-like: elseif (aDirective == CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE ||
aDirective == CSPDirective::WORKER_SRC_DIRECTIVE) { // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source // list? on request’s cryptographic nonce metadata and this directive’s // value is "Matches", return "Allowed". if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
CSPUTILSLOG((" Allowed by matching nonce (script-like)")); returntrue;
}
// Step 1.2. Let integrity expressions be the set of source expressions in // directive’s value that match the hash-source grammar.
nsTArray<nsCSPHashSrc*> integrityExpressions; bool hasStrictDynamicKeyword = false; // Optimization to reduce number of iterations. for (uint32_t i = 0; i < mSrcs.Length(); i++) { if (mSrcs[i]->isHash()) {
integrityExpressions.AppendElement( static_cast<nsCSPHashSrc*>(mSrcs[i]));
} elseif (mSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC)) {
hasStrictDynamicKeyword = true;
}
}
// Step 1.3. If integrity expressions is not empty: if (!integrityExpressions.IsEmpty()) { // Step 1.3.1. Let integrity sources be the result of executing the // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s // integrity metadata.
nsAutoString integrityMetadata;
aLoadInfo->GetIntegrityMetadata(integrityMetadata);
nsTArray<SRIMetadata> integritySources =
ParseSRIMetadata(integrityMetadata);
MOZ_ASSERT(
integritySources.IsEmpty() == integrityMetadata.IsEmpty(), "The integrity metadata should be only be empty, " "when the parsed string was completely empty, otherwise it should " "include at least one valid hash");
// Step 1.3.2. If integrity sources is "no metadata" or an empty set, // skip the remaining substeps. if (!integritySources.IsEmpty()) { // Step 1.3.3. Let bypass due to integrity match be true. bool bypass = true;
// Step 1.3.4. For each source of integrity sources: for (const SRIMetadata& source : integritySources) {
source.GetAlgorithm(&sourceAlgorithmUTF8);
sourceAlgorithm = NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8);
source.GetHash(0, &sourceHashUTF8);
sourceHash = NS_ConvertUTF8toUTF16(sourceHashUTF8);
// Step 1.3.4.1 If directive’s value does not contain a source // expression whose hash-algorithm is an ASCII case-insensitive // match for source’s hash-algorithm, and whose base64-value is // identical to source’s base64-value, then set bypass due to // integrity match to false. bool found = false; for (const nsCSPHashSrc* hashSrc : integrityExpressions) {
hashSrc->getAlgorithm(algorithm);
hashSrc->getHash(hash);
// The nsCSPHashSrc constructor lowercases algorithm, so this // is case-insensitive. if (sourceAlgorithm == algorithm && sourceHash == hash) {
found = true; break;
}
}
if (!found) {
bypass = false; break;
}
}
// Step 1.3.5. If bypass due to integrity match is true, return // "Allowed". if (bypass) {
CSPUTILSLOG(
(" Allowed by matching integrity metadata (script-like)")); returntrue;
}
}
}
// Step 1.4. If directive’s value contains a source expression that is an // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
// XXX I don't think we should apply strict-dynamic to XSLT. if (hasStrictDynamicKeyword && aLoadInfo->InternalContentPolicyType() !=
nsIContentPolicy::TYPE_XSLT) { // Step 1.4.1 If the request’s parser metadata is "parser-inserted", // return "Blocked". Otherwise, return "Allowed". if (aLoadInfo->GetParserCreatedScript()) {
CSPUTILSLOG(
(" Blocked by 'strict-dynamic' because parser-inserted")); returnfalse;
}
CSPUTILSLOG(
(" Allowed by 'strict-dynamic' because not-parser-inserted")); returntrue;
}
}
}
for (uint32_t i = 0; i < mSrcs.Length(); i++) { if (mSrcs[i]->permits(aUri, aWasRedirected, aReportOnly,
aUpgradeInsecure)) { returntrue;
}
} returnfalse;
}
// Step 2. For each expression of list: for (nsCSPBaseSrc* src : mSrcs) { // Step 2.1. If expression matches the nonce-source or hash-source grammar, // return "Does Not Allow". if (src->isNonce() || src->isHash()) { returnfalse;
}
// Step 2.2. If type is "script", "script attribute" or "navigation" and // expression matches the keyword-source "'strict-dynamic'", return "Does // Not Allow". if ((aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE ||
aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) &&
src->isKeyword(CSP_STRICT_DYNAMIC)) { returnfalse;
}
// Step 2.3. If expression is an ASCII case-insensitive match for the // keyword-source "'unsafe-inline'", set allow all inline to true. if (src->isKeyword(CSP_UNSAFE_INLINE)) {
allowAll = true;
}
}
// Step 3. If allow all inline is true, return "Allows". Otherwise, return // "Does Not Allow". return allowAll;
}
if (mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) { if (allows(CSP_NONE, EmptyString())) { // Step 2.4: if directive’s value only contains a tt-keyword which is a // match for a value 'none', set createViolation to true. // `nsCSPParser` ignores the 'none' keyword if other keywords or policy // names are present. Hence no additional checks required here. returntrue;
}
if (aCreatedPolicyNames.Contains(aPolicyName) &&
!allows(CSP_ALLOW_DUPLICATES, EmptyString())) { // Step 2.5: if createdPolicyNames contains policyName and directive’s // value does not contain a tt-keyword which is a match for a value // 'allow-duplicates', set createViolation to true. returntrue;
}
if (!ContainsTrustedTypesDirectivePolicyName(aPolicyName) &&
!ContainsTrustedTypesDirectivePolicyName(kWildcard)) { // Step 2.6: if directive’s value does not contain a tt-policy-name, which // value is policyName, and directive’s value does not contain a // tt-wildcard, set createViolation to true. returntrue;
}
}
returnfalse;
}
void nsCSPDirective::toString(nsAString& outStr) const { // Append directive name
outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
outViolatedDirective.Truncate();
outViolatedDirectiveString.Truncate();
nsCSPDirective* defaultDir = nullptr;
// Try to find a relevant directive // These directive arrays are short (1-5 elements), not worth using a // hashtable. for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->equals(aDir)) { if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected,
mReportOnly, mUpgradeInsecDir)) {
mDirectives[i]->getDirName(outViolatedDirective);
mDirectives[i]->toString(outViolatedDirectiveString); returnfalse;
} returntrue;
} if (mDirectives[i]->isDefaultDirective()) {
defaultDir = mDirectives[i];
}
}
// If the above loop runs through, we haven't found a matching directive. // Avoid relooping, just store the result of default-src while looping. if (!aSpecific && defaultDir) { if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly,
mUpgradeInsecDir)) {
defaultDir->getDirName(outViolatedDirective);
defaultDir->toString(outViolatedDirectiveString); returnfalse;
} returntrue;
}
// Nothing restricts this, so we're allowing the load // See bug 764937 returntrue;
}
if (nsCSPDirective* directive = matchingOrDefaultDirective(aDirective)) { return directive->allows(aKeyword, aHashOrNonce);
}
// No matching directive or default directive as fallback found, thus // allowing the load; see Bug 885433 // a) inline scripts (also unsafe eval) should only be blocked // if there is a [script-src] or [default-src] // b) inline styles should only be blocked // if there is a [style-src] or [default-src] returntrue;
}
// Try to find a matching directive for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->isDefaultDirective()) {
defaultDir = mDirectives[i]; continue;
} if (mDirectives[i]->equals(aDirective)) { return mDirectives[i];
}
}
for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
mDirectives[i]->toDomCSPStruct(outCSP);
}
}
bool nsCSPPolicy::hasDirective(CSPDirective aDir) const { for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->equals(aDir)) { returntrue;
}
} returnfalse;
}
bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
nsCSPDirective* directive = matchingOrDefaultDirective(aDir); if (!directive) { // No matching or default directive found thus allow the all inline // scripts or styles. (See nsCSPPolicy::allows) returntrue;
}
/* * Use this function only after ::allows() returned 'false' or if ensured by * other means that the directive is violated. First and foremost it's used to * get the violated directive before sending reports. The parameter * aDirectiveName is the equivalent of 'outViolatedDirective' for the * ::permits() function family.
*/ void nsCSPPolicy::getViolatedDirectiveInformation(
CSPDirective aDirective, nsAString& aDirectiveName,
nsAString& aDirectiveNameAndValue, bool* aReportSample) const {
*aReportSample = false;
nsCSPDirective* directive = matchingOrDefaultDirective(aDirective); if (!directive) {
MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
aDirectiveName.Truncate();
aDirectiveNameAndValue.Truncate(); return;
}
/* * Helper function that returns the underlying bit representation of sandbox * flags. The function returns SANDBOXED_NONE if there are no sandbox * directives.
*/
uint32_t nsCSPPolicy::getSandboxFlags() const { for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
nsAutoString flags;
mDirectives[i]->toString(flags);
if (flags.IsEmpty()) { return SANDBOX_ALL_FLAGS;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.