Add quic_framer_process_data_packet_fuzzer, a fuzz test for QuicFramer::ProcessPacket where the packet has a valid public header, is decryptable, and contains random QUIC payload. gfe-relnote: (n/a) Test only. PiperOrigin-RevId: 274791102 Change-Id: I63d55fec8e2a979a06c9e4c74b3095c19ce76c71
diff --git a/quic/test_tools/fuzzing/README.md b/quic/test_tools/fuzzing/README.md index b30ba8b..d914fb9 100644 --- a/quic/test_tools/fuzzing/README.md +++ b/quic/test_tools/fuzzing/README.md
@@ -1,6 +1,12 @@ -Examples of fuzz testing QUIC code using libfuzzer (go/libfuzzer). +This directory contains several fuzz tests for QUIC code: -To build and run the examples: +- quic_framer_fuzzer: A test for CryptoFramer::ParseMessage and + QuicFramer::ProcessPacket using random packet data. +- quic_framer_process_data_packet_fuzzer: A test for QuicFramer::ProcessPacket + where the packet has a valid public header, is decryptable, and contains + random QUIC payload. + +To build and run the fuzz tests, using quic_framer_fuzzer as an example: ```sh $ blaze build --config=asan-fuzzer //gfe/quic/test_tools/fuzzing/... @@ -8,8 +14,8 @@ $ ./blaze-bin/gfe/quic/test_tools/fuzzing/quic_framer_fuzzer ${CORPUS_DIR} -use_counters=0 ``` -By default this fuzzes with 64 byte chunks, to test the framer with more realistic -size input, try 1350 (max payload size of a QUIC packet): +By default this fuzzes with 64 byte chunks, to test the framer with more +realistic size input, try 1350 (max payload size of a QUIC packet): ```sh $ ./blaze-bin/gfe/quic/test_tools/fuzzing/quic_framer_fuzzer ${CORPUS_DIR} -use_counters=0 -max_len=1350
diff --git a/quic/test_tools/fuzzing/quic_framer_process_data_packet_fuzzer.cc b/quic/test_tools/fuzzing/quic_framer_process_data_packet_fuzzer.cc new file mode 100644 index 0000000..0aa2ca1 --- /dev/null +++ b/quic/test_tools/fuzzing/quic_framer_process_data_packet_fuzzer.cc
@@ -0,0 +1,275 @@ +// Copyright (c) 2019 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 <fuzzer/FuzzedDataProvider.h> +#include <cstdint> + +#include <algorithm> + +#include "net/third_party/quiche/src/quic/core/crypto/null_decrypter.h" +#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h" +#include "net/third_party/quiche/src/quic/core/quic_connection_id.h" +#include "net/third_party/quiche/src/quic/core/quic_constants.h" +#include "net/third_party/quiche/src/quic/core/quic_data_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_framer.h" +#include "net/third_party/quiche/src/quic/core/quic_time.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" + +using quic::DiversificationNonce; +using quic::EncryptionLevel; +using quic::FirstSendingPacketNumber; +using quic::GetPacketHeaderSize; +using quic::kEthernetMTU; +using quic::kQuicDefaultConnectionIdLength; +using quic::NullDecrypter; +using quic::NullEncrypter; +using quic::PacketHeaderFormat; +using quic::ParsedQuicVersion; +using quic::ParsedQuicVersionVector; +using quic::Perspective; +using quic::QuicConnectionId; +using quic::QuicDataReader; +using quic::QuicDataWriter; +using quic::QuicEncryptedPacket; +using quic::QuicFramer; +using quic::QuicFramerVisitorInterface; +using quic::QuicLongHeaderType; +using quic::QuicPacketHeader; +using quic::QuicPacketNumber; +using quic::QuicTime; +using quic::QuicTransportVersion; +using quic::test::NoOpFramerVisitor; +using quic::test::QuicFramerPeer; + +PacketHeaderFormat ConsumePacketHeaderFormat(FuzzedDataProvider* provider, + ParsedQuicVersion version) { + if (!VersionHasIetfInvariantHeader(version.transport_version)) { + return quic::GOOGLE_QUIC_PACKET; + } + return provider->ConsumeBool() ? quic::IETF_QUIC_LONG_HEADER_PACKET + : quic::IETF_QUIC_SHORT_HEADER_PACKET; +} + +ParsedQuicVersion ConsumeParsedQuicVersion(FuzzedDataProvider* provider) { + // TODO(wub): Add support for v49+. + const std::array<QuicTransportVersion, 5> transport_versions = { + {quic::QUIC_VERSION_39, quic::QUIC_VERSION_43, quic::QUIC_VERSION_46, + quic::QUIC_VERSION_47, quic::QUIC_VERSION_48}, + }; + + return ParsedQuicVersion( + quic::PROTOCOL_QUIC_CRYPTO, + transport_versions[provider->ConsumeIntegralInRange<char>( + 0, transport_versions.size() - 1)]); +} + +// QuicSelfContainedPacketHeader is a QuicPacketHeader with built-in stroage for +// diversification nonce. +struct QuicSelfContainedPacketHeader : public QuicPacketHeader { + DiversificationNonce nonce_storage; +}; + +// Construct a random data packet header that 1) can be successfully serialized +// at sender, and 2) the serialzied buffer can pass the receiver framer's +// ProcessPublicHeader and DecryptPayload functions. +QuicSelfContainedPacketHeader ConsumeQuicPacketHeader( + FuzzedDataProvider* provider, + Perspective receiver_perspective) { + QuicSelfContainedPacketHeader header; + + header.version = ConsumeParsedQuicVersion(provider); + + header.form = ConsumePacketHeaderFormat(provider, header.version); + + const string cid_bytes = + provider->ConsumeBytesAsString(kQuicDefaultConnectionIdLength); + if (receiver_perspective == Perspective::IS_SERVER) { + header.destination_connection_id = + QuicConnectionId(cid_bytes.c_str(), cid_bytes.size()); + header.destination_connection_id_included = quic::CONNECTION_ID_PRESENT; + header.source_connection_id_included = quic::CONNECTION_ID_ABSENT; + } else { + header.source_connection_id = + QuicConnectionId(cid_bytes.c_str(), cid_bytes.size()); + header.source_connection_id_included = quic::CONNECTION_ID_PRESENT; + header.destination_connection_id_included = quic::CONNECTION_ID_ABSENT; + } + + header.version_flag = receiver_perspective == Perspective::IS_SERVER; + header.reset_flag = false; + + header.packet_number = + QuicPacketNumber(provider->ConsumeIntegral<uint32_t>()); + if (header.packet_number < FirstSendingPacketNumber()) { + header.packet_number = FirstSendingPacketNumber(); + } + header.packet_number_length = quic::PACKET_4BYTE_PACKET_NUMBER; + + header.remaining_packet_length = 0; + + if (header.form != quic::GOOGLE_QUIC_PACKET && header.version_flag) { + header.long_packet_type = static_cast<QuicLongHeaderType>( + provider->ConsumeIntegralInRange<uint8_t>( + // INITIAL, ZERO_RTT_PROTECTED, or HANDSHAKE. + static_cast<uint8_t>(quic::INITIAL), + static_cast<uint8_t>(quic::HANDSHAKE))); + } else { + header.long_packet_type = quic::INVALID_PACKET_TYPE; + } + + if (header.form == quic::IETF_QUIC_LONG_HEADER_PACKET && + header.long_packet_type == quic::ZERO_RTT_PROTECTED && + receiver_perspective == Perspective::IS_CLIENT && + header.version.handshake_protocol == quic::PROTOCOL_QUIC_CRYPTO) { + for (size_t i = 0; i < header.nonce_storage.size(); ++i) { + header.nonce_storage[i] = provider->ConsumeIntegral<char>(); + } + header.nonce = &header.nonce_storage; + } else { + header.nonce = nullptr; + } + + return header; +} + +void SetupFramer(QuicFramer* framer, QuicFramerVisitorInterface* visitor) { + framer->set_visitor(visitor); + for (EncryptionLevel level : + {quic::ENCRYPTION_INITIAL, quic::ENCRYPTION_HANDSHAKE, + quic::ENCRYPTION_ZERO_RTT, quic::ENCRYPTION_FORWARD_SECURE}) { + framer->SetEncrypter( + level, std::make_unique<NullEncrypter>(framer->perspective())); + if (framer->version().KnowsWhichDecrypterToUse()) { + framer->InstallDecrypter( + level, std::make_unique<NullDecrypter>(framer->perspective())); + } + } +} + +class FuzzingFramerVisitor : public NoOpFramerVisitor { + public: + // Called after a successful ProcessPublicHeader. + bool OnUnauthenticatedPublicHeader( + const QuicPacketHeader& /*header*/) override { + ++process_public_header_success_count_; + return true; + } + + // Called after a successful DecryptPayload. + bool OnPacketHeader(const QuicPacketHeader& /*header*/) override { + ++decrypted_packet_count_; + return true; + } + + uint64_t process_public_header_success_count_ = 0; + uint64_t decrypted_packet_count_ = 0; +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider data_provider(data, size); + + const QuicTime creation_time = + QuicTime::Zero() + QuicTime::Delta::FromMicroseconds( + data_provider.ConsumeIntegral<int32_t>()); + Perspective receiver_perspective = data_provider.ConsumeBool() + ? Perspective::IS_CLIENT + : Perspective::IS_SERVER; + Perspective sender_perspective = + (receiver_perspective == Perspective::IS_CLIENT) ? Perspective::IS_SERVER + : Perspective::IS_CLIENT; + + QuicSelfContainedPacketHeader header = + ConsumeQuicPacketHeader(&data_provider, receiver_perspective); + + NoOpFramerVisitor sender_framer_visitor; + ParsedQuicVersionVector framer_versions = {header.version}; + QuicFramer sender_framer(framer_versions, creation_time, sender_perspective, + kQuicDefaultConnectionIdLength); + SetupFramer(&sender_framer, &sender_framer_visitor); + + FuzzingFramerVisitor receiver_framer_visitor; + QuicFramer receiver_framer(framer_versions, creation_time, + receiver_perspective, + kQuicDefaultConnectionIdLength); + SetupFramer(&receiver_framer, &receiver_framer_visitor); + if (receiver_perspective == Perspective::IS_CLIENT) { + QuicFramerPeer::SetLastSerializedServerConnectionId( + &receiver_framer, header.source_connection_id); + } else { + QuicFramerPeer::SetLastSerializedClientConnectionId( + &receiver_framer, header.source_connection_id); + } + + std::array<char, kEthernetMTU> packet_buffer; + while (data_provider.remaining_bytes() > 16) { + const size_t last_remaining_bytes = data_provider.remaining_bytes(); + + // Get a randomized packet size. + uint16_t max_payload_size = static_cast<uint16_t>( + std::min<size_t>(data_provider.remaining_bytes(), 1350u)); + uint16_t min_payload_size = std::min<uint16_t>(16u, max_payload_size); + uint16_t payload_size = data_provider.ConsumeIntegralInRange<uint16_t>( + min_payload_size, max_payload_size); + + CHECK_NE(last_remaining_bytes, data_provider.remaining_bytes()) + << "Check fail to avoid an infinite loop. ConsumeIntegralInRange(" + << min_payload_size << ", " << max_payload_size + << ") did not consume any bytes. remaining_bytes:" + << last_remaining_bytes; + + std::vector<char> payload_buffer = + data_provider.ConsumeBytes<char>(payload_size); + CHECK_GE(packet_buffer.size(), + GetPacketHeaderSize(sender_framer.transport_version(), header) + + payload_buffer.size()); + + // Serialize the null-encrypted packet into |packet_buffer|. + QuicDataWriter writer(packet_buffer.size(), packet_buffer.data()); + size_t length_field_offset = 0; + CHECK(sender_framer.AppendPacketHeader(header, &writer, + &length_field_offset)); + + CHECK(writer.WriteBytes(payload_buffer.data(), payload_buffer.size())); + + EncryptionLevel encryption_level = + quic::test::HeaderToEncryptionLevel(header); + CHECK(sender_framer.WriteIetfLongHeaderLength( + header, &writer, length_field_offset, encryption_level)); + + size_t encrypted_length = sender_framer.EncryptInPlace( + encryption_level, header.packet_number, + GetStartOfEncryptedData(sender_framer.transport_version(), header), + writer.length(), packet_buffer.size(), packet_buffer.data()); + CHECK_NE(encrypted_length, 0u); + + // Use receiver's framer to process the packet. Ensure both + // ProcessPublicHeader and DecryptPayload were called and succeeded. + QuicEncryptedPacket packet(packet_buffer.data(), encrypted_length); + QuicDataReader reader(packet.data(), packet.length()); + + const uint64_t process_public_header_success_count = + receiver_framer_visitor.process_public_header_success_count_; + const uint64_t decrypted_packet_count = + receiver_framer_visitor.decrypted_packet_count_; + + receiver_framer.ProcessPacket(packet); + + DCHECK_EQ(process_public_header_success_count + 1, + receiver_framer_visitor.process_public_header_success_count_) + << "ProcessPublicHeader failed. error:" + << QuicErrorCodeToString(receiver_framer.error()) + << ", error_detail:" << receiver_framer.detailed_error() + << ". header:" << header; + DCHECK_EQ(decrypted_packet_count + 1, + receiver_framer_visitor.decrypted_packet_count_) + << "Packet was not decrypted. error:" + << QuicErrorCodeToString(receiver_framer.error()) + << ", error_detail:" << receiver_framer.detailed_error() + << ". header:" << header; + } + return 0; +}