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/40219875): 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/40189703): 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 kTopLevel3PCDDeprecationTrial = 4,
282 // For 3PCD heuristics.
283 k3PCDHeuristics = 5,
284 // For Enterprise Policy : CookieAllowedForUrls and BlockThirdPartyCookies.
285 kEnterprisePolicy = 6,
286 kStorageAccess = 7,
287 kTopLevelStorageAccess = 8,
288 // Allowed by the scheme.
289 kScheme = 9,
290
291 // Keep last.
292 kMaxValue = kScheme
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 // Copyable.
303 CookieInclusionStatus(const CookieInclusionStatus& other);
304 CookieInclusionStatus& operator=(const CookieInclusionStatus& other);
305
306 bool operator==(const CookieInclusionStatus& other) const;
307 bool operator!=(const CookieInclusionStatus& other) const;
308
309 // Whether the status is to include the cookie, and has no other reasons for
310 // exclusion.
311 bool IsInclude() const;
312
313 // Whether the given reason for exclusion is present.
314 bool HasExclusionReason(ExclusionReason status_type) const;
315
316 // Whether the given reason for exclusion is present, and is the ONLY reason
317 // for exclusion.
318 bool HasOnlyExclusionReason(ExclusionReason status_type) const;
319
320 // Add an exclusion reason.
321 void AddExclusionReason(ExclusionReason status_type);
322
323 // Remove an exclusion reason.
324 void RemoveExclusionReason(ExclusionReason reason);
325
326 // Remove multiple exclusion reasons.
327 void RemoveExclusionReasons(const std::vector<ExclusionReason>& reasons);
328
329 // Only updates exemption reason if the cookie was not already excluded and
330 // doesn't already have an exemption reason.
331 void MaybeSetExemptionReason(ExemptionReason reason);
332
exemption_reason()333 ExemptionReason exemption_reason() const { return exemption_reason_; }
334
335 // If the cookie would have been excluded for reasons other than
336 // SameSite-related reasons, don't bother warning about it (clear the
337 // warning).
338 void MaybeClearSameSiteWarning();
339
340 // Whether to record the breaking downgrade metrics if the cookie is included
341 // or if it's only excluded because of insufficient same-site context.
342 bool ShouldRecordDowngradeMetrics() const;
343
344 // Whether the cookie should be warned about.
345 bool ShouldWarn() const;
346
347 // Whether the given reason for warning is present.
348 bool HasWarningReason(WarningReason reason) const;
349
350 // Whether a schemeful downgrade warning is present.
351 // A schemeful downgrade means that an included cookie with an effective
352 // SameSite is experiencing a SameSiteCookieContext::|context| ->
353 // SameSiteCookieContext::|schemeful_context| downgrade that will prevent its
354 // access schemefully. If the function returns true and |reason| is valid then
355 // |reason| will contain which warning was found.
356 bool HasSchemefulDowngradeWarning(
357 CookieInclusionStatus::WarningReason* reason = nullptr) const;
358
359 // Add an warning reason.
360 void AddWarningReason(WarningReason reason);
361
362 // Remove an warning reason.
363 void RemoveWarningReason(WarningReason reason);
364
365 // Used for serialization/deserialization.
exclusion_reasons()366 ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; }
367
warning_reasons()368 WarningReasonBitset warning_reasons() const { return warning_reasons_; }
369
370 ContextDowngradeMetricValues GetBreakingDowngradeMetricsEnumValue(
371 const GURL& url) const;
372
373 // Get exclusion reason(s) and warning in string format.
374 std::string GetDebugString() const;
375
376 // Checks whether the exclusion reasons are exactly the set of exclusion
377 // reasons in the vector. (Ignores warnings.)
378 bool HasExactlyExclusionReasonsForTesting(
379 const std::vector<ExclusionReason>& reasons) const;
380
381 // Checks whether the warning reasons are exactly the set of warning
382 // reasons in the vector. (Ignores exclusions.)
383 bool HasExactlyWarningReasonsForTesting(
384 const std::vector<WarningReason>& reasons) const;
385
386 // Validates mojo data, since mojo does not support bitsets. ExemptionReason
387 // is omitted intendedly.
388 // TODO(crbug.com/40219875): Improve serialization validation comments
389 // and check for mutually exclusive values.
390 static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,
391 uint32_t warning_reasons);
392
393 // Makes a status that contains the given reasons. If the given reasons are
394 // self-inconsistent, CHECKs.
395 static CookieInclusionStatus MakeFromReasonsForTesting(
396 const std::vector<ExclusionReason>& exclusions,
397 const std::vector<WarningReason>& warnings = std::vector<WarningReason>(),
398 ExemptionReason exemption = ExemptionReason::kNone);
399
400 // Returns true if the cookie was excluded because of user preferences or
401 // 3PCD.
402 bool ExcludedByUserPreferencesOrTPCD() const;
403
ResetForTesting()404 void ResetForTesting() {
405 exclusion_reasons_.reset();
406 warning_reasons_.reset();
407 exemption_reason_ = ExemptionReason::kNone;
408 }
409
410 private:
411 // Returns the `exclusion_reasons_` with the given `reasons` unset.
412 ExclusionReasonBitset ExclusionReasonsWithout(
413 const std::vector<ExclusionReason>& reasons) const;
414
415 // If the cookie would have been excluded by reasons that are not
416 // Third-party cookie phaseout related, clear the Third-party cookie phaseout
417 // warning/exclusion reason in this case.
418 void MaybeClearThirdPartyPhaseoutReason();
419
420 // A bit vector of the applicable exclusion reasons.
421 ExclusionReasonBitset exclusion_reasons_;
422
423 // A bit vector of the applicable warning reasons.
424 WarningReasonBitset warning_reasons_;
425
426 // A cookie can only have at most one exemption reason.
427 ExemptionReason exemption_reason_ = ExemptionReason::kNone;
428 };
429
430 NET_EXPORT inline std::ostream& operator<<(std::ostream& os,
431 const CookieInclusionStatus status) {
432 return os << status.GetDebugString();
433 }
434
435 // Provided to allow gtest to create more helpful error messages, instead of
436 // printing hex.
PrintTo(const CookieInclusionStatus & cis,std::ostream * os)437 inline void PrintTo(const CookieInclusionStatus& cis, std::ostream* os) {
438 *os << cis;
439 }
440
441 } // namespace net
442
443 #endif // NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
444