/ src / test / fuzz / crypto_chacha20.cpp
crypto_chacha20.cpp
  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  FUZZ_TARGET(crypto_chacha20)
 17  {
 18      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
 19  
 20      const auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
 21      ChaCha20 chacha20{key};
 22  
 23      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
 24          CallOneOf(
 25              fuzzed_data_provider,
 26              [&] {
 27                  auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
 28                  chacha20.SetKey(key);
 29              },
 30              [&] {
 31                  chacha20.Seek(
 32                      {
 33                          fuzzed_data_provider.ConsumeIntegral<uint32_t>(),
 34                          fuzzed_data_provider.ConsumeIntegral<uint64_t>()
 35                      }, fuzzed_data_provider.ConsumeIntegral<uint32_t>());
 36              },
 37              [&] {
 38                  std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
 39                  chacha20.Keystream(MakeWritableByteSpan(output));
 40              },
 41              [&] {
 42                  std::vector<std::byte> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
 43                  const auto input = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, output.size());
 44                  chacha20.Crypt(input, output);
 45              });
 46      }
 47  }
 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  void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
 61  {
 62      // Determine key, iv, start position, length.
 63      auto key_bytes = ConsumeFixedLengthByteVector<std::byte>(provider, ChaCha20::KEYLEN);
 64      uint64_t iv = provider.ConsumeIntegral<uint64_t>();
 65      uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>();
 66      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      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      ChaCha20 crypt1(key_bytes);
 72      ChaCha20 crypt2(key_bytes);
 73      crypt1.Seek({iv_prefix, iv}, seek);
 74      crypt2.Seek({iv_prefix, iv}, seek);
 75  
 76      // Construct vectors with data.
 77      std::vector<std::byte> data1, data2;
 78      data1.resize(total_bytes);
 79      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          uint64_t seed = provider.ConsumeIntegral<uint64_t>();
 85          XoRoShiRo128PlusPlus rng(seed);
 86          uint64_t bytes = 0;
 87          while (bytes < (total_bytes & ~uint64_t{7})) {
 88              uint64_t val = rng();
 89              WriteLE64(UCharCast(data1.data() + bytes), val);
 90              WriteLE64(UCharCast(data2.data() + bytes), val);
 91              bytes += 8;
 92          }
 93          if (bytes < total_bytes) {
 94              std::byte valbytes[8];
 95              uint64_t val = rng();
 96              WriteLE64(UCharCast(valbytes), val);
 97              std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
 98              std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
 99          }
100      }
101  
102      // Whether UseCrypt is used or not, the two byte arrays must match.
103      assert(data1 == data2);
104  
105      // Encrypt data1, the whole array at once.
106      if constexpr (UseCrypt) {
107          crypt1.Crypt(data1, data1);
108      } else {
109          crypt1.Keystream(data1);
110      }
111  
112      // Encrypt data2, in at most 256 chunks.
113      uint64_t bytes2 = 0;
114      int iter = 0;
115      while (true) {
116          bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool();
117          ++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          uint64_t now = is_last ? total_bytes - bytes2 :
121              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          if (UseCrypt || provider.ConsumeBool()) {
126              crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now));
127          } else {
128              crypt2.Keystream(Span{data2}.subspan(bytes2, now));
129          }
130          bytes2 += now;
131          if (is_last) break;
132      }
133      // We should have processed everything now.
134      assert(bytes2 == total_bytes);
135      // And the result should match.
136      assert(data1 == data2);
137  }
138  
139  } // namespace
140  
141  FUZZ_TARGET(chacha20_split_crypt)
142  {
143      FuzzedDataProvider provider{buffer.data(), buffer.size()};
144      ChaCha20SplitFuzz<true>(provider);
145  }
146  
147  FUZZ_TARGET(chacha20_split_keystream)
148  {
149      FuzzedDataProvider provider{buffer.data(), buffer.size()};
150      ChaCha20SplitFuzz<false>(provider);
151  }
152  
153  FUZZ_TARGET(crypto_fschacha20)
154  {
155      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
156  
157      auto key = fuzzed_data_provider.ConsumeBytes<std::byte>(FSChaCha20::KEYLEN);
158      key.resize(FSChaCha20::KEYLEN);
159  
160      auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 1024)};
161  
162      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
163      {
164          auto input = fuzzed_data_provider.ConsumeBytes<std::byte>(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096));
165          std::vector<std::byte> output;
166          output.resize(input.size());
167          fsc20.Crypt(input, output);
168      }
169  }