Make client drop packets with wrong versions In Google QUIC, packets with the version flag set and a different version number were version negotiation packets. In IETF QUIC, version negotiation packets have their own format, and it is now possible for a client to receive a packet that is not a version negotiation packet but still has a version different from what it expects. Those packets must be dropped. This issue was found by Chromium clusterfuzz: https://bugs.chromium.org/p/chromium/issues/detail?id=959143 I was able to reproduce the fuzzer DCHECK failure and verify that the fix prevents it. gfe-relnote: client-only change to handling of packets with invalid versions, not flag protected PiperOrigin-RevId: 246921247 Change-Id: I6610c9cd8c667bfad62fd335cc7f45fc425d3d2e
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc index 55208e9..0e026c6 100644 --- a/quic/core/quic_framer.cc +++ b/quic/core/quic_framer.cc
@@ -1498,19 +1498,30 @@ return true; } - if (perspective_ == Perspective::IS_SERVER && header.version_flag && - header.version != version_) { - if (!visitor_->OnProtocolVersionMismatch(header.version, header.form)) { - RecordDroppedPacketReason(DroppedPacketReason::VERSION_MISMATCH); - return true; + if (IsVersionNegotiation(header, packet_has_ietf_packet_header)) { + QUIC_DVLOG(1) << ENDPOINT << "Received version negotiation packet"; + return ProcessVersionNegotiationPacket(&reader, header); + } + + if (header.version_flag && header.version != version_) { + if (perspective_ == Perspective::IS_SERVER) { + if (!visitor_->OnProtocolVersionMismatch(header.version, header.form)) { + RecordDroppedPacketReason(DroppedPacketReason::VERSION_MISMATCH); + return true; + } + } else { + // A client received a packet of a different version but that packet is + // not a version negotiation packet. It is therefore invalid and dropped. + QUIC_DLOG(ERROR) << "Client received unexpected version " + << ParsedQuicVersionToString(header.version) + << " instead of " << ParsedQuicVersionToString(version_); + set_detailed_error("Client received unexpected version."); + return RaiseError(QUIC_INVALID_VERSION); } } bool rv; - if (IsVersionNegotiation(header, packet_has_ietf_packet_header)) { - QUIC_DVLOG(1) << ENDPOINT << "Received version negotiation packet"; - rv = ProcessVersionNegotiationPacket(&reader, header); - } else if (header.long_packet_type == RETRY) { + if (header.long_packet_type == RETRY) { rv = ProcessRetryPacket(&reader, header); } else if (header.reset_flag) { rv = ProcessPublicResetPacket(&reader, header);
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc index 56665df..0b5dca5 100644 --- a/quic/core/quic_framer_test.cc +++ b/quic/core/quic_framer_test.cc
@@ -12938,6 +12938,36 @@ ASSERT_EQ(visitor_.coalesced_packets_.size(), 0u); } +TEST_P(QuicFramerTest, ClientReceivesInvalidVersion) { + if (framer_.transport_version() < QUIC_VERSION_44) { + return; + } + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + + // clang-format off + unsigned char packet[] = { + // public flags (long header with packet type INITIAL) + 0xFF, + // version that is different from the framer's version + 'Q', '0', '4', '3', + // connection ID lengths + 0x05, + // source connection ID + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, + // packet number + 0x01, + // padding frame + 0x00, + }; + // clang-format on + + QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false); + EXPECT_FALSE(framer_.ProcessPacket(encrypted)); + + EXPECT_EQ(QUIC_INVALID_VERSION, framer_.error()); + EXPECT_EQ("Client received unexpected version.", framer_.detailed_error()); +} + TEST_P(QuicFramerTest, PacketHeaderWithVariableLengthConnectionId) { if (framer_.transport_version() < QUIC_VERSION_46) { return;