Coverage Report

Created: 2026-11-12 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}