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                                                  &current_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