LCOV - code coverage report
Current view: top level - src/test/fuzz - crypto_chacha20.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 5 95 5.3 %
Date: 2023-09-26 12:08:55 Functions: 8 18 44.4 %

          Line data    Source code
       1             : // Copyright (c) 2020-2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <crypto/chacha20.h>
       6             : #include <test/fuzz/FuzzedDataProvider.h>
       7             : #include <test/fuzz/fuzz.h>
       8             : #include <test/fuzz/util.h>
       9             : #include <test/util/xoroshiro128plusplus.h>
      10             : 
      11             : #include <array>
      12             : #include <cstddef>
      13             : #include <cstdint>
      14             : #include <vector>
      15             : 
      16           4 : FUZZ_TARGET(crypto_chacha20)
      17             : {
      18           0 :     FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
      19             : 
      20           0 :     const auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
      21           0 :     ChaCha20 chacha20{key};
      22             : 
      23           0 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
      24           0 :         CallOneOf(
      25           8 :             fuzzed_data_provider,
      26           0 :             [&] {
      27           0 :                 auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
      28           0 :                 chacha20.SetKey(key);
      29           0 :             },
      30           0 :             [&] {
      31           0 :                 chacha20.Seek(
      32           0 :                     {
      33           0 :                         fuzzed_data_provider.ConsumeIntegral<uint32_t>(),
      34           0 :                         fuzzed_data_provider.ConsumeIntegral<uint64_t>()
      35           0 :                     }, fuzzed_data_provider.ConsumeIntegral<uint32_t>());
      36           0 :             },
      37           0 :             [&] {
      38           0 :                 std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
      39           0 :                 chacha20.Keystream(MakeWritableByteSpan(output));
      40           0 :             },
      41           0 :             [&] {
      42           0 :                 std::vector<std::byte> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
      43           0 :                 const auto input = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, output.size());
      44           0 :                 chacha20.Crypt(input, output);
      45           0 :             });
      46           0 :     }
      47           0 : }
      48             : 
      49             : namespace
      50             : {
      51             : 
      52             : /** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times:
      53             :     once for a large block at once, and then the same data in chunks, comparing
      54             :     the outcome.
      55             : 
      56             :     If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt().
      57             :     If not, Keystream() is used directly, or sequences of 0x00 are encrypted.
      58             : */
      59             : template<bool UseCrypt>
      60           0 : void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
      61             : {
      62             :     // Determine key, iv, start position, length.
      63           0 :     auto key_bytes = ConsumeFixedLengthByteVector<std::byte>(provider, ChaCha20::KEYLEN);
      64           0 :     uint64_t iv = provider.ConsumeIntegral<uint64_t>();
      65           0 :     uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>();
      66           0 :     uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
      67             :     /* ~x = 2^BITS - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */
      68           0 :     uint32_t seek = provider.ConsumeIntegralInRange<uint32_t>(0, ~(uint32_t)(total_bytes >> 6));
      69             : 
      70             :     // Initialize two ChaCha20 ciphers, with the same key/iv/position.
      71           0 :     ChaCha20 crypt1(key_bytes);
      72           0 :     ChaCha20 crypt2(key_bytes);
      73           0 :     crypt1.Seek({iv_prefix, iv}, seek);
      74           0 :     crypt2.Seek({iv_prefix, iv}, seek);
      75             : 
      76             :     // Construct vectors with data.
      77           0 :     std::vector<std::byte> data1, data2;
      78           0 :     data1.resize(total_bytes);
      79           0 :     data2.resize(total_bytes);
      80             : 
      81             :     // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based
      82             :     // stream.
      83             :     if constexpr (UseCrypt) {
      84           0 :         uint64_t seed = provider.ConsumeIntegral<uint64_t>();
      85           0 :         XoRoShiRo128PlusPlus rng(seed);
      86           0 :         uint64_t bytes = 0;
      87           0 :         while (bytes < (total_bytes & ~uint64_t{7})) {
      88           0 :             uint64_t val = rng();
      89           0 :             WriteLE64(UCharCast(data1.data() + bytes), val);
      90           0 :             WriteLE64(UCharCast(data2.data() + bytes), val);
      91           0 :             bytes += 8;
      92             :         }
      93           0 :         if (bytes < total_bytes) {
      94             :             std::byte valbytes[8];
      95           0 :             uint64_t val = rng();
      96           0 :             WriteLE64(UCharCast(valbytes), val);
      97           0 :             std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
      98           0 :             std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
      99           0 :         }
     100             :     }
     101             : 
     102             :     // Whether UseCrypt is used or not, the two byte arrays must match.
     103           0 :     assert(data1 == data2);
     104             : 
     105             :     // Encrypt data1, the whole array at once.
     106             :     if constexpr (UseCrypt) {
     107           0 :         crypt1.Crypt(data1, data1);
     108             :     } else {
     109           0 :         crypt1.Keystream(data1);
     110             :     }
     111             : 
     112             :     // Encrypt data2, in at most 256 chunks.
     113           0 :     uint64_t bytes2 = 0;
     114           0 :     int iter = 0;
     115           0 :     while (true) {
     116           0 :         bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool();
     117           0 :         ++iter;
     118             :         // Determine how many bytes to encrypt in this chunk: a fuzzer-determined
     119             :         // amount for all but the last chunk (which processes all remaining bytes).
     120           0 :         uint64_t now = is_last ? total_bytes - bytes2 :
     121           0 :             provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2);
     122             :         // For each chunk, consider using Crypt() even when UseCrypt is false.
     123             :         // This tests that Keystream() has the same behavior as Crypt() applied
     124             :         // to 0x00 input bytes.
     125           0 :         if (UseCrypt || provider.ConsumeBool()) {
     126           0 :             crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now));
     127           0 :         } else {
     128           0 :             crypt2.Keystream(Span{data2}.subspan(bytes2, now));
     129             :         }
     130           0 :         bytes2 += now;
     131           0 :         if (is_last) break;
     132             :     }
     133             :     // We should have processed everything now.
     134           0 :     assert(bytes2 == total_bytes);
     135             :     // And the result should match.
     136           0 :     assert(data1 == data2);
     137           0 : }
     138             : 
     139             : } // namespace
     140             : 
     141           4 : FUZZ_TARGET(chacha20_split_crypt)
     142             : {
     143           0 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     144           0 :     ChaCha20SplitFuzz<true>(provider);
     145           0 : }
     146             : 
     147           4 : FUZZ_TARGET(chacha20_split_keystream)
     148             : {
     149           0 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     150           0 :     ChaCha20SplitFuzz<false>(provider);
     151           0 : }
     152             : 
     153           4 : FUZZ_TARGET(crypto_fschacha20)
     154             : {
     155           0 :     FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
     156             : 
     157           0 :     auto key = fuzzed_data_provider.ConsumeBytes<std::byte>(FSChaCha20::KEYLEN);
     158           0 :     key.resize(FSChaCha20::KEYLEN);
     159             : 
     160           0 :     auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 1024)};
     161             : 
     162           0 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
     163             :     {
     164           0 :         auto input = fuzzed_data_provider.ConsumeBytes<std::byte>(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096));
     165           0 :         std::vector<std::byte> output;
     166           0 :         output.resize(input.size());
     167           0 :         fsc20.Crypt(input, output);
     168           0 :     }
     169           0 : }

Generated by: LCOV version 1.14