Drop QUIC packets with invalid flags

We recently noticed an amplification attack where a QUIC server was receiving non-QUIC packets and responding to them with stateless resets. Since the purpose of stateless reset packets is to notify clients of server state loss, there is no purpose to sending them when the received packet is invalid, as no client could have generated that.

Protected by FLAGS_quic_restart_flag_quic_drop_invalid_flags.

PiperOrigin-RevId: 391182458
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index 3a98be3..e713fe7 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -944,6 +944,27 @@
   dispatcher_->ProcessPacket(server_address_, client_address, packet2);
 }
 
+TEST_P(QuicDispatcherTestOneVersion, DropPacketWithInvalidFlags) {
+  QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+  CreateTimeWaitListManager();
+  uint8_t all_zero_packet[1200] = {};
+  QuicReceivedPacket packet(reinterpret_cast<char*>(all_zero_packet),
+                            sizeof(all_zero_packet), QuicTime::Zero());
+  EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*time_wait_list_manager_, AddConnectionIdToTimeWait(_, _, _))
+      .Times(0);
+  if (GetQuicRestartFlag(quic_drop_invalid_flags)) {
+    EXPECT_CALL(*time_wait_list_manager_, SendPublicReset(_, _, _, _, _, _))
+        .Times(0);
+  } else {
+    EXPECT_CALL(*time_wait_list_manager_, SendPublicReset(_, _, _, _, _, _))
+        .Times(1);
+  }
+  dispatcher_->ProcessPacket(server_address_, client_address, packet);
+}
+
 // Makes sure nine-byte connection IDs are replaced by 8-byte ones.
 TEST_P(QuicDispatcherTestAllVersions, LongConnectionIdLengthReplaced) {
   if (!version_.AllowsVariableLengthConnectionIds()) {
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 0eedd30..97e94dc 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -123,6 +123,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_queue_until_handshake_complete, true)
 // When the STMP connection option is sent by the client, timestamps in the QUIC ACK frame are sent and processed.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)
+// When true, QuicDispatcher will silently drop QUIC packets that have invalid flags.
+QUIC_FLAG(FLAGS_quic_restart_flag_quic_drop_invalid_flags, false)
 // When true, defaults to BBR congestion control instead of Cubic.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false)
 // When true, prevents QUIC\'s PacingSender from generating bursts when the congestion controller is CWND limited and not pacing limited.
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 24b2be8..125e128 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -6557,6 +6557,27 @@
     return QUIC_INVALID_PACKET_HEADER;
   }
   const uint8_t first_byte = reader.PeekByte();
+  if (GetQuicRestartFlag(quic_drop_invalid_flags)) {
+    QUIC_RESTART_FLAG_COUNT(quic_drop_invalid_flags);
+    if ((first_byte & FLAGS_LONG_HEADER) == 0 &&
+        (first_byte & FLAGS_FIXED_BIT) == 0 &&
+        (first_byte & FLAGS_DEMULTIPLEXING_BIT) == 0) {
+      // All versions of Google QUIC up to and including Q043 set
+      // FLAGS_DEMULTIPLEXING_BIT to one on all client-to-server packets. Q044
+      // and Q045 were never default-enabled in production. All subsequent
+      // versions of Google QUIC (starting with Q046) require FLAGS_FIXED_BIT to
+      // be set to one on all packets. All versions of IETF QUIC (since
+      // draft-ietf-quic-transport-17 which was earlier than the first IETF QUIC
+      // version that was deployed in production by any implementation) also
+      // require FLAGS_FIXED_BIT to be set to one on all packets. If a packet
+      // has the FLAGS_LONG_HEADER bit set to one, it could be a first flight
+      // from an unknown future version that allows the other two bits to be set
+      // to zero. Based on this, packets that have all three of those bits set
+      // to zero are known to be invalid.
+      *detailed_error = "Invalid flags.";
+      return QUIC_INVALID_PACKET_HEADER;
+    }
+  }
   const bool ietf_format = QuicUtils::IsIetfPacketHeader(first_byte);
   uint8_t unused_first_byte;
   QuicVariableLengthIntegerLength retry_token_length_length;
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index f36bf5c..680cbe5 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -1286,6 +1286,28 @@
   EXPECT_EQ(FramerTestConnectionIdPlusOne(), source_connection_id);
 }
 
+TEST_P(QuicFramerTest, AllZeroPacketParsingFails) {
+  SetQuicRestartFlag(quic_drop_invalid_flags, true);
+  unsigned char packet[1200] = {};
+  QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false);
+  PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+  QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
+  bool version_flag = false;
+  QuicConnectionId destination_connection_id, source_connection_id;
+  QuicVersionLabel version_label = 0;
+  std::string detailed_error = "";
+  bool retry_token_present, use_length_prefix;
+  absl::string_view retry_token;
+  ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
+  const QuicErrorCode error_code = QuicFramer::ParsePublicHeaderDispatcher(
+      encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+      &version_flag, &use_length_prefix, &version_label, &parsed_version,
+      &destination_connection_id, &source_connection_id, &retry_token_present,
+      &retry_token, &detailed_error);
+  EXPECT_EQ(error_code, QUIC_INVALID_PACKET_HEADER);
+  EXPECT_EQ(detailed_error, "Invalid flags.");
+}
+
 TEST_P(QuicFramerTest, ParsePublicHeader) {
   // clang-format off
   unsigned char packet[] = {