From d002dc403b509d025292bbc8c61db59ad83b127e Mon Sep 17 00:00:00 2001 From: Dhruv Mehta <856960+dhruv@users.noreply.github.com> Date: Fri, 1 Oct 2021 11:29:39 -0700 Subject: [PATCH] fuzz: Add fuzz test for v2 transport {de}serialization --- src/Makefile.test.include | 1 + src/crypto/rfc8439.cpp | 1 - src/crypto/rfc8439.h | 8 ++ .../fuzz/p2p_v2_transport_serialization.cpp | 110 ++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/test/fuzz/p2p_v2_transport_serialization.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 72e285cd14..d7c823127f 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -291,6 +291,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/netbase_dns_lookup.cpp \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_serialization.cpp \ + test/fuzz/p2p_v2_transport_serialization.cpp \ test/fuzz/parse_hd_keypath.cpp \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.cpp \ diff --git a/src/crypto/rfc8439.cpp b/src/crypto/rfc8439.cpp index 05f68e5b20..6ff691afa7 100644 --- a/src/crypto/rfc8439.cpp +++ b/src/crypto/rfc8439.cpp @@ -4,7 +4,6 @@ #include -#include #include #include diff --git a/src/crypto/rfc8439.h b/src/crypto/rfc8439.h index 33b543ba4a..fa18e4ff2d 100644 --- a/src/crypto/rfc8439.h +++ b/src/crypto/rfc8439.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_CRYPTO_RFC8439_H #define BITCOIN_CRYPTO_RFC8439_H +#include #include #include @@ -19,4 +20,11 @@ void RFC8439Encrypt(const Span aad, const Span // returns false if authentication fails bool RFC8439Decrypt(const Span aad, const Span key, const std::array& nonce, const Span input, Span plaintext); + +void ComputeRFC8439Tag(const std::array& polykey, + Span aad, Span ciphertext, + Span tag_out); + +std::array GetPoly1305Key(ChaCha20& c20); + #endif // BITCOIN_CRYPTO_RFC8439_H diff --git a/src/test/fuzz/p2p_v2_transport_serialization.cpp b/src/test/fuzz/p2p_v2_transport_serialization.cpp new file mode 100644 index 0000000000..7c6375f8d9 --- /dev/null +++ b/src/test/fuzz/p2p_v2_transport_serialization.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2019-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +FUZZ_TARGET(p2p_v2_transport_serialization) +{ + FuzzedDataProvider fdp{buffer.data(), buffer.size()}; + + // Picking constant keys seems to give us higher fuzz test coverage + // The BIP324 Cipher suite is separately fuzzed, so we don't have to + // pick fuzzed keys here. + BIP324Key key_L, key_P; + memset(key_L.data(), 1, BIP324_KEY_LEN); + memset(key_P.data(), 2, BIP324_KEY_LEN); + + // Construct deserializer, with a dummy NodeId + V2TransportDeserializer deserializer{(NodeId)0, key_L, key_P}; + V2TransportSerializer serializer{key_L, key_P}; + FSChaCha20 fsc20{key_L, REKEY_INTERVAL}; + ChaCha20 c20{reinterpret_cast(key_P.data())}; + + std::array nonce; + memset(nonce.data(), 0, 12); + c20.SetRFC8439Nonce(nonce); + + bool length_assist = fdp.ConsumeBool(); + + // There is no sense in providing a mac assist if the length is incorrect. + bool mac_assist = length_assist && fdp.ConsumeBool(); + auto encrypted_packet = fdp.ConsumeRemainingBytes(); + bool is_decoy_packet{false}; + + if (encrypted_packet.size() >= V2_MIN_PACKET_LENGTH) { + if (length_assist) { + uint32_t contents_len = encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - BIP324_HEADER_LEN - RFC8439_EXPANSION; + contents_len = htole32(contents_len); + fsc20.Crypt({reinterpret_cast(&contents_len), BIP324_LENGTH_FIELD_LEN}, + {reinterpret_cast(encrypted_packet.data()), BIP324_LENGTH_FIELD_LEN}); + } + + if (mac_assist) { + std::array tag; + ComputeRFC8439Tag(GetPoly1305Key(c20), {}, + {reinterpret_cast(encrypted_packet.data()) + BIP324_LENGTH_FIELD_LEN, + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION}, + tag); + memcpy(encrypted_packet.data() + encrypted_packet.size() - RFC8439_EXPANSION, tag.data(), RFC8439_EXPANSION); + + std::vector dec_header_and_contents( + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION); + RFC8439Decrypt({}, key_P, nonce, + {reinterpret_cast(encrypted_packet.data() + BIP324_LENGTH_FIELD_LEN), + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN}, + dec_header_and_contents); + if (BIP324HeaderFlags((uint8_t)dec_header_and_contents.at(0) & BIP324_IGNORE) != BIP324_NONE) { + is_decoy_packet = true; + } + } + } + + Span pkt_bytes{encrypted_packet}; + while (pkt_bytes.size() > 0) { + const int handled = deserializer.Read(pkt_bytes); + if (handled < 0) { + break; + } + if (deserializer.Complete()) { + const std::chrono::microseconds m_time{std::numeric_limits::max()}; + bool reject_message{true}; + bool disconnect{true}; + CNetMessage result{deserializer.GetMessage(m_time, reject_message, disconnect)}; + + if (mac_assist) { + assert(!disconnect); + } + + if (is_decoy_packet) { + assert(reject_message); + } + + if (!reject_message) { + assert(result.m_type.size() <= CMessageHeader::COMMAND_SIZE); + assert(result.m_raw_message_size <= buffer.size()); + + auto message_type_size = result.m_raw_message_size - V2_MIN_PACKET_LENGTH - result.m_message_size; + assert(message_type_size <= 13); + assert(message_type_size >= 1); + assert(result.m_time == m_time); + + std::vector header; + auto msg = CNetMsgMaker{result.m_recv.GetVersion()}.Make(result.m_type, MakeUCharSpan(result.m_recv)); + // if decryption succeeds, encryption must succeed + assert(serializer.prepareForTransport(msg, header)); + } + } + } +}