1
0
mirror of https://github.com/kspalaiologos/bzip3.git synced 2026-10-23 14:48:53 +03:00
Files
bzip3/examples/fuzz-decompress.c
Sewer. 2b2c18c290 Improve Fuzzing, Enforce Sufficient Buffer Size on Decompress, Calc Memory Usage (#144)
* Added: bz3_memory_needed API.

* Added: bz3_decode_block_bound

* Renamed: bz3_decode_bound -> bz3_decode_block_bound in documentation

* Updated: libbz3.h to specify that it is a frame that is being compressed here.

* Reworked how the fuzzer script works. Can now generate its own input for easier debugging.

* Added: Multithreading to fuzz tutorial.

* Added: 010editor template for fuzzer input.

* Improved: Ignore the AFL Input/Outputs in .gitignore

* Added a note saying `afl-whatsup` is not realtime.

* Fixed: Variable name in embedded hex editor script.

* Added: Note on where to find crashing test case :3

* Added: Integration for bz3_decode_block_bound in bz3_decode_block

* Added: Guide for fuzzing with address sanitizer.

* Fixed: Wrong branch in binary template of fuzz.c

* Improved: Description in bz3_decode_block_bound

* Revert: data_size should not compare with bz3_decode_block_bound

* Removed: `bz3_decode_block_bound` API.

* Added: BZ3_ERR_DATA_SIZE_TOO_SMALL when bz3_decode_block is called with insufficient buffer.

* Implemented: Additional bounds checks in `bz3_decode_block` and `bz3_orig_size_sufficient_for_decode` API

* Adjusted: Old fuzz script now named fuzz-decompress. Enforce includes from local files, not system.

* Fixed: Bad quote usage in bz3_decode_block

* Added: Standard fuzzer inputs.

* Fixed: fuzz failures in decode_block and added a new fuzzer script for block decodes

* Added: Sanity check that the user provided a valid compressed data size to bz3_decode_block

* Changed: bz3_memory_needed -> bz3_min_memory_needed

* Added: Round Trip Fuzzer

* Fixed: Possible out of bounds read in `lzp_decode_block`. Mark exit branches as unlikely.

* Update comment

---------

Co-authored-by: Kamila Szewczyk <27734421+kspalaiologos@users.noreply.github.com>
2024-12-15 11:46:10 +01:00

312 lines
8.7 KiB
C

/* A tiny utility for fuzzing bzip3 frame decompression.
*
* Prerequisites:
*
* - AFL https://github.com/AFLplusplus/AFLplusplus
* - clang (part of LLVM)
*
* On Arch this is `pacman -S afl++ clang`
*
* # Instructions:
*
* 1. Prepare fuzzer directories
*
* mkdir -p afl_in && mkdir -p afl_out
*
* 2. Build binary (to compress test data).
*
* afl-clang fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
*
* 3. Make a fuzzer input file.
*
* With `your_file` being an arbitrary input to test, use this utility
* to generate a compressed test frame:
*
* ./fuzz hl-api.c hl-api.c.bz3 8
* mv hl-api.c.bz3 afl_in/
*
* 4. Build binary (for fuzzing).
*
* afl-clang-fast fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
*
* 5. Run the fuzzer.
*
* AFL_SKIP_CPUFREQ=1 afl-fuzz -i afl_in -o afl_out -- ./fuzz @@
*
* 6. Wanna go faster? Multithread.
*
* alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -M fuzzer01 -- ./fuzz @@; exec bash" &
* alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer02 -- ./fuzz @@; exec bash" &
* alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer03 -- ./fuzz @@; exec bash" &
* alacritty -e bash -c "afl-fuzz -i afl_in -o afl_out -S fuzzer04 -- ./fuzz @@; exec bash" &
*
* etc. Replace `alacritty` with your terminal.
*
* And check progress with `afl-whatsup afl_out` (updates periodically).
*
* 7. Found a crash?
*
* If you find a crash, consider also doing the following:
*
* clang fuzz-decompress.c -g3 -O3 -march=native -o fuzz_asan -I../include "-DVERSION=\"0.0.0\"" -fsanitize=undefined -fsanitize=address
*
* And run fuzz_asan on the crashing test case (you can find it in one of the `afl_out/crashes/` folders).
* Attach the test case /and/ the output of fuzz_asan to the bug report.
*
* If no error occurs, it could be that there was a memory corruption `between` the runs.
* In which case, you want to run AFL with address sanitizer. Use `export AFL_USE_ASAN=1` to enable
* addres sanitizer; then run AFL.
*
* export AFL_USE_ASAN=1
* afl-clang-fast fuzz-decompress.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native
*/
/*
This hex editor template can be used to help debug a breaking file.
Would provide for ImHex, but ImHex terminates if template is borked.
//------------------------------------------------
//--- 010 Editor v15.0.1 Binary Template
//
// File: bzip3-fuzz-decompress.bt
// Authors: Sewer56
// Version: 1.0.0
// Purpose: Parse bzip3 fuzzer data
//------------------------------------------------
// Colors for different sections
#define COLOR_HEADER 0xA0FFA0 // Frame header
#define COLOR_BLOCKHEAD 0xFFB0B0 // Block headers
#define COLOR_DATA 0xB0B0FF // Compressed data
local uint32 currentBlockSize; // Store block size globally
// Frame header structure
typedef struct {
char signature[5]; // "BZ3v1"
uint32 blockSize; // Maximum block size
uint32 block_count;
} FRAME_HEADER <bgcolor=COLOR_HEADER>;
// Regular block header (for blocks >= 64 bytes)
typedef struct {
uint32 crc32; // CRC32 checksum of uncompressed data
uint32 bwtIndex; // Burrows-Wheeler transform index
uint8 model; // Compression model flags:
// bit 1 (0x02): LZP was used
// bit 2 (0x04): RLE was used
// Optional size fields based on compression flags
if(model & 0x02)
uint32 lzpSize; // Size after LZP compression
if(model & 0x04)
uint32 rleSize; // Size after RLE compression
} BLOCK_HEADER <bgcolor=COLOR_BLOCKHEAD>;
// Small block header (for blocks < 64 bytes)
typedef struct {
uint32 crc32; // CRC32 checksum
uint32 literal; // Always 0xFFFFFFFF for small blocks
uint8 data[currentBlockSize - 8]; // Uncompressed data
} SMALL_BLOCK <bgcolor=COLOR_BLOCKHEAD>;
// Main block structure
typedef struct {
uint32 compressedSize; // Size of compressed block
uint32 origSize; // Original uncompressed size
currentBlockSize = compressedSize; // Store for use in SMALL_BLOCK
if(origSize < 64) {
SMALL_BLOCK content;
} else {
BLOCK_HEADER header;
uchar data[compressedSize - (Popcount(header.model) * 4 + 9)];
}
} BLOCK <bgcolor=COLOR_DATA>;
// Helper function for bit counting (used for header size calculation)
int Popcount(byte b) {
local int count = 0;
while(b) {
count += b & 1;
b >>= 1;
}
return count;
}
// Main parsing structure
uint32 orig_size;
FRAME_HEADER frameHeader;
// Read blocks until end of file
while(!FEof()) {
BLOCK block;
}
*/
#include "../include/libbz3.h"
#include "../src/libbz3.c"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define KiB(x) ((x)*1024)
// Required for AFL++ persistent mode
#ifdef __AFL_HAVE_MANUAL_CONTROL
#include <unistd.h>
__AFL_FUZZ_INIT();
#endif
// Maximum allowed size to prevent excessive memory allocation
#define MAX_SIZE 0x10000000 // 256MB
// Returns 0 on success, negative on input validation errors, positive on bzip3 errors
static int try_decompress(const uint8_t *input_buf, size_t input_len) {
if (input_len < 8) { // invalid, does not contain orig_size
return -1;
}
size_t orig_size = *(const uint32_t *)input_buf;
uint8_t *outbuf = malloc(orig_size);
if (!outbuf) {
return -3;
}
// We read orig_size from the input as we also want to fuzz it.
int bzerr = bz3_decompress(
input_buf + sizeof(uint32_t),
outbuf,
input_len - sizeof(uint32_t),
&orig_size
);
if (bzerr != BZ3_OK) {
printf("bz3_decompress() failed with error code %d\n", bzerr);
} else {
printf("OK, %d => %d\n", (int)input_len, (int)orig_size);
}
free(outbuf);
return bzerr;
}
static int compress_file(const char *infile, const char *outfile, uint32_t block_size) {
block_size = block_size <= KiB(65) ? KiB(65) : block_size;
// Read the data into `inbuf`
FILE *fp_in = fopen(infile, "rb");
if (!fp_in) {
perror("Failed to open input file");
return 1;
}
fseek(fp_in, 0, SEEK_END);
size_t insize = ftell(fp_in);
fseek(fp_in, 0, SEEK_SET);
uint8_t *inbuf = malloc(insize);
if (!inbuf) {
fclose(fp_in);
return 1;
}
fread(inbuf, 1, insize, fp_in);
fclose(fp_in);
// Make buffer for output.
size_t outsize = bz3_bound(insize);
uint8_t *outbuf = malloc(outsize + sizeof(uint32_t));
if (!outbuf) {
free(inbuf);
return 1;
}
// Store original size at the start
// This is important, the `try_decompress` will read this field during fuzzing.
// And pass it as a parameter to `bz3_decompress`.
*(uint32_t *)outbuf = insize;
int bzerr = bz3_compress(block_size, inbuf, outbuf + sizeof(uint32_t), insize, &outsize);
if (bzerr != BZ3_OK) {
printf("bz3_compress() failed with error code %d\n", bzerr);
free(inbuf);
free(outbuf);
return bzerr;
}
FILE *fp_out = fopen(outfile, "wb");
if (!fp_out) {
perror("Failed to open output file");
free(inbuf);
free(outbuf);
return 1;
}
fwrite(outbuf, 1, outsize + sizeof(uint32_t), fp_out);
fclose(fp_out);
printf("Compressed %s (%zu bytes) to %s (%zu bytes)\n",
infile, insize, outfile, outsize + sizeof(uint32_t));
free(inbuf);
free(outbuf);
return 0;
}
int main(int argc, char **argv) {
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
while (__AFL_LOOP(1000)) {
try_decompress(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN);
}
#else
if (argc == 4) {
// Compression mode: input_file output_file block_size
return compress_file(argv[1], argv[2], atoi(argv[3]));
}
if (argc != 2) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " Decompress: %s <input_file>\n", argv[0]);
fprintf(stderr, " Compress: %s <input_file> <output_file> <block_size>\n", argv[0]);
return 1;
}
// Decompression mode
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
perror("Failed to open input file");
return 1;
}
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (size < 64) {
fclose(fp);
return 0;
}
uint8_t *buffer = malloc(size);
if (!buffer) {
fclose(fp);
return 1;
}
fread(buffer, 1, size, fp);
fclose(fp);
int result = try_decompress(buffer, size);
free(buffer);
return result > 0 ? result : 0; // Return bzip3 errors but treat validation errors as success
#endif
return 0;
}