// Copyright (c) 2020-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 FUZZ_TARGET(crypto_chacha20) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; const auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); ChaCha20 chacha20{key}; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { CallOneOf( fuzzed_data_provider, [&] { auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); chacha20.SetKey(key); }, [&] { chacha20.Seek( { fuzzed_data_provider.ConsumeIntegral(), fuzzed_data_provider.ConsumeIntegral() }, fuzzed_data_provider.ConsumeIntegral()); }, [&] { std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); chacha20.Keystream(MakeWritableByteSpan(output)); }, [&] { std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); const auto input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); chacha20.Crypt(input, output); }); } } namespace { /** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times: once for a large block at once, and then the same data in chunks, comparing the outcome. If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt(). If not, Keystream() is used directly, or sequences of 0x00 are encrypted. */ template void ChaCha20SplitFuzz(FuzzedDataProvider& provider) { // Determine key, iv, start position, length. auto key_bytes = ConsumeFixedLengthByteVector(provider, ChaCha20::KEYLEN); uint64_t iv = provider.ConsumeIntegral(); uint32_t iv_prefix = provider.ConsumeIntegral(); uint64_t total_bytes = provider.ConsumeIntegralInRange(0, 1000000); /* ~x = 2^BITS - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ uint32_t seek = provider.ConsumeIntegralInRange(0, ~(uint32_t)(total_bytes >> 6)); // Initialize two ChaCha20 ciphers, with the same key/iv/position. ChaCha20 crypt1(key_bytes); ChaCha20 crypt2(key_bytes); crypt1.Seek({iv_prefix, iv}, seek); crypt2.Seek({iv_prefix, iv}, seek); // Construct vectors with data. std::vector data1, data2; data1.resize(total_bytes); data2.resize(total_bytes); // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based // stream. if constexpr (UseCrypt) { uint64_t seed = provider.ConsumeIntegral(); XoRoShiRo128PlusPlus rng(seed); uint64_t bytes = 0; while (bytes < (total_bytes & ~uint64_t{7})) { uint64_t val = rng(); WriteLE64(UCharCast(data1.data() + bytes), val); WriteLE64(UCharCast(data2.data() + bytes), val); bytes += 8; } if (bytes < total_bytes) { std::byte valbytes[8]; uint64_t val = rng(); WriteLE64(UCharCast(valbytes), val); std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); } } // Whether UseCrypt is used or not, the two byte arrays must match. assert(data1 == data2); // Encrypt data1, the whole array at once. if constexpr (UseCrypt) { crypt1.Crypt(data1, data1); } else { crypt1.Keystream(data1); } // Encrypt data2, in at most 256 chunks. uint64_t bytes2 = 0; int iter = 0; while (true) { bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool(); ++iter; // Determine how many bytes to encrypt in this chunk: a fuzzer-determined // amount for all but the last chunk (which processes all remaining bytes). uint64_t now = is_last ? total_bytes - bytes2 : provider.ConsumeIntegralInRange(0, total_bytes - bytes2); // For each chunk, consider using Crypt() even when UseCrypt is false. // This tests that Keystream() has the same behavior as Crypt() applied // to 0x00 input bytes. if (UseCrypt || provider.ConsumeBool()) { crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now)); } else { crypt2.Keystream(Span{data2}.subspan(bytes2, now)); } bytes2 += now; if (is_last) break; } // We should have processed everything now. assert(bytes2 == total_bytes); // And the result should match. assert(data1 == data2); } } // namespace FUZZ_TARGET(chacha20_split_crypt) { FuzzedDataProvider provider{buffer.data(), buffer.size()}; ChaCha20SplitFuzz(provider); } FUZZ_TARGET(chacha20_split_keystream) { FuzzedDataProvider provider{buffer.data(), buffer.size()}; ChaCha20SplitFuzz(provider); } FUZZ_TARGET(crypto_fschacha20) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; auto key = fuzzed_data_provider.ConsumeBytes(FSChaCha20::KEYLEN); key.resize(FSChaCha20::KEYLEN); auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange(1, 1024)}; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { auto input = fuzzed_data_provider.ConsumeBytes(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); std::vector output; output.resize(input.size()); fsc20.Crypt(input, output); } }