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
isDirectiveName(const String & name)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
getUseCounterType(ContentSecurityPolicyHeaderType type)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
mergeReferrerPolicies(ReferrerPolicy a,ReferrerPolicy b)126 static ReferrerPolicy mergeReferrerPolicies(ReferrerPolicy a, ReferrerPolicy b)
127 {
128 if (a != b)
129 return ReferrerPolicyNever;
130 return a;
131 }
132
ContentSecurityPolicy(ExecutionContext * executionContext)133 ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContext* executionContext)
134 : m_executionContext(executionContext)
135 , m_overrideInlineStyleAllowed(false)
136 , m_scriptHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone)
137 , m_styleHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone)
138 {
139 }
140
~ContentSecurityPolicy()141 ContentSecurityPolicy::~ContentSecurityPolicy()
142 {
143 }
144
copyStateFrom(const ContentSecurityPolicy * other)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
didReceiveHeaders(const ContentSecurityPolicyResponseHeaders & headers)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
didReceiveHeader(const String & header,ContentSecurityPolicyHeaderType type,ContentSecurityPolicyHeaderSource source)160 void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source)
161 {
162 addPolicyFromHeaderValue(header, type, source);
163 }
164
addPolicyFromHeaderValue(const String & header,ContentSecurityPolicyHeaderType type,ContentSecurityPolicyHeaderSource source)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
setOverrideAllowInlineStyle(bool value)215 void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value)
216 {
217 m_overrideInlineStyleAllowed = value;
218 }
219
deprecatedHeader() const220 const String& ContentSecurityPolicy::deprecatedHeader() const
221 {
222 return m_policies.isEmpty() ? emptyString() : m_policies[0]->header();
223 }
224
deprecatedHeaderType() const225 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>
isAllowedByAll(const CSPDirectiveListVector & policies,ContentSecurityPolicy::ReportingStatus reportingStatus)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>
isAllowedByAllWithState(const CSPDirectiveListVector & policies,ScriptState * scriptState,ContentSecurityPolicy::ReportingStatus reportingStatus)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>
isAllowedByAllWithContext(const CSPDirectiveListVector & policies,const String & contextURL,const WTF::OrdinalNumber & contextLine,ContentSecurityPolicy::ReportingStatus reportingStatus)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>
isAllowedByAllWithNonce(const CSPDirectiveListVector & policies,const String & nonce)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>
isAllowedByAllWithHash(const CSPDirectiveListVector & policies,const CSPHashValue & hashValue)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>
isAllowedByAllWithURL(const CSPDirectiveListVector & policies,const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus)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>
isAllowedByAllWithFrame(const CSPDirectiveListVector & policies,LocalFrame * frame,ContentSecurityPolicy::ReportingStatus reportingStatus)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>
checkDigest(const String & source,uint8_t hashAlgorithmsUsed,const CSPDirectiveListVector & policies)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
allowJavaScriptURLs(const String & contextURL,const WTF::OrdinalNumber & contextLine,ContentSecurityPolicy::ReportingStatus reportingStatus) const339 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
allowInlineEventHandlers(const String & contextURL,const WTF::OrdinalNumber & contextLine,ContentSecurityPolicy::ReportingStatus reportingStatus) const344 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
allowInlineScript(const String & contextURL,const WTF::OrdinalNumber & contextLine,ContentSecurityPolicy::ReportingStatus reportingStatus) const349 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
allowInlineStyle(const String & contextURL,const WTF::OrdinalNumber & contextLine,ContentSecurityPolicy::ReportingStatus reportingStatus) const354 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
allowEval(ScriptState * scriptState,ContentSecurityPolicy::ReportingStatus reportingStatus) const361 bool ContentSecurityPolicy::allowEval(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) const
362 {
363 return isAllowedByAllWithState<&CSPDirectiveList::allowEval>(m_policies, scriptState, reportingStatus);
364 }
365
evalDisabledErrorMessage() const366 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
allowPluginType(const String & type,const String & typeAttribute,const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const375 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
allowScriptFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const384 bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
385 {
386 return isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, reportingStatus);
387 }
388
allowScriptNonce(const String & nonce) const389 bool ContentSecurityPolicy::allowScriptNonce(const String& nonce) const
390 {
391 return isAllowedByAllWithNonce<&CSPDirectiveList::allowScriptNonce>(m_policies, nonce);
392 }
393
allowStyleNonce(const String & nonce) const394 bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const
395 {
396 return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce);
397 }
398
allowScriptHash(const String & source) const399 bool ContentSecurityPolicy::allowScriptHash(const String& source) const
400 {
401 return checkDigest<&CSPDirectiveList::allowScriptHash>(source, m_scriptHashAlgorithmsUsed, m_policies);
402 }
403
allowStyleHash(const String & source) const404 bool ContentSecurityPolicy::allowStyleHash(const String& source) const
405 {
406 return checkDigest<&CSPDirectiveList::allowStyleHash>(source, m_styleHashAlgorithmsUsed, m_policies);
407 }
408
usesScriptHashAlgorithms(uint8_t algorithms)409 void ContentSecurityPolicy::usesScriptHashAlgorithms(uint8_t algorithms)
410 {
411 m_scriptHashAlgorithmsUsed |= algorithms;
412 }
413
usesStyleHashAlgorithms(uint8_t algorithms)414 void ContentSecurityPolicy::usesStyleHashAlgorithms(uint8_t algorithms)
415 {
416 m_styleHashAlgorithmsUsed |= algorithms;
417 }
418
allowObjectFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const419 bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
420 {
421 return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus);
422 }
423
allowChildFrameFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const424 bool ContentSecurityPolicy::allowChildFrameFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
425 {
426 return isAllowedByAllWithURL<&CSPDirectiveList::allowChildFrameFromSource>(m_policies, url, reportingStatus);
427 }
428
allowImageFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const429 bool ContentSecurityPolicy::allowImageFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
430 {
431 return isAllowedByAllWithURL<&CSPDirectiveList::allowImageFromSource>(m_policies, url, reportingStatus);
432 }
433
allowStyleFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const434 bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
435 {
436 return isAllowedByAllWithURL<&CSPDirectiveList::allowStyleFromSource>(m_policies, url, reportingStatus);
437 }
438
allowFontFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const439 bool ContentSecurityPolicy::allowFontFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
440 {
441 return isAllowedByAllWithURL<&CSPDirectiveList::allowFontFromSource>(m_policies, url, reportingStatus);
442 }
443
allowMediaFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const444 bool ContentSecurityPolicy::allowMediaFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
445 {
446 return isAllowedByAllWithURL<&CSPDirectiveList::allowMediaFromSource>(m_policies, url, reportingStatus);
447 }
448
allowConnectToSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const449 bool ContentSecurityPolicy::allowConnectToSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
450 {
451 return isAllowedByAllWithURL<&CSPDirectiveList::allowConnectToSource>(m_policies, url, reportingStatus);
452 }
453
allowFormAction(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const454 bool ContentSecurityPolicy::allowFormAction(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
455 {
456 return isAllowedByAllWithURL<&CSPDirectiveList::allowFormAction>(m_policies, url, reportingStatus);
457 }
458
allowBaseURI(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const459 bool ContentSecurityPolicy::allowBaseURI(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
460 {
461 return isAllowedByAllWithURL<&CSPDirectiveList::allowBaseURI>(m_policies, url, reportingStatus);
462 }
463
allowAncestors(LocalFrame * frame,ContentSecurityPolicy::ReportingStatus reportingStatus) const464 bool ContentSecurityPolicy::allowAncestors(LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) const
465 {
466 return isAllowedByAllWithFrame<&CSPDirectiveList::allowAncestors>(m_policies, frame, reportingStatus);
467 }
468
allowChildContextFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const469 bool ContentSecurityPolicy::allowChildContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
470 {
471 return isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, reportingStatus);
472 }
473
allowWorkerContextFromSource(const KURL & url,ContentSecurityPolicy::ReportingStatus reportingStatus) const474 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
isActive() const489 bool ContentSecurityPolicy::isActive() const
490 {
491 return !m_policies.isEmpty();
492 }
493
reflectedXSSDisposition() const494 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
referrerPolicy() const504 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
didSetReferrerPolicy() const519 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
securityOrigin() const528 SecurityOrigin* ContentSecurityPolicy::securityOrigin() const
529 {
530 return m_executionContext->securityContext().securityOrigin();
531 }
532
url() const533 const KURL ContentSecurityPolicy::url() const
534 {
535 return m_executionContext->contextURL();
536 }
537
completeURL(const String & url) const538 KURL ContentSecurityPolicy::completeURL(const String& url) const
539 {
540 return m_executionContext->contextCompleteURL(url);
541 }
542
enforceSandboxFlags(SandboxFlags mask) const543 void ContentSecurityPolicy::enforceSandboxFlags(SandboxFlags mask) const
544 {
545 if (Document* document = this->document())
546 document->enforceSandboxFlags(mask);
547 }
548
stripURLForUseInReport(Document * document,const KURL & url)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
gatherSecurityPolicyViolationEventData(SecurityPolicyViolationEventInit & init,Document * document,const String & directiveText,const String & effectiveDirective,const KURL & blockedURL,const String & header)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
reportViolation(const String & directiveText,const String & effectiveDirective,const String & consoleMessage,const KURL & blockedURL,const Vector<KURL> & reportURIs,const String & header)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
reportInvalidReferrer(const String & invalidValue) const648 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
reportReportOnlyInMeta(const String & header) const653 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
reportMetaOutsideHead(const String & header) const658 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
reportInvalidInReportOnly(const String & name) const663 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
reportUnsupportedDirective(const String & name) const668 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
reportDirectiveAsSourceExpression(const String & directiveName,const String & sourceExpression) const688 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
reportDuplicateDirective(const String & name) const694 void ContentSecurityPolicy::reportDuplicateDirective(const String& name) const
695 {
696 String message = "Ignoring duplicate Content-Security-Policy directive '" + name + "'.\n";
697 logToConsole(message);
698 }
699
reportInvalidPluginTypes(const String & pluginType) const700 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
reportInvalidSandboxFlags(const String & invalidFlags) const710 void ContentSecurityPolicy::reportInvalidSandboxFlags(const String& invalidFlags) const
711 {
712 logToConsole("Error while parsing the 'sandbox' Content Security Policy directive: " + invalidFlags);
713 }
714
reportInvalidReflectedXSS(const String & invalidValue) const715 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
reportInvalidDirectiveValueCharacter(const String & directiveName,const String & value) const720 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
reportInvalidPathCharacter(const String & directiveName,const String & value,const char invalidChar) const726 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
reportInvalidSourceExpression(const String & directiveName,const String & source) const737 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
reportMissingReportURI(const String & policy) const745 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
logToConsole(const String & message) const750 void ContentSecurityPolicy::logToConsole(const String& message) const
751 {
752 m_executionContext->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message);
753 }
754
reportBlockedScriptExecutionToInspector(const String & directiveText) const755 void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector(const String& directiveText) const
756 {
757 m_executionContext->reportBlockedScriptExecutionToInspector(directiveText);
758 }
759
experimentalFeaturesEnabled() const760 bool ContentSecurityPolicy::experimentalFeaturesEnabled() const
761 {
762 return RuntimeEnabledFeatures::experimentalContentSecurityPolicyFeaturesEnabled();
763 }
764
shouldBypassMainWorld(ExecutionContext * context)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
shouldSendViolationReport(const String & report) const775 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
didSendViolationReport(const String & report)781 void ContentSecurityPolicy::didSendViolationReport(const String& report)
782 {
783 m_violationReportsSent.add(report.impl()->hash());
784 }
785
786 } // namespace WebCore
787