Check new connection ID length before reading it
This CL fixes a buffer overflow in the NEW_CONNECTION_ID read path, and adds sanity checks to prevent similar issues from reoccuring. The issue was found by clusterfuzz:
https://bugs.chromium.org/p/chromium/issues/detail?id=943951#c4
gfe-relnote: trivial security fix when parsing invalid frame, not flag protected
PiperOrigin-RevId: 239486794
Change-Id: I70b8e7b4adfd52afbbcb3308ba7dded0416c884e
diff --git a/quic/core/quic_data_reader.cc b/quic/core/quic_data_reader.cc
index 4200329..0488fd3 100644
--- a/quic/core/quic_data_reader.cc
+++ b/quic/core/quic_data_reader.cc
@@ -133,7 +133,11 @@
bool QuicDataReader::ReadConnectionId(QuicConnectionId* connection_id,
uint8_t length) {
- DCHECK_LE(length, kQuicMaxConnectionIdLength);
+ if (length > kQuicMaxConnectionIdLength) {
+ QUIC_BUG << "Attempted to read connection ID with length too high "
+ << static_cast<int>(length);
+ return false;
+ }
if (length == 0) {
connection_id->set_length(0);
return true;
diff --git a/quic/core/quic_data_writer_test.cc b/quic/core/quic_data_writer_test.cc
index aed14c1..031620d 100644
--- a/quic/core/quic_data_writer_test.cc
+++ b/quic/core/quic_data_writer_test.cc
@@ -10,6 +10,7 @@
#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
@@ -38,7 +39,8 @@
class QuicDataWriterTest : public QuicTestWithParam<TestParams> {};
-INSTANTIATE_TEST_SUITE_P(QuicDataWriterTests, QuicDataWriterTest,
+INSTANTIATE_TEST_SUITE_P(QuicDataWriterTests,
+ QuicDataWriterTest,
::testing::ValuesIn(GetTestParams()));
TEST_P(QuicDataWriterTest, SanityCheckUFloat16Consts) {
@@ -1097,6 +1099,21 @@
EXPECT_EQ(8, reader4.PeekVarInt62Length());
}
+TEST_P(QuicDataWriterTest, InvalidConnectionIdLengthRead) {
+ static const uint8_t bad_connection_id_length = 19;
+ static_assert(bad_connection_id_length > kQuicMaxConnectionIdLength,
+ "bad lengths");
+ char buffer[20] = {};
+ QuicDataReader reader(buffer, 20);
+ QuicConnectionId connection_id;
+ bool ok;
+ EXPECT_QUIC_BUG(
+ ok = reader.ReadConnectionId(&connection_id, bad_connection_id_length),
+ QuicStrCat("Attempted to read connection ID with length too high ",
+ static_cast<int>(bad_connection_id_length)));
+ EXPECT_FALSE(ok);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 333fe59..3e4f921 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -2579,6 +2579,9 @@
source_connection_id_length = scil;
}
+ DCHECK_LE(destination_connection_id_length, kQuicMaxConnectionIdLength);
+ DCHECK_LE(source_connection_id_length, kQuicMaxConnectionIdLength);
+
// Read connection ID.
if (!reader->ReadConnectionId(&header->destination_connection_id,
destination_connection_id_length)) {
@@ -5597,10 +5600,15 @@
return false;
}
+ if (connection_id_length > kQuicMaxConnectionIdLength) {
+ set_detailed_error("New connection ID length too high.");
+ return false;
+ }
+
if (connection_id_length != kQuicDefaultConnectionIdLength &&
!QuicUtils::VariableLengthConnectionIdAllowedForVersion(
transport_version())) {
- set_detailed_error("Invalid new connection ID length.");
+ set_detailed_error("Invalid new connection ID length for version.");
return false;
}
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 4604a08..ea362eb 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -11201,6 +11201,49 @@
CheckFramingBoundaries(packet99, QUIC_INVALID_NEW_CONNECTION_ID_DATA);
}
+// Verifies that parsing a NEW_CONNECTION_ID frame with a length above the
+// specified maximum fails.
+TEST_P(QuicFramerTest, InvalidLongNewConnectionIdFrame) {
+ if (framer_.transport_version() != QUIC_VERSION_99) {
+ // The NEW_CONNECTION_ID frame is only for version 99.
+ return;
+ }
+ // clang-format off
+ PacketFragments packet99 = {
+ // type (short header, 4 byte packet number)
+ {"",
+ {0x43}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (IETF_NEW_CONNECTION_ID frame)
+ {"",
+ {0x18}},
+ // error code
+ {"Unable to read new connection ID frame sequence number.",
+ {kVarInt62OneByte + 0x11}},
+ {"Unable to read new connection ID frame connection id length.",
+ {0x13}}, // connection ID length
+ {"Unable to read new connection ID frame connection id.",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ 0xF0, 0xD2, 0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E,
+ 0x42, 0x33, 0x42}},
+ {"Can not read new connection ID frame reset token.",
+ {0xb5, 0x69, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
+ };
+ // clang-format on
+
+ std::unique_ptr<QuicEncryptedPacket> encrypted(
+ AssemblePacketFromFragments(packet99));
+ EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+ EXPECT_EQ(QUIC_INVALID_NEW_CONNECTION_ID_DATA, framer_.error());
+ EXPECT_EQ("New connection ID length too high.", framer_.detailed_error());
+}
+
TEST_P(QuicFramerTest, BuildNewConnectionIdFramePacket) {
if (framer_.transport_version() != QUIC_VERSION_99) {
// This frame is only for version 99.