• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
6 #define NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
7 
8 #include <stdint.h>
9 
10 #include <bitset>
11 #include <cstdint>
12 #include <ostream>
13 #include <string>
14 #include <vector>
15 
16 #include "net/base/net_export.h"
17 
18 class GURL;
19 
20 namespace net {
21 
22 // This class represents if a cookie was included or excluded in a cookie get or
23 // set operation, and if excluded why. It holds a vector of reasons for
24 // exclusion, where cookie inclusion is represented by the absence of any
25 // exclusion reasons. Also marks whether a cookie should be warned about, e.g.
26 // for deprecation or intervention reasons.
27 // TODO(crbug.com/1310444): Improve serialization validation comments.
28 class NET_EXPORT CookieInclusionStatus {
29  public:
30   // Types of reasons why a cookie might be excluded.
31   // If adding a ExclusionReason, please also update the GetDebugString()
32   // method.
33   enum ExclusionReason {
34     EXCLUDE_UNKNOWN_ERROR = 0,
35 
36     // Statuses applied when accessing a cookie (either sending or setting):
37 
38     // Cookie was HttpOnly, but the attempted access was through a non-HTTP API.
39     EXCLUDE_HTTP_ONLY = 1,
40     // Cookie was Secure, but the URL was not allowed to access Secure cookies.
41     EXCLUDE_SECURE_ONLY = 2,
42     // The cookie's domain attribute did not match the domain of the URL
43     // attempting access.
44     EXCLUDE_DOMAIN_MISMATCH = 3,
45     // The cookie's path attribute did not match the path of the URL attempting
46     // access.
47     EXCLUDE_NOT_ON_PATH = 4,
48     // The cookie had SameSite=Strict, and the attempted access did not have an
49     // appropriate SameSiteCookieContext.
50     EXCLUDE_SAMESITE_STRICT = 5,
51     // The cookie had SameSite=Lax, and the attempted access did not have an
52     // appropriate SameSiteCookieContext.
53     EXCLUDE_SAMESITE_LAX = 6,
54     // The cookie did not specify a SameSite attribute, and therefore was
55     // treated as if it were SameSite=Lax, and the attempted access did not have
56     // an appropriate SameSiteCookieContext.
57     EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX = 7,
58     // The cookie specified SameSite=None, but it was not Secure.
59     EXCLUDE_SAMESITE_NONE_INSECURE = 8,
60     // Caller did not allow access to the cookie.
61     EXCLUDE_USER_PREFERENCES = 9,
62     // The cookie specified SameParty, but was used in a cross-party context.
63     EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT = 10,
64 
65     // Statuses only applied when creating/setting cookies:
66 
67     // Cookie was malformed and could not be stored, due to problem(s) while
68     // parsing.
69     // TODO(crbug.com/1228815): Use more specific reasons for parsing errors.
70     EXCLUDE_FAILURE_TO_STORE = 11,
71     // Attempted to set a cookie from a scheme that does not support cookies.
72     EXCLUDE_NONCOOKIEABLE_SCHEME = 12,
73     // Cookie would have overwritten a Secure cookie, and was not allowed to do
74     // so. (See "Leave Secure Cookies Alone":
75     // https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone-05 )
76     EXCLUDE_OVERWRITE_SECURE = 13,
77     // Cookie would have overwritten an HttpOnly cookie, and was not allowed to
78     // do so.
79     EXCLUDE_OVERWRITE_HTTP_ONLY = 14,
80     // Cookie was set with an invalid Domain attribute.
81     EXCLUDE_INVALID_DOMAIN = 15,
82     // Cookie was set with an invalid __Host- or __Secure- prefix.
83     EXCLUDE_INVALID_PREFIX = 16,
84     // Cookie was set with an invalid SameParty attribute in combination with
85     // other attributes. (SameParty is invalid if Secure is not present, or if
86     // SameSite=Strict is present.)
87     EXCLUDE_INVALID_SAMEPARTY = 17,
88     /// Cookie was set with an invalid Partitioned attribute, which is only
89     // valid if the cookie has a __Host- prefix and does not have the SameParty
90     // attribute.
91     EXCLUDE_INVALID_PARTITIONED = 18,
92     // Cookie exceeded the name/value pair size limit.
93     EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE = 19,
94     // Cookie exceeded the attribute size limit. Note that this exclusion value
95     // won't be used by code that parses cookie lines since RFC6265bis
96     // indicates that large attributes should be ignored instead of causing the
97     // whole cookie to be rejected. There will be a corresponding WarningReason
98     // to notify users that an attribute value was ignored in that case.
99     EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 20,
100     // Cookie was set with a Domain attribute containing non ASCII characters.
101     EXCLUDE_DOMAIN_NON_ASCII = 21,
102     // Special case for when a cookie is blocked by third-party cookie blocking
103     // but the two sites are in the same First-Party Set.
104     EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET = 22,
105 
106     // This should be kept last.
107     NUM_EXCLUSION_REASONS
108   };
109 
110   // Reason to warn about a cookie. Any information contained in WarningReason
111   // of an included cookie may be passed to an untrusted renderer.
112   // If you add one, please update GetDebugString().
113   enum WarningReason {
114     // Of the following 3 SameSite warnings, there will be, at most, a single
115     // active one.
116 
117     // Warn if a cookie with unspecified SameSite attribute is used in a
118     // cross-site context.
119     WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT = 0,
120     // Warn if a cookie with SameSite=None is not Secure.
121     WARN_SAMESITE_NONE_INSECURE = 1,
122     // Warn if a cookie with unspecified SameSite attribute is defaulted into
123     // Lax and is sent on a request with unsafe method, only because it is new
124     // enough to activate the Lax-allow-unsafe intervention.
125     WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE = 2,
126 
127     // The following warnings indicate that an included cookie with an effective
128     // SameSite is experiencing a SameSiteCookieContext::|context| ->
129     // SameSiteCookieContext::|schemeful_context| downgrade that will prevent
130     // its access schemefully.
131     // This situation means that a cookie is accessible when the
132     // SchemefulSameSite feature is disabled but not when it's enabled,
133     // indicating changed behavior and potential breakage.
134     //
135     // For example, a Strict to Lax downgrade for an effective SameSite=Strict
136     // cookie:
137     // This cookie would be accessible in the Strict context as its SameSite
138     // value is Strict. However its context for schemeful same-site becomes Lax.
139     // A strict cookie cannot be accessed in a Lax context and therefore the
140     // behavior has changed.
141     // As a counterexample, a Strict to Lax downgrade for an effective
142     // SameSite=Lax cookie: A Lax cookie can be accessed in both Strict and Lax
143     // contexts so there is no behavior change (and we don't warn about it).
144     //
145     // The warnings are in the following format:
146     // WARN_{context}_{schemeful_context}_DOWNGRADE_{samesite_value}_SAMESITE
147     //
148     // Of the following 5 SameSite warnings, there will be, at most, a single
149     // active one.
150 
151     // Strict to Lax downgrade for an effective SameSite=Strict cookie.
152     // This warning is only applicable for cookies being sent because a Strict
153     // cookie will be set in both Strict and Lax Contexts so the downgrade will
154     // not affect it.
155     WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE = 3,
156     // Strict to Cross-site downgrade for an effective SameSite=Strict cookie.
157     // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe
158     // behaving like Cross-site.
159     WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE = 4,
160     // Strict to Cross-site downgrade for an effective SameSite=Lax cookie.
161     // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe
162     // behaving like Cross-site.
163     WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE = 5,
164     // Lax to Cross-site downgrade for an effective SameSite=Strict cookie.
165     // This warning is only applicable for cookies being set because a Strict
166     // cookie will not be sent in a Lax context so the downgrade would not
167     // affect it.
168     WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE = 6,
169     // Lax to Cross-site downgrade for an effective SameSite=Lax cookie.
170     WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE = 7,
171 
172     // Advisory warning attached when a Secure cookie is accessed from (sent to,
173     // or set by) a non-cryptographic URL. This can happen if the URL is
174     // potentially trustworthy (e.g. a localhost URL, or another URL that
175     // the CookieAccessDelegate is configured to allow).
176     // TODO(chlily): Add metrics for how often and where this occurs.
177     WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC = 8,
178 
179     // The cookie was treated as SameParty. This is different from looking at
180     // whether the cookie has the SameParty attribute, since we may choose to
181     // ignore that attribute for one reason or another. E.g., we ignore the
182     // SameParty attribute if the site is not a member of a nontrivial
183     // First-Party Set.
184     WARN_TREATED_AS_SAMEPARTY = 9,
185 
186     // The cookie was excluded solely for SameParty reasons (i.e. it was in
187     // cross-party context), but would have been included by SameSite. (This can
188     // only occur in cross-party, cross-site contexts, for cookies that are
189     // 'SameParty; SameSite=None'.)
190     WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE = 10,
191 
192     // The cookie was included due to SameParty, even though it would have been
193     // excluded by SameSite. (This can only occur in same-party, cross-site
194     // contexts, for cookies that are 'SameParty; SameSite=Lax'.)
195     WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE = 11,
196 
197     // The cookie would have been included prior to the spec change considering
198     // redirects in the SameSite context calculation
199     // (https://github.com/httpwg/http-extensions/pull/1348)
200     // but would have been excluded after the spec change, due to a cross-site
201     // redirect causing the SameSite context calculation to be downgraded.
202     // This is applied if and only if the cookie's inclusion was changed by
203     // considering redirect chains (and is applied regardless of which context
204     // was actually used for the inclusion decision). This is not applied if
205     // the context was downgraded but the cookie would have been
206     // included/excluded in both cases.
207     WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION = 12,
208 
209     // The cookie exceeded the attribute size limit. RFC6265bis indicates that
210     // large attributes should be ignored instead of causing the whole cookie
211     // to be rejected. This is applied by the code that parses cookie lines and
212     // notifies the user that an attribute value was ignored.
213     WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 13,
214 
215     // Cookie was set with a Domain attribute containing non ASCII characters.
216     WARN_DOMAIN_NON_ASCII = 14,
217 
218     // This should be kept last.
219     NUM_WARNING_REASONS
220   };
221 
222   // These enums encode the context downgrade warnings + the secureness of the
223   // url sending/setting the cookie. They're used for metrics only. The format
224   // is k{context}{schemeful_context}{samesite_value}{securness}.
225   // kNoDowngrade{securness} indicates that a cookie didn't have a breaking
226   // context downgrade and was A) included B) excluded only due to insufficient
227   // same-site context. I.e. the cookie wasn't excluded due to other reasons
228   // such as third-party cookie blocking. Keep this in line with
229   // SameSiteCookieContextBreakingDowngradeWithSecureness in enums.xml.
230   enum class ContextDowngradeMetricValues {
231     kNoDowngradeInsecure = 0,
232     kNoDowngradeSecure = 1,
233 
234     kStrictLaxStrictInsecure = 2,
235     kStrictCrossStrictInsecure = 3,
236     kStrictCrossLaxInsecure = 4,
237     kLaxCrossStrictInsecure = 5,
238     kLaxCrossLaxInsecure = 6,
239 
240     kStrictLaxStrictSecure = 7,
241     kStrictCrossStrictSecure = 8,
242     kStrictCrossLaxSecure = 9,
243     kLaxCrossStrictSecure = 10,
244     kLaxCrossLaxSecure = 11,
245 
246     // Keep last.
247     kMaxValue = kLaxCrossLaxSecure
248   };
249 
250   using ExclusionReasonBitset =
251       std::bitset<ExclusionReason::NUM_EXCLUSION_REASONS>;
252   using WarningReasonBitset = std::bitset<WarningReason::NUM_WARNING_REASONS>;
253 
254   // Makes a status that says include and should not warn.
255   CookieInclusionStatus();
256 
257   // Make a status that contains the given exclusion reason.
258   explicit CookieInclusionStatus(ExclusionReason reason);
259   // Makes a status that contains the given exclusion reason and warning.
260   CookieInclusionStatus(ExclusionReason reason, WarningReason warning);
261   // Makes a status that contains the given warning.
262   explicit CookieInclusionStatus(WarningReason warning);
263 
264   // Copyable.
265   CookieInclusionStatus(const CookieInclusionStatus& other);
266   CookieInclusionStatus& operator=(const CookieInclusionStatus& other);
267 
268   bool operator==(const CookieInclusionStatus& other) const;
269   bool operator!=(const CookieInclusionStatus& other) const;
270 
271   // Whether the status is to include the cookie, and has no other reasons for
272   // exclusion.
273   bool IsInclude() const;
274 
275   // Whether the given reason for exclusion is present.
276   bool HasExclusionReason(ExclusionReason status_type) const;
277 
278   // Whether the given reason for exclusion is present, and is the ONLY reason
279   // for exclusion.
280   bool HasOnlyExclusionReason(ExclusionReason status_type) const;
281 
282   // Add an exclusion reason.
283   void AddExclusionReason(ExclusionReason status_type);
284 
285   // Remove an exclusion reason.
286   void RemoveExclusionReason(ExclusionReason reason);
287 
288   // Remove multiple exclusion reasons.
289   void RemoveExclusionReasons(const std::vector<ExclusionReason>& reasons);
290 
291   // If the cookie would have been excluded for reasons other than
292   // SameSite-related reasons, don't bother warning about it (clear the
293   // warning).
294   void MaybeClearSameSiteWarning();
295 
296   // Whether to record the breaking downgrade metrics if the cookie is included
297   // or if it's only excluded because of insufficient same-site context.
298   bool ShouldRecordDowngradeMetrics() const;
299 
300   // Whether the cookie should be warned about.
301   bool ShouldWarn() const;
302 
303   // Whether the given reason for warning is present.
304   bool HasWarningReason(WarningReason reason) const;
305 
306   // Whether a schemeful downgrade warning is present.
307   // A schemeful downgrade means that an included cookie with an effective
308   // SameSite is experiencing a SameSiteCookieContext::|context| ->
309   // SameSiteCookieContext::|schemeful_context| downgrade that will prevent its
310   // access schemefully. If the function returns true and |reason| is valid then
311   // |reason| will contain which warning was found.
312   bool HasSchemefulDowngradeWarning(
313       CookieInclusionStatus::WarningReason* reason = nullptr) const;
314 
315   // Add an warning reason.
316   void AddWarningReason(WarningReason reason);
317 
318   // Remove an warning reason.
319   void RemoveWarningReason(WarningReason reason);
320 
321   // Used for serialization/deserialization.
exclusion_reasons()322   ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; }
set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons)323   void set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons) {
324     exclusion_reasons_ = exclusion_reasons;
325   }
326 
warning_reasons()327   WarningReasonBitset warning_reasons() const { return warning_reasons_; }
set_warning_reasons(WarningReasonBitset warning_reasons)328   void set_warning_reasons(WarningReasonBitset warning_reasons) {
329     warning_reasons_ = warning_reasons;
330   }
331 
332   ContextDowngradeMetricValues GetBreakingDowngradeMetricsEnumValue(
333       const GURL& url) const;
334 
335   // Get exclusion reason(s) and warning in string format.
336   std::string GetDebugString() const;
337 
338   // Checks whether the exclusion reasons are exactly the set of exclusion
339   // reasons in the vector. (Ignores warnings.)
340   bool HasExactlyExclusionReasonsForTesting(
341       std::vector<ExclusionReason> reasons) const;
342 
343   // Checks whether the warning reasons are exactly the set of warning
344   // reasons in the vector. (Ignores exclusions.)
345   bool HasExactlyWarningReasonsForTesting(
346       std::vector<WarningReason> reasons) const;
347 
348   // Validates mojo data, since mojo does not support bitsets.
349   // TODO(crbug.com/1310444): Improve serialization validation comments
350   // and check for mutually exclusive values.
351   static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,
352                                                   uint32_t warning_reasons);
353 
354   // Makes a status that contains the given exclusion reasons and warning.
355   static CookieInclusionStatus MakeFromReasonsForTesting(
356       std::vector<ExclusionReason> reasons,
357       std::vector<WarningReason> warnings = std::vector<WarningReason>());
358 
359   // Returns true if the cookie was excluded because of user preferences.
360   // HasOnlyExclusionReason(EXCLUDE_USER_PREFERENCES) will not return true for
361   // third-party cookies blocked in sites in the same First-Party Set (note:
362   // this is not the same as the cookie being blocked in a same-party context,
363   // which takes the entire ancestor chain into account). See
364   // https://crbug.com/1366868.
365   bool ExcludedByUserPreferences() const;
366 
367  private:
368   // Returns the `exclusion_reasons_` with the given `reasons` unset.
369   ExclusionReasonBitset ExclusionReasonsWithout(
370       const std::vector<ExclusionReason>& reasons) const;
371 
372   // A bit vector of the applicable exclusion reasons.
373   ExclusionReasonBitset exclusion_reasons_;
374 
375   // A bit vector of the applicable warning reasons.
376   WarningReasonBitset warning_reasons_;
377 };
378 
379 NET_EXPORT inline std::ostream& operator<<(std::ostream& os,
380                                            const CookieInclusionStatus status) {
381   return os << status.GetDebugString();
382 }
383 
384 // Provided to allow gtest to create more helpful error messages, instead of
385 // printing hex.
PrintTo(const CookieInclusionStatus & cis,std::ostream * os)386 inline void PrintTo(const CookieInclusionStatus& cis, std::ostream* os) {
387   *os << cis;
388 }
389 
390 }  // namespace net
391 
392 #endif  // NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
393