diff options
Diffstat (limited to 'chromium/net')
-rw-r--r-- | chromium/net/base/features.cc | 9 | ||||
-rw-r--r-- | chromium/net/base/features.h | 9 | ||||
-rw-r--r-- | chromium/net/cookies/canonical_cookie.cc | 48 | ||||
-rw-r--r-- | chromium/net/cookies/canonical_cookie.h | 5 | ||||
-rw-r--r-- | chromium/net/cookies/canonical_cookie_unittest.cc | 125 | ||||
-rw-r--r-- | chromium/net/http/transport_security_state_static.json.gz | bin | 1353766 -> 1353766 bytes | |||
-rw-r--r-- | chromium/net/spdy/alps_decoder.cc | 57 | ||||
-rw-r--r-- | chromium/net/spdy/alps_decoder_test.cc | 86 |
8 files changed, 320 insertions, 19 deletions
diff --git a/chromium/net/base/features.cc b/chromium/net/base/features.cc index 20b3b04f50d..e2a91634275 100644 --- a/chromium/net/base/features.cc +++ b/chromium/net/base/features.cc @@ -293,5 +293,14 @@ const base::Feature kClampCookieExpiryTo400Days( "ClampCookieExpiryTo400Days", base::FEATURE_DISABLED_BY_DEFAULT); +const base::Feature kAlpsParsing{"AlpsParsing", + base::FEATURE_ENABLED_BY_DEFAULT}; + +const base::Feature kAlpsClientHintParsing{"AlpsClientHintParsing", + base::FEATURE_ENABLED_BY_DEFAULT}; + +const base::Feature kShouldKillSessionOnAcceptChMalformed{ + "ShouldKillSessionOnAcceptChMalformed", base::FEATURE_DISABLED_BY_DEFAULT}; + } // namespace features } // namespace net diff --git a/chromium/net/base/features.h b/chromium/net/base/features.h index ca9fe8f6922..469ffec6760 100644 --- a/chromium/net/base/features.h +++ b/chromium/net/base/features.h @@ -430,6 +430,15 @@ NET_EXPORT extern const base::Feature kSandboxHttpCache; // future. NET_EXPORT extern const base::Feature kClampCookieExpiryTo400Days; +// Whether ALPS parsing is on for any type of frame. +NET_EXPORT extern const base::Feature kAlpsParsing; + +// Whether ALPS parsing is on for client hint parsing specifically. +NET_EXPORT extern const base::Feature kAlpsClientHintParsing; + +// Whether to kill the session on Error::kAcceptChMalformed. +NET_EXPORT extern const base::Feature kShouldKillSessionOnAcceptChMalformed; + } // namespace features } // namespace net diff --git a/chromium/net/cookies/canonical_cookie.cc b/chromium/net/cookies/canonical_cookie.cc index 053f760ed76..d0120b13c5c 100644 --- a/chromium/net/cookies/canonical_cookie.cc +++ b/chromium/net/cookies/canonical_cookie.cc @@ -56,6 +56,7 @@ #include "base/metrics/histogram_macros.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "net/base/features.h" @@ -558,6 +559,11 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::Create( CookiePrefix prefix = GetCookiePrefix(parsed_cookie.Name()); bool is_cookie_prefix_valid = IsCookiePrefixValid(prefix, url, parsed_cookie); RecordCookiePrefixMetrics(prefix, is_cookie_prefix_valid); + + if (parsed_cookie.Name() == "") { + is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value()); + } + if (!is_cookie_prefix_valid) { DVLOG(net::cookie_util::kVlogSetCookies) << "Create() failed because the cookie violated prefix rules."; @@ -768,6 +774,11 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie( net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); } + if (name == "" && HasHiddenPrefixName(value)) { + status->AddExclusionReason( + net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); + } + if (!IsCookieSamePartyValid(same_party, secure, same_site)) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY); @@ -1472,6 +1483,9 @@ bool CanonicalCookie::IsCanonicalForFromStorage() const { return false; } + if (name_ == "" && HasHiddenPrefixName(value_)) + return false; + if (!IsCookieSamePartyValid(same_party_, secure_, same_site_)) return false; @@ -1594,6 +1608,40 @@ CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSite( } } +// static +bool CanonicalCookie::HasHiddenPrefixName( + const base::StringPiece cookie_value) { + // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9). + base::StringPiece value_without_BWS = + base::TrimString(cookie_value, " \t", base::TRIM_LEADING); + + const base::StringPiece host_prefix = "__Host-"; + + // Compare the value to the host_prefix. + if (base::StartsWith(value_without_BWS, host_prefix)) { + // The prefix matches, now check if the value string contains a subsequent + // '='. + if (value_without_BWS.find_first_of('=', host_prefix.size()) != + base::StringPiece::npos) { + // This value contains a hidden prefix name. + return true; + } + return false; + } + + // Do a similar check for the secure prefix + const base::StringPiece secure_prefix = "__Secure-"; + + if (base::StartsWith(value_without_BWS, secure_prefix)) { + if (value_without_BWS.find_first_of('=', secure_prefix.size()) != + base::StringPiece::npos) { + return true; + } + } + + return false; +} + bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const { return (base::Time::Now() - creation_date_) <= age_threshold; } diff --git a/chromium/net/cookies/canonical_cookie.h b/chromium/net/cookies/canonical_cookie.h index 19acf0a8821..0283553acf8 100644 --- a/chromium/net/cookies/canonical_cookie.h +++ b/chromium/net/cookies/canonical_cookie.h @@ -11,6 +11,7 @@ #include <vector> #include "base/gtest_prod_util.h" +#include "base/strings/string_piece.h" #include "base/time/time.h" #include "net/base/net_export.h" #include "net/cookies/cookie_access_result.h" @@ -421,6 +422,7 @@ class NET_EXPORT CanonicalCookie { private: FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms); + FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestHasHiddenPrefixName); // This constructor does not validate or canonicalize their inputs; // the resulting CanonicalCookies should not be relied on to be canonical @@ -484,6 +486,9 @@ class NET_EXPORT CanonicalCookie { CookieEffectiveSameSite GetEffectiveSameSite( CookieAccessSemantics access_semantics) const; + // Checks for values that could be misinterpreted as a cookie name prefix. + static bool HasHiddenPrefixName(const base::StringPiece cookie_value); + // Returns whether the cookie was created at most |age_threshold| ago. bool IsRecentlyCreated(base::TimeDelta age_threshold) const; diff --git a/chromium/net/cookies/canonical_cookie_unittest.cc b/chromium/net/cookies/canonical_cookie_unittest.cc index 7d8a0151548..da01a7888dc 100644 --- a/chromium/net/cookies/canonical_cookie_unittest.cc +++ b/chromium/net/cookies/canonical_cookie_unittest.cc @@ -2348,6 +2348,18 @@ TEST(CanonicalCookieTest, SecureCookiePrefix) { absl::nullopt /* cookie_partition_key */, &status)); EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX})); + + // Hidden __Secure- prefixes should be rejected. + EXPECT_FALSE(CanonicalCookie::Create( + https_url, "=__Secure-A=B; Secure", creation_time, server_time, + absl::nullopt /* cookie_partition_key */, &status)); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX})); + + // While tricky, this isn't considered hidden and is fine. + EXPECT_TRUE(CanonicalCookie::Create( + https_url, "A=__Secure-A=B; Secure", creation_time, server_time, + absl::nullopt /* cookie_partition_key */)); } TEST(CanonicalCookieTest, HostCookiePrefix) { @@ -2432,6 +2444,18 @@ TEST(CanonicalCookieTest, HostCookiePrefix) { EXPECT_TRUE(CanonicalCookie::Create( https_url, "__HostA=B; Domain=" + domain + "; Secure;", creation_time, server_time, absl::nullopt /* cookie_partition_key */)); + + // Hidden __Host- prefixes should be rejected. + EXPECT_FALSE(CanonicalCookie::Create( + https_url, "=__Host-A=B; Path=/; Secure;", creation_time, server_time, + absl::nullopt /* cookie_partition_key */, &status)); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX})); + + // While tricky, this isn't considered hidden and is fine. + EXPECT_TRUE(CanonicalCookie::Create( + https_url, "A=__Host-A=B; Path=/; Secure;", creation_time, server_time, + absl::nullopt /* cookie_partition_key */)); } TEST(CanonicalCookieTest, CanCreateSecureCookiesFromAnyScheme) { @@ -2874,6 +2898,31 @@ TEST(CanonicalCookieTest, IsCanonical) { CookiePartitionKey::FromURLForTesting( GURL("https://toplevelsite.com"))) ->IsCanonical()); + + // Hidden cookie prefixes. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "", "__Secure-a=b", "x.y", "/", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "", "__Host-a=b", "x.y", "/", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( + "a", "__Secure-a=b", "x.y", "/", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( + "a", "__Host-a=b", "x.y", "/", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); } TEST(CanonicalCookieTest, TestSetCreationDate) { @@ -3525,6 +3574,39 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { false /*same_party*/, absl::nullopt /*partition_key*/, &status)); EXPECT_TRUE(status.IsInclude()); + // Cookies with hidden prefixes should be rejected. + + EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie( + GURL("https://www.foo.com"), "", "__Host-A=B", "", "/", two_hours_ago, + one_hour_from_now, one_hour_ago, true, false, + CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX})); + + EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie( + GURL("https://www.foo.com"), "", "__Secure-A=B", "", "/", two_hours_ago, + one_hour_from_now, one_hour_ago, true, false, + CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX})); + + // While tricky, this aren't considered hidden prefixes and should succeed. + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("https://www.foo.com"), "A", "__Host-A=B", "", "/", two_hours_ago, + one_hour_from_now, one_hour_ago, true, false, + CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("https://www.foo.com"), "A", "__Secure-A=B", "", "/", two_hours_ago, + one_hour_from_now, one_hour_ago, true, false, + CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + // SameParty attribute requires Secure and forbids SameSite=Strict. EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( GURL("https://www.foo.com"), "A", "B", ".www.foo.com", "/", two_hours_ago, @@ -5114,4 +5196,47 @@ TEST(CanonicalCookieTest, TestIsCanonicalWithInvalidSizeHistograms) { histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kValid, 1); } +TEST(CanonicalCookieTest, TestHasHiddenPrefixName) { + const struct { + const char* value; + bool result; + } kTestCases[] = { + {"", false}, + {" ", false}, + {"foobar=", false}, + {"foo=bar", false}, + {" \t ", false}, + {"\t", false}, + {"__Secure-abc", false}, + {"__Secur=e-abc", false}, + {"__Secureabc", false}, + {"__Host-abc", false}, + {"__Hos=t-abc", false}, + {"_Host", false}, + {" __Secure-abc", false}, + {"\t__Host-", false}, + {"a__Host-abc=123", false}, + {"a__Secure-abc=123", false}, + {"__Host-abc=", true}, + {"__Host-abc=123", true}, + {" __Host-abc=123", true}, + {" __Host-abc=", true}, + {"\t\t\t\t\t__Host-abc=123", true}, + {"\t __Host-abc=", true}, + {"__Secure-abc=", true}, + {"__Secure-abc=123", true}, + {" __Secure-abc=123", true}, + {" __Secure-abc=", true}, + {"\t\t\t\t\t__Secure-abc=123", true}, + {"\t __Secure-abc=", true}, + {"__Secure-abc=123=d=4=fg=", true}, + }; + + for (auto test_case : kTestCases) { + EXPECT_EQ(CanonicalCookie::HasHiddenPrefixName(test_case.value), + test_case.result) + << test_case.value << " failed check"; + } +} + } // namespace net diff --git a/chromium/net/http/transport_security_state_static.json.gz b/chromium/net/http/transport_security_state_static.json.gz Binary files differindex df8112dc1ce..4aadbc25d28 100644 --- a/chromium/net/http/transport_security_state_static.json.gz +++ b/chromium/net/http/transport_security_state_static.json.gz diff --git a/chromium/net/spdy/alps_decoder.cc b/chromium/net/spdy/alps_decoder.cc index 69c8b710387..fb43d876a23 100644 --- a/chromium/net/spdy/alps_decoder.cc +++ b/chromium/net/spdy/alps_decoder.cc @@ -4,6 +4,10 @@ #include "net/spdy/alps_decoder.h" +#include "base/feature_list.h" +#include "base/metrics/histogram_functions.h" +#include "net/base/features.h" + namespace net { namespace { @@ -101,23 +105,34 @@ bool AlpsDecoder::AcceptChParser::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, uint8_t flags) { - if (type != static_cast<uint8_t>(spdy::SpdyFrameType::ACCEPT_CH) || - error_ != Error::kNoError) { - // Ignore every frame except for ACCEPT_CH. - // Ignore data after an error has occurred. - // Returning false causes Http2DecoderAdapter not to call OnFramePayload(). - return false; - } - if (stream_id != 0) { - error_ = Error::kAcceptChInvalidStream; + // Ignore data after an error has occurred. + if (error_ != Error::kNoError) return false; - } - if (flags != 0) { - error_ = Error::kAcceptChWithFlags; + // Stop all alps parsing if it's disabled. + if (!base::FeatureList::IsEnabled(features::kAlpsParsing)) return false; + // Handle per-type parsing. + switch (type) { + case static_cast<uint8_t>(spdy::SpdyFrameType::ACCEPT_CH): { + // Stop alps client hint parsing if it's disabled. + if (!base::FeatureList::IsEnabled(features::kAlpsClientHintParsing)) + return false; + // Check for issues with the frame. + if (stream_id != 0) { + error_ = Error::kAcceptChInvalidStream; + return false; + } + if (flags != 0) { + error_ = Error::kAcceptChWithFlags; + return false; + } + // This frame can be parsed in OnFramePayload. + return true; + } + default: + // Ignore all other types. + return false; } - - return true; } void AlpsDecoder::AcceptChParser::OnFramePayload(const char* data, size_t len) { @@ -130,8 +145,18 @@ void AlpsDecoder::AcceptChParser::OnFramePayload(const char* data, size_t len) { base::StringPiece value; if (!ReadUint16PrefixedStringPiece(&payload, &origin) || !ReadUint16PrefixedStringPiece(&payload, &value)) { - error_ = Error::kAcceptChMalformed; - return; + if (base::FeatureList::IsEnabled( + features::kShouldKillSessionOnAcceptChMalformed)) { + // This causes a session termination. + error_ = Error::kAcceptChMalformed; + return; + } else { + // This logs that a session termination was bypassed. + base::UmaHistogramEnumeration( + "Net.SpdySession.AlpsDecoderStatus.Bypassed", + Error::kAcceptChMalformed); + return; + } } accept_ch_.push_back( spdy::AcceptChOriginValuePair{std::string(origin), std::string(value)}); diff --git a/chromium/net/spdy/alps_decoder_test.cc b/chromium/net/spdy/alps_decoder_test.cc index 80c98c3a424..5f155e5c97d 100644 --- a/chromium/net/spdy/alps_decoder_test.cc +++ b/chromium/net/spdy/alps_decoder_test.cc @@ -4,6 +4,9 @@ #include "net/spdy/alps_decoder.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "net/base/features.h" #include "net/base/hex_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -123,6 +126,58 @@ TEST(AlpsDecoderTest, ParseLargeAcceptChFrame) { accept_ch_tokens})); } +TEST(AlpsDecoderTest, DisableAlpsParsing) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature(features::kAlpsParsing); + AlpsDecoder decoder; + AlpsDecoder::Error error = decoder.Decode(HexDecode( + // ACCEPT_CH frame + "00003d" // length + "89" // type ACCEPT_CH + "00" // flags + "00000000" // stream ID + "0017" // origin length + "68747470733a2f2f7777772e" // + "6578616d706c652e636f6d" // origin "https://www.example.com" + "0003" // value length + "666f6f" // value "foo" + "0018" // origin length + "68747470733a2f2f6d61696c" // + "2e6578616d706c652e636f6d" // origin "https://mail.example.com" + "0003" // value length + "626172" // value "bar" + )); + + EXPECT_EQ(AlpsDecoder::Error::kNoError, error); + EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty()); +} + +TEST(AlpsDecoderTest, DisableAlpsClientHintParsing) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature(features::kAlpsClientHintParsing); + AlpsDecoder decoder; + AlpsDecoder::Error error = decoder.Decode(HexDecode( + // ACCEPT_CH frame + "00003d" // length + "89" // type ACCEPT_CH + "00" // flags + "00000000" // stream ID + "0017" // origin length + "68747470733a2f2f7777772e" // + "6578616d706c652e636f6d" // origin "https://www.example.com" + "0003" // value length + "666f6f" // value "foo" + "0018" // origin length + "68747470733a2f2f6d61696c" // + "2e6578616d706c652e636f6d" // origin "https://mail.example.com" + "0003" // value length + "626172" // value "bar" + )); + + EXPECT_EQ(AlpsDecoder::Error::kNoError, error); + EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty()); +} + TEST(AlpsDecoderTest, IncompleteFrame) { AlpsDecoder decoder; AlpsDecoder::Error error = @@ -262,7 +317,23 @@ TEST(AlpsDecoderTest, UnknownFrame) { EXPECT_EQ(0, decoder.settings_frame_count()); } -TEST(AlpsDecoderTest, MalformedAcceptChFrame) { +class AlpsDecoderTestWithFeature : public ::testing::TestWithParam<bool> { + public: + bool ShouldKillSessionOnAcceptChMalformed() { return GetParam(); } + + private: + void SetUp() override { + feature_list_.InitWithFeatureState( + features::kShouldKillSessionOnAcceptChMalformed, + ShouldKillSessionOnAcceptChMalformed()); + } + + base::test::ScopedFeatureList feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(All, AlpsDecoderTestWithFeature, testing::Bool()); + +TEST_P(AlpsDecoderTestWithFeature, MalformedAcceptChFrame) { // Correct, complete payload. std::string payload = HexDecode( "0017" // origin length @@ -273,6 +344,7 @@ TEST(AlpsDecoderTest, MalformedAcceptChFrame) { for (uint8_t payload_length = 1; payload_length < payload.length(); payload_length++) { + base::HistogramTester histogram_tester; // First two bytes of length. std::string frame = HexDecode("0000"); // Last byte of length. @@ -287,8 +359,16 @@ TEST(AlpsDecoderTest, MalformedAcceptChFrame) { AlpsDecoder decoder; AlpsDecoder::Error error = decoder.Decode(frame); - - EXPECT_EQ(AlpsDecoder::Error::kAcceptChMalformed, error); + if (ShouldKillSessionOnAcceptChMalformed()) { + EXPECT_EQ(AlpsDecoder::Error::kAcceptChMalformed, error); + histogram_tester.ExpectTotalCount( + "Net.SpdySession.AlpsDecoderStatus.Bypassed", 0); + } else { + EXPECT_EQ(AlpsDecoder::Error::kNoError, error); + histogram_tester.ExpectUniqueSample( + "Net.SpdySession.AlpsDecoderStatus.Bypassed", + static_cast<int>(AlpsDecoder::Error::kAcceptChMalformed), 1); + } } } |