Introduce Chaos Protection

This feature reduces the likelihood of QUIC ossification due to middleboxes. When active, chaos protection modifies client initial packets in a way that defeats naive parsers but makes no difference to real QUIC implementations. This will hopefully encourage middlebox vendors to use robust third party QUIC libraries instead of looking for fields at fixed offsets.

PiperOrigin-RevId: 370906208
Change-Id: I36bba5dc2016f06cf9686cd77cb425a760ed9b4f
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index c485a25..af62865 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -343,6 +343,7 @@
 
 const QuicTag kNSLC = TAG('N', 'S', 'L', 'C');  // Always send connection close
                                                 // for idle timeout.
+const QuicTag kCHSP = TAG('C', 'H', 'S', 'P');  // Chaos protection.
 
 // Proof types (i.e. certificate types)
 // NOTE: although it would be silly to do so, specifying both kX509 and kX59R
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 5aa0c4f..d5bff8c 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -5171,6 +5171,74 @@
       0u);
 }
 
+// Testing packet writer that makes a copy of the first sent packets before
+// sending them. Useful for tests that need access to sent packets.
+class CopyingPacketWriter : public PacketDroppingTestWriter {
+ public:
+  explicit CopyingPacketWriter(int num_packets_to_copy)
+      : num_packets_to_copy_(num_packets_to_copy) {}
+  WriteResult WritePacket(const char* buffer,
+                          size_t buf_len,
+                          const QuicIpAddress& self_address,
+                          const QuicSocketAddress& peer_address,
+                          PerPacketOptions* options) override {
+    if (num_packets_to_copy_ > 0) {
+      num_packets_to_copy_--;
+      packets_.push_back(
+          QuicEncryptedPacket(buffer, buf_len, /*owns_buffer=*/false).Clone());
+    }
+    return PacketDroppingTestWriter::WritePacket(buffer, buf_len, self_address,
+                                                 peer_address, options);
+  }
+
+  std::vector<std::unique_ptr<QuicEncryptedPacket>>& packets() {
+    return packets_;
+  }
+
+ private:
+  int num_packets_to_copy_;
+  std::vector<std::unique_ptr<QuicEncryptedPacket>> packets_;
+};
+
+TEST_P(EndToEndTest, ChaosProtection) {
+  if (!version_.UsesCryptoFrames()) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  // Replace the client's writer with one that'll save the first packet.
+  auto copying_writer = new CopyingPacketWriter(1);
+  delete client_writer_;
+  client_writer_ = copying_writer;
+  // Enable chaos protection and perform an HTTP request.
+  client_config_.SetClientConnectionOptions(QuicTagVector{kCHSP});
+  ASSERT_TRUE(Initialize());
+  SendSynchronousFooRequestAndCheckResponse();
+  // Parse the saved packet to make sure it's valid.
+  SimpleQuicFramer validation_framer({version_});
+  validation_framer.framer()->SetInitialObfuscators(
+      GetClientConnection()->connection_id());
+  ASSERT_GT(copying_writer->packets().size(), 0u);
+  EXPECT_TRUE(validation_framer.ProcessPacket(*copying_writer->packets()[0]));
+  // TODO(dschinazi) figure out a way to use a MockRandom in this test so we
+  // can inspect the contents of this packet.
+}
+
+TEST_P(EndToEndTest, ChaosProtectionWithMultiPacketChlo) {
+  if (!version_.UsesCryptoFrames()) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  // Enable chaos protection.
+  client_config_.SetClientConnectionOptions(QuicTagVector{kCHSP});
+  // Add a transport parameter to make the client hello span multiple packets.
+  constexpr auto kCustomParameter =
+      static_cast<TransportParameters::TransportParameterId>(0xff34);
+  client_config_.custom_transport_parameters_to_send()[kCustomParameter] =
+      std::string(2000, '?');
+  ASSERT_TRUE(Initialize());
+  SendSynchronousFooRequestAndCheckResponse();
+}
+
 TEST_P(EndToEndTest, KeyUpdateInitiatedByClient) {
   if (!version_.UsesTls()) {
     // Key Update is only supported in TLS handshake.
diff --git a/quic/core/quic_chaos_protector.cc b/quic/core/quic_chaos_protector.cc
new file mode 100644
index 0000000..4eb353b
--- /dev/null
+++ b/quic/core/quic_chaos_protector.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quic/core/quic_chaos_protector.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "quic/core/crypto/quic_random.h"
+#include "quic/core/frames/quic_crypto_frame.h"
+#include "quic/core/frames/quic_frame.h"
+#include "quic/core/frames/quic_padding_frame.h"
+#include "quic/core/frames/quic_ping_frame.h"
+#include "quic/core/quic_data_reader.h"
+#include "quic/core/quic_data_writer.h"
+#include "quic/core/quic_framer.h"
+#include "quic/core/quic_packets.h"
+#include "quic/core/quic_stream_frame_data_producer.h"
+#include "quic/platform/api/quic_bug_tracker.h"
+#include "common/platform/api/quiche_logging.h"
+
+namespace quic {
+
+QuicChaosProtector::QuicChaosProtector(const QuicCryptoFrame& crypto_frame,
+                                       int num_padding_bytes,
+                                       size_t packet_size,
+                                       QuicFramer* framer,
+                                       QuicRandom* random)
+    : packet_size_(packet_size),
+      crypto_data_length_(crypto_frame.data_length),
+      crypto_buffer_offset_(crypto_frame.offset),
+      level_(crypto_frame.level),
+      remaining_padding_bytes_(num_padding_bytes),
+      framer_(framer),
+      random_(random) {
+  QUICHE_DCHECK_NE(framer_, nullptr);
+  QUICHE_DCHECK_NE(framer_->data_producer(), nullptr);
+  QUICHE_DCHECK_NE(random_, nullptr);
+}
+
+QuicChaosProtector::~QuicChaosProtector() {
+  DeleteFrames(&frames_);
+}
+
+absl::optional<size_t> QuicChaosProtector::BuildDataPacket(
+    const QuicPacketHeader& header,
+    char* buffer) {
+  if (!CopyCryptoDataToLocalBuffer()) {
+    return absl::nullopt;
+  }
+  SplitCryptoFrame();
+  AddPingFrames();
+  SpreadPadding();
+  ReorderFrames();
+  return BuildPacket(header, buffer);
+}
+
+WriteStreamDataResult QuicChaosProtector::WriteStreamData(
+    QuicStreamId id,
+    QuicStreamOffset offset,
+    QuicByteCount data_length,
+    QuicDataWriter* /*writer*/) {
+  QUIC_BUG(chaos stream) << "This should never be called; id " << id
+                         << " offset " << offset << " data_length "
+                         << data_length;
+  return STREAM_MISSING;
+}
+
+bool QuicChaosProtector::WriteCryptoData(EncryptionLevel level,
+                                         QuicStreamOffset offset,
+                                         QuicByteCount data_length,
+                                         QuicDataWriter* writer) {
+  if (level != level_) {
+    QUIC_BUG(chaos bad level) << "Unexpected " << level << " != " << level_;
+    return false;
+  }
+  // This is `offset + data_length > buffer_offset_ + buffer_length_`
+  // but with integer overflow protection.
+  if (offset < crypto_buffer_offset_ || data_length > crypto_data_length_ ||
+      offset - crypto_buffer_offset_ > crypto_data_length_ - data_length) {
+    QUIC_BUG(chaos bad lengths)
+        << "Unexpected buffer_offset_ " << crypto_buffer_offset_ << " offset "
+        << offset << " buffer_length_ " << crypto_data_length_
+        << " data_length " << data_length;
+    return false;
+  }
+  writer->WriteBytes(&crypto_data_buffer_[offset - crypto_buffer_offset_],
+                     data_length);
+  return true;
+}
+
+bool QuicChaosProtector::CopyCryptoDataToLocalBuffer() {
+  crypto_frame_buffer_ = std::make_unique<char[]>(packet_size_);
+  frames_.push_back(QuicFrame(
+      new QuicCryptoFrame(level_, crypto_buffer_offset_, crypto_data_length_)));
+  // We use |framer_| to serialize the CRYPTO frame in order to extract its
+  // data from the crypto data producer. This ensures that we reuse the
+  // usual serialization code path, but has the downside that we then need to
+  // parse the offset and length in order to skip over those fields.
+  QuicDataWriter writer(packet_size_, crypto_frame_buffer_.get());
+  if (!framer_->AppendCryptoFrame(*frames_.front().crypto_frame, &writer)) {
+    QUIC_BUG(chaos write crypto data);
+    return false;
+  }
+  QuicDataReader reader(crypto_frame_buffer_.get(), writer.length());
+  uint64_t parsed_offset, parsed_length;
+  if (!reader.ReadVarInt62(&parsed_offset) ||
+      !reader.ReadVarInt62(&parsed_length)) {
+    QUIC_BUG(chaos parse crypto frame);
+    return false;
+  }
+
+  absl::string_view crypto_data = reader.ReadRemainingPayload();
+  crypto_data_buffer_ = crypto_data.data();
+
+  QUICHE_DCHECK_EQ(parsed_offset, crypto_buffer_offset_);
+  QUICHE_DCHECK_EQ(parsed_length, crypto_data_length_);
+  QUICHE_DCHECK_EQ(parsed_length, crypto_data.length());
+
+  return true;
+}
+
+void QuicChaosProtector::SplitCryptoFrame() {
+  const int max_overhead_of_adding_a_crypto_frame =
+      static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+          crypto_buffer_offset_ + crypto_data_length_, crypto_data_length_));
+  // Pick a random number of CRYPTO frames to add.
+  constexpr uint64_t kMaxAddedCryptoFrames = 10;
+  const uint64_t num_added_crypto_frames =
+      random_->InsecureRandUint64() % (kMaxAddedCryptoFrames + 1);
+  for (uint64_t i = 0; i < num_added_crypto_frames; i++) {
+    if (remaining_padding_bytes_ < max_overhead_of_adding_a_crypto_frame) {
+      break;
+    }
+    // Pick a random frame and split it by shrinking the picked frame and
+    // moving the second half of its data to a new frame that is then appended
+    // to |frames|.
+    size_t frame_to_split_index =
+        random_->InsecureRandUint64() % frames_.size();
+    QuicCryptoFrame* frame_to_split =
+        frames_[frame_to_split_index].crypto_frame;
+    if (frame_to_split->data_length <= 1) {
+      continue;
+    }
+    const int frame_to_split_old_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            frame_to_split->offset, frame_to_split->data_length));
+    const QuicPacketLength frame_to_split_new_data_length =
+        1 + (random_->InsecureRandUint64() % (frame_to_split->data_length - 1));
+    const QuicPacketLength new_frame_data_length =
+        frame_to_split->data_length - frame_to_split_new_data_length;
+    const QuicStreamOffset new_frame_offset =
+        frame_to_split->offset + frame_to_split_new_data_length;
+    frame_to_split->data_length -= new_frame_data_length;
+    frames_.push_back(QuicFrame(
+        new QuicCryptoFrame(level_, new_frame_offset, new_frame_data_length)));
+    const int frame_to_split_new_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            frame_to_split->offset, frame_to_split->data_length));
+    const int new_frame_overhead =
+        static_cast<int>(QuicFramer::GetMinCryptoFrameSize(
+            new_frame_offset, new_frame_data_length));
+    QUICHE_DCHECK_LE(frame_to_split_new_overhead, frame_to_split_old_overhead);
+    // Readjust padding based on increased overhead.
+    remaining_padding_bytes_ -= new_frame_overhead;
+    remaining_padding_bytes_ -= frame_to_split_new_overhead;
+    remaining_padding_bytes_ += frame_to_split_old_overhead;
+  }
+}
+
+void QuicChaosProtector::AddPingFrames() {
+  constexpr uint64_t kMaxAddedPingFrames = 10;
+  const uint64_t num_ping_frames =
+      random_->InsecureRandUint64() %
+      std::min<uint64_t>(kMaxAddedPingFrames, remaining_padding_bytes_);
+  for (uint64_t i = 0; i < num_ping_frames; i++) {
+    frames_.push_back(QuicFrame(QuicPingFrame()));
+  }
+  remaining_padding_bytes_ -= static_cast<int>(num_ping_frames);
+}
+
+void QuicChaosProtector::ReorderFrames() {
+  // Walk the array backwards and swap each frame with a random earlier one.
+  for (size_t i = frames_.size() - 1; i > 0; i--) {
+    std::swap(frames_[i], frames_[random_->InsecureRandUint64() % (i + 1)]);
+  }
+}
+
+void QuicChaosProtector::SpreadPadding() {
+  for (auto it = frames_.begin(); it != frames_.end(); ++it) {
+    const int padding_bytes_in_this_frame =
+        random_->InsecureRandUint64() % (remaining_padding_bytes_ + 1);
+    if (padding_bytes_in_this_frame <= 0) {
+      continue;
+    }
+    it = frames_.insert(
+        it, QuicFrame(QuicPaddingFrame(padding_bytes_in_this_frame)));
+    ++it;  // Skip over the padding frame we just added.
+    remaining_padding_bytes_ -= padding_bytes_in_this_frame;
+  }
+  if (remaining_padding_bytes_ > 0) {
+    frames_.push_back(QuicFrame(QuicPaddingFrame(remaining_padding_bytes_)));
+  }
+}
+
+absl::optional<size_t> QuicChaosProtector::BuildPacket(
+    const QuicPacketHeader& header,
+    char* buffer) {
+  QuicStreamFrameDataProducer* original_data_producer =
+      framer_->data_producer();
+  framer_->set_data_producer(this);
+
+  size_t length =
+      framer_->BuildDataPacket(header, frames_, buffer, packet_size_, level_);
+
+  framer_->set_data_producer(original_data_producer);
+  if (length == 0) {
+    return absl::nullopt;
+  }
+  return length;
+}
+
+}  // namespace quic
diff --git a/quic/core/quic_chaos_protector.h b/quic/core/quic_chaos_protector.h
new file mode 100644
index 0000000..6bcd420
--- /dev/null
+++ b/quic/core/quic_chaos_protector.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_CHAOS_PROTECTOR_H_
+#define QUICHE_QUIC_CORE_QUIC_CHAOS_PROTECTOR_H_
+
+#include <cstddef>
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "quic/core/crypto/quic_random.h"
+#include "quic/core/frames/quic_crypto_frame.h"
+#include "quic/core/frames/quic_frame.h"
+#include "quic/core/quic_data_writer.h"
+#include "quic/core/quic_framer.h"
+#include "quic/core/quic_packets.h"
+#include "quic/core/quic_stream_frame_data_producer.h"
+#include "quic/core/quic_types.h"
+
+namespace quic {
+
+namespace test {
+class QuicChaosProtectorTest;
+}
+
+// QuicChaosProtector will take a crypto frame and an amount of padding and
+// build a data packet that will parse to something equivalent.
+class QUIC_EXPORT_PRIVATE QuicChaosProtector
+    : public QuicStreamFrameDataProducer {
+ public:
+  // |framer| and |random| must be valid for the lifetime of QuicChaosProtector.
+  explicit QuicChaosProtector(const QuicCryptoFrame& crypto_frame,
+                              int num_padding_bytes,
+                              size_t packet_size,
+                              QuicFramer* framer,
+                              QuicRandom* random);
+
+  ~QuicChaosProtector() override;
+
+  QuicChaosProtector(const QuicChaosProtector&) = delete;
+  QuicChaosProtector(QuicChaosProtector&&) = delete;
+  QuicChaosProtector& operator=(const QuicChaosProtector&) = delete;
+  QuicChaosProtector& operator=(QuicChaosProtector&&) = delete;
+
+  // Attempts to build a data packet with chaos protection. If an error occurs,
+  // then absl::nullopt is returned. Otherwise returns the serialized length.
+  absl::optional<size_t> BuildDataPacket(const QuicPacketHeader& header,
+                                         char* buffer);
+
+  // From QuicStreamFrameDataProducer.
+  WriteStreamDataResult WriteStreamData(QuicStreamId id,
+                                        QuicStreamOffset offset,
+                                        QuicByteCount data_length,
+                                        QuicDataWriter* /*writer*/) override;
+  bool WriteCryptoData(EncryptionLevel level,
+                       QuicStreamOffset offset,
+                       QuicByteCount data_length,
+                       QuicDataWriter* writer) override;
+
+ private:
+  friend class test::QuicChaosProtectorTest;
+
+  // Allocate the crypto data buffer, create the CRYPTO frame and write the
+  // crypto data to our buffer.
+  bool CopyCryptoDataToLocalBuffer();
+
+  // Split the CRYPTO frame in |frames_| into one or more CRYPTO frames that
+  // collectively represent the same data. Adjusts padding to compensate.
+  void SplitCryptoFrame();
+
+  // Add a random number of PING frames to |frames_| and adjust padding.
+  void AddPingFrames();
+
+  // Randomly reorder |frames_|.
+  void ReorderFrames();
+
+  // Add PADDING frames randomly between all other frames.
+  void SpreadPadding();
+
+  // Serialize |frames_| using |framer_|.
+  absl::optional<size_t> BuildPacket(const QuicPacketHeader& header,
+                                     char* buffer);
+
+  size_t packet_size_;
+  std::unique_ptr<char[]> crypto_frame_buffer_;
+  const char* crypto_data_buffer_ = nullptr;
+  QuicByteCount crypto_data_length_;
+  QuicStreamOffset crypto_buffer_offset_;
+  EncryptionLevel level_;
+  int remaining_padding_bytes_;
+  QuicFrames frames_;   // Inner frames owned, will be deleted by destructor.
+  QuicFramer* framer_;  // Unowned.
+  QuicRandom* random_;  // Unowned.
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_CHAOS_PROTECTOR_H_
diff --git a/quic/core/quic_chaos_protector_test.cc b/quic/core/quic_chaos_protector_test.cc
new file mode 100644
index 0000000..1ec985b
--- /dev/null
+++ b/quic/core/quic_chaos_protector_test.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quic/core/quic_chaos_protector.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "quic/core/frames/quic_crypto_frame.h"
+#include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_framer.h"
+#include "quic/core/quic_packet_number.h"
+#include "quic/core/quic_packets.h"
+#include "quic/core/quic_stream_frame_data_producer.h"
+#include "quic/core/quic_time.h"
+#include "quic/core/quic_types.h"
+#include "quic/core/quic_versions.h"
+#include "quic/platform/api/quic_test.h"
+#include "quic/test_tools/mock_random.h"
+#include "quic/test_tools/quic_test_utils.h"
+#include "quic/test_tools/simple_quic_framer.h"
+
+namespace quic {
+namespace test {
+
+class QuicChaosProtectorTest : public QuicTestWithParam<ParsedQuicVersion>,
+                               public QuicStreamFrameDataProducer {
+ public:
+  QuicChaosProtectorTest()
+      : version_(GetParam()),
+        framer_({version_},
+                QuicTime::Zero(),
+                Perspective::IS_CLIENT,
+                kQuicDefaultConnectionIdLength),
+        validation_framer_({version_}),
+        random_(/*base=*/3),
+        level_(ENCRYPTION_INITIAL),
+        crypto_offset_(0),
+        crypto_data_length_(100),
+        crypto_frame_(level_, crypto_offset_, crypto_data_length_),
+        num_padding_bytes_(50),
+        packet_size_(1000),
+        packet_buffer_(std::make_unique<char[]>(packet_size_)),
+        chaos_protector_(crypto_frame_,
+                         num_padding_bytes_,
+                         packet_size_,
+                         SetupHeaderAndFramers(),
+                         &random_) {}
+
+  // From QuicStreamFrameDataProducer.
+  WriteStreamDataResult WriteStreamData(QuicStreamId /*id*/,
+                                        QuicStreamOffset /*offset*/,
+                                        QuicByteCount /*data_length*/,
+                                        QuicDataWriter* /*writer*/) override {
+    ADD_FAILURE() << "This should never be called";
+    return STREAM_MISSING;
+  }
+
+  // From QuicStreamFrameDataProducer.
+  bool WriteCryptoData(EncryptionLevel level,
+                       QuicStreamOffset offset,
+                       QuicByteCount data_length,
+                       QuicDataWriter* writer) override {
+    EXPECT_EQ(level, level);
+    EXPECT_EQ(offset, crypto_offset_);
+    EXPECT_EQ(data_length, crypto_data_length_);
+    for (QuicByteCount i = 0; i < data_length; i++) {
+      EXPECT_TRUE(writer->WriteUInt8(static_cast<uint8_t>(i & 0xFF)));
+    }
+    return true;
+  }
+
+ protected:
+  QuicFramer* SetupHeaderAndFramers() {
+    // Setup header.
+    header_.destination_connection_id = TestConnectionId();
+    header_.destination_connection_id_included = CONNECTION_ID_PRESENT;
+    header_.source_connection_id = EmptyQuicConnectionId();
+    header_.source_connection_id_included = CONNECTION_ID_PRESENT;
+    header_.reset_flag = false;
+    header_.version_flag = true;
+    header_.has_possible_stateless_reset_token = false;
+    header_.packet_number_length = PACKET_4BYTE_PACKET_NUMBER;
+    header_.version = version_;
+    header_.packet_number = QuicPacketNumber(1);
+    header_.form = IETF_QUIC_LONG_HEADER_PACKET;
+    header_.long_packet_type = INITIAL;
+    header_.retry_token_length_length = VARIABLE_LENGTH_INTEGER_LENGTH_1;
+    header_.length_length = kQuicDefaultLongHeaderLengthLength;
+    // Setup validation framer.
+    validation_framer_.framer()->SetInitialObfuscators(
+        header_.destination_connection_id);
+    // Setup framer.
+    framer_.SetInitialObfuscators(header_.destination_connection_id);
+    framer_.set_data_producer(this);
+    return &framer_;
+  }
+
+  void BuildEncryptAndParse() {
+    absl::optional<size_t> length =
+        chaos_protector_.BuildDataPacket(header_, packet_buffer_.get());
+    ASSERT_TRUE(length.has_value());
+    ASSERT_GT(length.value(), 0u);
+    size_t encrypted_length = framer_.EncryptInPlace(
+        level_, header_.packet_number,
+        GetStartOfEncryptedData(framer_.transport_version(), header_),
+        length.value(), packet_size_, packet_buffer_.get());
+    ASSERT_GT(encrypted_length, 0u);
+    ASSERT_TRUE(validation_framer_.ProcessPacket(QuicEncryptedPacket(
+        absl::string_view(packet_buffer_.get(), encrypted_length))));
+  }
+
+  void ResetOffset(QuicStreamOffset offset) {
+    crypto_offset_ = offset;
+    crypto_frame_.offset = offset;
+    chaos_protector_.crypto_buffer_offset_ = offset;
+  }
+
+  ParsedQuicVersion version_;
+  QuicPacketHeader header_;
+  QuicFramer framer_;
+  SimpleQuicFramer validation_framer_;
+  MockRandom random_;
+  EncryptionLevel level_;
+  QuicStreamOffset crypto_offset_;
+  QuicByteCount crypto_data_length_;
+  QuicCryptoFrame crypto_frame_;
+  int num_padding_bytes_;
+  size_t packet_size_;
+  std::unique_ptr<char[]> packet_buffer_;
+  QuicChaosProtector chaos_protector_;
+};
+
+namespace {
+
+ParsedQuicVersionVector TestVersions() {
+  ParsedQuicVersionVector versions;
+  for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+    if (version.UsesCryptoFrames()) {
+      versions.push_back(version);
+    }
+  }
+  return versions;
+}
+
+INSTANTIATE_TEST_SUITE_P(QuicChaosProtectorTests,
+                         QuicChaosProtectorTest,
+                         ::testing::ValuesIn(TestVersions()),
+                         ::testing::PrintToStringParamName());
+
+TEST_P(QuicChaosProtectorTest, Main) {
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, 0u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length, 1u);
+  ASSERT_EQ(validation_framer_.ping_frames().size(), 3u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 7u);
+  EXPECT_EQ(validation_framer_.padding_frames()[0].num_padding_bytes, 3);
+}
+
+TEST_P(QuicChaosProtectorTest, DifferentRandom) {
+  random_.ResetBase(4);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  ASSERT_EQ(validation_framer_.ping_frames().size(), 4u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 8u);
+}
+
+TEST_P(QuicChaosProtectorTest, RandomnessZero) {
+  random_.ResetBase(0);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length,
+            crypto_data_length_);
+  ASSERT_EQ(validation_framer_.ping_frames().size(), 0u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 1u);
+}
+
+TEST_P(QuicChaosProtectorTest, Offset) {
+  ResetOffset(123);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 4u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length, 1u);
+  ASSERT_EQ(validation_framer_.ping_frames().size(), 3u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 7u);
+  EXPECT_EQ(validation_framer_.padding_frames()[0].num_padding_bytes, 3);
+}
+
+TEST_P(QuicChaosProtectorTest, OffsetAndRandomnessZero) {
+  ResetOffset(123);
+  random_.ResetBase(0);
+  BuildEncryptAndParse();
+  ASSERT_EQ(validation_framer_.crypto_frames().size(), 1u);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->offset, crypto_offset_);
+  EXPECT_EQ(validation_framer_.crypto_frames()[0]->data_length,
+            crypto_data_length_);
+  ASSERT_EQ(validation_framer_.ping_frames().size(), 0u);
+  ASSERT_EQ(validation_framer_.padding_frames().size(), 1u);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index c980f4e..1aaeae1 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -587,6 +587,9 @@
   } else {
     SetNetworkTimeouts(config.max_time_before_crypto_handshake(),
                        config.max_idle_time_before_crypto_handshake());
+    if (config.HasClientRequestedIndependentOption(kCHSP, perspective_)) {
+      packet_creator_.set_chaos_protection_enabled(true);
+    }
   }
 
   if (support_multiple_connection_ids_ &&
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 8e67068..2920db3 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -634,6 +634,8 @@
 
   Perspective perspective() const { return perspective_; }
 
+  QuicStreamFrameDataProducer* data_producer() const { return data_producer_; }
+
   void set_data_producer(QuicStreamFrameDataProducer* data_producer) {
     data_producer_ = data_producer;
   }
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index f60b98f..3d3eb67 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -15,10 +15,12 @@
 #include "absl/base/optimization.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
 #include "quic/core/crypto/crypto_protocol.h"
 #include "quic/core/frames/quic_frame.h"
 #include "quic/core/frames/quic_path_challenge_frame.h"
 #include "quic/core/frames/quic_stream_frame.h"
+#include "quic/core/quic_chaos_protector.h"
 #include "quic/core/quic_connection_id.h"
 #include "quic/core/quic_constants.h"
 #include "quic/core/quic_data_writer.h"
@@ -132,7 +134,8 @@
       flusher_attached_(false),
       fully_pad_crypto_handshake_packets_(true),
       latched_hard_max_packet_length_(0),
-      max_datagram_frame_size_(0) {
+      max_datagram_frame_size_(0),
+      chaos_protection_enabled_(false) {
   SetMaxPacketLength(kDefaultMaxPacketSize);
   if (!framer_->version().UsesTls()) {
     // QUIC+TLS negotiates the maximum datagram frame size via the
@@ -752,6 +755,35 @@
   return false;
 }
 
+absl::optional<size_t>
+QuicPacketCreator::MaybeBuildDataPacketWithChaosProtection(
+    const QuicPacketHeader& header,
+    char* buffer) {
+  if (!chaos_protection_enabled_ ||
+      packet_.encryption_level != ENCRYPTION_INITIAL ||
+      !framer_->version().UsesCryptoFrames() || queued_frames_.size() != 2u ||
+      queued_frames_[0].type != CRYPTO_FRAME ||
+      queued_frames_[1].type != PADDING_FRAME ||
+      // Do not perform chaos protection if we do not have a known number of
+      // padding bytes to work with.
+      queued_frames_[1].padding_frame.num_padding_bytes <= 0 ||
+      // Chaos protection relies on the framer using a crypto data producer,
+      // which is always the case in practice.
+      framer_->data_producer() == nullptr) {
+    return absl::nullopt;
+  }
+  const QuicCryptoFrame& crypto_frame = *queued_frames_[0].crypto_frame;
+  if (packet_.encryption_level != crypto_frame.level) {
+    QUIC_BUG(chaos frame level)
+        << ENDPOINT << packet_.encryption_level << " != " << crypto_frame.level;
+    return absl::nullopt;
+  }
+  QuicChaosProtector chaos_protector(
+      crypto_frame, queued_frames_[1].padding_frame.num_padding_bytes,
+      packet_size_, framer_, random_);
+  return chaos_protector.BuildDataPacket(header, buffer);
+}
+
 bool QuicPacketCreator::SerializePacket(QuicOwnedPacketBuffer encrypted_buffer,
                                         size_t encrypted_buffer_len) {
   if (packet_.encrypted_buffer != nullptr) {
@@ -801,9 +833,18 @@
   QUICHE_DCHECK_GE(max_plaintext_size_, packet_size_) << ENDPOINT;
   // Use the packet_size_ instead of the buffer size to ensure smaller
   // packet sizes are properly used.
-  size_t length =
-      framer_->BuildDataPacket(header, queued_frames_, encrypted_buffer.buffer,
-                               packet_size_, packet_.encryption_level);
+
+  size_t length;
+  absl::optional<size_t> length_with_chaos_protection =
+      MaybeBuildDataPacketWithChaosProtection(header, encrypted_buffer.buffer);
+  if (length_with_chaos_protection.has_value()) {
+    length = length_with_chaos_protection.value();
+  } else {
+    length = framer_->BuildDataPacket(header, queued_frames_,
+                                      encrypted_buffer.buffer, packet_size_,
+                                      packet_.encryption_level);
+  }
+
   if (length == 0) {
     QUIC_BUG(quic_bug_10752_16)
         << ENDPOINT << "Failed to serialize "
diff --git a/quic/core/quic_packet_creator.h b/quic/core/quic_packet_creator.h
index a04df43..d4a5805 100644
--- a/quic/core/quic_packet_creator.h
+++ b/quic/core/quic_packet_creator.h
@@ -22,6 +22,7 @@
 
 #include "absl/base/attributes.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
 #include "quic/core/frames/quic_stream_frame.h"
 #include "quic/core/quic_coalesced_packet.h"
 #include "quic/core/quic_connection_id.h"
@@ -308,6 +309,11 @@
   void set_encryption_level(EncryptionLevel level);
   EncryptionLevel encryption_level() { return packet_.encryption_level; }
 
+  // Sets whether initial packets are protected with chaos.
+  void set_chaos_protection_enabled(bool chaos_protection_enabled) {
+    chaos_protection_enabled_ = chaos_protection_enabled;
+  }
+
   // packet number of the last created packet, or 0 if no packets have been
   // created.
   QuicPacketNumber packet_number() const { return packet_.packet_number; }
@@ -508,6 +514,13 @@
     QuicPacketCreator* creator_;  // Unowned.
   };
 
+  // Attempts to build a data packet with chaos protection. If this packet isn't
+  // supposed to be protected or if serialization fails then absl::nullopt is
+  // returned. Otherwise returns the serialized length.
+  absl::optional<size_t> MaybeBuildDataPacketWithChaosProtection(
+      const QuicPacketHeader& header,
+      char* buffer);
+
   // Creates a stream frame which fits into the current open packet. If
   // |data_size| is 0 and fin is true, the expected behavior is to consume
   // the fin.
@@ -692,6 +705,9 @@
   // accept. There is no limit for QUIC_CRYPTO connections, but QUIC+TLS
   // negotiates this during the handshake.
   QuicByteCount max_datagram_frame_size_;
+
+  // Whether to attempt protecting initial packets with chaos.
+  bool chaos_protection_enabled_;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc
index b371bb3..57d1018 100644
--- a/quic/core/quic_packet_creator_test.cc
+++ b/quic/core/quic_packet_creator_test.cc
@@ -35,13 +35,14 @@
 #include "quic/test_tools/simple_quic_framer.h"
 #include "common/test_tools/quiche_test_utils.h"
 
-using testing::_;
-using testing::DoAll;
-using testing::InSequence;
-using testing::Invoke;
-using testing::Return;
-using testing::SaveArg;
-using testing::StrictMock;
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
 
 namespace quic {
 namespace test {
@@ -270,6 +271,8 @@
            n * 2;
   }
 
+  void TestChaosProtection(bool enabled);
+
   static constexpr QuicStreamOffset kOffset = 0u;
 
   char buffer_[kMaxOutgoingPacketSize];
@@ -1345,6 +1348,45 @@
   EXPECT_EQ(GetParam().version_serialization, header.version_flag);
 }
 
+void QuicPacketCreatorTest::TestChaosProtection(bool enabled) {
+  if (!GetParam().version.UsesCryptoFrames()) {
+    return;
+  }
+  MockRandom mock_random(2);
+  QuicPacketCreatorPeer::SetRandom(&creator_, &mock_random);
+  creator_.set_chaos_protection_enabled(enabled);
+  std::string data("ChAoS_ThEoRy!");
+  producer_.SaveCryptoData(ENCRYPTION_INITIAL, 0, data);
+  frames_.push_back(
+      QuicFrame(new QuicCryptoFrame(ENCRYPTION_INITIAL, 0, data.length())));
+  frames_.push_back(QuicFrame(QuicPaddingFrame(33)));
+  SerializedPacket serialized = SerializeAllFrames(frames_);
+  EXPECT_CALL(framer_visitor_, OnPacket());
+  EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+  EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+  EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_, _));
+  EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+  if (enabled) {
+    EXPECT_CALL(framer_visitor_, OnCryptoFrame(_)).Times(AtLeast(2));
+    EXPECT_CALL(framer_visitor_, OnPaddingFrame(_)).Times(AtLeast(2));
+    EXPECT_CALL(framer_visitor_, OnPingFrame(_)).Times(AtLeast(1));
+  } else {
+    EXPECT_CALL(framer_visitor_, OnCryptoFrame(_)).Times(1);
+    EXPECT_CALL(framer_visitor_, OnPaddingFrame(_)).Times(1);
+    EXPECT_CALL(framer_visitor_, OnPingFrame(_)).Times(0);
+  }
+  EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  ProcessPacket(serialized);
+}
+
+TEST_P(QuicPacketCreatorTest, ChaosProtectionEnabled) {
+  TestChaosProtection(/*enabled=*/true);
+}
+
+TEST_P(QuicPacketCreatorTest, ChaosProtectionDisabled) {
+  TestChaosProtection(/*enabled=*/false);
+}
+
 TEST_P(QuicPacketCreatorTest, ConsumeDataLargerThanOneStreamFrame) {
   if (!GetParam().version_serialization) {
     creator_.StopSendingVersion();
diff --git a/quic/test_tools/mock_random.cc b/quic/test_tools/mock_random.cc
index c50c75c..a789cdf 100644
--- a/quic/test_tools/mock_random.cc
+++ b/quic/test_tools/mock_random.cc
@@ -33,5 +33,10 @@
   increment_++;
 }
 
+void MockRandom::ResetBase(uint32_t base) {
+  base_ = base;
+  increment_ = 0;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/mock_random.h b/quic/test_tools/mock_random.h
index 63d438d..8dba860 100644
--- a/quic/test_tools/mock_random.h
+++ b/quic/test_tools/mock_random.h
@@ -33,6 +33,9 @@
   // |RandUint64| and the byte that |RandBytes| fills with, to change.
   void ChangeValue();
 
+  // Sets the base to |base| and resets increment to zero.
+  void ResetBase(uint32_t base);
+
  private:
   uint32_t base_;
   uint8_t increment_;
diff --git a/quic/test_tools/quic_packet_creator_peer.cc b/quic/test_tools/quic_packet_creator_peer.cc
index 0caf263..d638959 100644
--- a/quic/test_tools/quic_packet_creator_peer.cc
+++ b/quic/test_tools/quic_packet_creator_peer.cc
@@ -156,5 +156,11 @@
   return creator->queued_frames_;
 }
 
+// static
+void QuicPacketCreatorPeer::SetRandom(QuicPacketCreator* creator,
+                                      QuicRandom* random) {
+  creator->random_ = random;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_packet_creator_peer.h b/quic/test_tools/quic_packet_creator_peer.h
index 12f0086..259191d 100644
--- a/quic/test_tools/quic_packet_creator_peer.h
+++ b/quic/test_tools/quic_packet_creator_peer.h
@@ -10,6 +10,7 @@
 namespace quic {
 class QuicFramer;
 class QuicPacketCreator;
+class QuicRandom;
 
 namespace test {
 
@@ -61,6 +62,7 @@
   static QuicFramer* framer(QuicPacketCreator* creator);
   static std::string GetRetryToken(QuicPacketCreator* creator);
   static QuicFrames& QueuedFrames(QuicPacketCreator* creator);
+  static void SetRandom(QuicPacketCreator* creator, QuicRandom* random);
 };
 
 }  // namespace test