1 /* 2 * Copyright (C) 2011 Google, Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/frame/csp/ContentSecurityPolicy.h" 28 29 #include "bindings/v8/ScriptCallStackFactory.h" 30 #include "bindings/v8/ScriptController.h" 31 #include "core/dom/DOMStringList.h" 32 #include "core/dom/Document.h" 33 #include "core/events/SecurityPolicyViolationEvent.h" 34 #include "core/frame/LocalDOMWindow.h" 35 #include "core/frame/LocalFrame.h" 36 #include "core/frame/UseCounter.h" 37 #include "core/frame/csp/CSPDirectiveList.h" 38 #include "core/frame/csp/CSPSource.h" 39 #include "core/frame/csp/CSPSourceList.h" 40 #include "core/frame/csp/MediaListDirective.h" 41 #include "core/frame/csp/SourceListDirective.h" 42 #include "core/inspector/InspectorInstrumentation.h" 43 #include "core/inspector/ScriptCallStack.h" 44 #include "core/loader/DocumentLoader.h" 45 #include "core/loader/PingLoader.h" 46 #include "platform/Crypto.h" 47 #include "platform/JSONValues.h" 48 #include "platform/NotImplemented.h" 49 #include "platform/ParsingUtilities.h" 50 #include "platform/RuntimeEnabledFeatures.h" 51 #include "platform/network/ContentSecurityPolicyParsers.h" 52 #include "platform/network/ContentSecurityPolicyResponseHeaders.h" 53 #include "platform/network/FormData.h" 54 #include "platform/network/ResourceResponse.h" 55 #include "platform/weborigin/KURL.h" 56 #include "platform/weborigin/KnownPorts.h" 57 #include "platform/weborigin/SchemeRegistry.h" 58 #include "platform/weborigin/SecurityOrigin.h" 59 #include "public/platform/Platform.h" 60 #include "public/platform/WebArrayBuffer.h" 61 #include "public/platform/WebCrypto.h" 62 #include "public/platform/WebCryptoAlgorithm.h" 63 #include "wtf/StringHasher.h" 64 #include "wtf/text/StringBuilder.h" 65 #include "wtf/text/StringUTF8Adaptor.h" 66 67 namespace WebCore { 68 69 // CSP 1.0 Directives 70 const char ContentSecurityPolicy::ConnectSrc[] = "connect-src"; 71 const char ContentSecurityPolicy::DefaultSrc[] = "default-src"; 72 const char ContentSecurityPolicy::FontSrc[] = "font-src"; 73 const char ContentSecurityPolicy::FrameSrc[] = "frame-src"; 74 const char ContentSecurityPolicy::ImgSrc[] = "img-src"; 75 const char ContentSecurityPolicy::MediaSrc[] = "media-src"; 76 const char ContentSecurityPolicy::ObjectSrc[] = "object-src"; 77 const char ContentSecurityPolicy::ReportURI[] = "report-uri"; 78 const char ContentSecurityPolicy::Sandbox[] = "sandbox"; 79 const char ContentSecurityPolicy::ScriptSrc[] = "script-src"; 80 const char ContentSecurityPolicy::StyleSrc[] = "style-src"; 81 82 // CSP 1.1 Directives 83 const char ContentSecurityPolicy::BaseURI[] = "base-uri"; 84 const char ContentSecurityPolicy::ChildSrc[] = "child-src"; 85 const char ContentSecurityPolicy::FormAction[] = "form-action"; 86 const char ContentSecurityPolicy::FrameAncestors[] = "frame-ancestors"; 87 const char ContentSecurityPolicy::PluginTypes[] = "plugin-types"; 88 const char ContentSecurityPolicy::ReflectedXSS[] = "reflected-xss"; 89 const char ContentSecurityPolicy::Referrer[] = "referrer"; 90 91 bool ContentSecurityPolicy::isDirectiveName(const String& name) 92 { 93 return (equalIgnoringCase(name, ConnectSrc) 94 || equalIgnoringCase(name, DefaultSrc) 95 || equalIgnoringCase(name, FontSrc) 96 || equalIgnoringCase(name, FrameSrc) 97 || equalIgnoringCase(name, ImgSrc) 98 || equalIgnoringCase(name, MediaSrc) 99 || equalIgnoringCase(name, ObjectSrc) 100 || equalIgnoringCase(name, ReportURI) 101 || equalIgnoringCase(name, Sandbox) 102 || equalIgnoringCase(name, ScriptSrc) 103 || equalIgnoringCase(name, StyleSrc) 104 || equalIgnoringCase(name, BaseURI) 105 || equalIgnoringCase(name, ChildSrc) 106 || equalIgnoringCase(name, FormAction) 107 || equalIgnoringCase(name, FrameAncestors) 108 || equalIgnoringCase(name, PluginTypes) 109 || equalIgnoringCase(name, ReflectedXSS) 110 || equalIgnoringCase(name, Referrer) 111 ); 112 } 113 114 static UseCounter::Feature getUseCounterType(ContentSecurityPolicyHeaderType type) 115 { 116 switch (type) { 117 case ContentSecurityPolicyHeaderTypeEnforce: 118 return UseCounter::ContentSecurityPolicy; 119 case ContentSecurityPolicyHeaderTypeReport: 120 return UseCounter::ContentSecurityPolicyReportOnly; 121 } 122 ASSERT_NOT_REACHED(); 123 return UseCounter::NumberOfFeatures; 124 } 125 126 static ReferrerPolicy mergeReferrerPolicies(ReferrerPolicy a, ReferrerPolicy b) 127 { 128 if (a != b) 129 return ReferrerPolicyNever; 130 return a; 131 } 132 133 ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContext* executionContext) 134 : m_executionContext(executionContext) 135 , m_overrideInlineStyleAllowed(false) 136 , m_scriptHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone) 137 , m_styleHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone) 138 { 139 } 140 141 ContentSecurityPolicy::~ContentSecurityPolicy() 142 { 143 } 144 145 void ContentSecurityPolicy::copyStateFrom(const ContentSecurityPolicy* other) 146 { 147 ASSERT(m_policies.isEmpty()); 148 for (CSPDirectiveListVector::const_iterator iter = other->m_policies.begin(); iter != other->m_policies.end(); ++iter) 149 addPolicyFromHeaderValue((*iter)->header(), (*iter)->headerType(), (*iter)->headerSource()); 150 } 151 152 void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicyResponseHeaders& headers) 153 { 154 if (!headers.contentSecurityPolicy().isEmpty()) 155 didReceiveHeader(headers.contentSecurityPolicy(), ContentSecurityPolicyHeaderTypeEnforce, ContentSecurityPolicyHeaderSourceHTTP); 156 if (!headers.contentSecurityPolicyReportOnly().isEmpty()) 157 didReceiveHeader(headers.contentSecurityPolicyReportOnly(), ContentSecurityPolicyHeaderTypeReport, ContentSecurityPolicyHeaderSourceHTTP); 158 } 159 160 void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) 161 { 162 addPolicyFromHeaderValue(header, type, source); 163 } 164 165 void ContentSecurityPolicy::addPolicyFromHeaderValue(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) 166 { 167 Document* document = this->document(); 168 if (document) { 169 UseCounter::count(*document, getUseCounterType(type)); 170 171 // CSP 1.1 defines report-only in a <meta> element as invalid. Measure for now, disable in experimental mode. 172 if (source == ContentSecurityPolicyHeaderSourceMeta && type == ContentSecurityPolicyHeaderTypeReport) { 173 UseCounter::count(*document, UseCounter::ContentSecurityPolicyReportOnlyInMeta); 174 if (experimentalFeaturesEnabled()) { 175 reportReportOnlyInMeta(header); 176 return; 177 } 178 } 179 } 180 181 182 Vector<UChar> characters; 183 header.appendTo(characters); 184 185 const UChar* begin = characters.data(); 186 const UChar* end = begin + characters.size(); 187 188 // RFC2616, section 4.2 specifies that headers appearing multiple times can 189 // be combined with a comma. Walk the header string, and parse each comma 190 // separated chunk as a separate header. 191 const UChar* position = begin; 192 while (position < end) { 193 skipUntil<UChar>(position, end, ','); 194 195 // header1,header2 OR header1 196 // ^ ^ 197 OwnPtr<CSPDirectiveList> policy = CSPDirectiveList::create(this, begin, position, type, source); 198 199 // We disable 'eval()' even in the case of report-only policies, and rely on the check in the V8Initializer::codeGenerationCheckCallbackInMainThread callback to determine whether the call should execute or not. 200 if (!policy->allowEval(0, SuppressReport)) 201 m_executionContext->disableEval(policy->evalDisabledErrorMessage()); 202 203 m_policies.append(policy.release()); 204 205 // Skip the comma, and begin the next header from the current position. 206 ASSERT(position == end || *position == ','); 207 skipExactly<UChar>(position, end, ','); 208 begin = position; 209 } 210 211 if (document && type != ContentSecurityPolicyHeaderTypeReport && didSetReferrerPolicy()) 212 document->setReferrerPolicy(referrerPolicy()); 213 } 214 215 void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value) 216 { 217 m_overrideInlineStyleAllowed = value; 218 } 219 220 const String& ContentSecurityPolicy::deprecatedHeader() const 221 { 222 return m_policies.isEmpty() ? emptyString() : m_policies[0]->header(); 223 } 224 225 ContentSecurityPolicyHeaderType ContentSecurityPolicy::deprecatedHeaderType() const 226 { 227 return m_policies.isEmpty() ? ContentSecurityPolicyHeaderTypeEnforce : m_policies[0]->headerType(); 228 } 229 230 template<bool (CSPDirectiveList::*allowed)(ContentSecurityPolicy::ReportingStatus) const> 231 bool isAllowedByAll(const CSPDirectiveListVector& policies, ContentSecurityPolicy::ReportingStatus reportingStatus) 232 { 233 for (size_t i = 0; i < policies.size(); ++i) { 234 if (!(policies[i].get()->*allowed)(reportingStatus)) 235 return false; 236 } 237 return true; 238 } 239 240 template<bool (CSPDirectiveList::*allowed)(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus) const> 241 bool isAllowedByAllWithState(const CSPDirectiveListVector& policies, ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) 242 { 243 for (size_t i = 0; i < policies.size(); ++i) { 244 if (!(policies[i].get()->*allowed)(scriptState, reportingStatus)) 245 return false; 246 } 247 return true; 248 } 249 250 template<bool (CSPDirectiveList::*allowed)(const String&, const WTF::OrdinalNumber&, ContentSecurityPolicy::ReportingStatus) const> 251 bool isAllowedByAllWithContext(const CSPDirectiveListVector& policies, const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) 252 { 253 for (size_t i = 0; i < policies.size(); ++i) { 254 if (!(policies[i].get()->*allowed)(contextURL, contextLine, reportingStatus)) 255 return false; 256 } 257 return true; 258 } 259 260 template<bool (CSPDirectiveList::*allowed)(const String&) const> 261 bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const String& nonce) 262 { 263 for (size_t i = 0; i < policies.size(); ++i) { 264 if (!(policies[i].get()->*allowed)(nonce)) 265 return false; 266 } 267 return true; 268 } 269 270 template<bool (CSPDirectiveList::*allowed)(const CSPHashValue&) const> 271 bool isAllowedByAllWithHash(const CSPDirectiveListVector& policies, const CSPHashValue& hashValue) 272 { 273 for (size_t i = 0; i < policies.size(); ++i) { 274 if (!(policies[i].get()->*allowed)(hashValue)) 275 return false; 276 } 277 return true; 278 } 279 280 template<bool (CSPDirectiveList::*allowFromURL)(const KURL&, ContentSecurityPolicy::ReportingStatus) const> 281 bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) 282 { 283 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol())) 284 return true; 285 286 for (size_t i = 0; i < policies.size(); ++i) { 287 if (!(policies[i].get()->*allowFromURL)(url, reportingStatus)) 288 return false; 289 } 290 return true; 291 } 292 293 template<bool (CSPDirectiveList::*allowed)(LocalFrame*, ContentSecurityPolicy::ReportingStatus) const> 294 bool isAllowedByAllWithFrame(const CSPDirectiveListVector& policies, LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) 295 { 296 for (size_t i = 0; i < policies.size(); ++i) { 297 if (!(policies[i].get()->*allowed)(frame, reportingStatus)) 298 return false; 299 } 300 return true; 301 } 302 303 template<bool (CSPDirectiveList::*allowed)(const CSPHashValue&) const> 304 bool checkDigest(const String& source, uint8_t hashAlgorithmsUsed, const CSPDirectiveListVector& policies) 305 { 306 // Any additions or subtractions from this struct should also modify the 307 // respective entries in the kSupportedPrefixes array in 308 // CSPSourceList::parseHash(). 309 static const struct { 310 ContentSecurityPolicyHashAlgorithm cspHashAlgorithm; 311 HashAlgorithm algorithm; 312 } kAlgorithmMap[] = { 313 { ContentSecurityPolicyHashAlgorithmSha1, HashAlgorithmSha1 }, 314 { ContentSecurityPolicyHashAlgorithmSha256, HashAlgorithmSha256 }, 315 { ContentSecurityPolicyHashAlgorithmSha384, HashAlgorithmSha384 }, 316 { ContentSecurityPolicyHashAlgorithmSha512, HashAlgorithmSha512 } 317 }; 318 319 // Only bother normalizing the source/computing digests if there are any checks to be done. 320 if (hashAlgorithmsUsed == ContentSecurityPolicyHashAlgorithmNone) 321 return false; 322 323 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF::EntitiesForUnencodables); 324 325 // See comment in CSPSourceList::parseHash about why we are using this sizeof 326 // calculation instead of WTF_ARRAY_LENGTH. 327 for (size_t i = 0; i < (sizeof(kAlgorithmMap) / sizeof(kAlgorithmMap[0])); i++) { 328 DigestValue digest; 329 if (kAlgorithmMap[i].cspHashAlgorithm & hashAlgorithmsUsed) { 330 bool digestSuccess = computeDigest(kAlgorithmMap[i].algorithm, normalizedSource.data(), normalizedSource.length(), digest); 331 if (digestSuccess && isAllowedByAllWithHash<allowed>(policies, CSPHashValue(kAlgorithmMap[i].cspHashAlgorithm, digest))) 332 return true; 333 } 334 } 335 336 return false; 337 } 338 339 bool ContentSecurityPolicy::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const 340 { 341 return isAllowedByAllWithContext<&CSPDirectiveList::allowJavaScriptURLs>(m_policies, contextURL, contextLine, reportingStatus); 342 } 343 344 bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const 345 { 346 return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineEventHandlers>(m_policies, contextURL, contextLine, reportingStatus); 347 } 348 349 bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const 350 { 351 return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineScript>(m_policies, contextURL, contextLine, reportingStatus); 352 } 353 354 bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const 355 { 356 if (m_overrideInlineStyleAllowed) 357 return true; 358 return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineStyle>(m_policies, contextURL, contextLine, reportingStatus); 359 } 360 361 bool ContentSecurityPolicy::allowEval(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) const 362 { 363 return isAllowedByAllWithState<&CSPDirectiveList::allowEval>(m_policies, scriptState, reportingStatus); 364 } 365 366 String ContentSecurityPolicy::evalDisabledErrorMessage() const 367 { 368 for (size_t i = 0; i < m_policies.size(); ++i) { 369 if (!m_policies[i]->allowEval(0, SuppressReport)) 370 return m_policies[i]->evalDisabledErrorMessage(); 371 } 372 return String(); 373 } 374 375 bool ContentSecurityPolicy::allowPluginType(const String& type, const String& typeAttribute, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 376 { 377 for (size_t i = 0; i < m_policies.size(); ++i) { 378 if (!m_policies[i]->allowPluginType(type, typeAttribute, url, reportingStatus)) 379 return false; 380 } 381 return true; 382 } 383 384 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 385 { 386 return isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, reportingStatus); 387 } 388 389 bool ContentSecurityPolicy::allowScriptNonce(const String& nonce) const 390 { 391 return isAllowedByAllWithNonce<&CSPDirectiveList::allowScriptNonce>(m_policies, nonce); 392 } 393 394 bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const 395 { 396 return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce); 397 } 398 399 bool ContentSecurityPolicy::allowScriptHash(const String& source) const 400 { 401 return checkDigest<&CSPDirectiveList::allowScriptHash>(source, m_scriptHashAlgorithmsUsed, m_policies); 402 } 403 404 bool ContentSecurityPolicy::allowStyleHash(const String& source) const 405 { 406 return checkDigest<&CSPDirectiveList::allowStyleHash>(source, m_styleHashAlgorithmsUsed, m_policies); 407 } 408 409 void ContentSecurityPolicy::usesScriptHashAlgorithms(uint8_t algorithms) 410 { 411 m_scriptHashAlgorithmsUsed |= algorithms; 412 } 413 414 void ContentSecurityPolicy::usesStyleHashAlgorithms(uint8_t algorithms) 415 { 416 m_styleHashAlgorithmsUsed |= algorithms; 417 } 418 419 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 420 { 421 return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus); 422 } 423 424 bool ContentSecurityPolicy::allowChildFrameFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 425 { 426 return isAllowedByAllWithURL<&CSPDirectiveList::allowChildFrameFromSource>(m_policies, url, reportingStatus); 427 } 428 429 bool ContentSecurityPolicy::allowImageFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 430 { 431 return isAllowedByAllWithURL<&CSPDirectiveList::allowImageFromSource>(m_policies, url, reportingStatus); 432 } 433 434 bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 435 { 436 return isAllowedByAllWithURL<&CSPDirectiveList::allowStyleFromSource>(m_policies, url, reportingStatus); 437 } 438 439 bool ContentSecurityPolicy::allowFontFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 440 { 441 return isAllowedByAllWithURL<&CSPDirectiveList::allowFontFromSource>(m_policies, url, reportingStatus); 442 } 443 444 bool ContentSecurityPolicy::allowMediaFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 445 { 446 return isAllowedByAllWithURL<&CSPDirectiveList::allowMediaFromSource>(m_policies, url, reportingStatus); 447 } 448 449 bool ContentSecurityPolicy::allowConnectToSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 450 { 451 return isAllowedByAllWithURL<&CSPDirectiveList::allowConnectToSource>(m_policies, url, reportingStatus); 452 } 453 454 bool ContentSecurityPolicy::allowFormAction(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 455 { 456 return isAllowedByAllWithURL<&CSPDirectiveList::allowFormAction>(m_policies, url, reportingStatus); 457 } 458 459 bool ContentSecurityPolicy::allowBaseURI(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 460 { 461 return isAllowedByAllWithURL<&CSPDirectiveList::allowBaseURI>(m_policies, url, reportingStatus); 462 } 463 464 bool ContentSecurityPolicy::allowAncestors(LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) const 465 { 466 return isAllowedByAllWithFrame<&CSPDirectiveList::allowAncestors>(m_policies, frame, reportingStatus); 467 } 468 469 bool ContentSecurityPolicy::allowChildContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 470 { 471 return isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, reportingStatus); 472 } 473 474 bool ContentSecurityPolicy::allowWorkerContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const 475 { 476 // CSP 1.1 moves workers from 'script-src' to the new 'child-src'. Measure the impact of this backwards-incompatible change. 477 if (m_executionContext->isDocument()) { 478 Document* document = static_cast<Document*>(m_executionContext); 479 UseCounter::count(*document, UseCounter::WorkerSubjectToCSP); 480 if (isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, SuppressReport) && !isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, SuppressReport)) 481 UseCounter::count(*document, UseCounter::WorkerAllowedByChildBlockedByScript); 482 } 483 484 return experimentalFeaturesEnabled() ? 485 isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, reportingStatus) : 486 isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, reportingStatus); 487 } 488 489 bool ContentSecurityPolicy::isActive() const 490 { 491 return !m_policies.isEmpty(); 492 } 493 494 ReflectedXSSDisposition ContentSecurityPolicy::reflectedXSSDisposition() const 495 { 496 ReflectedXSSDisposition disposition = ReflectedXSSUnset; 497 for (size_t i = 0; i < m_policies.size(); ++i) { 498 if (m_policies[i]->reflectedXSSDisposition() > disposition) 499 disposition = std::max(disposition, m_policies[i]->reflectedXSSDisposition()); 500 } 501 return disposition; 502 } 503 504 ReferrerPolicy ContentSecurityPolicy::referrerPolicy() const 505 { 506 ReferrerPolicy policy = ReferrerPolicyDefault; 507 bool first = true; 508 for (size_t i = 0; i < m_policies.size(); ++i) { 509 if (m_policies[i]->didSetReferrerPolicy()) { 510 if (first) 511 policy = m_policies[i]->referrerPolicy(); 512 else 513 policy = mergeReferrerPolicies(policy, m_policies[i]->referrerPolicy()); 514 } 515 } 516 return policy; 517 } 518 519 bool ContentSecurityPolicy::didSetReferrerPolicy() const 520 { 521 for (size_t i = 0; i < m_policies.size(); ++i) { 522 if (m_policies[i]->didSetReferrerPolicy()) 523 return true; 524 } 525 return false; 526 } 527 528 SecurityOrigin* ContentSecurityPolicy::securityOrigin() const 529 { 530 return m_executionContext->securityContext().securityOrigin(); 531 } 532 533 const KURL ContentSecurityPolicy::url() const 534 { 535 return m_executionContext->contextURL(); 536 } 537 538 KURL ContentSecurityPolicy::completeURL(const String& url) const 539 { 540 return m_executionContext->contextCompleteURL(url); 541 } 542 543 void ContentSecurityPolicy::enforceSandboxFlags(SandboxFlags mask) const 544 { 545 if (Document* document = this->document()) 546 document->enforceSandboxFlags(mask); 547 } 548 549 static String stripURLForUseInReport(Document* document, const KURL& url) 550 { 551 if (!url.isValid()) 552 return String(); 553 if (!url.isHierarchical() || url.protocolIs("file")) 554 return url.protocol(); 555 return document->securityOrigin()->canRequest(url) ? url.strippedForUseAsReferrer() : SecurityOrigin::create(url)->toString(); 556 } 557 558 static void gatherSecurityPolicyViolationEventData(SecurityPolicyViolationEventInit& init, Document* document, const String& directiveText, const String& effectiveDirective, const KURL& blockedURL, const String& header) 559 { 560 init.documentURI = document->url().string(); 561 init.referrer = document->referrer(); 562 init.blockedURI = stripURLForUseInReport(document, blockedURL); 563 init.violatedDirective = directiveText; 564 init.effectiveDirective = effectiveDirective; 565 init.originalPolicy = header; 566 init.sourceFile = String(); 567 init.lineNumber = 0; 568 init.columnNumber = 0; 569 init.statusCode = 0; 570 571 if (!SecurityOrigin::isSecure(document->url()) && document->loader()) 572 init.statusCode = document->loader()->response().httpStatusCode(); 573 574 RefPtrWillBeRawPtr<ScriptCallStack> stack = createScriptCallStack(1, false); 575 if (!stack) 576 return; 577 578 const ScriptCallFrame& callFrame = stack->at(0); 579 580 if (callFrame.lineNumber()) { 581 KURL source = KURL(ParsedURLString, callFrame.sourceURL()); 582 init.sourceFile = stripURLForUseInReport(document, source); 583 init.lineNumber = callFrame.lineNumber(); 584 init.columnNumber = callFrame.columnNumber(); 585 } 586 } 587 588 void ContentSecurityPolicy::reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const Vector<KURL>& reportURIs, const String& header) 589 { 590 // FIXME: Support sending reports from worker. 591 if (!m_executionContext->isDocument()) 592 return; 593 594 Document* document = this->document(); 595 LocalFrame* frame = document->frame(); 596 if (!frame) 597 return; 598 599 SecurityPolicyViolationEventInit violationData; 600 gatherSecurityPolicyViolationEventData(violationData, document, directiveText, effectiveDirective, blockedURL, header); 601 602 if (experimentalFeaturesEnabled()) 603 frame->domWindow()->enqueueDocumentEvent(SecurityPolicyViolationEvent::create(EventTypeNames::securitypolicyviolation, violationData)); 604 605 if (reportURIs.isEmpty()) 606 return; 607 608 // We need to be careful here when deciding what information to send to the 609 // report-uri. Currently, we send only the current document's URL and the 610 // directive that was violated. The document's URL is safe to send because 611 // it's the document itself that's requesting that it be sent. You could 612 // make an argument that we shouldn't send HTTPS document URLs to HTTP 613 // report-uris (for the same reasons that we supress the Referer in that 614 // case), but the Referer is sent implicitly whereas this request is only 615 // sent explicitly. As for which directive was violated, that's pretty 616 // harmless information. 617 618 RefPtr<JSONObject> cspReport = JSONObject::create(); 619 cspReport->setString("document-uri", violationData.documentURI); 620 cspReport->setString("referrer", violationData.referrer); 621 cspReport->setString("violated-directive", violationData.violatedDirective); 622 if (experimentalFeaturesEnabled()) 623 cspReport->setString("effective-directive", violationData.effectiveDirective); 624 cspReport->setString("original-policy", violationData.originalPolicy); 625 cspReport->setString("blocked-uri", violationData.blockedURI); 626 if (!violationData.sourceFile.isEmpty() && violationData.lineNumber) { 627 cspReport->setString("source-file", violationData.sourceFile); 628 cspReport->setNumber("line-number", violationData.lineNumber); 629 cspReport->setNumber("column-number", violationData.columnNumber); 630 } 631 cspReport->setNumber("status-code", violationData.statusCode); 632 633 RefPtr<JSONObject> reportObject = JSONObject::create(); 634 reportObject->setObject("csp-report", cspReport.release()); 635 String stringifiedReport = reportObject->toJSONString(); 636 637 if (!shouldSendViolationReport(stringifiedReport)) 638 return; 639 640 RefPtr<FormData> report = FormData::create(stringifiedReport.utf8()); 641 642 for (size_t i = 0; i < reportURIs.size(); ++i) 643 PingLoader::sendViolationReport(frame, reportURIs[i], report, PingLoader::ContentSecurityPolicyViolationReport); 644 645 didSendViolationReport(stringifiedReport); 646 } 647 648 void ContentSecurityPolicy::reportInvalidReferrer(const String& invalidValue) const 649 { 650 logToConsole("The 'referrer' Content Security Policy directive has the invalid value \"" + invalidValue + "\". Valid values are \"always\", \"default\", \"never\", and \"origin\"."); 651 } 652 653 void ContentSecurityPolicy::reportReportOnlyInMeta(const String& header) const 654 { 655 logToConsole("The report-only Content Security Policy '" + header + "' was delivered via a <meta> element, which is disallowed. The policy has been ignored."); 656 } 657 658 void ContentSecurityPolicy::reportMetaOutsideHead(const String& header) const 659 { 660 logToConsole("The Content Security Policy '" + header + "' was delivered via a <meta> element outside the document's <head>, which is disallowed. The policy has been ignored."); 661 } 662 663 void ContentSecurityPolicy::reportInvalidInReportOnly(const String& name) const 664 { 665 logToConsole("The Content Security Policy directive '" + name + "' is ignored when delivered in a report-only policy."); 666 } 667 668 void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) const 669 { 670 DEFINE_STATIC_LOCAL(String, allow, ("allow")); 671 DEFINE_STATIC_LOCAL(String, options, ("options")); 672 DEFINE_STATIC_LOCAL(String, policyURI, ("policy-uri")); 673 DEFINE_STATIC_LOCAL(String, allowMessage, ("The 'allow' directive has been replaced with 'default-src'. Please use that directive instead, as 'allow' has no effect.")); 674 DEFINE_STATIC_LOCAL(String, optionsMessage, ("The 'options' directive has been replaced with 'unsafe-inline' and 'unsafe-eval' source expressions for the 'script-src' and 'style-src' directives. Please use those directives instead, as 'options' has no effect.")); 675 DEFINE_STATIC_LOCAL(String, policyURIMessage, ("The 'policy-uri' directive has been removed from the specification. Please specify a complete policy via the Content-Security-Policy header.")); 676 677 String message = "Unrecognized Content-Security-Policy directive '" + name + "'.\n"; 678 if (equalIgnoringCase(name, allow)) 679 message = allowMessage; 680 else if (equalIgnoringCase(name, options)) 681 message = optionsMessage; 682 else if (equalIgnoringCase(name, policyURI)) 683 message = policyURIMessage; 684 685 logToConsole(message); 686 } 687 688 void ContentSecurityPolicy::reportDirectiveAsSourceExpression(const String& directiveName, const String& sourceExpression) const 689 { 690 String message = "The Content Security Policy directive '" + directiveName + "' contains '" + sourceExpression + "' as a source expression. Did you mean '" + directiveName + " ...; " + sourceExpression + "...' (note the semicolon)?"; 691 logToConsole(message); 692 } 693 694 void ContentSecurityPolicy::reportDuplicateDirective(const String& name) const 695 { 696 String message = "Ignoring duplicate Content-Security-Policy directive '" + name + "'.\n"; 697 logToConsole(message); 698 } 699 700 void ContentSecurityPolicy::reportInvalidPluginTypes(const String& pluginType) const 701 { 702 String message; 703 if (pluginType.isNull()) 704 message = "'plugin-types' Content Security Policy directive is empty; all plugins will be blocked.\n"; 705 else 706 message = "Invalid plugin type in 'plugin-types' Content Security Policy directive: '" + pluginType + "'.\n"; 707 logToConsole(message); 708 } 709 710 void ContentSecurityPolicy::reportInvalidSandboxFlags(const String& invalidFlags) const 711 { 712 logToConsole("Error while parsing the 'sandbox' Content Security Policy directive: " + invalidFlags); 713 } 714 715 void ContentSecurityPolicy::reportInvalidReflectedXSS(const String& invalidValue) const 716 { 717 logToConsole("The 'reflected-xss' Content Security Policy directive has the invalid value \"" + invalidValue + "\". Valid values are \"allow\", \"filter\", and \"block\"."); 718 } 719 720 void ContentSecurityPolicy::reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const 721 { 722 String message = "The value for Content Security Policy directive '" + directiveName + "' contains an invalid character: '" + value + "'. Non-whitespace characters outside ASCII 0x21-0x7E must be percent-encoded, as described in RFC 3986, section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1."; 723 logToConsole(message); 724 } 725 726 void ContentSecurityPolicy::reportInvalidPathCharacter(const String& directiveName, const String& value, const char invalidChar) const 727 { 728 ASSERT(invalidChar == '#' || invalidChar == '?'); 729 730 String ignoring = "The fragment identifier, including the '#', will be ignored."; 731 if (invalidChar == '?') 732 ignoring = "The query component, including the '?', will be ignored."; 733 String message = "The source list for Content Security Policy directive '" + directiveName + "' contains a source with an invalid path: '" + value + "'. " + ignoring; 734 logToConsole(message); 735 } 736 737 void ContentSecurityPolicy::reportInvalidSourceExpression(const String& directiveName, const String& source) const 738 { 739 String message = "The source list for Content Security Policy directive '" + directiveName + "' contains an invalid source: '" + source + "'. It will be ignored."; 740 if (equalIgnoringCase(source, "'none'")) 741 message = message + " Note that 'none' has no effect unless it is the only expression in the source list."; 742 logToConsole(message); 743 } 744 745 void ContentSecurityPolicy::reportMissingReportURI(const String& policy) const 746 { 747 logToConsole("The Content Security Policy '" + policy + "' was delivered in report-only mode, but does not specify a 'report-uri'; the policy will have no effect. Please either add a 'report-uri' directive, or deliver the policy via the 'Content-Security-Policy' header."); 748 } 749 750 void ContentSecurityPolicy::logToConsole(const String& message) const 751 { 752 m_executionContext->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); 753 } 754 755 void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector(const String& directiveText) const 756 { 757 m_executionContext->reportBlockedScriptExecutionToInspector(directiveText); 758 } 759 760 bool ContentSecurityPolicy::experimentalFeaturesEnabled() const 761 { 762 return RuntimeEnabledFeatures::experimentalContentSecurityPolicyFeaturesEnabled(); 763 } 764 765 bool ContentSecurityPolicy::shouldBypassMainWorld(ExecutionContext* context) 766 { 767 if (context && context->isDocument()) { 768 Document* document = toDocument(context); 769 if (document->frame()) 770 return document->frame()->script().shouldBypassMainWorldContentSecurityPolicy(); 771 } 772 return false; 773 } 774 775 bool ContentSecurityPolicy::shouldSendViolationReport(const String& report) const 776 { 777 // Collisions have no security impact, so we can save space by storing only the string's hash rather than the whole report. 778 return !m_violationReportsSent.contains(report.impl()->hash()); 779 } 780 781 void ContentSecurityPolicy::didSendViolationReport(const String& report) 782 { 783 m_violationReportsSent.add(report.impl()->hash()); 784 } 785 786 } // namespace WebCore 787