xref: /aosp_15_r20/external/cronet/net/cookies/cookie_inclusion_status.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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   enum ExclusionReason {
32     EXCLUDE_UNKNOWN_ERROR = 0,
33 
34     // Statuses applied when accessing a cookie (either sending or setting):
35 
36     // Cookie was HttpOnly, but the attempted access was through a non-HTTP API.
37     EXCLUDE_HTTP_ONLY = 1,
38     // Cookie was Secure, but the URL was not allowed to access Secure cookies.
39     EXCLUDE_SECURE_ONLY = 2,
40     // The cookie's domain attribute did not match the domain of the URL
41     // attempting access.
42     EXCLUDE_DOMAIN_MISMATCH = 3,
43     // The cookie's path attribute did not match the path of the URL attempting
44     // access.
45     EXCLUDE_NOT_ON_PATH = 4,
46     // The cookie had SameSite=Strict, and the attempted access did not have an
47     // appropriate SameSiteCookieContext.
48     EXCLUDE_SAMESITE_STRICT = 5,
49     // The cookie had SameSite=Lax, and the attempted access did not have an
50     // appropriate SameSiteCookieContext.
51     EXCLUDE_SAMESITE_LAX = 6,
52     // The cookie did not specify a SameSite attribute, and therefore was
53     // treated as if it were SameSite=Lax, and the attempted access did not have
54     // an appropriate SameSiteCookieContext.
55     EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX = 7,
56     // The cookie specified SameSite=None, but it was not Secure.
57     EXCLUDE_SAMESITE_NONE_INSECURE = 8,
58     // Caller did not allow access to the cookie.
59     EXCLUDE_USER_PREFERENCES = 9,
60 
61     // Statuses only applied when creating/setting cookies:
62 
63     // Cookie was malformed and could not be stored, due to problem(s) while
64     // parsing.
65     // TODO(crbug.com/1228815): Use more specific reasons for parsing errors.
66     EXCLUDE_FAILURE_TO_STORE = 10,
67     // Attempted to set a cookie from a scheme that does not support cookies.
68     EXCLUDE_NONCOOKIEABLE_SCHEME = 11,
69     // Cookie would have overwritten a Secure cookie, and was not allowed to do
70     // so. (See "Leave Secure Cookies Alone":
71     // https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone-05 )
72     EXCLUDE_OVERWRITE_SECURE = 12,
73     // Cookie would have overwritten an HttpOnly cookie, and was not allowed to
74     // do so.
75     EXCLUDE_OVERWRITE_HTTP_ONLY = 13,
76     // Cookie was set with an invalid Domain attribute.
77     EXCLUDE_INVALID_DOMAIN = 14,
78     // Cookie was set with an invalid __Host- or __Secure- prefix.
79     EXCLUDE_INVALID_PREFIX = 15,
80     /// Cookie was set with an invalid Partitioned attribute, which is only
81     // valid if the cookie has a __Host- prefix.
82     EXCLUDE_INVALID_PARTITIONED = 16,
83     // Cookie exceeded the name/value pair size limit.
84     EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE = 17,
85     // Cookie exceeded the attribute size limit. Note that this exclusion value
86     // won't be used by code that parses cookie lines since RFC6265bis
87     // indicates that large attributes should be ignored instead of causing the
88     // whole cookie to be rejected. There will be a corresponding WarningReason
89     // to notify users that an attribute value was ignored in that case.
90     EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 18,
91     // Cookie was set with a Domain attribute containing non ASCII characters.
92     EXCLUDE_DOMAIN_NON_ASCII = 19,
93     // Special case for when a cookie is blocked by third-party cookie blocking
94     // but the two sites are in the same First-Party Set.
95     EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET = 20,
96     // Cookie's source_port did not match the port of the request.
97     EXCLUDE_PORT_MISMATCH = 21,
98     // Cookie's source_scheme did not match the scheme of the request.
99     EXCLUDE_SCHEME_MISMATCH = 22,
100     // Cookie is a domain cookie and has the same name as an origin cookie on
101     // this origin.
102     EXCLUDE_SHADOWING_DOMAIN = 23,
103     // Cookie contains ASCII control characters (including the tab character,
104     // when it appears in the middle of the cookie name, value, an attribute
105     // name, or an attribute value).
106     EXCLUDE_DISALLOWED_CHARACTER = 24,
107     // Cookie is blocked for third-party cookie phaseout.
108     EXCLUDE_THIRD_PARTY_PHASEOUT = 25,
109     // Cookie contains no content or only whitespace.
110     EXCLUDE_NO_COOKIE_CONTENT = 26,
111 
112     // This should be kept last.
113     NUM_EXCLUSION_REASONS
114   };
115 
116   // Mojom and some tests assume that all the exclusion reasons will fit within
117   // a uint32_t. Once that's not longer true those assumptions need to be
118   // updated (along with this assert).
119   static_assert(ExclusionReason::NUM_EXCLUSION_REASONS <= 32,
120                 "Expanding ExclusionReasons past 32 reasons requires updating "
121                 "usage assumptions.");
122 
123   // Reason to warn about a cookie. Any information contained in
124   // WarningReason of an included cookie may be passed to an untrusted
125   // renderer.
126   enum WarningReason {
127     // Of the following 3 SameSite warnings, there will be, at most, a single
128     // active one.
129 
130     // Warn if a cookie with unspecified SameSite attribute is used in a
131     // cross-site context.
132     WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT = 0,
133     // Warn if a cookie with SameSite=None is not Secure.
134     WARN_SAMESITE_NONE_INSECURE = 1,
135     // Warn if a cookie with unspecified SameSite attribute is defaulted into
136     // Lax and is sent on a request with unsafe method, only because it is new
137     // enough to activate the Lax-allow-unsafe intervention.
138     WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE = 2,
139 
140     // The following warnings indicate that an included cookie with an effective
141     // SameSite is experiencing a SameSiteCookieContext::|context| ->
142     // SameSiteCookieContext::|schemeful_context| downgrade that will prevent
143     // its access schemefully.
144     // This situation means that a cookie is accessible when the
145     // SchemefulSameSite feature is disabled but not when it's enabled,
146     // indicating changed behavior and potential breakage.
147     //
148     // For example, a Strict to Lax downgrade for an effective SameSite=Strict
149     // cookie:
150     // This cookie would be accessible in the Strict context as its SameSite
151     // value is Strict. However its context for schemeful same-site becomes Lax.
152     // A strict cookie cannot be accessed in a Lax context and therefore the
153     // behavior has changed.
154     // As a counterexample, a Strict to Lax downgrade for an effective
155     // SameSite=Lax cookie: A Lax cookie can be accessed in both Strict and Lax
156     // contexts so there is no behavior change (and we don't warn about it).
157     //
158     // The warnings are in the following format:
159     // WARN_{context}_{schemeful_context}_DOWNGRADE_{samesite_value}_SAMESITE
160     //
161     // Of the following 5 SameSite warnings, there will be, at most, a single
162     // active one.
163 
164     // Strict to Lax downgrade for an effective SameSite=Strict cookie.
165     // This warning is only applicable for cookies being sent because a Strict
166     // cookie will be set in both Strict and Lax Contexts so the downgrade will
167     // not affect it.
168     WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE = 3,
169     // Strict to Cross-site downgrade for an effective SameSite=Strict cookie.
170     // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe
171     // behaving like Cross-site.
172     WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE = 4,
173     // Strict to Cross-site downgrade for an effective SameSite=Lax cookie.
174     // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe
175     // behaving like Cross-site.
176     WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE = 5,
177     // Lax to Cross-site downgrade for an effective SameSite=Strict cookie.
178     // This warning is only applicable for cookies being set because a Strict
179     // cookie will not be sent in a Lax context so the downgrade would not
180     // affect it.
181     WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE = 6,
182     // Lax to Cross-site downgrade for an effective SameSite=Lax cookie.
183     WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE = 7,
184 
185     // Advisory warning attached when a Secure cookie is accessed from (sent to,
186     // or set by) a non-cryptographic URL. This can happen if the URL is
187     // potentially trustworthy (e.g. a localhost URL, or another URL that
188     // the CookieAccessDelegate is configured to allow). This also applies to
189     // cookies with secure source schemes when scheme binding is enabled.
190     // TODO(chlily): Add metrics for how often and where this occurs.
191     WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC = 8,
192 
193     // The cookie would have been included prior to the spec change considering
194     // redirects in the SameSite context calculation
195     // (https://github.com/httpwg/http-extensions/pull/1348)
196     // but would have been excluded after the spec change, due to a cross-site
197     // redirect causing the SameSite context calculation to be downgraded.
198     // This is applied if and only if the cookie's inclusion was changed by
199     // considering redirect chains (and is applied regardless of which context
200     // was actually used for the inclusion decision). This is not applied if
201     // the context was downgraded but the cookie would have been
202     // included/excluded in both cases.
203     WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION = 9,
204 
205     // The cookie exceeded the attribute size limit. RFC6265bis indicates that
206     // large attributes should be ignored instead of causing the whole cookie
207     // to be rejected. This is applied by the code that parses cookie lines and
208     // notifies the user that an attribute value was ignored.
209     WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 10,
210 
211     // The cookie was set with a Domain attribute containing non ASCII
212     // characters.
213     WARN_DOMAIN_NON_ASCII = 11,
214     // The cookie's source_port did not match the port of the request.
215     WARN_PORT_MISMATCH = 12,
216     // The cookie's source_scheme did not match the scheme of the request.
217     WARN_SCHEME_MISMATCH = 13,
218     // The cookie's creation url is non-cryptographic but it specified the
219     // "Secure" attribute. A trustworthy url may be setting this cookie, but we
220     // can't confirm/deny that at the time of creation.
221     WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME = 14,
222     // Cookie is a domain cookie and has the same name as an origin cookie on
223     // this origin. This cookie would be blocked if shadowing protection was
224     // enabled.
225     WARN_SHADOWING_DOMAIN = 15,
226 
227     // This cookie will be blocked for third-party cookie phaseout.
228     WARN_THIRD_PARTY_PHASEOUT = 16,
229 
230     // This should be kept last.
231     NUM_WARNING_REASONS
232   };
233 
234   // Mojom and some tests assume that all the warning reasons will fit within
235   // a uint32_t. Once that's not longer true those assumptions need to be
236   // updated (along with this assert).
237   static_assert(WarningReason::NUM_WARNING_REASONS <= 32,
238                 "Expanding WarningReasons past 32 reasons requires updating "
239                 "usage assumptions.");
240 
241   // These enums encode the context downgrade warnings + the secureness of the
242   // url sending/setting the cookie. They're used for metrics only. The format
243   // is k{context}{schemeful_context}{samesite_value}{securness}.
244   // kNoDowngrade{securness} indicates that a cookie didn't have a breaking
245   // context downgrade and was A) included B) excluded only due to insufficient
246   // same-site context. I.e. the cookie wasn't excluded due to other reasons
247   // such as third-party cookie blocking. Keep this in line with
248   // SameSiteCookieContextBreakingDowngradeWithSecureness in enums.xml.
249   enum class ContextDowngradeMetricValues {
250     kNoDowngradeInsecure = 0,
251     kNoDowngradeSecure = 1,
252 
253     kStrictLaxStrictInsecure = 2,
254     kStrictCrossStrictInsecure = 3,
255     kStrictCrossLaxInsecure = 4,
256     kLaxCrossStrictInsecure = 5,
257     kLaxCrossLaxInsecure = 6,
258 
259     kStrictLaxStrictSecure = 7,
260     kStrictCrossStrictSecure = 8,
261     kStrictCrossLaxSecure = 9,
262     kLaxCrossStrictSecure = 10,
263     kLaxCrossLaxSecure = 11,
264 
265     // Keep last.
266     kMaxValue = kLaxCrossLaxSecure
267   };
268 
269   // Types of reasons why a cookie should-have-been-blocked by 3pcd got
270   // exempted and included.
271   enum class ExemptionReason {
272     // The default exemption reason. The cookie with this reason could either be
273     // included, or blocked due to 3pcd-unrelated reasons.
274     kNone = 0,
275     // For user explicit settings, including User bypass.
276     kUserSetting = 1,
277     // For 3PCD metadata .
278     k3PCDMetadata = 2,
279     // For 3PCD 1P and 3P deprecation trial.
280     k3PCDDeprecationTrial = 3,
281     // For 3PCD heuristics.
282     k3PCDHeuristics = 4,
283     // For Enterprise Policy : CookieAllowedForUrls and BlockThirdPartyCookies.
284     kEnterprisePolicy = 5,
285     kStorageAccess = 6,
286     kTopLevelStorageAccess = 7,
287     // For CorsException in the ABA contexts, which the inner iframe is
288     // same-site with the top-level site but has cross-site ancestor(s).
289     kCorsOptIn = 8,
290 
291     // Keep last.
292     kMaxValue = kCorsOptIn
293   };
294 
295   using ExclusionReasonBitset =
296       std::bitset<ExclusionReason::NUM_EXCLUSION_REASONS>;
297   using WarningReasonBitset = std::bitset<WarningReason::NUM_WARNING_REASONS>;
298 
299   // Makes a status that says include and should not warn.
300   CookieInclusionStatus();
301 
302   // Make a status that contains the given exclusion reason.
303   explicit CookieInclusionStatus(ExclusionReason reason);
304   // Makes a status that contains the given exclusion reason and warning.
305   // TODO(shuuran): only called in tests, use `MakeFromReasonsForTesting`
306   // instead.
307   CookieInclusionStatus(ExclusionReason reason, WarningReason warning);
308   // Makes a status that contains the given warning.
309   // TODO(shuuran): only called in tests, use `MakeFromReasonsForTesting`
310   // instead.
311   explicit CookieInclusionStatus(WarningReason warning);
312 
313   // Copyable.
314   CookieInclusionStatus(const CookieInclusionStatus& other);
315   CookieInclusionStatus& operator=(const CookieInclusionStatus& other);
316 
317   bool operator==(const CookieInclusionStatus& other) const;
318   bool operator!=(const CookieInclusionStatus& other) const;
319   bool operator<(const CookieInclusionStatus& other) const;
320 
321   // Whether the status is to include the cookie, and has no other reasons for
322   // exclusion.
323   bool IsInclude() const;
324 
325   // Whether the given reason for exclusion is present.
326   bool HasExclusionReason(ExclusionReason status_type) const;
327 
328   // Whether the given reason for exclusion is present, and is the ONLY reason
329   // for exclusion.
330   bool HasOnlyExclusionReason(ExclusionReason status_type) const;
331 
332   // Add an exclusion reason.
333   void AddExclusionReason(ExclusionReason status_type);
334 
335   // Remove an exclusion reason.
336   void RemoveExclusionReason(ExclusionReason reason);
337 
338   // Remove multiple exclusion reasons.
339   void RemoveExclusionReasons(const std::vector<ExclusionReason>& reasons);
340 
341   // Only updates exemption reason if the cookie was not already excluded and
342   // doesn't already have an exemption reason.
343   void MaybeSetExemptionReason(ExemptionReason reason);
344 
exemption_reason()345   ExemptionReason exemption_reason() const { return exemption_reason_; }
346 
347   // If the cookie would have been excluded for reasons other than
348   // SameSite-related reasons, don't bother warning about it (clear the
349   // warning).
350   void MaybeClearSameSiteWarning();
351 
352   // Whether to record the breaking downgrade metrics if the cookie is included
353   // or if it's only excluded because of insufficient same-site context.
354   bool ShouldRecordDowngradeMetrics() const;
355 
356   // Whether the cookie should be warned about.
357   bool ShouldWarn() const;
358 
359   // Whether the given reason for warning is present.
360   bool HasWarningReason(WarningReason reason) const;
361 
362   // Whether a schemeful downgrade warning is present.
363   // A schemeful downgrade means that an included cookie with an effective
364   // SameSite is experiencing a SameSiteCookieContext::|context| ->
365   // SameSiteCookieContext::|schemeful_context| downgrade that will prevent its
366   // access schemefully. If the function returns true and |reason| is valid then
367   // |reason| will contain which warning was found.
368   bool HasSchemefulDowngradeWarning(
369       CookieInclusionStatus::WarningReason* reason = nullptr) const;
370 
371   // Add an warning reason.
372   void AddWarningReason(WarningReason reason);
373 
374   // Remove an warning reason.
375   void RemoveWarningReason(WarningReason reason);
376 
377   // Used for serialization/deserialization.
exclusion_reasons()378   ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; }
set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons)379   void set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons) {
380     exclusion_reasons_ = exclusion_reasons;
381   }
382 
warning_reasons()383   WarningReasonBitset warning_reasons() const { return warning_reasons_; }
set_warning_reasons(WarningReasonBitset warning_reasons)384   void set_warning_reasons(WarningReasonBitset warning_reasons) {
385     warning_reasons_ = warning_reasons;
386   }
387 
388   ContextDowngradeMetricValues GetBreakingDowngradeMetricsEnumValue(
389       const GURL& url) const;
390 
391   // Get exclusion reason(s) and warning in string format.
392   std::string GetDebugString() const;
393 
394   // Checks whether the exclusion reasons are exactly the set of exclusion
395   // reasons in the vector. (Ignores warnings.)
396   bool HasExactlyExclusionReasonsForTesting(
397       std::vector<ExclusionReason> reasons) const;
398 
399   // Checks whether the warning reasons are exactly the set of warning
400   // reasons in the vector. (Ignores exclusions.)
401   bool HasExactlyWarningReasonsForTesting(
402       std::vector<WarningReason> reasons) const;
403 
404   // Validates mojo data, since mojo does not support bitsets. ExemptionReason
405   // is omitted intendedly.
406   // TODO(crbug.com/1310444): Improve serialization validation comments
407   // and check for mutually exclusive values.
408   static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,
409                                                   uint32_t warning_reasons);
410 
411   // Makes a status that contains the given reasons. If 'use_literal' is true,
412   // this method permits status to have reason combinations that cannot occur
413   // under normal circumstances; otherwise it can cause a CHECK failure.
414   static CookieInclusionStatus MakeFromReasonsForTesting(
415       std::vector<ExclusionReason> exclusions,
416       std::vector<WarningReason> warnings = std::vector<WarningReason>(),
417       ExemptionReason exemption = ExemptionReason::kNone,
418       bool use_literal = false);
419 
420   // Returns true if the cookie was excluded because of user preferences or
421   // 3PCD.
422   bool ExcludedByUserPreferencesOrTPCD() const;
423 
ResetForTesting()424   void ResetForTesting() {
425     exclusion_reasons_.reset();
426     warning_reasons_.reset();
427     exemption_reason_ = ExemptionReason::kNone;
428   }
429 
430  private:
431   // Makes a status that contains the exact given exclusion reason and warning
432   // and exemption.
433   CookieInclusionStatus(std::vector<ExclusionReason> exclusions,
434                         std::vector<WarningReason> warnings,
435                         ExemptionReason exemption);
436 
437   // Returns the `exclusion_reasons_` with the given `reasons` unset.
438   ExclusionReasonBitset ExclusionReasonsWithout(
439       const std::vector<ExclusionReason>& reasons) const;
440 
441   // If the cookie would have been excluded by reasons that are not
442   // Third-party cookie phaseout related, clear the Third-party cookie phaseout
443   // warning/exclusion reason in this case.
444   void MaybeClearThirdPartyPhaseoutReason();
445 
446   // A bit vector of the applicable exclusion reasons.
447   ExclusionReasonBitset exclusion_reasons_;
448 
449   // A bit vector of the applicable warning reasons.
450   WarningReasonBitset warning_reasons_;
451 
452   // A cookie can only have at most one exemption reason.
453   ExemptionReason exemption_reason_ = ExemptionReason::kNone;
454 };
455 
456 NET_EXPORT inline std::ostream& operator<<(std::ostream& os,
457                                            const CookieInclusionStatus status) {
458   return os << status.GetDebugString();
459 }
460 
461 // Provided to allow gtest to create more helpful error messages, instead of
462 // printing hex.
PrintTo(const CookieInclusionStatus & cis,std::ostream * os)463 inline void PrintTo(const CookieInclusionStatus& cis, std::ostream* os) {
464   *os << cis;
465 }
466 
467 }  // namespace net
468 
469 #endif  // NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
470