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