/src/lz4/ossfuzz/compress_frame_stream_fuzzer.c
Line | Count | Source |
1 | | /** |
2 | | * This fuzz target compresses the fuzzed data with the LZ4F streaming API, |
3 | | * using a random sequence of operations, into random output capacities. |
4 | | * Capacities may be smaller than LZ4F_compressBound() : begin and updates must |
5 | | * succeed if and only if their output fits, and then produce the same output |
6 | | * as a reference context which always receives a bound-sized buffer. |
7 | | * A failed operation invalidates the frame. |
8 | | * Flush and end fill dstBuffer, or complete : they are invoked again while they fill it, |
9 | | * producing the same output in pieces. Capacity 0 is rejected when output remains. |
10 | | * Completed frames must round trip correctly. |
11 | | * Note : output of linked blocks at fast levels may depend on context history. |
12 | | * Hence, after a failure, both contexts restart from a fresh state. |
13 | | * Multi-threading (nbWorkers >= 1) : both contexts employ different nb of workers. |
14 | | * Without autoFlush, output lags behind input, depending on scheduling : |
15 | | * then only whole frames are compared, and each operation must succeed with a bound-sized buffer. |
16 | | */ |
17 | | |
18 | | #include <stddef.h> |
19 | | #include <stdint.h> |
20 | | #include <stdlib.h> |
21 | | #include <string.h> |
22 | | |
23 | | #include "fuzz_helpers.h" |
24 | | #include "lz4_helpers.h" |
25 | | #include "lz4frame.h" |
26 | | #include "lz4frame_static.h" |
27 | | |
28 | 168k | #define MAX_OPS 64 |
29 | | |
30 | | typedef enum { op_begin, op_update, op_uncompressedUpdate, op_flush, op_end } opType_e; |
31 | | |
32 | | typedef struct { |
33 | | LZ4F_cctx* refCtx; /* always receives a bound-sized buffer */ |
34 | | LZ4F_cctx* cctx; /* receives random capacities */ |
35 | | const LZ4F_preferences_t* refPrefs; |
36 | | const LZ4F_preferences_t* prefs; |
37 | | const LZ4F_compressOptions_t* cOpts; |
38 | | int lagging; /* output lags behind input : only whole frames are comparable */ |
39 | | char* refOut; |
40 | | size_t refOutCapacity; |
41 | | char* refFrame; |
42 | | size_t refFrameSize; |
43 | | char* frame; |
44 | | size_t frameCapacity; |
45 | | size_t frameSize; |
46 | | } state_t; |
47 | | |
48 | | static size_t runOp(const state_t* s, LZ4F_cctx* cctx, const LZ4F_preferences_t* prefs, opType_e type, |
49 | | void* dst, size_t dstCapacity, |
50 | | const uint8_t* src, size_t srcSize) |
51 | 290k | { |
52 | 290k | switch (type) { |
53 | 103k | case op_begin: |
54 | 103k | return LZ4F_compressBegin(cctx, dst, dstCapacity, prefs); |
55 | 93.0k | case op_update: |
56 | 107k | case op_uncompressedUpdate: { |
57 | 107k | LZ4F_compressOptions_t cOpts = *s->cOpts; |
58 | 107k | if (type == op_uncompressedUpdate) cOpts.uncompressed = 1; |
59 | 107k | return LZ4F_compressUpdate(cctx, dst, dstCapacity, src, srcSize, &cOpts); |
60 | 93.0k | } |
61 | 17.2k | case op_flush: |
62 | 17.2k | return LZ4F_flush(cctx, dst, dstCapacity, s->cOpts); |
63 | 61.6k | case op_end: |
64 | 61.6k | return LZ4F_compressEnd(cctx, dst, dstCapacity, s->cOpts); |
65 | 290k | } |
66 | 0 | return (size_t)-1; |
67 | 290k | } |
68 | | |
69 | | static size_t opBound(const LZ4F_preferences_t* prefs, opType_e type, size_t srcSize) |
70 | 268k | { |
71 | 268k | switch (type) { |
72 | 103k | case op_begin: |
73 | 103k | return LZ4F_HEADER_SIZE_MAX; |
74 | 93.0k | case op_update: |
75 | 107k | case op_uncompressedUpdate: |
76 | 107k | return LZ4F_compressBound(srcSize, prefs); |
77 | 16.3k | case op_flush: |
78 | 57.6k | case op_end: |
79 | 57.6k | return LZ4F_compressBound(0, prefs); |
80 | 268k | } |
81 | 0 | return 0; |
82 | 268k | } |
83 | | |
84 | | static void freshContexts(state_t* s) |
85 | 47.9k | { |
86 | 47.9k | LZ4F_freeCompressionContext(s->refCtx); |
87 | 47.9k | LZ4F_freeCompressionContext(s->cctx); |
88 | 47.9k | FUZZ_ASSERT(!LZ4F_isError(LZ4F_createCompressionContext(&s->refCtx, LZ4F_VERSION))); |
89 | 47.9k | FUZZ_ASSERT(!LZ4F_isError(LZ4F_createCompressionContext(&s->cctx, LZ4F_VERSION))); |
90 | 47.9k | } |
91 | | |
92 | | static void checkInvalidated(LZ4F_cctx* cctx) |
93 | 31.0k | { |
94 | 31.0k | char dst[LZ4F_HEADER_SIZE_MAX]; |
95 | 31.0k | size_t const r = LZ4F_compressEnd(cctx, dst, sizeof(dst), NULL); |
96 | 31.0k | FUZZ_ASSERT_MSG(LZ4F_getErrorCode(r) == LZ4F_ERROR_compressionState_uninitialized, |
97 | 31.0k | "A failed operation must invalidate the frame"); |
98 | 31.0k | } |
99 | | |
100 | | /* Flush and end fill dstBuffer, or complete : invoked again, with other capacities, while they fill it. |
101 | | * Each invocation receives exactly its capacity, to detect overflows. |
102 | | * @return : total output, written into @out */ |
103 | | static size_t runPieces(const state_t* s, opType_e type, char* out, size_t outCapacity, size_t capacity, uint32_t* seed) |
104 | 28.8k | { |
105 | 28.8k | size_t pos = 0; |
106 | 50.0k | for (;;) { |
107 | 50.0k | char* const dst = (char*)malloc(capacity ? capacity : 1); |
108 | 50.0k | size_t r; |
109 | 50.0k | FUZZ_ASSERT(dst != NULL); |
110 | 50.0k | r = runOp(s, s->cctx, s->prefs, type, dst, capacity, NULL, 0); |
111 | 50.0k | if (capacity == 0) { |
112 | 4.02k | free(dst); |
113 | 4.02k | if (!LZ4F_isError(r)) { |
114 | 3.32k | FUZZ_ASSERT_MSG((type == op_flush) && (r == 0), "Only an empty flush succeeds into 0 byte"); |
115 | 3.32k | return pos; |
116 | 3.32k | } |
117 | 696 | FUZZ_ASSERT_MSG(LZ4F_getErrorCode(r) == LZ4F_ERROR_dstCapacity_tooSmall, |
118 | 696 | "Capacity 0 must be rejected when output remains"); |
119 | 696 | capacity = FUZZ_rand32(seed, 1, 256); |
120 | 696 | continue; |
121 | 696 | } |
122 | 46.0k | FUZZ_ASSERT_MSG(!LZ4F_isError(r), "Flush and end must not fail for lack of capacity"); |
123 | 46.0k | FUZZ_ASSERT(r <= capacity); |
124 | 46.0k | FUZZ_ASSERT(pos + r <= outCapacity); |
125 | 46.0k | memcpy(out + pos, dst, r); |
126 | 46.0k | pos += r; |
127 | 46.0k | free(dst); |
128 | 46.0k | if (r < capacity) return pos; |
129 | 20.5k | capacity = FUZZ_rand32(seed, 1, 4096); |
130 | 20.5k | } |
131 | 28.8k | } |
132 | | |
133 | | /* Runs @type on both contexts, and appends its output to @s->frame. |
134 | | * @return : 1 on success, 0 if the operation failed because capacity was too small */ |
135 | | static int runBoth(state_t* s, opType_e type, const uint8_t* src, size_t srcSize, uint32_t* seed) |
136 | 134k | { |
137 | 134k | size_t const refBound = opBound(s->refPrefs, type, srcSize); |
138 | 134k | size_t const bound = opBound(s->prefs, type, srcSize); |
139 | 134k | size_t capacity; |
140 | 134k | FUZZ_ASSERT(refBound <= s->refOutCapacity); |
141 | | |
142 | 134k | size_t refSize; |
143 | 134k | if (s->lagging && ((type == op_flush) || (type == op_end))) { |
144 | | /* multi-threaded : jobs in flight are emitted in several invocations (at least one job each) */ |
145 | 4.27k | size_t r; |
146 | 4.27k | refSize = 0; |
147 | 4.27k | do { |
148 | 4.27k | FUZZ_ASSERT(refSize + refBound <= s->refOutCapacity); |
149 | 4.27k | r = runOp(s, s->refCtx, s->refPrefs, type, s->refOut + refSize, refBound, src, srcSize); |
150 | 4.27k | FUZZ_ASSERT_MSG(!LZ4F_isError(r), "Flush and end must succeed with a bound-sized buffer"); |
151 | 4.27k | refSize += r; |
152 | 4.27k | } while (r == refBound); |
153 | 130k | } else { |
154 | 130k | refSize = runOp(s, s->refCtx, s->refPrefs, type, s->refOut, refBound, src, srcSize); |
155 | 130k | FUZZ_ASSERT_MSG(!LZ4F_isError(refSize), "Operation must succeed with a bound-sized buffer"); |
156 | 130k | FUZZ_ASSERT(refSize <= refBound); |
157 | 130k | if ((type == op_flush) || (type == op_end)) { |
158 | 24.5k | FUZZ_ASSERT_MSG(refSize < refBound, "Single-threaded : output must remain strictly below LZ4F_compressBound(0)"); |
159 | 24.5k | } |
160 | 130k | } |
161 | | |
162 | 134k | if (s->lagging) { |
163 | 15.7k | FUZZ_ASSERT(s->refFrameSize + refSize <= s->frameCapacity); |
164 | 15.7k | memcpy(s->refFrame + s->refFrameSize, s->refOut, refSize); |
165 | 15.7k | s->refFrameSize += refSize; |
166 | 15.7k | switch (FUZZ_rand32(seed, 0, 3)) { |
167 | 3.58k | case 0: |
168 | 7.48k | case 1: capacity = bound; break; |
169 | 4.36k | case 2: capacity = FUZZ_rand32(seed, 0, (uint32_t)bound); break; |
170 | 3.91k | default: capacity = FUZZ_rand32(seed, 0, (uint32_t)MIN(bound, 256)); break; |
171 | 15.7k | } |
172 | 15.7k | if ((type == op_flush) || (type == op_end)) { |
173 | 4.27k | s->frameSize += runPieces(s, type, s->frame + s->frameSize, s->frameCapacity - s->frameSize, capacity, seed); |
174 | 4.27k | return 1; |
175 | 4.27k | } |
176 | 11.4k | char* const dst = (char*)malloc(capacity ? capacity : 1); |
177 | 11.4k | FUZZ_ASSERT(dst != NULL); |
178 | 11.4k | size_t const r = runOp(s, s->cctx, s->prefs, type, dst, capacity, src, srcSize); |
179 | 11.4k | if (LZ4F_isError(r)) { |
180 | 769 | FUZZ_ASSERT_MSG(capacity < bound, "Operation must succeed with a bound-sized buffer"); |
181 | 769 | FUZZ_ASSERT_MSG(LZ4F_getErrorCode(r) == LZ4F_ERROR_dstCapacity_tooSmall, |
182 | 769 | "Operation may only fail when its output doesn't fit"); |
183 | 769 | free(dst); |
184 | 769 | checkInvalidated(s->cctx); |
185 | 769 | freshContexts(s); |
186 | 769 | return 0; |
187 | 769 | } |
188 | 10.7k | FUZZ_ASSERT(r <= capacity); |
189 | 10.7k | FUZZ_ASSERT(s->frameSize + r <= s->frameCapacity); |
190 | 10.7k | memcpy(s->frame + s->frameSize, dst, r); |
191 | 10.7k | s->frameSize += r; |
192 | 10.7k | free(dst); |
193 | 10.7k | return 1; |
194 | 10.7k | } |
195 | | |
196 | 118k | switch (FUZZ_rand32(seed, 0, 7)) { |
197 | 16.0k | case 0: |
198 | 31.2k | case 1: capacity = refSize; break; |
199 | 13.9k | case 2: |
200 | 27.2k | case 3: capacity = FUZZ_rand32(seed, (uint32_t)refSize, (uint32_t)bound); break; |
201 | 14.5k | case 4: capacity = refSize ? refSize - 1 : 0; break; |
202 | 15.7k | case 5: capacity = FUZZ_rand32(seed, 0, (uint32_t)refSize); break; |
203 | 29.9k | default: capacity = FUZZ_rand32(seed, 0, (uint32_t)bound); break; |
204 | 118k | } |
205 | | |
206 | 118k | if ((type == op_flush) || (type == op_end)) { |
207 | 24.5k | size_t const total = runPieces(s, type, s->frame + s->frameSize, s->frameCapacity - s->frameSize, capacity, seed); |
208 | 24.5k | FUZZ_ASSERT_MSG(total == refSize, "Output in pieces must have the same size"); |
209 | 24.5k | FUZZ_ASSERT_MSG(!memcmp(s->frame + s->frameSize, s->refOut, refSize), "Output must not depend on capacity"); |
210 | 24.5k | s->frameSize += total; |
211 | 24.5k | return 1; |
212 | 24.5k | } |
213 | | |
214 | | /* exact size, to detect overflows */ |
215 | 94.1k | char* const dst = (char*)malloc(capacity ? capacity : 1); |
216 | 94.1k | FUZZ_ASSERT(dst != NULL); |
217 | 94.1k | size_t const r = runOp(s, s->cctx, s->prefs, type, dst, capacity, src, srcSize); |
218 | 94.1k | if (capacity < refSize) { |
219 | 30.2k | FUZZ_ASSERT_MSG(LZ4F_getErrorCode(r) == LZ4F_ERROR_dstCapacity_tooSmall, |
220 | 30.2k | "Operation must fail when its output doesn't fit"); |
221 | 30.2k | free(dst); |
222 | 30.2k | checkInvalidated(s->cctx); |
223 | 30.2k | freshContexts(s); |
224 | 30.2k | return 0; |
225 | 30.2k | } |
226 | 63.8k | FUZZ_ASSERT_MSG(r == refSize, "Operation must succeed when its output fits"); |
227 | 63.8k | FUZZ_ASSERT_MSG(!memcmp(dst, s->refOut, refSize), "Output must not depend on capacity"); |
228 | 63.8k | FUZZ_ASSERT(s->frameSize + refSize <= s->frameCapacity); |
229 | 63.8k | memcpy(s->frame + s->frameSize, dst, refSize); |
230 | 63.8k | s->frameSize += refSize; |
231 | 63.8k | free(dst); |
232 | 63.8k | return 1; |
233 | 63.8k | } |
234 | | |
235 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
236 | 16.9k | { |
237 | 16.9k | uint32_t seed = FUZZ_seed(&data, &size); |
238 | 16.9k | LZ4F_preferences_t prefs = FUZZ_randomPreferences(&seed); |
239 | 16.9k | LZ4F_preferences_t refPrefs; |
240 | 16.9k | if (FUZZ_rand32(&seed, 0, 1)) prefs.nbWorkers = FUZZ_rand32(&seed, 1, 3); |
241 | 16.9k | refPrefs = prefs; |
242 | 16.9k | if (prefs.nbWorkers) refPrefs.nbWorkers = FUZZ_rand32(&seed, 1, 3); /* output must not depend on nbWorkers */ |
243 | 16.9k | size_t const blockSize = LZ4F_getBlockSize(prefs.frameInfo.blockSizeID); |
244 | 16.9k | LZ4F_compressOptions_t cOpts; |
245 | 16.9k | state_t s; |
246 | 16.9k | size_t pos = 0, frameStart = 0; |
247 | 16.9k | int inFrame = 0; |
248 | 16.9k | unsigned nbOps; |
249 | | |
250 | 16.9k | memset(&cOpts, 0, sizeof(cOpts)); |
251 | 16.9k | cOpts.stableSrc = FUZZ_rand32(&seed, LZ4F_srcUnstable, LZ4F_srcStableUntilFlush); /* input remains until the end */ |
252 | | |
253 | 16.9k | memset(&s, 0, sizeof(s)); |
254 | 16.9k | s.refPrefs = &refPrefs; |
255 | 16.9k | s.prefs = &prefs; |
256 | 16.9k | s.cOpts = &cOpts; |
257 | 16.9k | s.lagging = (prefs.nbWorkers != 0) && !prefs.autoFlush; |
258 | | /* multi-threaded flush and end : all jobs in flight, in several invocations of LZ4F_compressBound(0) */ |
259 | 16.9k | s.refOutCapacity = LZ4F_compressBound(size, &refPrefs) + LZ4F_compressFrameBound(size, &refPrefs) |
260 | 16.9k | + 2 * MAX_OPS * (LZ4F_BLOCK_HEADER_SIZE + LZ4F_BLOCK_CHECKSUM_SIZE) + LZ4F_compressBound(0, &refPrefs); |
261 | 16.9k | s.refOut = (char*)malloc(s.refOutCapacity); |
262 | | /* each operation can add up to 2 partial blocks */ |
263 | 16.9k | s.frameCapacity = LZ4F_compressFrameBound(size, &prefs) |
264 | 16.9k | + 2 * MAX_OPS * (LZ4F_BLOCK_HEADER_SIZE + LZ4F_BLOCK_CHECKSUM_SIZE); |
265 | 16.9k | s.frame = (char*)malloc(s.frameCapacity); |
266 | 16.9k | s.refFrame = (char*)malloc(s.frameCapacity); |
267 | 16.9k | char* const rt = (char*)malloc(size ? size : 1); |
268 | 16.9k | FUZZ_ASSERT(s.refOut != NULL); |
269 | 16.9k | FUZZ_ASSERT(s.frame != NULL); |
270 | 16.9k | FUZZ_ASSERT(s.refFrame != NULL); |
271 | 16.9k | FUZZ_ASSERT(rt != NULL); |
272 | 16.9k | freshContexts(&s); |
273 | | |
274 | 134k | for (nbOps = 0; nbOps < MAX_OPS; nbOps++) { |
275 | 134k | if (!inFrame) { |
276 | 51.6k | frameStart = pos; |
277 | 51.6k | s.frameSize = 0; |
278 | 51.6k | s.refFrameSize = 0; |
279 | 51.6k | inFrame = runBoth(&s, op_begin, NULL, 0, &seed); |
280 | 51.6k | continue; |
281 | 51.6k | } |
282 | 82.8k | if (pos == size || FUZZ_rand32(&seed, 0, 15) == 0) { |
283 | 20.6k | if (runBoth(&s, op_end, NULL, 0, &seed)) { |
284 | 20.6k | if (s.lagging) { |
285 | 3.12k | FUZZ_ASSERT_MSG(s.frameSize == s.refFrameSize && !memcmp(s.frame, s.refFrame, s.frameSize), |
286 | 3.12k | "Frame must not depend on nbWorkers, capacities, nor scheduling"); |
287 | 3.12k | } |
288 | 20.6k | size_t const rtSize = FUZZ_decompressFrame(rt, size, s.frame, s.frameSize, |
289 | 20.6k | FUZZ_rand32(&seed, 0, 7) == 0 /* occasionally skip checksums */); |
290 | 20.6k | FUZZ_ASSERT_MSG(rtSize == pos - frameStart, "Incorrect regenerated size"); |
291 | 20.6k | FUZZ_ASSERT_MSG(!memcmp(data + frameStart, rt, rtSize), "Corruption!"); |
292 | 20.6k | } |
293 | 20.6k | inFrame = 0; |
294 | 20.6k | if (pos == size) break; |
295 | 3.69k | continue; |
296 | 20.6k | } |
297 | 62.1k | { uint32_t const t = FUZZ_rand32(&seed, 0, 7); |
298 | 62.1k | opType_e const type = (t == 0) ? op_flush |
299 | 62.1k | : (t == 1) ? op_uncompressedUpdate |
300 | 53.9k | : op_update; |
301 | 62.1k | size_t const remaining = size - pos; |
302 | 62.1k | size_t srcSize = 0; |
303 | 62.1k | if (type != op_flush) { |
304 | 53.9k | switch (FUZZ_rand32(&seed, 0, 3)) { |
305 | 14.5k | case 0: srcSize = FUZZ_rand32(&seed, 0, 16); break; |
306 | 14.7k | case 1: srcSize = blockSize - 1 + FUZZ_rand32(&seed, 0, 2); break; |
307 | 24.6k | default: srcSize = FUZZ_rand32(&seed, 0, (uint32_t)MIN(remaining, 2 * blockSize)); break; |
308 | 53.9k | } |
309 | 53.9k | srcSize = MIN(srcSize, remaining); |
310 | 53.9k | } |
311 | 62.1k | inFrame = runBoth(&s, type, data + pos, srcSize, &seed); |
312 | 62.1k | pos += srcSize; |
313 | 62.1k | } |
314 | 62.1k | } |
315 | | |
316 | 16.9k | free(s.refOut); |
317 | 16.9k | free(s.frame); |
318 | 16.9k | free(s.refFrame); |
319 | 16.9k | free(rt); |
320 | 16.9k | LZ4F_freeCompressionContext(s.refCtx); |
321 | 16.9k | LZ4F_freeCompressionContext(s.cctx); |
322 | | |
323 | 16.9k | return 0; |
324 | 16.9k | } |