:: bzip3 / examples / fuzz-decompress.c 8.7 KB raw

1
/* A tiny utility for fuzzing bzip3 frame decompression.
2
 *
3
 * Prerequisites:
4
 * 
5
 * - AFL https://github.com/AFLplusplus/AFLplusplus
6
 * - clang (part of LLVM)
7
 * 
8
 * On Arch this is `pacman -S afl++ clang`
9
 *
10
 * # Instructions:
11
 * 
12
 * 1. Prepare fuzzer directories
13
 * 
14
 * mkdir -p afl_in && mkdir -p afl_out
15
 * 
16
 * 2. Build binary (to compress test data).
17
 * 
18
 * afl-clang fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
19
 * 
20
 * 3. Make a fuzzer input file.
21
 * 
22
 * With `your_file` being an arbitrary input to test, use this utility
23
 * to generate a compressed test frame:
24
 * 
25
 * ./fuzz hl-api.c hl-api.c.bz3 8
26
 * mv hl-api.c.bz3 afl_in/
27
 * 
28
 * 4. Build binary (for fuzzing).
29
 * 
30
 * afl-clang-fast fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
31
 * 
32
 * 5. Run the fuzzer.
33
 * 
34
 * AFL_SKIP_CPUFREQ=1 afl-fuzz -i afl_in -o afl_out -- ./fuzz @@
35
 *
36
 * 6. Wanna go faster? Multithread.
37
 * 
38
 * alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -M fuzzer01 -- ./fuzz @@; exec bash" &
39
 * alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer02 -- ./fuzz @@; exec bash" &
40
 * alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer03 -- ./fuzz @@; exec bash" &
41
 * alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer04 -- ./fuzz @@; exec bash" &
42
 * 
43
 * etc. Replace `alacritty` with your terminal.
44
 * 
45
 * And check progress with `afl-whatsup afl_out` (updates periodically).
46
 * 
47
 * 7. Found a crash?
48
 * 
49
 * If you find a crash, consider also doing the following:
50
 * 
51
 *      clang fuzz-decompress.c -g3 -O3 -march=native -o fuzz_asan -I../include "-DVERSION=\"0.0.0\"" -fsanitize=undefined -fsanitize=address
52
 *
53
 * And run fuzz_asan on the crashing test case (you can find it in one of the `afl_out/crashes/` folders).
54
 * Attach the test case /and/ the output of fuzz_asan to the bug report.
55
 * 
56
 * If no error occurs, it could be that there was a memory corruption `between` the runs.
57
 * In which case, you want to run AFL with address sanitizer. Use `export AFL_USE_ASAN=1` to enable
58
 * addres sanitizer; then run AFL.
59
 * 
60
 * export AFL_USE_ASAN=1
61
 * afl-clang-fast fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
62
 */
63
64
65
/*
66
This hex editor template can be used to help debug a breaking file.
67
Would provide for ImHex, but ImHex terminates if template is borked.
68
69
//------------------------------------------------
70
//--- 010 Editor v15.0.1 Binary Template
71
//
72
//      File: bzip3-fuzz-decompress.bt
73
//   Authors: Sewer56
74
//   Version: 1.0.0
75
//   Purpose: Parse bzip3 fuzzer data
76
//------------------------------------------------
77
78
// Colors for different sections
79
#define COLOR_HEADER     0xA0FFA0 // Frame header
80
#define COLOR_BLOCKHEAD  0xFFB0B0 // Block headers
81
#define COLOR_DATA       0xB0B0FF // Compressed data
82
83
local uint32 currentBlockSize; // Store block size globally
84
85
// Frame header structure
86
typedef struct {
87
    char signature[5];     // "BZ3v1"
88
    uint32 blockSize;      // Maximum block size
89
    uint32 block_count;
90
} FRAME_HEADER <bgcolor=COLOR_HEADER>;
91
92
// Regular block header (for blocks >= 64 bytes)
93
typedef struct {
94
    uint32 crc32;         // CRC32 checksum of uncompressed data
95
    uint32 bwtIndex;      // Burrows-Wheeler transform index
96
    uint8  model;         // Compression model flags:
97
                         // bit 1 (0x02): LZP was used
98
                         // bit 2 (0x04): RLE was used
99
    
100
    // Optional size fields based on compression flags
101
    if(model & 0x02)     
102
        uint32 lzpSize;   // Size after LZP compression
103
    if(model & 0x04)     
104
        uint32 rleSize;   // Size after RLE compression
105
} BLOCK_HEADER <bgcolor=COLOR_BLOCKHEAD>;
106
107
// Small block header (for blocks < 64 bytes)
108
typedef struct {
109
    uint32 crc32;        // CRC32 checksum
110
    uint32 literal;      // Always 0xFFFFFFFF for small blocks
111
    uint8 data[currentBlockSize - 8]; // Uncompressed data
112
} SMALL_BLOCK <bgcolor=COLOR_BLOCKHEAD>;
113
114
// Main block structure
115
typedef struct {
116
    uint32 compressedSize;  // Size of compressed block
117
    uint32 origSize;        // Original uncompressed size
118
    
119
    currentBlockSize = compressedSize; // Store for use in SMALL_BLOCK
120
    
121
    if(origSize < 64) {
122
        SMALL_BLOCK content;
123
    } else {
124
        BLOCK_HEADER header;
125
        uchar data[compressedSize - (Popcount(header.model) * 4 + 9)];
126
    }
127
} BLOCK <bgcolor=COLOR_DATA>;
128
129
// Helper function for bit counting (used for header size calculation)
130
int Popcount(byte b) {
131
    local int count = 0;
132
    while(b) {
133
        count += b & 1;
134
        b >>= 1;
135
    }
136
    return count;
137
}
138
139
// Main parsing structure
140
uint32 orig_size;
141
FRAME_HEADER frameHeader;
142
143
// Read blocks until end of file
144
while(!FEof()) {
145
    BLOCK block;
146
}
147
148
*/
149
150
#include "../include/libbz3.h"
151
#include "../src/libbz3.c"
152
#include <stdio.h>
153
#include <stdlib.h>
154
#include <stdint.h>
155
#include <string.h>
156
157
#define KiB(x) ((x)*1024)
158
159
// Required for AFL++ persistent mode
160
#ifdef __AFL_HAVE_MANUAL_CONTROL
161
#include <unistd.h>
162
__AFL_FUZZ_INIT();
163
#endif
164
165
// Maximum allowed size to prevent excessive memory allocation
166
#define MAX_SIZE 0x10000000 // 256MB
167
168
// Returns 0 on success, negative on input validation errors, positive on bzip3 errors
169
static int try_decompress(const uint8_t *input_buf, size_t input_len) {
170
    if (input_len < 8) { // invalid, does not contain orig_size
171
        return -1;
172
    }
173
174
    size_t orig_size = *(const uint32_t *)input_buf;
175
    uint8_t *outbuf = malloc(orig_size);
176
    if (!outbuf) {
177
        return -3;
178
    }
179
180
    // We read orig_size from the input as we also want to fuzz it.
181
    int bzerr = bz3_decompress(
182
        input_buf + sizeof(uint32_t),
183
        outbuf,
184
        input_len - sizeof(uint32_t),
185
        &orig_size
186
    );
187
188
    if (bzerr != BZ3_OK) {
189
        printf("bz3_decompress() failed with error code %d\n", bzerr);
190
    } else {
191
        printf("OK, %d => %d\n", (int)input_len, (int)orig_size);
192
    }
193
194
    free(outbuf);
195
    return bzerr;
196
}
197
198
static int compress_file(const char *infile, const char *outfile, uint32_t block_size) {
199
    block_size = block_size <= KiB(65) ? KiB(65) : block_size;
200
    
201
    // Read the data into `inbuf`
202
    FILE *fp_in = fopen(infile, "rb");
203
    if (!fp_in) {
204
        perror("Failed to open input file");
205
        return 1;
206
    }
207
208
    fseek(fp_in, 0, SEEK_END);
209
    size_t insize = ftell(fp_in);
210
    fseek(fp_in, 0, SEEK_SET);
211
212
    uint8_t *inbuf = malloc(insize);
213
    if (!inbuf) {
214
        fclose(fp_in);
215
        return 1;
216
    }
217
218
    fread(inbuf, 1, insize, fp_in);
219
    fclose(fp_in);
220
221
    // Make buffer for output.
222
    size_t outsize = bz3_bound(insize);
223
    uint8_t *outbuf = malloc(outsize + sizeof(uint32_t));
224
    if (!outbuf) {
225
        free(inbuf);
226
        return 1;
227
    }
228
229
    // Store original size at the start
230
    // This is important, the `try_decompress` will read this field during fuzzing.
231
    // And pass it as a parameter to `bz3_decompress`. 
232
    *(uint32_t *)outbuf = insize;
233
234
    int bzerr = bz3_compress(block_size, inbuf, outbuf + sizeof(uint32_t), insize, &outsize);
235
    if (bzerr != BZ3_OK) {
236
        printf("bz3_compress() failed with error code %d\n", bzerr);
237
        free(inbuf);
238
        free(outbuf);
239
        return bzerr;
240
    }
241
242
    FILE *fp_out = fopen(outfile, "wb");
243
    if (!fp_out) {
244
        perror("Failed to open output file");
245
        free(inbuf);
246
        free(outbuf);
247
        return 1;
248
    }
249
250
    fwrite(outbuf, 1, outsize + sizeof(uint32_t), fp_out);
251
    fclose(fp_out);
252
253
    printf("Compressed %s (%zu bytes) to %s (%zu bytes)\n", 
254
           infile, insize, outfile, outsize + sizeof(uint32_t));
255
256
    free(inbuf);
257
    free(outbuf);
258
    return 0;
259
}
260
261
int main(int argc, char **argv) {
262
#ifdef __AFL_HAVE_MANUAL_CONTROL
263
    __AFL_INIT();
264
    
265
    while (__AFL_LOOP(1000)) {
266
        try_decompress(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN);
267
    }
268
#else
269
    if (argc == 4) {
270
        // Compression mode: input_file output_file block_size
271
        return compress_file(argv[1], argv[2], atoi(argv[3]));
272
    }
273
    
274
    if (argc != 2) {
275
        fprintf(stderr, "Usage:\n");
276
        fprintf(stderr, "  Decompress: %s <input_file>\n", argv[0]);
277
        fprintf(stderr, "  Compress:   %s <input_file> <output_file> <block_size>\n", argv[0]);
278
        return 1;
279
    }
280
281
    // Decompression mode
282
    FILE *fp = fopen(argv[1], "rb");
283
    if (!fp) {
284
        perror("Failed to open input file");
285
        return 1;
286
    }
287
288
    fseek(fp, 0, SEEK_END);
289
    size_t size = ftell(fp);
290
    fseek(fp, 0, SEEK_SET);
291
292
    if (size < 64) {
293
        fclose(fp);
294
        return 0;
295
    }
296
297
    uint8_t *buffer = malloc(size);
298
    if (!buffer) {
299
        fclose(fp);
300
        return 1;
301
    }
302
303
    fread(buffer, 1, size, fp);
304
    fclose(fp);
305
306
    int result = try_decompress(buffer, size);
307
    free(buffer);
308
    return result > 0 ? result : 0; // Return bzip3 errors but treat validation errors as success
309
#endif
310
311
    return 0;
312
}
tab: 248 wrap: offon