// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef NET_COOKIES_COOKIE_INCLUSION_STATUS_H_ #define NET_COOKIES_COOKIE_INCLUSION_STATUS_H_ #include #include #include #include #include #include #include "net/base/net_export.h" class GURL; namespace net { // This class represents if a cookie was included or excluded in a cookie get or // set operation, and if excluded why. It holds a vector of reasons for // exclusion, where cookie inclusion is represented by the absence of any // exclusion reasons. Also marks whether a cookie should be warned about, e.g. // for deprecation or intervention reasons. // TODO(crbug.com/1310444): Improve serialization validation comments. class NET_EXPORT CookieInclusionStatus { public: // Types of reasons why a cookie might be excluded. enum ExclusionReason { EXCLUDE_UNKNOWN_ERROR = 0, // Statuses applied when accessing a cookie (either sending or setting): // Cookie was HttpOnly, but the attempted access was through a non-HTTP API. EXCLUDE_HTTP_ONLY = 1, // Cookie was Secure, but the URL was not allowed to access Secure cookies. EXCLUDE_SECURE_ONLY = 2, // The cookie's domain attribute did not match the domain of the URL // attempting access. EXCLUDE_DOMAIN_MISMATCH = 3, // The cookie's path attribute did not match the path of the URL attempting // access. EXCLUDE_NOT_ON_PATH = 4, // The cookie had SameSite=Strict, and the attempted access did not have an // appropriate SameSiteCookieContext. EXCLUDE_SAMESITE_STRICT = 5, // The cookie had SameSite=Lax, and the attempted access did not have an // appropriate SameSiteCookieContext. EXCLUDE_SAMESITE_LAX = 6, // The cookie did not specify a SameSite attribute, and therefore was // treated as if it were SameSite=Lax, and the attempted access did not have // an appropriate SameSiteCookieContext. EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX = 7, // The cookie specified SameSite=None, but it was not Secure. EXCLUDE_SAMESITE_NONE_INSECURE = 8, // Caller did not allow access to the cookie. EXCLUDE_USER_PREFERENCES = 9, // Statuses only applied when creating/setting cookies: // Cookie was malformed and could not be stored, due to problem(s) while // parsing. // TODO(crbug.com/1228815): Use more specific reasons for parsing errors. EXCLUDE_FAILURE_TO_STORE = 10, // Attempted to set a cookie from a scheme that does not support cookies. EXCLUDE_NONCOOKIEABLE_SCHEME = 11, // Cookie would have overwritten a Secure cookie, and was not allowed to do // so. (See "Leave Secure Cookies Alone": // https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone-05 ) EXCLUDE_OVERWRITE_SECURE = 12, // Cookie would have overwritten an HttpOnly cookie, and was not allowed to // do so. EXCLUDE_OVERWRITE_HTTP_ONLY = 13, // Cookie was set with an invalid Domain attribute. EXCLUDE_INVALID_DOMAIN = 14, // Cookie was set with an invalid __Host- or __Secure- prefix. EXCLUDE_INVALID_PREFIX = 15, /// Cookie was set with an invalid Partitioned attribute, which is only // valid if the cookie has a __Host- prefix. EXCLUDE_INVALID_PARTITIONED = 16, // Cookie exceeded the name/value pair size limit. EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE = 17, // Cookie exceeded the attribute size limit. Note that this exclusion value // won't be used by code that parses cookie lines since RFC6265bis // indicates that large attributes should be ignored instead of causing the // whole cookie to be rejected. There will be a corresponding WarningReason // to notify users that an attribute value was ignored in that case. EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 18, // Cookie was set with a Domain attribute containing non ASCII characters. EXCLUDE_DOMAIN_NON_ASCII = 19, // Special case for when a cookie is blocked by third-party cookie blocking // but the two sites are in the same First-Party Set. EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET = 20, // Cookie's source_port did not match the port of the request. EXCLUDE_PORT_MISMATCH = 21, // Cookie's source_scheme did not match the scheme of the request. EXCLUDE_SCHEME_MISMATCH = 22, // Cookie is a domain cookie and has the same name as an origin cookie on // this origin. EXCLUDE_SHADOWING_DOMAIN = 23, // Cookie contains ASCII control characters (including the tab character, // when it appears in the middle of the cookie name, value, an attribute // name, or an attribute value). EXCLUDE_DISALLOWED_CHARACTER = 24, // Cookie is blocked for third-party cookie phaseout. EXCLUDE_THIRD_PARTY_PHASEOUT = 25, // Cookie contains no content or only whitespace. EXCLUDE_NO_COOKIE_CONTENT = 26, // This should be kept last. NUM_EXCLUSION_REASONS }; // Mojom and some tests assume that all the exclusion reasons will fit within // a uint32_t. Once that's not longer true those assumptions need to be // updated (along with this assert). static_assert(ExclusionReason::NUM_EXCLUSION_REASONS <= 32, "Expanding ExclusionReasons past 32 reasons requires updating " "usage assumptions."); // Reason to warn about a cookie. Any information contained in // WarningReason of an included cookie may be passed to an untrusted // renderer. enum WarningReason { // Of the following 3 SameSite warnings, there will be, at most, a single // active one. // Warn if a cookie with unspecified SameSite attribute is used in a // cross-site context. WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT = 0, // Warn if a cookie with SameSite=None is not Secure. WARN_SAMESITE_NONE_INSECURE = 1, // Warn if a cookie with unspecified SameSite attribute is defaulted into // Lax and is sent on a request with unsafe method, only because it is new // enough to activate the Lax-allow-unsafe intervention. WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE = 2, // The following warnings indicate that an included cookie with an effective // SameSite is experiencing a SameSiteCookieContext::|context| -> // SameSiteCookieContext::|schemeful_context| downgrade that will prevent // its access schemefully. // This situation means that a cookie is accessible when the // SchemefulSameSite feature is disabled but not when it's enabled, // indicating changed behavior and potential breakage. // // For example, a Strict to Lax downgrade for an effective SameSite=Strict // cookie: // This cookie would be accessible in the Strict context as its SameSite // value is Strict. However its context for schemeful same-site becomes Lax. // A strict cookie cannot be accessed in a Lax context and therefore the // behavior has changed. // As a counterexample, a Strict to Lax downgrade for an effective // SameSite=Lax cookie: A Lax cookie can be accessed in both Strict and Lax // contexts so there is no behavior change (and we don't warn about it). // // The warnings are in the following format: // WARN_{context}_{schemeful_context}_DOWNGRADE_{samesite_value}_SAMESITE // // Of the following 5 SameSite warnings, there will be, at most, a single // active one. // Strict to Lax downgrade for an effective SameSite=Strict cookie. // This warning is only applicable for cookies being sent because a Strict // cookie will be set in both Strict and Lax Contexts so the downgrade will // not affect it. WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE = 3, // Strict to Cross-site downgrade for an effective SameSite=Strict cookie. // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe // behaving like Cross-site. WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE = 4, // Strict to Cross-site downgrade for an effective SameSite=Lax cookie. // This also applies to Strict to Lax Unsafe downgrades due to Lax Unsafe // behaving like Cross-site. WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE = 5, // Lax to Cross-site downgrade for an effective SameSite=Strict cookie. // This warning is only applicable for cookies being set because a Strict // cookie will not be sent in a Lax context so the downgrade would not // affect it. WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE = 6, // Lax to Cross-site downgrade for an effective SameSite=Lax cookie. WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE = 7, // Advisory warning attached when a Secure cookie is accessed from (sent to, // or set by) a non-cryptographic URL. This can happen if the URL is // potentially trustworthy (e.g. a localhost URL, or another URL that // the CookieAccessDelegate is configured to allow). This also applies to // cookies with secure source schemes when scheme binding is enabled. // TODO(chlily): Add metrics for how often and where this occurs. WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC = 8, // The cookie would have been included prior to the spec change considering // redirects in the SameSite context calculation // (https://github.com/httpwg/http-extensions/pull/1348) // but would have been excluded after the spec change, due to a cross-site // redirect causing the SameSite context calculation to be downgraded. // This is applied if and only if the cookie's inclusion was changed by // considering redirect chains (and is applied regardless of which context // was actually used for the inclusion decision). This is not applied if // the context was downgraded but the cookie would have been // included/excluded in both cases. WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION = 9, // The cookie exceeded the attribute size limit. RFC6265bis indicates that // large attributes should be ignored instead of causing the whole cookie // to be rejected. This is applied by the code that parses cookie lines and // notifies the user that an attribute value was ignored. WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 10, // The cookie was set with a Domain attribute containing non ASCII // characters. WARN_DOMAIN_NON_ASCII = 11, // The cookie's source_port did not match the port of the request. WARN_PORT_MISMATCH = 12, // The cookie's source_scheme did not match the scheme of the request. WARN_SCHEME_MISMATCH = 13, // The cookie's creation url is non-cryptographic but it specified the // "Secure" attribute. A trustworthy url may be setting this cookie, but we // can't confirm/deny that at the time of creation. WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME = 14, // Cookie is a domain cookie and has the same name as an origin cookie on // this origin. This cookie would be blocked if shadowing protection was // enabled. WARN_SHADOWING_DOMAIN = 15, // This cookie will be blocked for third-party cookie phaseout. WARN_THIRD_PARTY_PHASEOUT = 16, // This should be kept last. NUM_WARNING_REASONS }; // Mojom and some tests assume that all the warning reasons will fit within // a uint32_t. Once that's not longer true those assumptions need to be // updated (along with this assert). static_assert(WarningReason::NUM_WARNING_REASONS <= 32, "Expanding WarningReasons past 32 reasons requires updating " "usage assumptions."); // These enums encode the context downgrade warnings + the secureness of the // url sending/setting the cookie. They're used for metrics only. The format // is k{context}{schemeful_context}{samesite_value}{securness}. // kNoDowngrade{securness} indicates that a cookie didn't have a breaking // context downgrade and was A) included B) excluded only due to insufficient // same-site context. I.e. the cookie wasn't excluded due to other reasons // such as third-party cookie blocking. Keep this in line with // SameSiteCookieContextBreakingDowngradeWithSecureness in enums.xml. enum class ContextDowngradeMetricValues { kNoDowngradeInsecure = 0, kNoDowngradeSecure = 1, kStrictLaxStrictInsecure = 2, kStrictCrossStrictInsecure = 3, kStrictCrossLaxInsecure = 4, kLaxCrossStrictInsecure = 5, kLaxCrossLaxInsecure = 6, kStrictLaxStrictSecure = 7, kStrictCrossStrictSecure = 8, kStrictCrossLaxSecure = 9, kLaxCrossStrictSecure = 10, kLaxCrossLaxSecure = 11, // Keep last. kMaxValue = kLaxCrossLaxSecure }; // Types of reasons why a cookie should-have-been-blocked by 3pcd got // exempted and included. enum class ExemptionReason { // The default exemption reason. The cookie with this reason could either be // included, or blocked due to 3pcd-unrelated reasons. kNone = 0, // For user explicit settings, including User bypass. kUserSetting = 1, // For 3PCD metadata . k3PCDMetadata = 2, // For 3PCD 1P and 3P deprecation trial. k3PCDDeprecationTrial = 3, // For 3PCD heuristics. k3PCDHeuristics = 4, // For Enterprise Policy : CookieAllowedForUrls and BlockThirdPartyCookies. kEnterprisePolicy = 5, kStorageAccess = 6, kTopLevelStorageAccess = 7, // For CorsException in the ABA contexts, which the inner iframe is // same-site with the top-level site but has cross-site ancestor(s). kCorsOptIn = 8, // Keep last. kMaxValue = kCorsOptIn }; using ExclusionReasonBitset = std::bitset; using WarningReasonBitset = std::bitset; // Makes a status that says include and should not warn. CookieInclusionStatus(); // Make a status that contains the given exclusion reason. explicit CookieInclusionStatus(ExclusionReason reason); // Makes a status that contains the given exclusion reason and warning. // TODO(shuuran): only called in tests, use `MakeFromReasonsForTesting` // instead. CookieInclusionStatus(ExclusionReason reason, WarningReason warning); // Makes a status that contains the given warning. // TODO(shuuran): only called in tests, use `MakeFromReasonsForTesting` // instead. explicit CookieInclusionStatus(WarningReason warning); // Copyable. CookieInclusionStatus(const CookieInclusionStatus& other); CookieInclusionStatus& operator=(const CookieInclusionStatus& other); bool operator==(const CookieInclusionStatus& other) const; bool operator!=(const CookieInclusionStatus& other) const; bool operator<(const CookieInclusionStatus& other) const; // Whether the status is to include the cookie, and has no other reasons for // exclusion. bool IsInclude() const; // Whether the given reason for exclusion is present. bool HasExclusionReason(ExclusionReason status_type) const; // Whether the given reason for exclusion is present, and is the ONLY reason // for exclusion. bool HasOnlyExclusionReason(ExclusionReason status_type) const; // Add an exclusion reason. void AddExclusionReason(ExclusionReason status_type); // Remove an exclusion reason. void RemoveExclusionReason(ExclusionReason reason); // Remove multiple exclusion reasons. void RemoveExclusionReasons(const std::vector& reasons); // Only updates exemption reason if the cookie was not already excluded and // doesn't already have an exemption reason. void MaybeSetExemptionReason(ExemptionReason reason); ExemptionReason exemption_reason() const { return exemption_reason_; } // If the cookie would have been excluded for reasons other than // SameSite-related reasons, don't bother warning about it (clear the // warning). void MaybeClearSameSiteWarning(); // Whether to record the breaking downgrade metrics if the cookie is included // or if it's only excluded because of insufficient same-site context. bool ShouldRecordDowngradeMetrics() const; // Whether the cookie should be warned about. bool ShouldWarn() const; // Whether the given reason for warning is present. bool HasWarningReason(WarningReason reason) const; // Whether a schemeful downgrade warning is present. // A schemeful downgrade means that an included cookie with an effective // SameSite is experiencing a SameSiteCookieContext::|context| -> // SameSiteCookieContext::|schemeful_context| downgrade that will prevent its // access schemefully. If the function returns true and |reason| is valid then // |reason| will contain which warning was found. bool HasSchemefulDowngradeWarning( CookieInclusionStatus::WarningReason* reason = nullptr) const; // Add an warning reason. void AddWarningReason(WarningReason reason); // Remove an warning reason. void RemoveWarningReason(WarningReason reason); // Used for serialization/deserialization. ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; } void set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons) { exclusion_reasons_ = exclusion_reasons; } WarningReasonBitset warning_reasons() const { return warning_reasons_; } void set_warning_reasons(WarningReasonBitset warning_reasons) { warning_reasons_ = warning_reasons; } ContextDowngradeMetricValues GetBreakingDowngradeMetricsEnumValue( const GURL& url) const; // Get exclusion reason(s) and warning in string format. std::string GetDebugString() const; // Checks whether the exclusion reasons are exactly the set of exclusion // reasons in the vector. (Ignores warnings.) bool HasExactlyExclusionReasonsForTesting( std::vector reasons) const; // Checks whether the warning reasons are exactly the set of warning // reasons in the vector. (Ignores exclusions.) bool HasExactlyWarningReasonsForTesting( std::vector reasons) const; // Validates mojo data, since mojo does not support bitsets. ExemptionReason // is omitted intendedly. // TODO(crbug.com/1310444): Improve serialization validation comments // and check for mutually exclusive values. static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons, uint32_t warning_reasons); // Makes a status that contains the given reasons. If 'use_literal' is true, // this method permits status to have reason combinations that cannot occur // under normal circumstances; otherwise it can cause a CHECK failure. static CookieInclusionStatus MakeFromReasonsForTesting( std::vector exclusions, std::vector warnings = std::vector(), ExemptionReason exemption = ExemptionReason::kNone, bool use_literal = false); // Returns true if the cookie was excluded because of user preferences or // 3PCD. bool ExcludedByUserPreferencesOrTPCD() const; void ResetForTesting() { exclusion_reasons_.reset(); warning_reasons_.reset(); exemption_reason_ = ExemptionReason::kNone; } private: // Makes a status that contains the exact given exclusion reason and warning // and exemption. CookieInclusionStatus(std::vector exclusions, std::vector warnings, ExemptionReason exemption); // Returns the `exclusion_reasons_` with the given `reasons` unset. ExclusionReasonBitset ExclusionReasonsWithout( const std::vector& reasons) const; // If the cookie would have been excluded by reasons that are not // Third-party cookie phaseout related, clear the Third-party cookie phaseout // warning/exclusion reason in this case. void MaybeClearThirdPartyPhaseoutReason(); // A bit vector of the applicable exclusion reasons. ExclusionReasonBitset exclusion_reasons_; // A bit vector of the applicable warning reasons. WarningReasonBitset warning_reasons_; // A cookie can only have at most one exemption reason. ExemptionReason exemption_reason_ = ExemptionReason::kNone; }; NET_EXPORT inline std::ostream& operator<<(std::ostream& os, const CookieInclusionStatus status) { return os << status.GetDebugString(); } // Provided to allow gtest to create more helpful error messages, instead of // printing hex. inline void PrintTo(const CookieInclusionStatus& cis, std::ostream* os) { *os << cis; } } // namespace net #endif // NET_COOKIES_COOKIE_INCLUSION_STATUS_H_