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 : }
|