Use deterministic replacement connection IDs
This CL removes a DoS attack vector where an attacker could grow QuicDispatcher::connection_id_map_ unboundedly. It does so by no longer using random connection IDs that are saved in connection_id_map_; instead we now generate deterministic replacement connection IDs, removing the need for a map. It should not impact the GFE because the GFE overrides QuicDispatcher::GenerateNewServerConnectionId with an already deterministic method, but is still flag protected just in case.
gfe-relnote: use deterministic replacement connection IDs, protected by new disabled flag gfe2_restart_flag_quic_deterministic_replacement_connection_ids
PiperOrigin-RevId: 264192278
Change-Id: I843bf0d846830d4b13e0bb1b470a71b2428ad7c8
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index b659bda..553fcd4 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -305,18 +305,29 @@
}
DCHECK(QuicUtils::VariableLengthConnectionIdAllowedForVersion(
version.transport_version));
- auto it = connection_id_map_.find(server_connection_id);
- if (it != connection_id_map_.end()) {
- return it->second;
+
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ auto it = connection_id_map_.find(server_connection_id);
+ if (it != connection_id_map_.end()) {
+ return it->second;
+ }
+ } else {
+ // TODO(dschinazi) Remove QuicDispatcher::connection_id_map_ entirely
+ // when quic_deterministic_replacement_connection_ids is deprecated.
+ QUIC_RESTART_FLAG_COUNT_N(quic_deterministic_replacement_connection_ids, 1,
+ 2);
}
QuicConnectionId new_connection_id =
GenerateNewServerConnectionId(version, server_connection_id);
DCHECK_EQ(expected_server_connection_id_length_, new_connection_id.length());
- // TODO(dschinazi) Prevent connection_id_map_ from growing indefinitely
- // before we ship a version that supports variable length connection IDs
- // to production.
- connection_id_map_.insert(
- std::make_pair(server_connection_id, new_connection_id));
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ connection_id_map_.insert(
+ std::make_pair(server_connection_id, new_connection_id));
+ } else {
+ // Verify that GenerateNewServerConnectionId is deterministic.
+ DCHECK_EQ(new_connection_id,
+ GenerateNewServerConnectionId(version, server_connection_id));
+ }
QUIC_DLOG(INFO) << "Replacing incoming connection ID " << server_connection_id
<< " with " << new_connection_id;
return new_connection_id;
@@ -324,8 +335,15 @@
QuicConnectionId QuicDispatcher::GenerateNewServerConnectionId(
ParsedQuicVersion /*version*/,
- QuicConnectionId /*connection_id*/) const {
- return QuicUtils::CreateRandomConnectionId();
+ QuicConnectionId connection_id) const {
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ return QuicUtils::CreateRandomConnectionId();
+ }
+
+ QUIC_RESTART_FLAG_COUNT_N(quic_deterministic_replacement_connection_ids, 2,
+ 2);
+
+ return QuicUtils::CreateReplacementConnectionId(connection_id);
}
bool QuicDispatcher::MaybeDispatchPacket(
diff --git a/quic/core/quic_dispatcher.h b/quic/core/quic_dispatcher.h
index f7b0450..566d3bc 100644
--- a/quic/core/quic_dispatcher.h
+++ b/quic/core/quic_dispatcher.h
@@ -153,6 +153,8 @@
virtual bool MaybeDispatchPacket(const ReceivedPacketInfo& packet_info);
// Generate a connection ID with a length that is expected by the dispatcher.
+ // Note that this MUST produce a deterministic result (calling this method
+ // with two connection IDs that are equal must produce the same result).
virtual QuicConnectionId GenerateNewServerConnectionId(
ParsedQuicVersion version,
QuicConnectionId connection_id) const;
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index b41c57f..b8cc470 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -134,8 +134,13 @@
QuicConnectionId GenerateNewServerConnectionId(
ParsedQuicVersion /*version*/,
- QuicConnectionId /*connection_id*/) const override {
- return QuicUtils::CreateRandomConnectionId(random_);
+ QuicConnectionId connection_id) const override {
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ return QuicUtils::CreateRandomConnectionId(random_);
+ }
+ // TODO(dschinazi) Remove this override entirely when
+ // quic_deterministic_replacement_connection_ids is deprecated.
+ return QuicUtils::CreateReplacementConnectionId(connection_id);
}
struct TestQuicPerPacketContext : public QuicPerPacketContext {
@@ -755,8 +760,14 @@
QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
QuicConnectionId bad_connection_id = TestConnectionIdNineBytesLong(2);
- QuicConnectionId fixed_connection_id =
- QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ QuicConnectionId fixed_connection_id;
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ fixed_connection_id =
+ QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ } else {
+ fixed_connection_id =
+ QuicUtils::CreateReplacementConnectionId(bad_connection_id);
+ }
EXPECT_CALL(*dispatcher_,
CreateQuicSession(fixed_connection_id, client_address,
@@ -788,8 +799,14 @@
QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
QuicConnectionId bad_connection_id = EmptyQuicConnectionId();
- QuicConnectionId fixed_connection_id =
- QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ QuicConnectionId fixed_connection_id;
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ fixed_connection_id =
+ QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ } else {
+ fixed_connection_id =
+ QuicUtils::CreateReplacementConnectionId(bad_connection_id);
+ }
// Disable validation of invalid short connection IDs.
dispatcher_->SetAllowShortInitialServerConnectionIds(true);
@@ -825,8 +842,14 @@
QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
QuicConnectionId bad_connection_id = TestConnectionIdNineBytesLong(2);
- QuicConnectionId fixed_connection_id =
- QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ QuicConnectionId fixed_connection_id;
+ if (!GetQuicRestartFlag(quic_deterministic_replacement_connection_ids)) {
+ fixed_connection_id =
+ QuicUtils::CreateRandomConnectionId(mock_helper_.GetRandomGenerator());
+ } else {
+ fixed_connection_id =
+ QuicUtils::CreateReplacementConnectionId(bad_connection_id);
+ }
EXPECT_CALL(*dispatcher_,
CreateQuicSession(TestConnectionId(1), client_address,
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 1e2c5fc..a3366da 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -485,6 +485,15 @@
}
// static
+QuicConnectionId QuicUtils::CreateReplacementConnectionId(
+ QuicConnectionId connection_id) {
+ const uint64_t connection_id_hash = FNV1a_64_Hash(
+ QuicStringPiece(connection_id.data(), connection_id.length()));
+ return QuicConnectionId(reinterpret_cast<const char*>(&connection_id_hash),
+ sizeof(connection_id_hash));
+}
+
+// static
QuicConnectionId QuicUtils::CreateRandomConnectionId() {
return CreateRandomConnectionId(kQuicDefaultConnectionIdLength,
QuicRandom::GetInstance());
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index 11154a5..de855a0 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -11,6 +11,7 @@
#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
#include "net/third_party/quiche/src/quic/core/frames/quic_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.h"
@@ -162,6 +163,12 @@
QuicTransportVersion version,
Perspective perspective);
+ // Generates a 64bit connection ID derived from the input connection ID.
+ // This is guaranteed to be deterministic (calling this method with two
+ // connection IDs that are equal is guaranteed to produce the same result).
+ static QuicConnectionId CreateReplacementConnectionId(
+ QuicConnectionId connection_id);
+
// Generates a random 64bit connection ID.
static QuicConnectionId CreateRandomConnectionId();
diff --git a/quic/core/quic_utils_test.cc b/quic/core/quic_utils_test.cc
index d0ce7e8..dc46133 100644
--- a/quic/core/quic_utils_test.cc
+++ b/quic/core/quic_utils_test.cc
@@ -165,6 +165,47 @@
EXPECT_FALSE(QuicUtils::IsIetfPacketShortHeader(first_byte));
}
+TEST_F(QuicUtilsTest, ReplacementConnectionIdIsDeterministic) {
+ // Verify that two equal connection IDs get the same replacement.
+ QuicConnectionId connection_id64a = TestConnectionId(33);
+ QuicConnectionId connection_id64b = TestConnectionId(33);
+ EXPECT_EQ(connection_id64a, connection_id64b);
+ EXPECT_EQ(QuicUtils::CreateReplacementConnectionId(connection_id64a),
+ QuicUtils::CreateReplacementConnectionId(connection_id64b));
+ QuicConnectionId connection_id72a = TestConnectionIdNineBytesLong(42);
+ QuicConnectionId connection_id72b = TestConnectionIdNineBytesLong(42);
+ EXPECT_EQ(connection_id72a, connection_id72b);
+ EXPECT_EQ(QuicUtils::CreateReplacementConnectionId(connection_id72a),
+ QuicUtils::CreateReplacementConnectionId(connection_id72b));
+}
+
+TEST_F(QuicUtilsTest, ReplacementConnectionIdLengthIsCorrect) {
+ // Verify that all lengths get replaced by kQuicDefaultConnectionIdLength.
+ const char connection_id_bytes[kQuicMaxConnectionIdAllVersionsLength] = {};
+ for (uint8_t i = 0; i < sizeof(connection_id_bytes) - 1; ++i) {
+ QuicConnectionId connection_id(connection_id_bytes, i);
+ QuicConnectionId replacement_connection_id =
+ QuicUtils::CreateReplacementConnectionId(connection_id);
+ EXPECT_EQ(kQuicDefaultConnectionIdLength,
+ replacement_connection_id.length());
+ }
+}
+
+TEST_F(QuicUtilsTest, ReplacementConnectionIdHasEntropy) {
+ // Make sure all these test connection IDs have different replacements.
+ for (uint64_t i = 0; i < 256; ++i) {
+ QuicConnectionId connection_id_i = TestConnectionId(i);
+ EXPECT_NE(connection_id_i,
+ QuicUtils::CreateReplacementConnectionId(connection_id_i));
+ for (uint64_t j = i + 1; j <= 256; ++j) {
+ QuicConnectionId connection_id_j = TestConnectionId(j);
+ EXPECT_NE(connection_id_i, connection_id_j);
+ EXPECT_NE(QuicUtils::CreateReplacementConnectionId(connection_id_i),
+ QuicUtils::CreateReplacementConnectionId(connection_id_j));
+ }
+ }
+}
+
TEST_F(QuicUtilsTest, RandomConnectionId) {
MockRandom random(33);
QuicConnectionId connection_id = QuicUtils::CreateRandomConnectionId(&random);