Automated g4 rollback of changelist 455516399.

*** Reason for rollback ***

Send a connection close in QuicDispatcher if a fatal TLS alert is raised while extracting CHLO. The only known such case is that when the SNI hostname contains a null byte, an 'unrecognized name' alert will be raised.

This is the prerequisite for the rollback.

*** Original change description ***

Automated g4 rollback of changelist 455484069.

*** Reason for rollback ***

This is breaking GFE TAP builds. b/236320376

*** Original change description ***

Fuzz SNI and ALPN in quic_tls_server_fuzz_test.

***

***

Protected by FLAGS_quic_restart_flag_quic_dispatcher_send_connection_close_for_tls_alerts.

PiperOrigin-RevId: 457560550
diff --git a/quiche/quic/core/quic_dispatcher.cc b/quiche/quic/core/quic_dispatcher.cc
index 81226f6..7f5b1ba 100644
--- a/quiche/quic/core/quic_dispatcher.cc
+++ b/quiche/quic/core/quic_dispatcher.cc
@@ -716,37 +716,61 @@
   // Packet's connection ID is unknown.  Apply the validity checks.
   QuicPacketFate fate = ValidityChecks(*packet_info);
 
+  // |connection_close_error_code| is used if the final packet fate is
+  // kFateTimeWait.
+  QuicErrorCode connection_close_error_code = QUIC_HANDSHAKE_FAILED;
+
+  // If a fatal TLS alert was received when extracting Client Hello,
+  // |tls_alert_error_detail| will be set and will be used as the error_details
+  // of the connection close.
+  std::string tls_alert_error_detail;
+
   if (fate == kFateProcess) {
     ExtractChloResult extract_chlo_result =
         TryExtractChloOrBufferEarlyPacket(*packet_info);
     auto& parsed_chlo = extract_chlo_result.parsed_chlo;
-    if (!parsed_chlo.has_value()) {
+
+    if (send_connection_close_for_tls_alerts_ &&
+        extract_chlo_result.tls_alert.has_value()) {
+      QUIC_BUG_IF(quic_dispatcher_parsed_chlo_and_tls_alert_coexist_1,
+                  parsed_chlo.has_value())
+          << "parsed_chlo and tls_alert should not be set at the same time.";
+      // Fatal TLS alert when parsing Client Hello.
+      fate = kFateTimeWait;
+      uint8_t tls_alert = *extract_chlo_result.tls_alert;
+      connection_close_error_code = TlsAlertToQuicErrorCode(tls_alert);
+      tls_alert_error_detail =
+          absl::StrCat("TLS handshake failure (",
+                       EncryptionLevelToString(ENCRYPTION_INITIAL), ") ",
+                       static_cast<int>(tls_alert), ": ",
+                       SSL_alert_desc_string_long(tls_alert));
+    } else if (!parsed_chlo.has_value()) {
       // Client Hello incomplete. Packet has been buffered or (rarely) dropped.
       return;
-    }
+    } else {
+      // Client Hello fully received.
+      fate = ValidityChecksOnFullChlo(*packet_info, *parsed_chlo);
 
-    // Client Hello fully received.
-    fate = ValidityChecksOnFullChlo(*packet_info, *parsed_chlo);
+      if (fate == kFateProcess) {
+        QUICHE_DCHECK(
+            parsed_chlo->legacy_version_encapsulation_inner_packet.empty() ||
+            !packet_info->version.UsesTls());
+        if (GetQuicRestartFlag(quic_disable_legacy_version_encapsulation)) {
+          if (!parsed_chlo->legacy_version_encapsulation_inner_packet.empty()) {
+            QUIC_CODE_COUNT(
+                quic_disable_legacy_version_encapsulation_process_header);
+          }
+        } else {
+          if (MaybeHandleLegacyVersionEncapsulation(
+                  this, parsed_chlo->legacy_version_encapsulation_inner_packet,
+                  *packet_info)) {
+            return;
+          }
+        }
 
-    if (fate == kFateProcess) {
-      QUICHE_DCHECK(
-          parsed_chlo->legacy_version_encapsulation_inner_packet.empty() ||
-          !packet_info->version.UsesTls());
-      if (GetQuicRestartFlag(quic_disable_legacy_version_encapsulation)) {
-        if (!parsed_chlo->legacy_version_encapsulation_inner_packet.empty()) {
-          QUIC_CODE_COUNT(
-              quic_disable_legacy_version_encapsulation_process_header);
-        }
-      } else {
-        if (MaybeHandleLegacyVersionEncapsulation(
-                this, parsed_chlo->legacy_version_encapsulation_inner_packet,
-                *packet_info)) {
-          return;
-        }
+        ProcessChlo(*std::move(parsed_chlo), packet_info);
+        return;
       }
-
-      ProcessChlo(*std::move(parsed_chlo), packet_info);
-      return;
     }
   }
 
@@ -755,16 +779,19 @@
       // kFateProcess have been processed above.
       QUIC_BUG(quic_dispatcher_bad_packet_fate) << fate;
       break;
-    case kFateTimeWait:
+    case kFateTimeWait: {
       // Add this connection_id to the time-wait state, to safely reject
       // future packets.
       QUIC_DLOG(INFO) << "Adding connection ID " << server_connection_id
                       << " to time-wait list.";
       QUIC_CODE_COUNT(quic_reject_fate_time_wait);
+      const std::string& connection_close_error_detail =
+          tls_alert_error_detail.empty() ? "Reject connection"
+                                         : tls_alert_error_detail;
       StatelesslyTerminateConnection(
           server_connection_id, packet_info->form, packet_info->version_flag,
           packet_info->use_length_prefix, packet_info->version,
-          QUIC_HANDSHAKE_FAILED, "Reject connection",
+          connection_close_error_code, connection_close_error_detail,
           quic::QuicTimeWaitListManager::SEND_STATELESS_RESET);
 
       QUICHE_DCHECK(time_wait_list_manager_->IsConnectionIdInTimeWait(
@@ -775,7 +802,7 @@
           GetPerPacketContext());
 
       buffered_packets_.DiscardPackets(server_connection_id);
-      break;
+    } break;
     case kFateDrop:
       break;
   }
@@ -815,6 +842,14 @@
         result.tls_alert = tls_chlo_extractor.tls_alert();
       }
     }
+
+    if (send_connection_close_for_tls_alerts_ && result.tls_alert.has_value()) {
+      QUIC_BUG_IF(quic_dispatcher_parsed_chlo_and_tls_alert_coexist_2,
+                  has_full_tls_chlo)
+          << "parsed_chlo and tls_alert should not be set at the same time.";
+      return result;
+    }
+
     if (!has_full_tls_chlo) {
       // This packet does not contain a full CHLO. It could be a 0-RTT
       // packet that arrived before the CHLO (due to loss or reordering),
diff --git a/quiche/quic/core/quic_dispatcher.h b/quiche/quic/core/quic_dispatcher.h
index e230739..d262d1e 100644
--- a/quiche/quic/core/quic_dispatcher.h
+++ b/quiche/quic/core/quic_dispatcher.h
@@ -475,6 +475,9 @@
   // If true, change expected_server_connection_id_length_ to be the received
   // destination connection ID length of all IETF long headers.
   bool should_update_expected_server_connection_id_length_;
+
+  const bool send_connection_close_for_tls_alerts_ =
+      GetQuicRestartFlag(quic_dispatcher_send_connection_close_for_tls_alerts);
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 661477e..c45c53c 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -69,6 +69,8 @@
 QUIC_FLAG(quic_reloadable_flag_quic_flush_pending_frames_and_padding_bytes_on_migration, true)
 // If true, ietf connection migration is no longer conditioned on connection option RVCM.
 QUIC_FLAG(quic_reloadable_flag_quic_remove_connection_migration_connection_option_v2, false)
+// If true, if a fatal tls alert is raised while extracting CHLO, QuicDispatcher will send a connection close.
+QUIC_FLAG(quic_restart_flag_quic_dispatcher_send_connection_close_for_tls_alerts, true)
 // If true, include offset in QUIC STREAM_DATA_BLOCKED and DATA_BLOCKED frames.
 QUIC_FLAG(quic_reloadable_flag_quic_include_offset_in_blocked_frames, true)
 // If true, include stream information in idle timeout connection close detail.