Skip to content

Commit 38eaece

Browse files
committed
Add fuzz test for testing that ChaCha20 works as a stream
1 parent 5f05b27 commit 38eaece

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

src/test/fuzz/crypto_chacha20.cpp

+108
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <test/fuzz/FuzzedDataProvider.h>
77
#include <test/fuzz/fuzz.h>
88
#include <test/fuzz/util.h>
9+
#include <test/util/xoroshiro128plusplus.h>
910

1011
#include <cstdint>
1112
#include <vector>
@@ -43,3 +44,110 @@ FUZZ_TARGET(crypto_chacha20)
4344
});
4445
}
4546
}
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 Xoroshiro128++ 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+
void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
60+
{
61+
// Determine key, iv, start position, length.
62+
unsigned char key[32] = {0};
63+
auto key_bytes = provider.ConsumeBytes<unsigned char>(32);
64+
std::copy(key_bytes.begin(), key_bytes.end(), key);
65+
uint64_t iv = provider.ConsumeIntegral<uint64_t>();
66+
uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
67+
/* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */
68+
uint64_t seek = provider.ConsumeIntegralInRange<uint64_t>(0, ~(total_bytes >> 6));
69+
70+
// Initialize two ChaCha20 ciphers, with the same key/iv/position.
71+
ChaCha20 crypt1(key, 32);
72+
ChaCha20 crypt2(key, 32);
73+
crypt1.SetIV(iv);
74+
crypt1.Seek64(seek);
75+
crypt2.SetIV(iv);
76+
crypt2.Seek64(seek);
77+
78+
// Construct vectors with data.
79+
std::vector<unsigned char> data1, data2;
80+
data1.resize(total_bytes);
81+
data2.resize(total_bytes);
82+
83+
// If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based
84+
// stream.
85+
if constexpr (UseCrypt) {
86+
uint64_t seed = provider.ConsumeIntegral<uint64_t>();
87+
XoRoShiRo128PlusPlus rng(seed);
88+
uint64_t bytes = 0;
89+
while (bytes < (total_bytes & ~uint64_t{7})) {
90+
uint64_t val = rng();
91+
WriteLE64(data1.data() + bytes, val);
92+
WriteLE64(data2.data() + bytes, val);
93+
bytes += 8;
94+
}
95+
if (bytes < total_bytes) {
96+
unsigned char valbytes[8];
97+
uint64_t val = rng();
98+
WriteLE64(valbytes, val);
99+
std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
100+
std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
101+
}
102+
}
103+
104+
// Whether UseCrypt is used or not, the two byte arrays must match.
105+
assert(data1 == data2);
106+
107+
// Encrypt data1, the whole array at once.
108+
if constexpr (UseCrypt) {
109+
crypt1.Crypt(data1.data(), data1.data(), total_bytes);
110+
} else {
111+
crypt1.Keystream(data1.data(), total_bytes);
112+
}
113+
114+
// Encrypt data2, in at most 256 chunks.
115+
uint64_t bytes2 = 0;
116+
int iter = 0;
117+
while (true) {
118+
bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool();
119+
++iter;
120+
// Determine how many bytes to encrypt in this chunk: a fuzzer-determined
121+
// amount for all but the last chunk (which processes all remaining bytes).
122+
uint64_t now = is_last ? total_bytes - bytes2 :
123+
provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2);
124+
// For each chunk, consider using Crypt() even when UseCrypt is false.
125+
// This tests that Keystream() has the same behavior as Crypt() applied
126+
// to 0x00 input bytes.
127+
if (UseCrypt || provider.ConsumeBool()) {
128+
crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now);
129+
} else {
130+
crypt2.Keystream(data2.data() + bytes2, now);
131+
}
132+
bytes2 += now;
133+
if (is_last) break;
134+
}
135+
// We should have processed everything now.
136+
assert(bytes2 == total_bytes);
137+
// And the result should match.
138+
assert(data1 == data2);
139+
}
140+
141+
} // namespace
142+
143+
FUZZ_TARGET(chacha20_split_crypt)
144+
{
145+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
146+
ChaCha20SplitFuzz<true>(provider);
147+
}
148+
149+
FUZZ_TARGET(chacha20_split_keystream)
150+
{
151+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
152+
ChaCha20SplitFuzz<false>(provider);
153+
}

0 commit comments

Comments
 (0)