Branch data 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 [ + - ][ + - ]: 6 : 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 : : 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 : 8 : [&] {
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 [ + - ][ + - ]: 6 : FUZZ_TARGET(chacha20_split_crypt)
142 : : {
143 : 0 : FuzzedDataProvider provider{buffer.data(), buffer.size()};
144 : 0 : ChaCha20SplitFuzz<true>(provider);
145 : 0 : }
146 : :
147 [ + - ][ + - ]: 6 : FUZZ_TARGET(chacha20_split_keystream)
148 : : {
149 : 0 : FuzzedDataProvider provider{buffer.data(), buffer.size()};
150 : 0 : ChaCha20SplitFuzz<false>(provider);
151 : 0 : }
152 : :
153 [ + - ][ + - ]: 6 : 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 : }
|