/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 | 89.3k | { |
43 | 89.3k | cursor_t cursor; |
44 | 89.3k | cursor.buf = (char*)malloc(size); |
45 | 89.3k | cursor.size = size; |
46 | 89.3k | cursor.pos = 0; |
47 | 89.3k | FUZZ_ASSERT(cursor.buf); |
48 | 89.3k | return cursor; |
49 | 89.3k | } |
50 | | |
51 | | typedef void (*round_trip_t)(state_t* state); |
52 | | |
53 | | void cursor_free(cursor_t cursor) |
54 | 89.3k | { |
55 | 89.3k | free(cursor.buf); |
56 | 89.3k | } |
57 | | |
58 | | state_t state_create(char const* data, size_t size, uint32_t seed) |
59 | 14.0k | { |
60 | 14.0k | state_t state; |
61 | | |
62 | 14.0k | state.seed = seed; |
63 | | |
64 | 14.0k | state.data.buf = (char const*)data; |
65 | 14.0k | state.data.size = size; |
66 | 14.0k | state.data.pos = 0; |
67 | | |
68 | | /* Extra margin because we are streaming. */ |
69 | 14.0k | state.compressed = cursor_create(1024 + 2 * LZ4_compressBound(size)); |
70 | 14.0k | state.roundTrip = cursor_create(size); |
71 | | |
72 | 14.0k | state.cstream = LZ4_createStream(); |
73 | 14.0k | FUZZ_ASSERT(state.cstream); |
74 | 14.0k | state.cstreamHC = LZ4_createStreamHC(); |
75 | 14.0k | FUZZ_ASSERT(state.cstream); |
76 | 14.0k | state.dstream = LZ4_createStreamDecode(); |
77 | 14.0k | FUZZ_ASSERT(state.dstream); |
78 | | |
79 | 14.0k | return state; |
80 | 14.0k | } |
81 | | |
82 | | void state_free(state_t state) |
83 | 14.0k | { |
84 | 14.0k | cursor_free(state.compressed); |
85 | 14.0k | cursor_free(state.roundTrip); |
86 | 14.0k | LZ4_freeStream(state.cstream); |
87 | 14.0k | LZ4_freeStreamHC(state.cstreamHC); |
88 | 14.0k | LZ4_freeStreamDecode(state.dstream); |
89 | 14.0k | } |
90 | | |
91 | | static void state_reset(state_t* state, uint32_t seed) |
92 | 112k | { |
93 | 112k | state->level = FUZZ_rand32(&seed, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX); |
94 | 112k | LZ4_resetStream_fast(state->cstream); |
95 | 112k | LZ4_resetStreamHC_fast(state->cstreamHC, state->level); |
96 | 112k | LZ4_setStreamDecode(state->dstream, NULL, 0); |
97 | 112k | state->data.pos = 0; |
98 | 112k | state->compressed.pos = 0; |
99 | 112k | state->roundTrip.pos = 0; |
100 | 112k | state->seed = seed; |
101 | 112k | } |
102 | | |
103 | | static void state_decompress(state_t* state, char const* src, int srcSize) |
104 | 633k | { |
105 | 633k | char* dst = state->roundTrip.buf + state->roundTrip.pos; |
106 | 633k | int const dstCapacity = state->roundTrip.size - state->roundTrip.pos; |
107 | 633k | int const dSize = LZ4_decompress_safe_continue(state->dstream, src, dst, |
108 | 633k | srcSize, dstCapacity); |
109 | 633k | FUZZ_ASSERT(dSize >= 0); |
110 | 633k | state->roundTrip.pos += dSize; |
111 | 633k | } |
112 | | |
113 | | static void state_checkRoundTrip(state_t const* state) |
114 | 112k | { |
115 | 112k | char const* data = state->data.buf; |
116 | 112k | size_t const size = state->data.size; |
117 | 112k | FUZZ_ASSERT_MSG(size == state->roundTrip.pos, "Incorrect size!"); |
118 | 112k | FUZZ_ASSERT_MSG(!memcmp(data, state->roundTrip.buf, size), "Corruption!"); |
119 | 112k | } |
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 | 56.0k | { |
127 | | /* 64 KB is the max dict size, allow slightly beyond that to test trim. */ |
128 | 56.0k | uint32_t maxDictSize = MIN(70 * 1024, state->data.size); |
129 | 56.0k | size_t const dictSize = FUZZ_rand32(&state->seed, 0, maxDictSize); |
130 | 56.0k | DEBUGLOG(2, "dictSize = %zu", dictSize); |
131 | 56.0k | FUZZ_ASSERT(state->data.pos == 0); |
132 | 56.0k | FUZZ_ASSERT(state->roundTrip.pos == 0); |
133 | 56.0k | memcpy(state->roundTrip.buf, state->data.buf, dictSize); |
134 | 56.0k | state->data.pos += dictSize; |
135 | 56.0k | state->roundTrip.pos += dictSize; |
136 | 56.0k | return dictSize; |
137 | 56.0k | } |
138 | | |
139 | | static void state_prefixRoundTrip(state_t* state) |
140 | 25.4k | { |
141 | 174k | while (state->data.pos != state->data.size) { |
142 | 148k | char const* src = state->data.buf + state->data.pos; |
143 | 148k | char* dst = state->compressed.buf + state->compressed.pos; |
144 | 148k | int const srcRemaining = state->data.size - state->data.pos; |
145 | 148k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
146 | 148k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
147 | 148k | int const cSize = LZ4_compress_fast_continue(state->cstream, src, dst, |
148 | 148k | srcSize, dstCapacity, 0); |
149 | 148k | FUZZ_ASSERT(cSize > 0); |
150 | 148k | state->data.pos += srcSize; |
151 | 148k | state->compressed.pos += cSize; |
152 | 148k | state_decompress(state, dst, cSize); |
153 | 148k | } |
154 | 25.4k | } |
155 | | |
156 | | static void state_extDictRoundTrip(state_t* state) |
157 | 30.6k | { |
158 | 30.6k | int i = 0; |
159 | 30.6k | cursor_t data2 = cursor_create(state->data.size); |
160 | 30.6k | memcpy(data2.buf, state->data.buf, state->data.size); |
161 | 198k | while (state->data.pos != state->data.size) { |
162 | 167k | char const* data = (i++ & 1) ? state->data.buf : data2.buf; |
163 | 167k | char const* src = data + state->data.pos; |
164 | 167k | char* dst = state->compressed.buf + state->compressed.pos; |
165 | 167k | int const srcRemaining = state->data.size - state->data.pos; |
166 | 167k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
167 | 167k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
168 | 167k | int const cSize = LZ4_compress_fast_continue(state->cstream, src, dst, |
169 | 167k | srcSize, dstCapacity, 0); |
170 | 167k | FUZZ_ASSERT(cSize > 0); |
171 | 167k | state->data.pos += srcSize; |
172 | 167k | state->compressed.pos += cSize; |
173 | 167k | state_decompress(state, dst, cSize); |
174 | 167k | } |
175 | 30.6k | cursor_free(data2); |
176 | 30.6k | } |
177 | | |
178 | | static void state_randomRoundTrip(state_t* state, round_trip_t rt0, |
179 | | round_trip_t rt1) |
180 | 56.0k | { |
181 | 56.0k | if (FUZZ_rand32(&state->seed, 0, 1)) { |
182 | 22.8k | rt0(state); |
183 | 33.2k | } else { |
184 | 33.2k | rt1(state); |
185 | 33.2k | } |
186 | 56.0k | } |
187 | | |
188 | | static void state_loadDictRoundTrip(state_t* state) |
189 | 14.0k | { |
190 | 14.0k | char const* dict = state->data.buf; |
191 | 14.0k | size_t const dictSize = state_trimDict(state); |
192 | 14.0k | LZ4_loadDict(state->cstream, dict, dictSize); |
193 | 14.0k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
194 | 14.0k | state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip); |
195 | 14.0k | } |
196 | | |
197 | | static void state_attachDictRoundTrip(state_t* state) |
198 | 14.0k | { |
199 | 14.0k | char const* dict = state->data.buf; |
200 | 14.0k | size_t const dictSize = state_trimDict(state); |
201 | 14.0k | LZ4_stream_t* dictStream = LZ4_createStream(); |
202 | 14.0k | LZ4_loadDict(dictStream, dict, dictSize); |
203 | 14.0k | LZ4_attach_dictionary(state->cstream, dictStream); |
204 | 14.0k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
205 | 14.0k | state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip); |
206 | 14.0k | LZ4_freeStream(dictStream); |
207 | 14.0k | } |
208 | | |
209 | | static void state_prefixHCRoundTrip(state_t* state) |
210 | 25.4k | { |
211 | 174k | while (state->data.pos != state->data.size) { |
212 | 148k | char const* src = state->data.buf + state->data.pos; |
213 | 148k | char* dst = state->compressed.buf + state->compressed.pos; |
214 | 148k | int const srcRemaining = state->data.size - state->data.pos; |
215 | 148k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
216 | 148k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
217 | 148k | int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst, |
218 | 148k | srcSize, dstCapacity); |
219 | 148k | FUZZ_ASSERT(cSize > 0); |
220 | 148k | state->data.pos += srcSize; |
221 | 148k | state->compressed.pos += cSize; |
222 | 148k | state_decompress(state, dst, cSize); |
223 | 148k | } |
224 | 25.4k | } |
225 | | |
226 | | static void state_extDictHCRoundTrip(state_t* state) |
227 | 30.6k | { |
228 | 30.6k | int i = 0; |
229 | 30.6k | cursor_t data2 = cursor_create(state->data.size); |
230 | 30.6k | DEBUGLOG(2, "extDictHC"); |
231 | 30.6k | memcpy(data2.buf, state->data.buf, state->data.size); |
232 | 198k | while (state->data.pos != state->data.size) { |
233 | 167k | char const* data = (i++ & 1) ? state->data.buf : data2.buf; |
234 | 167k | char const* src = data + state->data.pos; |
235 | 167k | char* dst = state->compressed.buf + state->compressed.pos; |
236 | 167k | int const srcRemaining = state->data.size - state->data.pos; |
237 | 167k | int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining); |
238 | 167k | int const dstCapacity = state->compressed.size - state->compressed.pos; |
239 | 167k | int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst, |
240 | 167k | srcSize, dstCapacity); |
241 | 167k | FUZZ_ASSERT(cSize > 0); |
242 | 167k | DEBUGLOG(2, "srcSize = %d", srcSize); |
243 | 167k | state->data.pos += srcSize; |
244 | 167k | state->compressed.pos += cSize; |
245 | 167k | state_decompress(state, dst, cSize); |
246 | 167k | } |
247 | 30.6k | cursor_free(data2); |
248 | 30.6k | } |
249 | | |
250 | | static void state_loadDictHCRoundTrip(state_t* state) |
251 | 14.0k | { |
252 | 14.0k | char const* dict = state->data.buf; |
253 | 14.0k | size_t const dictSize = state_trimDict(state); |
254 | 14.0k | LZ4_loadDictHC(state->cstreamHC, dict, dictSize); |
255 | 14.0k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
256 | 14.0k | state_randomRoundTrip(state, state_prefixHCRoundTrip, |
257 | 14.0k | state_extDictHCRoundTrip); |
258 | 14.0k | } |
259 | | |
260 | | static void state_attachDictHCRoundTrip(state_t* state) |
261 | 14.0k | { |
262 | 14.0k | char const* dict = state->data.buf; |
263 | 14.0k | size_t const dictSize = state_trimDict(state); |
264 | 14.0k | LZ4_streamHC_t* dictStream = LZ4_createStreamHC(); |
265 | 14.0k | LZ4_setCompressionLevel(dictStream, state->level); |
266 | 14.0k | LZ4_loadDictHC(dictStream, dict, dictSize); |
267 | 14.0k | LZ4_attach_HC_dictionary(state->cstreamHC, dictStream); |
268 | 14.0k | LZ4_setStreamDecode(state->dstream, dict, dictSize); |
269 | 14.0k | state_randomRoundTrip(state, state_prefixHCRoundTrip, |
270 | 14.0k | state_extDictHCRoundTrip); |
271 | 14.0k | LZ4_freeStreamHC(dictStream); |
272 | 14.0k | } |
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 | 14.0k | { |
287 | 14.0k | uint32_t seed = FUZZ_seed(&data, &size); |
288 | 14.0k | state_t state = state_create((char const*)data, size, seed); |
289 | 14.0k | const int n = sizeof(roundTrips) / sizeof(round_trip_t); |
290 | 14.0k | int i; |
291 | | |
292 | 126k | for (i = 0; i < n; ++i) { |
293 | 112k | DEBUGLOG(2, "Round trip %d", i); |
294 | 112k | state_reset(&state, seed); |
295 | 112k | roundTrips[i](&state); |
296 | 112k | state_checkRoundTrip(&state); |
297 | 112k | } |
298 | | |
299 | 14.0k | state_free(state); |
300 | | |
301 | 14.0k | return 0; |
302 | 14.0k | } |