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