nsCSPParser.cpp (54088B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsCSPParser.h" 8 9 #include <cstdint> 10 #include <utility> 11 12 #include "mozilla/Preferences.h" 13 #include "mozilla/StaticPrefs_dom.h" 14 #include "mozilla/StaticPrefs_security.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/TrustedTypesConstants.h" 17 #include "nsCOMPtr.h" 18 #include "nsCSPUtils.h" 19 #include "nsContentUtils.h" 20 #include "nsIScriptError.h" 21 #include "nsNetUtil.h" 22 #include "nsReadableUtils.h" 23 #include "nsServiceManagerUtils.h" 24 #include "nsUnicharUtils.h" 25 26 using namespace mozilla; 27 using namespace mozilla::dom; 28 29 static LogModule* GetCspParserLog() { 30 static LazyLogModule gCspParserPRLog("CSPParser"); 31 return gCspParserPRLog; 32 } 33 34 #define CSPPARSERLOG(args) \ 35 MOZ_LOG(GetCspParserLog(), mozilla::LogLevel::Debug, args) 36 #define CSPPARSERLOGENABLED() \ 37 MOZ_LOG_TEST(GetCspParserLog(), mozilla::LogLevel::Debug) 38 39 static const uint32_t kSubHostPathCharacterCutoff = 512; 40 41 static const char* const kHashSourceValidFns[] = {"sha256", "sha384", "sha512"}; 42 static const uint32_t kHashSourceValidFnsLen = 3; 43 44 /* ===== nsCSPParser ==================== */ 45 46 nsCSPParser::nsCSPParser(policyTokens& aTokens, nsIURI* aSelfURI, 47 nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag, 48 bool aSuppressLogMessages) 49 : mCurChar(nullptr), 50 mEndChar(nullptr), 51 mHasHashOrNonce(false), 52 mHasAnyUnsafeEval(false), 53 mStrictDynamic(false), 54 mUnsafeInlineKeywordSrc(nullptr), 55 mChildSrc(nullptr), 56 mFrameSrc(nullptr), 57 mWorkerSrc(nullptr), 58 mScriptSrc(nullptr), 59 mStyleSrc(nullptr), 60 mParsingFrameAncestorsDir(false), 61 mTokens(aTokens.Clone()), 62 mSelfURI(aSelfURI), 63 mPolicy(nullptr), 64 mCSPContext(aCSPContext), 65 mDeliveredViaMetaTag(aDeliveredViaMetaTag), 66 mSuppressLogMessages(aSuppressLogMessages) { 67 CSPPARSERLOG(("nsCSPParser::nsCSPParser")); 68 } 69 70 nsCSPParser::~nsCSPParser() { CSPPARSERLOG(("nsCSPParser::~nsCSPParser")); } 71 72 static bool isCharacterToken(char16_t aSymbol) { 73 return (aSymbol >= 'a' && aSymbol <= 'z') || 74 (aSymbol >= 'A' && aSymbol <= 'Z'); 75 } 76 77 bool isNumberToken(char16_t aSymbol) { 78 return (aSymbol >= '0' && aSymbol <= '9'); 79 } 80 81 bool isValidHexDig(char16_t aHexDig) { 82 return (isNumberToken(aHexDig) || (aHexDig >= 'A' && aHexDig <= 'F') || 83 (aHexDig >= 'a' && aHexDig <= 'f')); 84 } 85 86 bool isGroupDelim(char16_t aSymbol) { 87 return (aSymbol == '{' || aSymbol == '}' || aSymbol == ',' || 88 aSymbol == '/' || aSymbol == ':' || aSymbol == ';' || 89 aSymbol == '<' || aSymbol == '=' || aSymbol == '>' || 90 aSymbol == '?' || aSymbol == '@' || aSymbol == '[' || 91 aSymbol == '\\' || aSymbol == ']' || aSymbol == '"'); 92 } 93 94 static bool isValidBase64Value(const char16_t* cur, const char16_t* end) { 95 // Using grammar at 96 // https://w3c.github.io/webappsec-csp/#grammardef-nonce-source 97 98 // May end with one or two = 99 if (end > cur && *(end - 1) == EQUALS) end--; 100 if (end > cur && *(end - 1) == EQUALS) end--; 101 102 // Must have at least one character aside from any = 103 if (end == cur) { 104 return false; 105 } 106 107 // Rest must all be A-Za-z0-9+/-_ 108 for (; cur < end; ++cur) { 109 if (!(isCharacterToken(*cur) || isNumberToken(*cur) || *cur == PLUS || 110 *cur == SLASH || *cur == DASH || *cur == UNDERLINE)) { 111 return false; 112 } 113 } 114 115 return true; 116 } 117 118 void nsCSPParser::resetCurChar(const nsAString& aToken) { 119 mCurChar = aToken.BeginReading(); 120 mEndChar = aToken.EndReading(); 121 resetCurValue(); 122 } 123 124 // The path is terminated by the first question mark ("?") or 125 // number sign ("#") character, or by the end of the URI. 126 // http://tools.ietf.org/html/rfc3986#section-3.3 127 bool nsCSPParser::atEndOfPath() { 128 return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN)); 129 } 130 131 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 132 bool nsCSPParser::atValidUnreservedChar() { 133 return (peek(isCharacterToken) || peek(isNumberToken) || peek(DASH) || 134 peek(DOT) || peek(UNDERLINE) || peek(TILDE)); 135 } 136 137 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 138 // / "*" / "+" / "," / ";" / "=" 139 // Please note that even though ',' and ';' appear to be 140 // valid sub-delims according to the RFC production of paths, 141 // both can not appear here by itself, they would need to be 142 // pct-encoded in order to be part of the path. 143 bool nsCSPParser::atValidSubDelimChar() { 144 return (peek(EXCLAMATION) || peek(DOLLAR) || peek(AMPERSAND) || 145 peek(SINGLEQUOTE) || peek(OPENBRACE) || peek(CLOSINGBRACE) || 146 peek(WILDCARD) || peek(PLUS) || peek(EQUALS)); 147 } 148 149 // pct-encoded = "%" HEXDIG HEXDIG 150 bool nsCSPParser::atValidPctEncodedChar() { 151 const char16_t* pctCurChar = mCurChar; 152 153 if ((pctCurChar + 2) >= mEndChar) { 154 // string too short, can't be a valid pct-encoded char. 155 return false; 156 } 157 158 // Any valid pct-encoding must follow the following format: 159 // "% HEXDIG HEXDIG" 160 if (PERCENT_SIGN != *pctCurChar || !isValidHexDig(*(pctCurChar + 1)) || 161 !isValidHexDig(*(pctCurChar + 2))) { 162 return false; 163 } 164 return true; 165 } 166 167 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 168 // http://tools.ietf.org/html/rfc3986#section-3.3 169 bool nsCSPParser::atValidPathChar() { 170 return (atValidUnreservedChar() || atValidSubDelimChar() || 171 atValidPctEncodedChar() || peek(COLON) || peek(ATSYMBOL)); 172 } 173 174 void nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag, 175 const char* aProperty, 176 const nsTArray<nsString>& aParams) { 177 CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty)); 178 179 if (mSuppressLogMessages) { 180 return; 181 } 182 183 // send console messages off to the context and let the context 184 // deal with it (potentially messages need to be queued up) 185 mCSPContext->logToConsole(aProperty, aParams, 186 ""_ns, // aSourceName 187 u""_ns, // aSourceLine 188 0, // aLineNumber 189 1, // aColumnNumber 190 aSeverityFlag); // aFlags 191 } 192 193 bool nsCSPParser::hostChar() { 194 if (atEnd()) { 195 return false; 196 } 197 return accept(isCharacterToken) || accept(isNumberToken) || accept(DASH); 198 } 199 200 // (ALPHA / DIGIT / "+" / "-" / "." ) 201 bool nsCSPParser::schemeChar() { 202 if (atEnd()) { 203 return false; 204 } 205 return accept(isCharacterToken) || accept(isNumberToken) || accept(PLUS) || 206 accept(DASH) || accept(DOT); 207 } 208 209 // port = ":" ( 1*DIGIT / "*" ) 210 bool nsCSPParser::port() { 211 CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s", 212 NS_ConvertUTF16toUTF8(mCurToken).get(), 213 NS_ConvertUTF16toUTF8(mCurValue).get())); 214 215 // Consume the COLON we just peeked at in houstSource 216 accept(COLON); 217 218 // Resetting current value since we start to parse a port now. 219 // e.g; "http://www.example.com:8888" then we have already parsed 220 // everything up to (including) ":"; 221 resetCurValue(); 222 223 // Port might be "*" 224 if (accept(WILDCARD)) { 225 return true; 226 } 227 228 // Port must start with a number 229 if (!accept(isNumberToken)) { 230 AutoTArray<nsString, 1> params = {mCurToken}; 231 logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParsePort", 232 params); 233 return false; 234 } 235 // Consume more numbers and set parsed port to the nsCSPHost 236 while (accept(isNumberToken)) { /* consume */ 237 } 238 return true; 239 } 240 241 bool nsCSPParser::subPath(nsCSPHostSrc* aCspHost) { 242 CSPPARSERLOG(("nsCSPParser::subPath, mCurToken: %s, mCurValue: %s", 243 NS_ConvertUTF16toUTF8(mCurToken).get(), 244 NS_ConvertUTF16toUTF8(mCurValue).get())); 245 246 // Emergency exit to avoid endless loops in case a path in a CSP policy 247 // is longer than 512 characters, or also to avoid endless loops 248 // in case we are parsing unrecognized characters in the following loop. 249 uint32_t charCounter = 0; 250 nsString pctDecodedSubPath; 251 252 while (!atEndOfPath()) { 253 if (peek(SLASH)) { 254 // before appendig any additional portion of a subpath we have to 255 // pct-decode that portion of the subpath. atValidPathChar() already 256 // verified a correct pct-encoding, now we can safely decode and append 257 // the decoded-sub path. 258 CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath); 259 aCspHost->appendPath(pctDecodedSubPath); 260 // Resetting current value since we are appending parts of the path 261 // to aCspHost, e.g; "http://www.example.com/path1/path2" then the 262 // first part is "/path1", second part "/path2" 263 resetCurValue(); 264 } else if (!atValidPathChar()) { 265 AutoTArray<nsString, 1> params = {mCurToken}; 266 logWarningErrorToConsole(nsIScriptError::warningFlag, 267 "couldntParseInvalidSource", params); 268 return false; 269 } 270 // potentially we have encountred a valid pct-encoded character in 271 // atValidPathChar(); if so, we have to account for "% HEXDIG HEXDIG" and 272 // advance the pointer past the pct-encoded char. 273 if (peek(PERCENT_SIGN)) { 274 advance(); 275 advance(); 276 } 277 advance(); 278 if (++charCounter > kSubHostPathCharacterCutoff) { 279 return false; 280 } 281 } 282 // before appendig any additional portion of a subpath we have to pct-decode 283 // that portion of the subpath. atValidPathChar() already verified a correct 284 // pct-encoding, now we can safely decode and append the decoded-sub path. 285 CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath); 286 aCspHost->appendPath(pctDecodedSubPath); 287 resetCurValue(); 288 return true; 289 } 290 291 bool nsCSPParser::path(nsCSPHostSrc* aCspHost) { 292 CSPPARSERLOG(("nsCSPParser::path, mCurToken: %s, mCurValue: %s", 293 NS_ConvertUTF16toUTF8(mCurToken).get(), 294 NS_ConvertUTF16toUTF8(mCurValue).get())); 295 296 // Resetting current value and forgetting everything we have parsed so far 297 // e.g. parsing "http://www.example.com/path1/path2", then 298 // "http://www.example.com" has already been parsed so far 299 // forget about it. 300 resetCurValue(); 301 302 if (!accept(SLASH)) { 303 AutoTArray<nsString, 1> params = {mCurToken}; 304 logWarningErrorToConsole(nsIScriptError::warningFlag, 305 "couldntParseInvalidSource", params); 306 return false; 307 } 308 if (atEndOfPath()) { 309 // one slash right after host [port] is also considered a path, e.g. 310 // www.example.com/ should result in www.example.com/ 311 // please note that we do not have to perform any pct-decoding here 312 // because we are just appending a '/' and not any actual chars. 313 aCspHost->appendPath(u"/"_ns); 314 return true; 315 } 316 // path can begin with "/" but not "//" 317 // see http://tools.ietf.org/html/rfc3986#section-3.3 318 if (peek(SLASH)) { 319 AutoTArray<nsString, 1> params = {mCurToken}; 320 logWarningErrorToConsole(nsIScriptError::warningFlag, 321 "couldntParseInvalidSource", params); 322 return false; 323 } 324 return subPath(aCspHost); 325 } 326 327 bool nsCSPParser::subHost() { 328 CSPPARSERLOG(("nsCSPParser::subHost, mCurToken: %s, mCurValue: %s", 329 NS_ConvertUTF16toUTF8(mCurToken).get(), 330 NS_ConvertUTF16toUTF8(mCurValue).get())); 331 332 // Emergency exit to avoid endless loops in case a host in a CSP policy 333 // is longer than 512 characters, or also to avoid endless loops 334 // in case we are parsing unrecognized characters in the following loop. 335 uint32_t charCounter = 0; 336 337 while (!atEndOfPath() && !peek(COLON) && !peek(SLASH)) { 338 ++charCounter; 339 while (hostChar()) { 340 /* consume */ 341 ++charCounter; 342 } 343 if (accept(DOT) && !hostChar()) { 344 return false; 345 } 346 if (charCounter > kSubHostPathCharacterCutoff) { 347 return false; 348 } 349 } 350 return true; 351 } 352 353 // host = "*" / [ "*." ] 1*host-char *( "." 1*host-char ) 354 nsCSPHostSrc* nsCSPParser::host() { 355 CSPPARSERLOG(("nsCSPParser::host, mCurToken: %s, mCurValue: %s", 356 NS_ConvertUTF16toUTF8(mCurToken).get(), 357 NS_ConvertUTF16toUTF8(mCurValue).get())); 358 359 // Check if the token starts with "*"; please remember that we handle 360 // a single "*" as host in sourceExpression, but we still have to handle 361 // the case where a scheme was defined, e.g., as: 362 // "https://*", "*.example.com", "*:*", etc. 363 if (accept(WILDCARD)) { 364 // Might solely be the wildcard 365 if (atEnd() || peek(COLON)) { 366 return new nsCSPHostSrc(mCurValue); 367 } 368 // If the token is not only the "*", a "." must follow right after 369 if (!accept(DOT)) { 370 AutoTArray<nsString, 1> params = {mCurToken}; 371 logWarningErrorToConsole(nsIScriptError::warningFlag, 372 "couldntParseInvalidHost", params); 373 return nullptr; 374 } 375 } 376 377 // Expecting at least one host-char 378 if (!hostChar()) { 379 AutoTArray<nsString, 1> params = {mCurToken}; 380 logWarningErrorToConsole(nsIScriptError::warningFlag, 381 "couldntParseInvalidHost", params); 382 return nullptr; 383 } 384 385 // There might be several sub hosts defined. 386 if (!subHost()) { 387 AutoTArray<nsString, 1> params = {mCurToken}; 388 logWarningErrorToConsole(nsIScriptError::warningFlag, 389 "couldntParseInvalidHost", params); 390 return nullptr; 391 } 392 393 // HostName might match a keyword, log to the console. 394 if (CSP_IsQuotelessKeyword(mCurValue)) { 395 nsString keyword = mCurValue; 396 ToLowerCase(keyword); 397 AutoTArray<nsString, 2> params = {mCurToken, keyword}; 398 logWarningErrorToConsole(nsIScriptError::warningFlag, 399 "hostNameMightBeKeyword", params); 400 } 401 402 // Create a new nsCSPHostSrc with the parsed host. 403 return new nsCSPHostSrc(mCurValue); 404 } 405 406 // keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" / 407 // "'wasm-unsafe-eval'" 408 nsCSPBaseSrc* nsCSPParser::keywordSource() { 409 CSPPARSERLOG(("nsCSPParser::keywordSource, mCurToken: %s, mCurValue: %s", 410 NS_ConvertUTF16toUTF8(mCurToken).get(), 411 NS_ConvertUTF16toUTF8(mCurValue).get())); 412 413 // Special case handling for 'self' which is not stored internally as a 414 // keyword, but rather creates a nsCSPHostSrc using the selfURI 415 if (CSP_IsKeyword(mCurToken, CSP_SELF)) { 416 return CSP_CreateHostSrcFromSelfURI(mSelfURI); 417 } 418 419 if (CSP_IsKeyword(mCurToken, CSP_REPORT_SAMPLE)) { 420 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 421 } 422 423 if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) { 424 if (!CSP_IsDirective(mCurDir[0], 425 nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) && 426 !CSP_IsDirective(mCurDir[0], 427 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) && 428 !CSP_IsDirective(mCurDir[0], 429 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) && 430 !CSP_IsDirective(mCurDir[0], 431 nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE)) { 432 AutoTArray<nsString, 1> params = {u"strict-dynamic"_ns}; 433 logWarningErrorToConsole(nsIScriptError::warningFlag, 434 "ignoringStrictDynamic", params); 435 } 436 437 mStrictDynamic = true; 438 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 439 } 440 441 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE)) { 442 // make sure script-src only contains 'unsafe-inline' once; 443 // ignore duplicates and log warning 444 if (mUnsafeInlineKeywordSrc) { 445 AutoTArray<nsString, 1> params = {mCurToken}; 446 logWarningErrorToConsole(nsIScriptError::warningFlag, 447 "ignoringDuplicateSrc", params); 448 return nullptr; 449 } 450 // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in 451 // case that script-src directive also contains hash- or nonce-. 452 mUnsafeInlineKeywordSrc = 453 new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 454 return mUnsafeInlineKeywordSrc; 455 } 456 457 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) { 458 mHasAnyUnsafeEval = true; 459 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 460 } 461 462 if (CSP_IsKeyword(mCurToken, CSP_WASM_UNSAFE_EVAL)) { 463 mHasAnyUnsafeEval = true; 464 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 465 } 466 467 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_HASHES)) { 468 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 469 } 470 471 if (StaticPrefs::dom_security_trusted_types_enabled() && 472 CSP_IsKeyword(mCurToken, CSP_TRUSTED_TYPES_EVAL)) { 473 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken)); 474 } 475 476 return nullptr; 477 } 478 479 // host-source = [ scheme "://" ] host [ port ] [ path ] 480 nsCSPHostSrc* nsCSPParser::hostSource() { 481 CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s", 482 NS_ConvertUTF16toUTF8(mCurToken).get(), 483 NS_ConvertUTF16toUTF8(mCurValue).get())); 484 485 nsCSPHostSrc* cspHost = host(); 486 if (!cspHost) { 487 // Error was reported in host() 488 return nullptr; 489 } 490 491 // Calling port() to see if there is a port to parse, if an error 492 // occurs, port() reports the error, if port() returns true; 493 // we have a valid port, so we add it to cspHost. 494 if (peek(COLON)) { 495 if (!port()) { 496 delete cspHost; 497 return nullptr; 498 } 499 cspHost->setPort(mCurValue); 500 } 501 502 if (atEndOfPath()) { 503 return cspHost; 504 } 505 506 // Calling path() to see if there is a path to parse, if an error 507 // occurs, path() reports the error; handing cspHost as an argument 508 // which simplifies parsing of several paths. 509 if (!path(cspHost)) { 510 // If the host [port] is followed by a path, it has to be a valid path, 511 // otherwise we pass the nullptr, indicating an error, up the callstack. 512 // see also http://www.w3.org/TR/CSP11/#source-list 513 delete cspHost; 514 return nullptr; 515 } 516 return cspHost; 517 } 518 519 // scheme-source = scheme ":" 520 nsCSPSchemeSrc* nsCSPParser::schemeSource() { 521 CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s", 522 NS_ConvertUTF16toUTF8(mCurToken).get(), 523 NS_ConvertUTF16toUTF8(mCurValue).get())); 524 525 if (!accept(isCharacterToken)) { 526 return nullptr; 527 } 528 while (schemeChar()) { /* consume */ 529 } 530 nsString scheme = mCurValue; 531 532 // If the potential scheme is not followed by ":" - it's not a scheme 533 if (!accept(COLON)) { 534 return nullptr; 535 } 536 537 // If the chraracter following the ":" is a number or the "*" 538 // then we are not parsing a scheme; but rather a host; 539 if (peek(isNumberToken) || peek(WILDCARD)) { 540 return nullptr; 541 } 542 543 return new nsCSPSchemeSrc(scheme); 544 } 545 546 // nonce-source = "'nonce-" nonce-value "'" 547 nsCSPNonceSrc* nsCSPParser::nonceSource() { 548 CSPPARSERLOG(("nsCSPParser::nonceSource, mCurToken: %s, mCurValue: %s", 549 NS_ConvertUTF16toUTF8(mCurToken).get(), 550 NS_ConvertUTF16toUTF8(mCurValue).get())); 551 552 // Check if mCurToken begins with "'nonce-" and ends with "'" 553 if (!StringBeginsWith(mCurToken, 554 nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE)), 555 nsASCIICaseInsensitiveStringComparator) || 556 mCurToken.Last() != SINGLEQUOTE) { 557 return nullptr; 558 } 559 560 // Trim surrounding single quotes 561 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2); 562 563 int32_t dashIndex = expr.FindChar(DASH); 564 if (dashIndex < 0) { 565 return nullptr; 566 } 567 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1, 568 expr.EndReading())) { 569 return nullptr; 570 } 571 572 // cache if encountering hash or nonce to invalidate unsafe-inline 573 mHasHashOrNonce = true; 574 return new nsCSPNonceSrc( 575 Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1)); 576 } 577 578 // hash-source = "'" hash-algo "-" base64-value "'" 579 nsCSPHashSrc* nsCSPParser::hashSource() { 580 CSPPARSERLOG(("nsCSPParser::hashSource, mCurToken: %s, mCurValue: %s", 581 NS_ConvertUTF16toUTF8(mCurToken).get(), 582 NS_ConvertUTF16toUTF8(mCurValue).get())); 583 584 // Check if mCurToken starts and ends with "'" 585 if (mCurToken.First() != SINGLEQUOTE || mCurToken.Last() != SINGLEQUOTE) { 586 return nullptr; 587 } 588 589 // Trim surrounding single quotes 590 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2); 591 592 int32_t dashIndex = expr.FindChar(DASH); 593 if (dashIndex < 0) { 594 return nullptr; 595 } 596 597 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1, 598 expr.EndReading())) { 599 return nullptr; 600 } 601 602 nsAutoString algo(Substring(expr, 0, dashIndex)); 603 nsAutoString hash( 604 Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1)); 605 606 for (uint32_t i = 0; i < kHashSourceValidFnsLen; i++) { 607 if (algo.LowerCaseEqualsASCII(kHashSourceValidFns[i])) { 608 // cache if encountering hash or nonce to invalidate unsafe-inline 609 mHasHashOrNonce = true; 610 return new nsCSPHashSrc(algo, hash); 611 } 612 } 613 return nullptr; 614 } 615 616 // source-expression = scheme-source / host-source / keyword-source 617 // / nonce-source / hash-source 618 nsCSPBaseSrc* nsCSPParser::sourceExpression() { 619 CSPPARSERLOG(("nsCSPParser::sourceExpression, mCurToken: %s, mCurValue: %s", 620 NS_ConvertUTF16toUTF8(mCurToken).get(), 621 NS_ConvertUTF16toUTF8(mCurValue).get())); 622 623 // Check if it is a keyword 624 if (nsCSPBaseSrc* cspKeyword = keywordSource()) { 625 return cspKeyword; 626 } 627 628 // Check if it is a nonce-source 629 if (nsCSPNonceSrc* cspNonce = nonceSource()) { 630 return cspNonce; 631 } 632 633 // Check if it is a hash-source 634 if (nsCSPHashSrc* cspHash = hashSource()) { 635 return cspHash; 636 } 637 638 // We handle a single "*" as host here, to avoid any confusion when applying 639 // the default scheme. However, we still would need to apply the default 640 // scheme in case we would parse "*:80". 641 if (mCurToken.EqualsASCII("*")) { 642 return new nsCSPHostSrc(u"*"_ns); 643 } 644 645 // Calling resetCurChar allows us to use mCurChar and mEndChar 646 // to parse mCurToken; e.g. mCurToken = "http://www.example.com", then 647 // mCurChar = 'h' 648 // mEndChar = points just after the last 'm' 649 // mCurValue = "" 650 resetCurChar(mCurToken); 651 652 // Check if mCurToken starts with a scheme 653 nsAutoString parsedScheme; 654 if (nsCSPSchemeSrc* cspScheme = schemeSource()) { 655 // mCurToken might only enforce a specific scheme 656 if (atEnd()) { 657 return cspScheme; 658 } 659 // If something follows the scheme, we do not create 660 // a nsCSPSchemeSrc, but rather a nsCSPHostSrc, which 661 // needs to know the scheme to enforce; remember the 662 // scheme and delete cspScheme; 663 cspScheme->toString(parsedScheme); 664 parsedScheme.Trim(":", false, true); 665 delete cspScheme; 666 667 // If mCurToken provides not only a scheme, but also a host, we have to 668 // check if two slashes follow the scheme. 669 if (!accept(SLASH) || !accept(SLASH)) { 670 AutoTArray<nsString, 1> params = {mCurToken}; 671 logWarningErrorToConsole(nsIScriptError::warningFlag, 672 "failedToParseUnrecognizedSource", params); 673 return nullptr; 674 } 675 } 676 677 // Calling resetCurValue allows us to keep pointers for mCurChar and mEndChar 678 // alive, but resets mCurValue; e.g. mCurToken = "http://www.example.com", 679 // then mCurChar = 'w' mEndChar = 'm' mCurValue = "" 680 resetCurValue(); 681 682 // If mCurToken does not provide a scheme (scheme-less source), we apply the 683 // scheme from selfURI 684 if (parsedScheme.IsEmpty()) { 685 // Resetting internal helpers, because we might already have parsed some of 686 // the host when trying to parse a scheme. 687 resetCurChar(mCurToken); 688 nsAutoCString selfScheme; 689 mSelfURI->GetScheme(selfScheme); 690 parsedScheme.AssignASCII(selfScheme.get()); 691 } 692 693 // At this point we are expecting a host to be parsed. 694 // Trying to create a new nsCSPHost. 695 if (nsCSPHostSrc* cspHost = hostSource()) { 696 // Do not forget to set the parsed scheme. 697 cspHost->setScheme(parsedScheme); 698 cspHost->setWithinFrameAncestorsDir(mParsingFrameAncestorsDir); 699 return cspHost; 700 } 701 // Error was reported in hostSource() 702 return nullptr; 703 } 704 705 void nsCSPParser::logWarningForIgnoringNoneKeywordToConsole() { 706 AutoTArray<nsString, 1> params; 707 params.AppendElement(CSP_EnumToUTF16Keyword(CSP_NONE)); 708 logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnknownOption", 709 params); 710 } 711 712 // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ] 713 // / *WSP "'none'" *WSP 714 void nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs) { 715 bool isNone = false; 716 717 // remember, srcs start at index 1 718 for (uint32_t i = 1; i < mCurDir.Length(); i++) { 719 // mCurToken is only set here and remains the current token 720 // to be processed, which avoid passing arguments between functions. 721 mCurToken = mCurDir[i]; 722 resetCurValue(); 723 724 CSPPARSERLOG(("nsCSPParser::sourceList, mCurToken: %s, mCurValue: %s", 725 NS_ConvertUTF16toUTF8(mCurToken).get(), 726 NS_ConvertUTF16toUTF8(mCurValue).get())); 727 728 // Special case handling for none: 729 // Ignore 'none' if any other src is available. 730 // (See http://www.w3.org/TR/CSP11/#parsing) 731 if (CSP_IsKeyword(mCurToken, CSP_NONE)) { 732 isNone = true; 733 continue; 734 } 735 // Must be a regular source expression 736 nsCSPBaseSrc* src = sourceExpression(); 737 if (src) { 738 outSrcs.AppendElement(src); 739 } 740 } 741 742 // Check if the directive contains a 'none' 743 if (isNone) { 744 // If the directive contains no other srcs, then we set the 'none' 745 if (outSrcs.IsEmpty() || 746 (outSrcs.Length() == 1 && outSrcs[0]->isReportSample())) { 747 nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE); 748 outSrcs.InsertElementAt(0, keyword); 749 } 750 // Otherwise, we ignore 'none' and report a warning 751 else { 752 logWarningForIgnoringNoneKeywordToConsole(); 753 } 754 } 755 } 756 757 void nsCSPParser::reportURIList(nsCSPDirective* aDir) { 758 CSPPARSERLOG(("nsCSPParser::reportURIList")); 759 760 nsTArray<nsCSPBaseSrc*> srcs; 761 nsCOMPtr<nsIURI> uri; 762 nsresult rv; 763 764 // remember, srcs start at index 1 765 for (uint32_t i = 1; i < mCurDir.Length(); i++) { 766 mCurToken = mCurDir[i]; 767 768 CSPPARSERLOG(("nsCSPParser::reportURIList, mCurToken: %s, mCurValue: %s", 769 NS_ConvertUTF16toUTF8(mCurToken).get(), 770 NS_ConvertUTF16toUTF8(mCurValue).get())); 771 772 rv = NS_NewURI(getter_AddRefs(uri), mCurToken, "", mSelfURI); 773 774 // If creating the URI casued an error, skip this URI 775 if (NS_FAILED(rv)) { 776 AutoTArray<nsString, 1> params = {mCurToken}; 777 logWarningErrorToConsole(nsIScriptError::warningFlag, 778 "couldNotParseReportURI", params); 779 continue; 780 } 781 782 // Create new nsCSPReportURI and append to the list. 783 nsCSPReportURI* reportURI = new nsCSPReportURI(uri); 784 srcs.AppendElement(reportURI); 785 } 786 787 if (srcs.Length() == 0) { 788 AutoTArray<nsString, 1> directiveName = {mCurToken}; 789 logWarningErrorToConsole(nsIScriptError::warningFlag, 790 "ignoringDirectiveWithNoValues", directiveName); 791 delete aDir; 792 return; 793 } 794 795 aDir->addSrcs(srcs); 796 mPolicy->addDirective(aDir); 797 } 798 799 void nsCSPParser::reportGroup(nsCSPDirective* aDir) { 800 CSPPARSERLOG(("nsCSPParser::reportGroup")); 801 802 if (mCurDir.Length() < 2) { 803 AutoTArray<nsString, 1> directiveName = {mCurToken}; 804 logWarningErrorToConsole(nsIScriptError::warningFlag, 805 "ignoringDirectiveWithNoValues", directiveName); 806 delete aDir; 807 return; 808 } 809 810 nsTArray<nsCSPBaseSrc*> srcs; 811 mCurToken = mCurDir[1]; 812 813 CSPPARSERLOG(("nsCSPParser::reportGroup, mCurToken: %s, mCurValue: %s", 814 NS_ConvertUTF16toUTF8(mCurToken).get(), 815 NS_ConvertUTF16toUTF8(mCurValue).get())); 816 817 resetCurChar(mCurToken); 818 while (!atEnd()) { 819 if (isGroupDelim(*mCurChar) || 820 nsContentUtils::IsHTMLWhitespace(*mCurChar)) { 821 nsString badChar(mozilla::Span(mCurChar, 1)); 822 AutoTArray<nsString, 2> params = {mCurToken, badChar}; 823 logWarningErrorToConsole(nsIScriptError::warningFlag, 824 "ignoringInvalidGroupSyntax", params); 825 delete aDir; 826 return; 827 } 828 advance(); 829 } 830 831 nsCSPGroup* group = new nsCSPGroup(mCurToken); 832 srcs.AppendElement(group); 833 aDir->addSrcs(srcs); 834 mPolicy->addDirective(aDir); 835 aDir = nullptr; 836 }; 837 838 /* Helper function for parsing sandbox flags. This function solely concatenates 839 * all the source list tokens (the sandbox flags) so the attribute parser 840 * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them. 841 */ 842 void nsCSPParser::sandboxFlagList(nsCSPDirective* aDir) { 843 CSPPARSERLOG(("nsCSPParser::sandboxFlagList")); 844 845 nsAutoString flags; 846 847 // remember, srcs start at index 1 848 for (uint32_t i = 1; i < mCurDir.Length(); i++) { 849 mCurToken = mCurDir[i]; 850 851 CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s", 852 NS_ConvertUTF16toUTF8(mCurToken).get(), 853 NS_ConvertUTF16toUTF8(mCurValue).get())); 854 855 if (!nsContentUtils::IsValidSandboxFlag(mCurToken)) { 856 AutoTArray<nsString, 1> params = {mCurToken}; 857 logWarningErrorToConsole(nsIScriptError::warningFlag, 858 "couldntParseInvalidSandboxFlag", params); 859 continue; 860 } 861 862 flags.Append(mCurToken); 863 if (i != mCurDir.Length() - 1) { 864 flags.AppendLiteral(" "); 865 } 866 } 867 868 // Please note that the sandbox directive can exist 869 // by itself (not containing any flags). 870 nsTArray<nsCSPBaseSrc*> srcs; 871 srcs.AppendElement(new nsCSPSandboxFlags(flags)); 872 aDir->addSrcs(srcs); 873 mPolicy->addDirective(aDir); 874 } 875 876 static bool IsValidRequireTrustedTypesForDirectiveValue( 877 const nsAString& aToken) { 878 return aToken.Equals(kValidRequireTrustedTypesForDirectiveValue); 879 } 880 881 void nsCSPParser::handleRequireTrustedTypesForDirective(nsCSPDirective* aDir) { 882 CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective")); 883 884 // "srcs" start at index 1. Here "srcs" should represent Trusted Types' sink 885 // groups 886 // (https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive). 887 // We align with other browsers and make the syntax forgiving i.e. invalid 888 // trusted-types-sink-group-keyword are discarded without invalidating the 889 // whole directive. However, if no valid trusted-types-sink-group-keyword is 890 // found, the directive has no effect and can just be discarded completely. 891 // See https://github.com/w3c/trusted-types/issues/580. 892 if (mCurDir.Length() < 2) { 893 nsString numberOfTokensStr; 894 895 // Casting is required to avoid ambiguous function calls on some platforms. 896 numberOfTokensStr.AppendInt(static_cast<uint64_t>(mCurDir.Length())); 897 898 AutoTArray<nsString, 1> numberOfTokensArr = {std::move(numberOfTokensStr)}; 899 logWarningErrorToConsole(nsIScriptError::errorFlag, 900 "invalidNumberOfTrustedTypesForDirectiveValues", 901 numberOfTokensArr); 902 return; 903 } 904 905 nsTArray<nsCSPBaseSrc*> trustedTypesSinkGroupKeywords; 906 bool foundValidTrustedTypesSinkGroupKeyword = false; 907 for (uint32_t i = 1; i < mCurDir.Length(); ++i) { 908 mCurToken = mCurDir[i]; 909 910 CSPPARSERLOG( 911 ("nsCSPParser::handleRequireTrustedTypesForDirective, mCurToken: %s", 912 NS_ConvertUTF16toUTF8(mCurToken).get())); 913 914 if (!IsValidRequireTrustedTypesForDirectiveValue(mCurToken)) { 915 AutoTArray<nsString, 1> token = {mCurToken}; 916 logWarningErrorToConsole(nsIScriptError::warningFlag, 917 "invalidRequireTrustedTypesForDirectiveValue", 918 token); 919 } else { 920 foundValidTrustedTypesSinkGroupKeyword = true; 921 } 922 trustedTypesSinkGroupKeywords.AppendElement( 923 new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken)); 924 } 925 if (!foundValidTrustedTypesSinkGroupKeyword) { 926 for (auto* trustedTypesSinkGroupKeyword : trustedTypesSinkGroupKeywords) { 927 delete trustedTypesSinkGroupKeyword; 928 } 929 return; 930 } 931 aDir->addSrcs(trustedTypesSinkGroupKeywords); 932 mPolicy->addDirective(aDir); 933 } 934 935 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive 936 static bool IsValidTrustedTypesWildcard(const nsAString& aToken) { 937 // tt-wildcard = "*" 938 return aToken.Length() == 1 && aToken.First() == WILDCARD; 939 } 940 941 static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar) { 942 // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" / 943 // "." / "%") 944 return nsContentUtils::IsAlphanumeric(aChar) || aChar == DASH || 945 aChar == NUMBER_SIGN || aChar == EQUALS || aChar == UNDERLINE || 946 aChar == SLASH || aChar == ATSYMBOL || aChar == DOT || 947 aChar == PERCENT_SIGN; 948 } 949 950 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive 951 static bool IsValidTrustedTypesPolicyName(const nsAString& aToken) { 952 // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" / 953 // "." / "%") 954 955 if (aToken.IsEmpty()) { 956 return false; 957 } 958 959 for (uint32_t i = 0; i < aToken.Length(); ++i) { 960 if (!IsValidTrustedTypesPolicyNameChar(aToken.CharAt(i))) { 961 return false; 962 } 963 } 964 965 return true; 966 } 967 968 void nsCSPParser::handleTrustedTypesDirective(nsCSPDirective* aDir) { 969 CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective")); 970 971 nsTArray<nsCSPBaseSrc*> trustedTypesExpressions; 972 973 bool containsKeywordNone = false; 974 975 // "srcs" start and index 1. Here they should represent the tt-expressions 976 // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive). 977 for (uint32_t i = 1; i < mCurDir.Length(); ++i) { 978 mCurToken = mCurDir[i]; 979 980 CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective, mCurToken: %s", 981 NS_ConvertUTF16toUTF8(mCurToken).get())); 982 983 // tt-expression = tt-policy-name / tt-keyword / tt-wildcard 984 if (IsValidTrustedTypesPolicyName(mCurToken)) { 985 trustedTypesExpressions.AppendElement( 986 new nsCSPTrustedTypesDirectivePolicyName(mCurToken)); 987 } else if (CSP_IsKeyword(mCurToken, CSP_NONE)) { 988 containsKeywordNone = true; 989 } else if (CSP_IsKeyword(mCurToken, CSP_ALLOW_DUPLICATES)) { 990 trustedTypesExpressions.AppendElement( 991 new nsCSPKeywordSrc(CSP_ALLOW_DUPLICATES)); 992 } else if (IsValidTrustedTypesWildcard(mCurToken)) { 993 trustedTypesExpressions.AppendElement( 994 new nsCSPTrustedTypesDirectivePolicyName(mCurToken)); 995 } else { 996 AutoTArray<nsString, 1> token = {mCurToken}; 997 logWarningErrorToConsole(nsIScriptError::warningFlag, 998 "invalidTrustedTypesExpression", token); 999 trustedTypesExpressions.AppendElement( 1000 new nsCSPTrustedTypesDirectiveInvalidToken(mCurToken)); 1001 } 1002 } 1003 1004 if (trustedTypesExpressions.IsEmpty()) { 1005 // No tt-expression is equivalent to 'none', see 1006 // <https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive>. 1007 trustedTypesExpressions.AppendElement(new nsCSPKeywordSrc(CSP_NONE)); 1008 } else if (containsKeywordNone) { 1009 // See step 2.4's note at 1010 // <https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy>. 1011 logWarningForIgnoringNoneKeywordToConsole(); 1012 } 1013 1014 aDir->addSrcs(trustedTypesExpressions); 1015 mPolicy->addDirective(aDir); 1016 } 1017 1018 // directive-value = *( WSP / <VCHAR except ";" and ","> ) 1019 void nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs) { 1020 CSPPARSERLOG(("nsCSPParser::directiveValue")); 1021 1022 // Just forward to sourceList 1023 sourceList(outSrcs); 1024 } 1025 1026 // directive-name = 1*( ALPHA / DIGIT / "-" ) 1027 nsCSPDirective* nsCSPParser::directiveName() { 1028 CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s", 1029 NS_ConvertUTF16toUTF8(mCurToken).get(), 1030 NS_ConvertUTF16toUTF8(mCurValue).get())); 1031 1032 // Check if it is a valid directive 1033 CSPDirective directive = CSP_StringToCSPDirective(mCurToken); 1034 if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE || 1035 (!StaticPrefs::dom_security_trusted_types_enabled() && 1036 (directive == 1037 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE || 1038 directive == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE))) { 1039 AutoTArray<nsString, 1> params = {mCurToken}; 1040 logWarningErrorToConsole(nsIScriptError::warningFlag, 1041 "couldNotProcessUnknownDirective", params); 1042 return nullptr; 1043 } 1044 1045 // The directive 'reflected-xss' is part of CSP 1.1, see: 1046 // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss 1047 // Currently we are not supporting that directive, hence we log a 1048 // warning to the console and ignore the directive including its values. 1049 if (directive == nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE) { 1050 AutoTArray<nsString, 1> params = {mCurToken}; 1051 logWarningErrorToConsole(nsIScriptError::warningFlag, 1052 "notSupportingDirective", params); 1053 return nullptr; 1054 } 1055 1056 // Make sure the directive does not already exist 1057 // (see http://www.w3.org/TR/CSP11/#parsing) 1058 if (mPolicy->hasDirective(directive)) { 1059 AutoTArray<nsString, 1> params = {mCurToken}; 1060 logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective", 1061 params); 1062 return nullptr; 1063 } 1064 1065 // CSP delivered via meta tag should ignore the following directives: 1066 // report-uri, frame-ancestors, and sandbox, see: 1067 // http://www.w3.org/TR/CSP11/#delivery-html-meta-element 1068 if (mDeliveredViaMetaTag && 1069 ((directive == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE) || 1070 (directive == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE) || 1071 (directive == nsIContentSecurityPolicy::SANDBOX_DIRECTIVE))) { 1072 // log to the console to indicate that meta CSP is ignoring the directive 1073 AutoTArray<nsString, 1> params = {mCurToken}; 1074 logWarningErrorToConsole(nsIScriptError::warningFlag, 1075 "ignoringSrcFromMetaCSP", params); 1076 return nullptr; 1077 } 1078 1079 // special case handling for block-all-mixed-content 1080 if (directive == nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT) { 1081 // If mixed content upgrade is enabled for display content, then 1082 // block-all-mixed-content is obsolete. 1083 if (mozilla::StaticPrefs:: 1084 security_mixed_content_upgrade_display_content()) { 1085 // log to the console that if mixed content display upgrading is enabled 1086 // block-all-mixed-content is obsolete. 1087 AutoTArray<nsString, 1> params = {mCurToken}; 1088 logWarningErrorToConsole(nsIScriptError::warningFlag, 1089 "obsoleteBlockAllMixedContent", params); 1090 } 1091 return new nsBlockAllMixedContentDirective(directive); 1092 } 1093 1094 // special case handling for upgrade-insecure-requests 1095 if (directive == nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE) { 1096 return new nsUpgradeInsecureDirective(directive); 1097 } 1098 1099 // if we have a child-src, cache it as a fallback for 1100 // * workers (if worker-src is not explicitly specified) 1101 // * frames (if frame-src is not explicitly specified) 1102 if (directive == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE) { 1103 mChildSrc = new nsCSPChildSrcDirective(directive); 1104 return mChildSrc; 1105 } 1106 1107 // if we have a frame-src, cache it so we can discard child-src for frames 1108 if (directive == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { 1109 mFrameSrc = new nsCSPDirective(directive); 1110 return mFrameSrc; 1111 } 1112 1113 // if we have a worker-src, cache it so we can discard child-src for workers 1114 if (directive == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { 1115 mWorkerSrc = new nsCSPDirective(directive); 1116 return mWorkerSrc; 1117 } 1118 1119 // if we have a script-src, cache it as a fallback for worker-src 1120 // in case child-src is not present. It is also used as a fallback for 1121 // script-src-elem and script-src-attr. 1122 if (directive == nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) { 1123 mScriptSrc = new nsCSPScriptSrcDirective(directive); 1124 return mScriptSrc; 1125 } 1126 1127 // If we have a style-src, cache it as a fallback for style-src-elem and 1128 // style-src-attr. 1129 if (directive == nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) { 1130 mStyleSrc = new nsCSPStyleSrcDirective(directive); 1131 return mStyleSrc; 1132 } 1133 1134 return new nsCSPDirective(directive); 1135 } 1136 1137 // directive = *WSP [ directive-name [ WSP directive-value ] ] 1138 void nsCSPParser::directive() { 1139 // Make sure that the directive-srcs-array contains at least 1140 // one directive. 1141 if (mCurDir.Length() == 0) { 1142 AutoTArray<nsString, 1> params = {u"directive missing"_ns}; 1143 logWarningErrorToConsole(nsIScriptError::warningFlag, 1144 "failedToParseUnrecognizedSource", params); 1145 return; 1146 } 1147 1148 // Set the directiveName to mCurToken 1149 // Remember, the directive name is stored at index 0 1150 mCurToken = mCurDir[0]; 1151 1152 CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s", 1153 NS_ConvertUTF16toUTF8(mCurToken).get(), 1154 NS_ConvertUTF16toUTF8(mCurValue).get())); 1155 1156 if (CSP_IsEmptyDirective(mCurValue, mCurToken)) { 1157 return; 1158 } 1159 1160 // Try to create a new CSPDirective 1161 nsCSPDirective* cspDir = directiveName(); 1162 if (!cspDir) { 1163 // if we can not create a CSPDirective, we can skip parsing the srcs for 1164 // that array 1165 return; 1166 } 1167 1168 // special case handling for block-all-mixed-content, which is only specified 1169 // by a directive name but does not include any srcs. 1170 if (cspDir->equals(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) { 1171 if (mCurDir.Length() > 1) { 1172 AutoTArray<nsString, 1> params = {u"block-all-mixed-content"_ns}; 1173 logWarningErrorToConsole(nsIScriptError::warningFlag, 1174 "ignoreSrcForDirective", params); 1175 } 1176 // add the directive and return 1177 mPolicy->addDirective(cspDir); 1178 return; 1179 } 1180 1181 // special case handling for upgrade-insecure-requests, which is only 1182 // specified by a directive name but does not include any srcs. 1183 if (cspDir->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { 1184 if (mCurDir.Length() > 1) { 1185 AutoTArray<nsString, 1> params = {u"upgrade-insecure-requests"_ns}; 1186 logWarningErrorToConsole(nsIScriptError::warningFlag, 1187 "ignoreSrcForDirective", params); 1188 } 1189 // add the directive and return 1190 mPolicy->addUpgradeInsecDir( 1191 static_cast<nsUpgradeInsecureDirective*>(cspDir)); 1192 return; 1193 } 1194 1195 // special case handling for report-uri directive (since it doesn't contain 1196 // a valid source list but rather actual URIs) 1197 if (CSP_IsDirective(mCurDir[0], 1198 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) { 1199 reportURIList(cspDir); 1200 return; 1201 } 1202 1203 // special case handling for report-to directive (since it doesn't contain 1204 // a valid source list but rather an endpoint group) 1205 if (CSP_IsDirective(mCurDir[0], 1206 nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE)) { 1207 reportGroup(cspDir); 1208 return; 1209 } 1210 1211 // special case handling for sandbox directive (since it doe4sn't contain 1212 // a valid source list but rather special sandbox flags) 1213 if (CSP_IsDirective(mCurDir[0], 1214 nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) { 1215 sandboxFlagList(cspDir); 1216 return; 1217 } 1218 1219 // Special case handling since these directives don't contain source lists. 1220 if (CSP_IsDirective( 1221 mCurDir[0], 1222 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) { 1223 handleRequireTrustedTypesForDirective(cspDir); 1224 return; 1225 } 1226 1227 if (cspDir->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) { 1228 handleTrustedTypesDirective(cspDir); 1229 return; 1230 } 1231 1232 // make sure to reset cache variables when trying to invalidate unsafe-inline; 1233 // unsafe-inline might not only appear in script-src, but also in default-src 1234 mHasHashOrNonce = false; 1235 mHasAnyUnsafeEval = false; 1236 mStrictDynamic = false; 1237 mUnsafeInlineKeywordSrc = nullptr; 1238 1239 mParsingFrameAncestorsDir = CSP_IsDirective( 1240 mCurDir[0], nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE); 1241 1242 // Try to parse all the srcs by handing the array off to directiveValue 1243 nsTArray<nsCSPBaseSrc*> srcs; 1244 directiveValue(srcs); 1245 1246 // If we can not parse any srcs; we let the source expression be the empty set 1247 // ('none') see, http://www.w3.org/TR/CSP11/#source-list-parsing 1248 if (srcs.IsEmpty() || (srcs.Length() == 1 && srcs[0]->isReportSample())) { 1249 nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE); 1250 srcs.InsertElementAt(0, keyword); 1251 } 1252 1253 MaybeWarnAboutIgnoredSources(srcs); 1254 MaybeWarnAboutUnsafeInline(*cspDir); 1255 MaybeWarnAboutUnsafeEval(*cspDir); 1256 1257 // Add the newly created srcs to the directive and add the directive to the 1258 // policy 1259 cspDir->addSrcs(srcs); 1260 mPolicy->addDirective(cspDir); 1261 } 1262 1263 void nsCSPParser::MaybeWarnAboutIgnoredSources( 1264 const nsTArray<nsCSPBaseSrc*>& aSrcs) { 1265 // If policy contains 'strict-dynamic' warn about ignored sources. 1266 if (mStrictDynamic && 1267 !CSP_IsDirective(mCurDir[0], 1268 nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE)) { 1269 for (uint32_t i = 0; i < aSrcs.Length(); i++) { 1270 nsAutoString srcStr; 1271 aSrcs[i]->toString(srcStr); 1272 // Hashes and nonces continue to apply with 'strict-dynamic', as well as 1273 // 'unsafe-eval', 'wasm-unsafe-eval', 'trusted-types-eval' and 1274 // 'unsafe-hashes'. 1275 if (!aSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC) && 1276 !aSrcs[i]->isKeyword(CSP_TRUSTED_TYPES_EVAL) && 1277 !aSrcs[i]->isKeyword(CSP_UNSAFE_EVAL) && 1278 !aSrcs[i]->isKeyword(CSP_WASM_UNSAFE_EVAL) && 1279 !aSrcs[i]->isKeyword(CSP_UNSAFE_HASHES) && !aSrcs[i]->isNonce() && 1280 !aSrcs[i]->isHash()) { 1281 AutoTArray<nsString, 2> params = {srcStr, mCurDir[0]}; 1282 logWarningErrorToConsole(nsIScriptError::warningFlag, 1283 "ignoringScriptSrcForStrictDynamic", params); 1284 } 1285 } 1286 1287 // Log a warning that all scripts might be blocked because the policy 1288 // contains 'strict-dynamic' but no valid nonce or hash. 1289 if (!mHasHashOrNonce) { 1290 AutoTArray<nsString, 1> params = {mCurDir[0]}; 1291 logWarningErrorToConsole(nsIScriptError::warningFlag, 1292 "strictDynamicButNoHashOrNonce", params); 1293 } 1294 } 1295 } 1296 1297 void nsCSPParser::MaybeWarnAboutUnsafeInline(const nsCSPDirective& aDirective) { 1298 // From https://w3c.github.io/webappsec-csp/#allow-all-inline 1299 // follows that when either a hash or nonce is specified, 'unsafe-inline' 1300 // should not apply. 1301 if (mHasHashOrNonce && mUnsafeInlineKeywordSrc && 1302 (aDirective.isDefaultDirective() || 1303 aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) || 1304 aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || 1305 aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) || 1306 aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) || 1307 aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) || 1308 aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE))) { 1309 // Log to the console that unsafe-inline will be ignored. 1310 AutoTArray<nsString, 2> params = {u"'unsafe-inline'"_ns, mCurDir[0]}; 1311 logWarningErrorToConsole(nsIScriptError::warningFlag, 1312 "ignoringSrcWithinNonceOrHashDirective", params); 1313 } 1314 } 1315 1316 void nsCSPParser::MaybeWarnAboutUnsafeEval(const nsCSPDirective& aDirective) { 1317 if (mHasAnyUnsafeEval && 1318 (aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || 1319 aDirective.equals( 1320 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE))) { 1321 // Log to the console that (wasm-)unsafe-eval will be ignored. 1322 AutoTArray<nsString, 1> params = {mCurDir[0]}; 1323 logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnsafeEval", 1324 params); 1325 } 1326 } 1327 1328 // policy = [ directive *( ";" [ directive ] ) ] 1329 nsCSPPolicy* nsCSPParser::policy() { 1330 CSPPARSERLOG(("nsCSPParser::policy")); 1331 1332 mPolicy = new nsCSPPolicy(); 1333 for (uint32_t i = 0; i < mTokens.Length(); i++) { 1334 // https://w3c.github.io/webappsec-csp/#parse-serialized-policy 1335 // Step 2.2. ..., or if token is not an ASCII string, continue. 1336 // https://w3c.github.io/webappsec-csp/#grammardef-directive-value 1337 // Also, if token contains characters outside 0x21-0x7E, continue. 1338 // 1339 // Note: In the spec the token isn't split by whitespace yet. 1340 bool isValid = true; 1341 for (const auto& token : mTokens[i]) { 1342 if (CSP_IsInvalidDirectiveValue(token)) { 1343 AutoTArray<nsString, 1> params = {mTokens[i][0], token}; 1344 logWarningErrorToConsole(nsIScriptError::warningFlag, 1345 "ignoringInvalidToken", params); 1346 isValid = false; 1347 break; 1348 } 1349 } 1350 if (!isValid) { 1351 continue; 1352 } 1353 1354 // All input is already tokenized; set one tokenized array in the form of 1355 // [ name, src, src, ... ] 1356 // to mCurDir and call directive which processes the current directive. 1357 mCurDir = mTokens[i].Clone(); 1358 directive(); 1359 } 1360 1361 if (mChildSrc) { 1362 if (!mFrameSrc) { 1363 // if frame-src is specified explicitly for that policy than child-src 1364 // should not restrict frames; if not, than child-src needs to restrict 1365 // frames. 1366 mChildSrc->setRestrictFrames(); 1367 } 1368 if (!mWorkerSrc) { 1369 // if worker-src is specified explicitly for that policy than child-src 1370 // should not restrict workers; if not, than child-src needs to restrict 1371 // workers. 1372 mChildSrc->setRestrictWorkers(); 1373 } 1374 } 1375 1376 // if script-src is specified, but not worker-src and also no child-src, then 1377 // script-src has to govern workers. 1378 if (mScriptSrc && !mWorkerSrc && !mChildSrc) { 1379 mScriptSrc->setRestrictWorkers(); 1380 } 1381 1382 // If script-src is specified and script-src-elem is not specified, then 1383 // script-src has to govern script requests and script blocks. 1384 if (mScriptSrc && !mPolicy->hasDirective( 1385 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE)) { 1386 mScriptSrc->setRestrictScriptElem(); 1387 } 1388 1389 // If script-src is specified and script-src-attr is not specified, then 1390 // script-src has to govern script attr (event handlers). 1391 if (mScriptSrc && !mPolicy->hasDirective( 1392 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE)) { 1393 mScriptSrc->setRestrictScriptAttr(); 1394 } 1395 1396 // If style-src is specified and style-src-elem is not specified, then 1397 // style-src serves as a fallback. 1398 if (mStyleSrc && !mPolicy->hasDirective( 1399 nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE)) { 1400 mStyleSrc->setRestrictStyleElem(); 1401 } 1402 1403 // If style-src is specified and style-attr-elem is not specified, then 1404 // style-src serves as a fallback. 1405 if (mStyleSrc && !mPolicy->hasDirective( 1406 nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE)) { 1407 mStyleSrc->setRestrictStyleAttr(); 1408 } 1409 1410 return mPolicy; 1411 } 1412 1413 nsCSPPolicy* nsCSPParser::parseContentSecurityPolicy( 1414 const nsAString& aPolicyString, nsIURI* aSelfURI, bool aReportOnly, 1415 nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag, 1416 bool aSuppressLogMessages) { 1417 if (CSPPARSERLOGENABLED()) { 1418 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s", 1419 NS_ConvertUTF16toUTF8(aPolicyString).get())); 1420 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s", 1421 aSelfURI->GetSpecOrDefault().get())); 1422 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s", 1423 (aReportOnly ? "true" : "false"))); 1424 CSPPARSERLOG( 1425 ("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s", 1426 (aDeliveredViaMetaTag ? "true" : "false"))); 1427 } 1428 1429 NS_ASSERTION(aSelfURI, "Can not parseContentSecurityPolicy without aSelfURI"); 1430 1431 // Separate all input into tokens and store them in the form of: 1432 // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ] 1433 // The tokenizer itself can not fail; all eventual errors 1434 // are detected in the parser itself. 1435 1436 nsTArray<CopyableTArray<nsString> > tokens; 1437 PolicyTokenizer::tokenizePolicy(aPolicyString, tokens); 1438 1439 nsCSPParser parser(tokens, aSelfURI, aCSPContext, aDeliveredViaMetaTag, 1440 aSuppressLogMessages); 1441 1442 // Start the parser to generate a new CSPPolicy using the generated tokens. 1443 nsCSPPolicy* policy = parser.policy(); 1444 1445 // Check that report-only policies define a report-uri, otherwise log warning. 1446 if (aReportOnly) { 1447 policy->setReportOnlyFlag(true); 1448 if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE) && 1449 !policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE) && 1450 !CSP_IsBrowserXHTML(aSelfURI)) { 1451 nsAutoCString prePath; 1452 nsresult rv = aSelfURI->GetPrePath(prePath); 1453 NS_ENSURE_SUCCESS(rv, policy); 1454 AutoTArray<nsString, 1> params; 1455 CopyUTF8toUTF16(prePath, *params.AppendElement()); 1456 parser.logWarningErrorToConsole( 1457 nsIScriptError::warningFlag, 1458 "reportURINorReportToNotInReportOnlyHeader", params); 1459 } 1460 } 1461 1462 policy->setDeliveredViaMetaTagFlag(aDeliveredViaMetaTag); 1463 1464 if (policy->getNumDirectives() == 0) { 1465 // Individual errors were already reported in the parser, but if 1466 // we do not have an enforcable directive at all, we return null. 1467 delete policy; 1468 return nullptr; 1469 } 1470 1471 if (CSPPARSERLOGENABLED()) { 1472 nsString parsedPolicy; 1473 policy->toString(parsedPolicy); 1474 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s", 1475 NS_ConvertUTF16toUTF8(parsedPolicy).get())); 1476 } 1477 1478 return policy; 1479 }