:: bzip3 / examples / fuzz-decode-block.c 9.5 KB raw

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