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