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 using ExclusionReasonBitset =
270 std::bitset<ExclusionReason::NUM_EXCLUSION_REASONS>;
271 using WarningReasonBitset = std::bitset<WarningReason::NUM_WARNING_REASONS>;
272
273 // Makes a status that says include and should not warn.
274 CookieInclusionStatus();
275
276 // Make a status that contains the given exclusion reason.
277 explicit CookieInclusionStatus(ExclusionReason reason);
278 // Makes a status that contains the given exclusion reason and warning.
279 CookieInclusionStatus(ExclusionReason reason, WarningReason warning);
280 // Makes a status that contains the given warning.
281 explicit CookieInclusionStatus(WarningReason warning);
282
283 // Copyable.
284 CookieInclusionStatus(const CookieInclusionStatus& other);
285 CookieInclusionStatus& operator=(const CookieInclusionStatus& other);
286
287 bool operator==(const CookieInclusionStatus& other) const;
288 bool operator!=(const CookieInclusionStatus& other) const;
289 bool operator<(const CookieInclusionStatus& other) const;
290
291 // Whether the status is to include the cookie, and has no other reasons for
292 // exclusion.
293 bool IsInclude() const;
294
295 // Whether the given reason for exclusion is present.
296 bool HasExclusionReason(ExclusionReason status_type) const;
297
298 // Whether the given reason for exclusion is present, and is the ONLY reason
299 // for exclusion.
300 bool HasOnlyExclusionReason(ExclusionReason status_type) const;
301
302 // Add an exclusion reason.
303 void AddExclusionReason(ExclusionReason status_type);
304
305 // Remove an exclusion reason.
306 void RemoveExclusionReason(ExclusionReason reason);
307
308 // Remove multiple exclusion reasons.
309 void RemoveExclusionReasons(const std::vector<ExclusionReason>& reasons);
310
311 // If the cookie would have been excluded for reasons other than
312 // SameSite-related reasons, don't bother warning about it (clear the
313 // warning).
314 void MaybeClearSameSiteWarning();
315
316 // Whether to record the breaking downgrade metrics if the cookie is included
317 // or if it's only excluded because of insufficient same-site context.
318 bool ShouldRecordDowngradeMetrics() const;
319
320 // Whether the cookie should be warned about.
321 bool ShouldWarn() const;
322
323 // Whether the given reason for warning is present.
324 bool HasWarningReason(WarningReason reason) const;
325
326 // Whether a schemeful downgrade warning is present.
327 // A schemeful downgrade means that an included cookie with an effective
328 // SameSite is experiencing a SameSiteCookieContext::|context| ->
329 // SameSiteCookieContext::|schemeful_context| downgrade that will prevent its
330 // access schemefully. If the function returns true and |reason| is valid then
331 // |reason| will contain which warning was found.
332 bool HasSchemefulDowngradeWarning(
333 CookieInclusionStatus::WarningReason* reason = nullptr) const;
334
335 // Add an warning reason.
336 void AddWarningReason(WarningReason reason);
337
338 // Remove an warning reason.
339 void RemoveWarningReason(WarningReason reason);
340
341 // Used for serialization/deserialization.
exclusion_reasons()342 ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; }
set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons)343 void set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons) {
344 exclusion_reasons_ = exclusion_reasons;
345 }
346
warning_reasons()347 WarningReasonBitset warning_reasons() const { return warning_reasons_; }
set_warning_reasons(WarningReasonBitset warning_reasons)348 void set_warning_reasons(WarningReasonBitset warning_reasons) {
349 warning_reasons_ = warning_reasons;
350 }
351
352 ContextDowngradeMetricValues GetBreakingDowngradeMetricsEnumValue(
353 const GURL& url) const;
354
355 // Get exclusion reason(s) and warning in string format.
356 std::string GetDebugString() const;
357
358 // Checks whether the exclusion reasons are exactly the set of exclusion
359 // reasons in the vector. (Ignores warnings.)
360 bool HasExactlyExclusionReasonsForTesting(
361 std::vector<ExclusionReason> reasons) const;
362
363 // Checks whether the warning reasons are exactly the set of warning
364 // reasons in the vector. (Ignores exclusions.)
365 bool HasExactlyWarningReasonsForTesting(
366 std::vector<WarningReason> reasons) const;
367
368 // Validates mojo data, since mojo does not support bitsets.
369 // TODO(crbug.com/1310444): Improve serialization validation comments
370 // and check for mutually exclusive values.
371 static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons,
372 uint32_t warning_reasons);
373
374 // Makes a status that contains the given exclusion reasons and warning.
375 static CookieInclusionStatus MakeFromReasonsForTesting(
376 std::vector<ExclusionReason> reasons,
377 std::vector<WarningReason> warnings = std::vector<WarningReason>());
378
379 // Returns true if the cookie was excluded because of user preferences.
380 // HasOnlyExclusionReason(EXCLUDE_USER_PREFERENCES) will not return true for
381 // third-party cookies blocked in sites in the same First-Party Set. See
382 // https://crbug.com/1366868.
383 bool ExcludedByUserPreferences() const;
384
ResetForTesting()385 void ResetForTesting() {
386 exclusion_reasons_.reset();
387 warning_reasons_.reset();
388 }
389
390 private:
391 // Returns the `exclusion_reasons_` with the given `reasons` unset.
392 ExclusionReasonBitset ExclusionReasonsWithout(
393 const std::vector<ExclusionReason>& reasons) const;
394
395 // If the cookie would have been excluded by reasons that are not
396 // Third-party cookie phaseout related, clear the Third-party cookie phaseout
397 // warning/exclusion reason in this case.
398 void MaybeClearThirdPartyPhaseoutReason();
399
400 // A bit vector of the applicable exclusion reasons.
401 ExclusionReasonBitset exclusion_reasons_;
402
403 // A bit vector of the applicable warning reasons.
404 WarningReasonBitset warning_reasons_;
405 };
406
407 NET_EXPORT inline std::ostream& operator<<(std::ostream& os,
408 const CookieInclusionStatus status) {
409 return os << status.GetDebugString();
410 }
411
412 // Provided to allow gtest to create more helpful error messages, instead of
413 // printing hex.
PrintTo(const CookieInclusionStatus & cis,std::ostream * os)414 inline void PrintTo(const CookieInclusionStatus& cis, std::ostream* os) {
415 *os << cis;
416 }
417
418 } // namespace net
419
420 #endif // NET_COOKIES_COOKIE_INCLUSION_STATUS_H_
421