/src/lz4/ossfuzz/round_trip_stream_fuzzer.c
Line | Count | Source |
1 | | /** |
2 | | * This fuzz target performs a lz4 streaming round-trip test |
3 | | * (compress & decompress), compares the result with the original, and calls |
4 | | * abort() on corruption. |
5 | | */ |
6 | | |
7 | | #include <stddef.h> |
8 | | #include <stdint.h> |
9 | | #include <stdlib.h> |
10 | | #include <string.h> |
11 | | |
12 | | #include "fuzz_helpers.h" |
13 | | #define LZ4_STATIC_LINKING_ONLY |
14 | | #include "lz4.h" |
15 | | #define LZ4_HC_STATIC_LINKING_ONLY |
16 | | #include "lz4hc.h" |
17 | | |
18 | | typedef struct { |
19 | | char const* buf; |
20 | | size_t size; |
21 | | size_t pos; |
22 | | } const_cursor_t; |
23 | | |
24 | | typedef struct { |
25 | | char* buf; |
26 | | size_t size; |
27 | | size_t pos; |
28 | | } cursor_t; |
29 | | |
30 | | typedef struct { |
31 | | LZ4_stream_t* cstream; |
32 | | LZ4_streamHC_t* cstreamHC; |
33 | | LZ4_streamDecode_t* dstream; |
34 | | const_cursor_t data; |
35 | | cursor_t compressed; |
36 | | cursor_t roundTrip; |
37 | | uint32_t seed; |
38 | | int level; |
39 | | } state_t; |
40 | | |
41 | | cursor_t cursor_create(size_t size) |
42 | 98.4k | { |
43 | 98.4k | cursor_t cursor; |
44 | 98.4k | cursor.buf = (char*)malloc(size); |
45 | 98.4k | cursor.size = size; |
46 | 98.4k | cursor.pos = 0; |
47 | 98.4k | FUZZ_ASSERT(cursor.buf); |
48 | 98.4k | return cursor; |
49 | 98.4k | } |
50 | | |
51 | | typedef void (*round_trip_t)(state_t* state); |
52 | | |
53 | | void cursor_free(cursor_t cursor) |
54 | 98.4k | { |
55 | 98.4k | free(cursor.buf); |
56 | 98.4k | } |
57 | | |
58 | | state_t state_create(char const* data, size_t size, uint32_t seed) |
59 | 15.4k | { |
60 | 15.4k | state_t state; |
61 | | |
62 | 15.4k | state.seed = seed; |
63 | | |
64 | 15.4k | state.data.buf = (char const*)data; |
65 | 15.4k | state.data.size = size; |
66 | 15.4k | state.data.pos = 0; |
67 | | |
68 | | /* Extra margin because we are streaming. */ |
69 | 15.4k | state.compressed = cursor_create(1024 + 2 * LZ4_compressBound(size)); |
70 | 15.4k | state.roundTrip = cursor_create(size); |
71 | | |
72 | 15.4k | state.cstream = LZ4_createStream(); |
73 | 15.4k | FUZZ_ASSERT(state.cstream); |
74 | 15.4k | state.cstreamHC = LZ4_createStreamHC(); |
75 | 15.4k | FUZZ_ASSERT(state.cstream); |
76 | 15.4k | state.dstream = LZ4_createStreamDecode(); |
77 | 15.4k | FUZZ_ASSERT(state.dstream); |
78 | | |
79 | 15.4k | return state; |
80 | 15.4k | } |
81 | | |
82 | | void state_free(state_t state) |
83 | 15.4k | { |
84 | 15.4k | cursor_free(state.compressed); |
85 | 15.4k | cursor_free(state.roundTrip); |
86 | 15.4k | LZ4_freeStream(state.cstream); |
87 | 15.4k | LZ4_freeStreamHC(state.cstreamHC); |
88 | 15.4k | LZ4_freeStreamDecode(state.dstream); |
89 | 15.4k | } |
90 | | |
91 | | static void state_reset(state_t* state, uint32_t seed) |
92 | 123k | { |
93 | 123k | state->level = FUZZ_rand32(&seed, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX); |
94 | 123k | LZ4_resetStream_fast(state->cstream); |
95 | 123k | LZ4_resetStreamHC_fast(state->cstreamHC, state->level); |
96 | 123k | LZ4_setStreamDecode(state->dstream, NULL, 0); |
97 | 123k | state->data.pos = 0; |
98 | 123k | state->compressed.pos = 0; |
99 | 123k | state->roundTrip.pos = 0; |
100 | 123k | state->seed = seed; |
101 | 123k | } |
102 | | |
103 | | static void state_decompress(state_t* state, char const* src, int srcSize) |
104 | 673k | { |
105 | 673k | char* dst = state->roundTrip.buf + state->roundTrip.pos; |
106 | 673k | int const dstCapacity = state->roundTrip.size - state->roundTrip.pos; |
107 | 673k | int const dSize = LZ4_decompress_safe_continue(state->dstream, src, dst, |
108 | 673k | srcSize, dstCapacity); |
109 | 673k | FUZZ_ASSERT(dSize >= 0); |
110 | 673k | state->roundTrip.pos += dSize; |
111 | 673k | } |
112 | | |
113 | | static void state_checkRoundTrip(state_t const* state) |
114 | 123k | { |
115 | 123k | char const* data = state->data.buf; |
116 | 123k | size_t const size = state->data.size; |
117 | 123k | FUZZ_ASSERT_MSG(size == state->roundTrip.pos, "Incorrect size!"); |
118 | 123k | FUZZ_ASSERT_MSG(!memcmp(data, state->roundTrip.buf, size), "Corruption!"); |
119 | 123k | } |
120 | | |
121 | | /** |
122 | | * Picks a dictionary size and trims the dictionary off of the data. |
123 | | * We copy the dictionary to the roundTrip so our validation passes. |
124 | | */ |
125 | | static size_t state_trimDict(state_t* state) |
126 | 61.7k | { |
127 | | /* 64 KB is the max dict size, allow slightly beyond that to test trim. */ |
128 | 61.7k | uint32_t maxDictSize = MIN(70 * 1024, state->data.size); |
129 | 61.7k | size_t const dictSize = FUZZ_rand32(&state->seed, 0, maxDictSize); |
130 | 61.7k | DEBUGLOG(2, "dictSize = %zu", dictSize); |
131 | 61.7k | FUZZ_ASSERT(state->data.pos == 0); |
132 | 61.7k | FUZZ_ASSERT(state->roundTrip.pos == 0); |
133 | 61.7k | memcpy(state->roundTrip.buf, state->data.buf, dictSize); |
134 | 61.7k | state->data.pos += dictSize; |
135 | 61.7k | state->roundTrip.pos += dictSize; |
136 | 61.7k | return dictSize; |
137 | 61.7k | } |
138 | | |
139 | | static void state_prefixRoundTrip(state_t* state) |
140 | 28.0k | { |
141 | 185k | while (state->data.pos != state->data.size) { |
142 | 157k | char const* src = state->data.buf + state->data.pos; |
143 | 157k | char* dst = state->compressed.buf + state->compressed.pos; |
144 | 157k | int const srcRemaining = state->data.size - state->data.pos; |
145 | 157k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
146 | 157k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
147 | 157k | int const cSize = LZ4_compress_fast_continue(state->cstream, src, dst, |
148 | 157k | srcSize, dstCapacity, 0); |
149 | 157k | FUZZ_ASSERT(cSize > 0); |
150 | 157k | state->data.pos += srcSize; |
151 | 157k | state->compressed.pos += cSize; |
152 | 157k | state_decompress(state, dst, cSize); |
153 | 157k | } |
154 | 28.0k | } |
155 | | |
156 | | static void state_extDictRoundTrip(state_t* state) |
157 | 33.7k | { |
158 | 33.7k | int i = 0; |
159 | 33.7k | cursor_t data2 = cursor_create(state->data.size); |
160 | 33.7k | memcpy(data2.buf, state->data.buf, state->data.size); |
161 | 212k | while (state->data.pos != state->data.size) { |
162 | 178k | char const* data = (i++ & 1) ? state->data.buf : data2.buf; |
163 | 178k | char const* src = data + state->data.pos; |
164 | 178k | char* dst = state->compressed.buf + state->compressed.pos; |
165 | 178k | int const srcRemaining = state->data.size - state->data.pos; |
166 | 178k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
167 | 178k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
168 | 178k | int const cSize = LZ4_compress_fast_continue(state->cstream, src, dst, |
169 | 178k | srcSize, dstCapacity, 0); |
170 | 178k | FUZZ_ASSERT(cSize > 0); |
171 | 178k | state->data.pos += srcSize; |
172 | 178k | state->compressed.pos += cSize; |
173 | 178k | state_decompress(state, dst, cSize); |
174 | 178k | } |
175 | 33.7k | cursor_free(data2); |
176 | 33.7k | } |
177 | | |
178 | | static void state_randomRoundTrip(state_t* state, round_trip_t rt0, |
179 | | round_trip_t rt1) |
180 | 61.7k | { |
181 | 61.7k | if (FUZZ_rand32(&state->seed, 0, 1)) { |
182 | 25.1k | rt0(state); |
183 | 36.6k | } else { |
184 | 36.6k | rt1(state); |
185 | 36.6k | } |
186 | 61.7k | } |
187 | | |
188 | | static void state_loadDictRoundTrip(state_t* state) |
189 | 15.4k | { |
190 | 15.4k | char const* dict = state->data.buf; |
191 | 15.4k | size_t const dictSize = state_trimDict(state); |
192 | 15.4k | LZ4_loadDict(state->cstream, dict, dictSize); |
193 | 15.4k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
194 | 15.4k | state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip); |
195 | 15.4k | } |
196 | | |
197 | | static void state_attachDictRoundTrip(state_t* state) |
198 | 15.4k | { |
199 | 15.4k | char const* dict = state->data.buf; |
200 | 15.4k | size_t const dictSize = state_trimDict(state); |
201 | 15.4k | LZ4_stream_t* dictStream = LZ4_createStream(); |
202 | 15.4k | LZ4_loadDict(dictStream, dict, dictSize); |
203 | 15.4k | LZ4_attach_dictionary(state->cstream, dictStream); |
204 | 15.4k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
205 | 15.4k | state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip); |
206 | 15.4k | LZ4_freeStream(dictStream); |
207 | 15.4k | } |
208 | | |
209 | | static void state_prefixHCRoundTrip(state_t* state) |
210 | 28.0k | { |
211 | 185k | while (state->data.pos != state->data.size) { |
212 | 157k | char const* src = state->data.buf + state->data.pos; |
213 | 157k | char* dst = state->compressed.buf + state->compressed.pos; |
214 | 157k | int const srcRemaining = state->data.size - state->data.pos; |
215 | 157k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
216 | 157k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
217 | 157k | int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst, |
218 | 157k | srcSize, dstCapacity); |
219 | 157k | FUZZ_ASSERT(cSize > 0); |
220 | 157k | state->data.pos += srcSize; |
221 | 157k | state->compressed.pos += cSize; |
222 | 157k | state_decompress(state, dst, cSize); |
223 | 157k | } |
224 | 28.0k | } |
225 | | |
226 | | static void state_extDictHCRoundTrip(state_t* state) |
227 | 33.7k | { |
228 | 33.7k | int i = 0; |
229 | 33.7k | cursor_t data2 = cursor_create(state->data.size); |
230 | 33.7k | DEBUGLOG(2, "extDictHC"); |
231 | 33.7k | memcpy(data2.buf, state->data.buf, state->data.size); |
232 | 212k | while (state->data.pos != state->data.size) { |
233 | 178k | char const* data = (i++ & 1) ? state->data.buf : data2.buf; |
234 | 178k | char const* src = data + state->data.pos; |
235 | 178k | char* dst = state->compressed.buf + state->compressed.pos; |
236 | 178k | int const srcRemaining = state->data.size - state->data.pos; |
237 | 178k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
238 | 178k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
239 | 178k | int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst, |
240 | 178k | srcSize, dstCapacity); |
241 | 178k | FUZZ_ASSERT(cSize > 0); |
242 | 178k | DEBUGLOG(2, "srcSize = %d", srcSize); |
243 | 178k | state->data.pos += srcSize; |
244 | 178k | state->compressed.pos += cSize; |
245 | 178k | state_decompress(state, dst, cSize); |
246 | 178k | } |
247 | 33.7k | cursor_free(data2); |
248 | 33.7k | } |
249 | | |
250 | | static void state_loadDictHCRoundTrip(state_t* state) |
251 | 15.4k | { |
252 | 15.4k | char const* dict = state->data.buf; |
253 | 15.4k | size_t const dictSize = state_trimDict(state); |
254 | 15.4k | LZ4_loadDictHC(state->cstreamHC, dict, dictSize); |
255 | 15.4k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
256 | 15.4k | state_randomRoundTrip(state, state_prefixHCRoundTrip, |
257 | 15.4k | state_extDictHCRoundTrip); |
258 | 15.4k | } |
259 | | |
260 | | static void state_attachDictHCRoundTrip(state_t* state) |
261 | 15.4k | { |
262 | 15.4k | char const* dict = state->data.buf; |
263 | 15.4k | size_t const dictSize = state_trimDict(state); |
264 | 15.4k | LZ4_streamHC_t* dictStream = LZ4_createStreamHC(); |
265 | 15.4k | LZ4_setCompressionLevel(dictStream, state->level); |
266 | 15.4k | LZ4_loadDictHC(dictStream, dict, dictSize); |
267 | 15.4k | LZ4_attach_HC_dictionary(state->cstreamHC, dictStream); |
268 | 15.4k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
269 | 15.4k | state_randomRoundTrip(state, state_prefixHCRoundTrip, |
270 | 15.4k | state_extDictHCRoundTrip); |
271 | 15.4k | LZ4_freeStreamHC(dictStream); |
272 | 15.4k | } |
273 | | |
274 | | round_trip_t roundTrips[] = { |
275 | | &state_prefixRoundTrip, |
276 | | &state_extDictRoundTrip, |
277 | | &state_loadDictRoundTrip, |
278 | | &state_attachDictRoundTrip, |
279 | | &state_prefixHCRoundTrip, |
280 | | &state_extDictHCRoundTrip, |
281 | | &state_loadDictHCRoundTrip, |
282 | | &state_attachDictHCRoundTrip, |
283 | | }; |
284 | | |
285 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
286 | 15.4k | { |
287 | 15.4k | uint32_t seed = FUZZ_seed(&data, &size); |
288 | 15.4k | state_t state = state_create((char const*)data, size, seed); |
289 | 15.4k | const int n = sizeof(roundTrips) / sizeof(round_trip_t); |
290 | 15.4k | int i; |
291 | | |
292 | 138k | for (i = 0; i < n; ++i) { |
293 | 123k | DEBUGLOG(2, "Round trip %d", i); |
294 | 123k | state_reset(&state, seed); |
295 | 123k | roundTrips[i](&state); |
296 | 123k | state_checkRoundTrip(&state); |
297 | 123k | } |
298 | | |
299 | 15.4k | state_free(state); |
300 | | |
301 | 15.4k | return 0; |
302 | 15.4k | } |