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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Google, Inc. ("Google") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY GOOGLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
30
31 #include <memory>
32
33 #include "base/strings/pattern.h"
34 #include "services/network/public/cpp/cors/origin_access_list.h"
35 #include "services/network/public/mojom/referrer_policy.mojom-blink.h"
36 #include "third_party/blink/public/platform/web_string.h"
37 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
38 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
39 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
40 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
41 #include "third_party/blink/renderer/platform/wtf/assertions.h"
42 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
43 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
44 #include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h"
45 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
46 #include "third_party/blink/renderer/platform/wtf/threading.h"
47 #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
48 #include "third_party/blink/renderer/platform/wtf/wtf.h"
49 #include "url/gurl.h"
50
51 namespace blink {
52
GetMutex()53 static Mutex& GetMutex() {
54 DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, mutex, ());
55 return mutex;
56 }
57
GetOriginAccessList()58 static network::cors::OriginAccessList& GetOriginAccessList() {
59 DEFINE_THREAD_SAFE_STATIC_LOCAL(network::cors::OriginAccessList,
60 origin_access_list, ());
61 return origin_access_list;
62 }
63
64 using OriginSet = HashSet<String>;
65
TrustworthyOriginSafelist()66 static OriginSet& TrustworthyOriginSafelist() {
67 DEFINE_STATIC_LOCAL(OriginSet, safelist, ());
68 return safelist;
69 }
70
ReferrerPolicyResolveDefault(network::mojom::ReferrerPolicy referrer_policy)71 network::mojom::ReferrerPolicy ReferrerPolicyResolveDefault(
72 network::mojom::ReferrerPolicy referrer_policy) {
73 if (referrer_policy == network::mojom::ReferrerPolicy::kDefault) {
74 if (RuntimeEnabledFeatures::ReducedReferrerGranularityEnabled()) {
75 return network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin;
76 } else {
77 return network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade;
78 }
79 }
80
81 return referrer_policy;
82 }
83
Init()84 void SecurityPolicy::Init() {
85 TrustworthyOriginSafelist();
86 }
87
ShouldHideReferrer(const KURL & url,const KURL & referrer)88 bool SecurityPolicy::ShouldHideReferrer(const KURL& url, const KURL& referrer) {
89 bool referrer_is_secure_url = referrer.ProtocolIs("https");
90 bool scheme_is_allowed =
91 SchemeRegistry::ShouldTreatURLSchemeAsAllowedForReferrer(
92 referrer.Protocol());
93
94 if (!scheme_is_allowed)
95 return true;
96
97 if (!referrer_is_secure_url)
98 return false;
99
100 bool url_is_secure_url = url.ProtocolIs("https");
101
102 return !url_is_secure_url;
103 }
104
GenerateReferrer(network::mojom::ReferrerPolicy referrer_policy,const KURL & url,const String & referrer)105 Referrer SecurityPolicy::GenerateReferrer(
106 network::mojom::ReferrerPolicy referrer_policy,
107 const KURL& url,
108 const String& referrer) {
109 network::mojom::ReferrerPolicy referrer_policy_no_default =
110 ReferrerPolicyResolveDefault(referrer_policy);
111 if (referrer == Referrer::NoReferrer())
112 return Referrer(Referrer::NoReferrer(), referrer_policy_no_default);
113 DCHECK(!referrer.IsEmpty());
114
115 KURL referrer_url = KURL(NullURL(), referrer);
116 String scheme = referrer_url.Protocol();
117 if (!SchemeRegistry::ShouldTreatURLSchemeAsAllowedForReferrer(scheme))
118 return Referrer(Referrer::NoReferrer(), referrer_policy_no_default);
119
120 if (SecurityOrigin::ShouldUseInnerURL(url))
121 return Referrer(Referrer::NoReferrer(), referrer_policy_no_default);
122
123 switch (referrer_policy_no_default) {
124 case network::mojom::ReferrerPolicy::kNever:
125 return Referrer(Referrer::NoReferrer(), referrer_policy_no_default);
126 case network::mojom::ReferrerPolicy::kAlways:
127 return Referrer(referrer, referrer_policy_no_default);
128 case network::mojom::ReferrerPolicy::kOrigin: {
129 String origin = SecurityOrigin::Create(referrer_url)->ToString();
130 // A security origin is not a canonical URL as it lacks a path. Add /
131 // to turn it into a canonical URL we can use as referrer.
132 return Referrer(origin + "/", referrer_policy_no_default);
133 }
134 case network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin: {
135 scoped_refptr<const SecurityOrigin> referrer_origin =
136 SecurityOrigin::Create(referrer_url);
137 scoped_refptr<const SecurityOrigin> url_origin =
138 SecurityOrigin::Create(url);
139 if (!url_origin->IsSameOriginWith(referrer_origin.get())) {
140 String origin = referrer_origin->ToString();
141 return Referrer(origin + "/", referrer_policy_no_default);
142 }
143 break;
144 }
145 case network::mojom::ReferrerPolicy::kSameOrigin: {
146 scoped_refptr<const SecurityOrigin> referrer_origin =
147 SecurityOrigin::Create(referrer_url);
148 scoped_refptr<const SecurityOrigin> url_origin =
149 SecurityOrigin::Create(url);
150 if (!url_origin->IsSameOriginWith(referrer_origin.get())) {
151 return Referrer(Referrer::NoReferrer(), referrer_policy_no_default);
152 }
153 return Referrer(referrer, referrer_policy_no_default);
154 }
155 case network::mojom::ReferrerPolicy::kStrictOrigin: {
156 String origin = SecurityOrigin::Create(referrer_url)->ToString();
157 return Referrer(ShouldHideReferrer(url, referrer_url)
158 ? Referrer::NoReferrer()
159 : origin + "/",
160 referrer_policy_no_default);
161 }
162 case network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin: {
163 scoped_refptr<const SecurityOrigin> referrer_origin =
164 SecurityOrigin::Create(referrer_url);
165 scoped_refptr<const SecurityOrigin> url_origin =
166 SecurityOrigin::Create(url);
167 if (!url_origin->IsSameOriginWith(referrer_origin.get())) {
168 String origin = referrer_origin->ToString();
169 return Referrer(ShouldHideReferrer(url, referrer_url)
170 ? Referrer::NoReferrer()
171 : origin + "/",
172 referrer_policy_no_default);
173 }
174 break;
175 }
176 case network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade:
177 break;
178 case network::mojom::ReferrerPolicy::kDefault:
179 NOTREACHED();
180 break;
181 }
182
183 return Referrer(
184 ShouldHideReferrer(url, referrer_url) ? Referrer::NoReferrer() : referrer,
185 referrer_policy_no_default);
186 }
187
AddOriginToTrustworthySafelist(const String & origin_or_pattern)188 void SecurityPolicy::AddOriginToTrustworthySafelist(
189 const String& origin_or_pattern) {
190 #if DCHECK_IS_ON()
191 // Must be called before we start other threads.
192 DCHECK(WTF::IsBeforeThreadCreated());
193 #endif
194 // Origins and hostname patterns must be canonicalized (including
195 // canonicalization to 8-bit strings) before being inserted into
196 // TrustworthyOriginSafelist().
197 CHECK(origin_or_pattern.Is8Bit());
198 TrustworthyOriginSafelist().insert(origin_or_pattern);
199 }
200
IsOriginTrustworthySafelisted(const SecurityOrigin & origin)201 bool SecurityPolicy::IsOriginTrustworthySafelisted(
202 const SecurityOrigin& origin) {
203 // Early return if |origin| cannot possibly be matched.
204 if (origin.IsOpaque() || TrustworthyOriginSafelist().IsEmpty())
205 return false;
206
207 if (TrustworthyOriginSafelist().Contains(origin.ToRawString()))
208 return true;
209
210 // KURL and SecurityOrigin hosts should be canonicalized to 8-bit strings.
211 CHECK(origin.Host().Is8Bit());
212 StringUTF8Adaptor host_adaptor(origin.Host());
213 for (const auto& origin_or_pattern : TrustworthyOriginSafelist()) {
214 StringUTF8Adaptor origin_or_pattern_adaptor(origin_or_pattern);
215 if (base::MatchPattern(host_adaptor.AsStringPiece(),
216 origin_or_pattern_adaptor.AsStringPiece())) {
217 return true;
218 }
219 }
220
221 return false;
222 }
223
IsUrlTrustworthySafelisted(const KURL & url)224 bool SecurityPolicy::IsUrlTrustworthySafelisted(const KURL& url) {
225 // Early return to avoid initializing the SecurityOrigin.
226 if (TrustworthyOriginSafelist().IsEmpty())
227 return false;
228 return IsOriginTrustworthySafelisted(*SecurityOrigin::Create(url).get());
229 }
230
IsOriginAccessAllowed(const SecurityOrigin * active_origin,const SecurityOrigin * target_origin)231 bool SecurityPolicy::IsOriginAccessAllowed(
232 const SecurityOrigin* active_origin,
233 const SecurityOrigin* target_origin) {
234 MutexLocker lock(GetMutex());
235 return GetOriginAccessList().CheckAccessState(
236 active_origin->ToUrlOrigin(),
237 target_origin->ToUrlOrigin().GetURL()) ==
238 network::cors::OriginAccessList::AccessState::kAllowed;
239 }
240
IsOriginAccessToURLAllowed(const SecurityOrigin * active_origin,const KURL & url)241 bool SecurityPolicy::IsOriginAccessToURLAllowed(
242 const SecurityOrigin* active_origin,
243 const KURL& url) {
244 MutexLocker lock(GetMutex());
245 return GetOriginAccessList().CheckAccessState(active_origin->ToUrlOrigin(),
246 url) ==
247 network::cors::OriginAccessList::AccessState::kAllowed;
248 }
249
AddOriginAccessAllowListEntry(const SecurityOrigin & source_origin,const String & destination_protocol,const String & destination_domain,const uint16_t port,const network::mojom::CorsDomainMatchMode domain_match_mode,const network::mojom::CorsPortMatchMode port_match_mode,const network::mojom::CorsOriginAccessMatchPriority priority)250 void SecurityPolicy::AddOriginAccessAllowListEntry(
251 const SecurityOrigin& source_origin,
252 const String& destination_protocol,
253 const String& destination_domain,
254 const uint16_t port,
255 const network::mojom::CorsDomainMatchMode domain_match_mode,
256 const network::mojom::CorsPortMatchMode port_match_mode,
257 const network::mojom::CorsOriginAccessMatchPriority priority) {
258 MutexLocker lock(GetMutex());
259 GetOriginAccessList().AddAllowListEntryForOrigin(
260 source_origin.ToUrlOrigin(), destination_protocol.Utf8(),
261 destination_domain.Utf8(), port, domain_match_mode, port_match_mode,
262 priority);
263 }
264
AddOriginAccessBlockListEntry(const SecurityOrigin & source_origin,const String & destination_protocol,const String & destination_domain,const uint16_t port,const network::mojom::CorsDomainMatchMode domain_match_mode,const network::mojom::CorsPortMatchMode port_match_mode,const network::mojom::CorsOriginAccessMatchPriority priority)265 void SecurityPolicy::AddOriginAccessBlockListEntry(
266 const SecurityOrigin& source_origin,
267 const String& destination_protocol,
268 const String& destination_domain,
269 const uint16_t port,
270 const network::mojom::CorsDomainMatchMode domain_match_mode,
271 const network::mojom::CorsPortMatchMode port_match_mode,
272 const network::mojom::CorsOriginAccessMatchPriority priority) {
273 MutexLocker lock(GetMutex());
274 GetOriginAccessList().AddBlockListEntryForOrigin(
275 source_origin.ToUrlOrigin(), destination_protocol.Utf8(),
276 destination_domain.Utf8(), port, domain_match_mode, port_match_mode,
277 priority);
278 }
279
ClearOriginAccessListForOrigin(const SecurityOrigin & source_origin)280 void SecurityPolicy::ClearOriginAccessListForOrigin(
281 const SecurityOrigin& source_origin) {
282 MutexLocker lock(GetMutex());
283 GetOriginAccessList().ClearForOrigin(source_origin.ToUrlOrigin());
284 }
285
ClearOriginAccessList()286 void SecurityPolicy::ClearOriginAccessList() {
287 MutexLocker lock(GetMutex());
288 GetOriginAccessList().Clear();
289 }
290
ReferrerPolicyFromString(const String & policy,ReferrerPolicyLegacyKeywordsSupport legacy_keywords_support,network::mojom::ReferrerPolicy * result)291 bool SecurityPolicy::ReferrerPolicyFromString(
292 const String& policy,
293 ReferrerPolicyLegacyKeywordsSupport legacy_keywords_support,
294 network::mojom::ReferrerPolicy* result) {
295 DCHECK(!policy.IsNull());
296 bool support_legacy_keywords =
297 (legacy_keywords_support == kSupportReferrerPolicyLegacyKeywords);
298
299 if (EqualIgnoringASCIICase(policy, "no-referrer") ||
300 (support_legacy_keywords && (EqualIgnoringASCIICase(policy, "never") ||
301 EqualIgnoringASCIICase(policy, "none")))) {
302 *result = network::mojom::ReferrerPolicy::kNever;
303 return true;
304 }
305 if (EqualIgnoringASCIICase(policy, "unsafe-url") ||
306 (support_legacy_keywords && EqualIgnoringASCIICase(policy, "always"))) {
307 *result = network::mojom::ReferrerPolicy::kAlways;
308 return true;
309 }
310 if (EqualIgnoringASCIICase(policy, "origin")) {
311 *result = network::mojom::ReferrerPolicy::kOrigin;
312 return true;
313 }
314 if (EqualIgnoringASCIICase(policy, "origin-when-cross-origin") ||
315 (support_legacy_keywords &&
316 EqualIgnoringASCIICase(policy, "origin-when-crossorigin"))) {
317 *result = network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin;
318 return true;
319 }
320 if (EqualIgnoringASCIICase(policy, "same-origin")) {
321 *result = network::mojom::ReferrerPolicy::kSameOrigin;
322 return true;
323 }
324 if (EqualIgnoringASCIICase(policy, "strict-origin")) {
325 *result = network::mojom::ReferrerPolicy::kStrictOrigin;
326 return true;
327 }
328 if (EqualIgnoringASCIICase(policy, "strict-origin-when-cross-origin")) {
329 *result = network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin;
330 return true;
331 }
332 if (EqualIgnoringASCIICase(policy, "no-referrer-when-downgrade") ||
333 (support_legacy_keywords && EqualIgnoringASCIICase(policy, "default"))) {
334 *result = network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade;
335 return true;
336 }
337 return false;
338 }
339
340 namespace {
341
342 template <typename CharType>
IsASCIIAlphaOrHyphen(CharType c)343 inline bool IsASCIIAlphaOrHyphen(CharType c) {
344 return IsASCIIAlpha(c) || c == '-';
345 }
346
347 } // namespace
348
ReferrerPolicyFromHeaderValue(const String & header_value,ReferrerPolicyLegacyKeywordsSupport legacy_keywords_support,network::mojom::ReferrerPolicy * result)349 bool SecurityPolicy::ReferrerPolicyFromHeaderValue(
350 const String& header_value,
351 ReferrerPolicyLegacyKeywordsSupport legacy_keywords_support,
352 network::mojom::ReferrerPolicy* result) {
353 network::mojom::ReferrerPolicy referrer_policy =
354 network::mojom::ReferrerPolicy::kDefault;
355
356 Vector<String> tokens;
357 header_value.Split(',', true, tokens);
358 for (const auto& token : tokens) {
359 network::mojom::ReferrerPolicy current_result;
360 auto stripped_token = token.StripWhiteSpace();
361 if (SecurityPolicy::ReferrerPolicyFromString(token.StripWhiteSpace(),
362 legacy_keywords_support,
363 ¤t_result)) {
364 referrer_policy = current_result;
365 } else {
366 Vector<UChar> characters;
367 stripped_token.AppendTo(characters);
368 const UChar* position = characters.data();
369 UChar* end = characters.data() + characters.size();
370 SkipWhile<UChar, IsASCIIAlphaOrHyphen>(position, end);
371 if (position != end)
372 return false;
373 }
374 }
375
376 if (referrer_policy == network::mojom::ReferrerPolicy::kDefault)
377 return false;
378
379 *result = referrer_policy;
380 return true;
381 }
382
383 } // namespace blink
384