diff --git a/examples/fuzz-round-trip.c b/examples/fuzz-round-trip.c new file mode 100644 index 0000000..dd9ce95 --- /dev/null +++ b/examples/fuzz-round-trip.c @@ -0,0 +1,155 @@ +/* A tiny utility for fuzzing bzip3 round-trip compression/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. Insert a test file to afl_in/ + * + * cp ./standard_test_files/63_byte_file.bin afl_in/ + * + * 3. Build binary (for fuzzing) + * + * afl-clang-fast fuzz-round-trip.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native + * + * 4. Run the fuzzer + * + * AFL_SKIP_CPUFREQ=1 afl-fuzz -i afl_in -o afl_out -- ./fuzz @@ + * + * 5. For ASAN testing: + * + * export AFL_USE_ASAN=1 + * afl-clang-fast fuzz-round-trip.c -I../include -o fuzz -g3 "-DVERSION=\"0.0.0\"" -O3 -march=native + */ + +#include "../include/libbz3.h" +#include "../src/libbz3.c" +#include +#include +#include +#include + +#define KiB(x) ((x)*1024) +#define DEFAULT_BLOCK_SIZE KiB(65) + +// Required for AFL++ persistent mode +#ifdef __AFL_HAVE_MANUAL_CONTROL +#include +__AFL_FUZZ_INIT(); +#endif + +// Function to emulate a crash for diagnostic purposes +static void __attribute__((noreturn)) crash_with_message(const char* msg) { + fprintf(stderr, "Emulating crash: %s\n", msg); + // Use abort() to generate a crash that ASAN and other tools can catch + abort(); +} + +// Returns 0 on success, crashes on failure +static int try_round_trip(const uint8_t *input_buf, size_t input_len) { + if (input_len == 0) return 0; + + // Use the larger of DEFAULT_BLOCK_SIZE or input_len + size_t block_size = input_len > DEFAULT_BLOCK_SIZE ? input_len : DEFAULT_BLOCK_SIZE; + + struct bz3_state *state = bz3_new(block_size); + if (!state) { + return -1; // allocation failures not tested. + } + + // Allocate buffer for both compression and decompression + // Using block_size to ensure we have enough space for both operations + size_t comp_buf_len = bz3_bound(input_len); + uint8_t *comp_buf = malloc(comp_buf_len); + if (!comp_buf) { + bz3_free(state); + return -1; // allocation failures not tested. + } + + // Step 0: Move input to compress buffer + memmove(comp_buf, input_buf, input_len); + + // Step 1: Compress the input + int32_t comp_size = bz3_encode_block(state, comp_buf, input_len); + if (comp_size < 0) { + bz3_free(state); + free(comp_buf); + crash_with_message("Compression failed"); + } + + // Step 2: Decompress + int bzerr = bz3_decode_block(state, comp_buf, comp_buf_len, comp_size, input_len); + if (bzerr < 0 || bzerr != input_len) { + bz3_free(state); + free(comp_buf); + crash_with_message("Decompression failed"); + } + + // Step 3: Compare + if (memcmp(input_buf, comp_buf, input_len) != 0) { + bz3_free(state); + free(comp_buf); + crash_with_message("Round-trip data mismatch"); + } + + bz3_free(state); + free(comp_buf); + return 0; +} + +static int test_file(const char *filename) { + FILE *fp = fopen(filename, "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); + + uint8_t *buffer = malloc(size); + if (!buffer) { + fclose(fp); + crash_with_message("Failed to allocate input buffer"); + } + + if (fread(buffer, 1, size, fp) != size) { + fclose(fp); + free(buffer); + crash_with_message("Failed to read input file"); + } + fclose(fp); + + int result = try_round_trip(buffer, size); + free(buffer); + return result; +} + +int main(int argc, char **argv) { +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); + + while (__AFL_LOOP(1000)) { + try_round_trip(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN); + } +#else + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + return test_file(argv[1]); +#endif + + return 0; +} \ No newline at end of file