Coverage Report

Created: 2026-01-10 06:25

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