diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh index 68528bc46ae7d76f1b6804b97ec2db2dd2f5dd80..041759644958560ecd92ceefc66ad9db98aca0d5 100755 --- a/.clusterfuzzlite/build.sh +++ b/.clusterfuzzlite/build.sh @@ -16,7 +16,7 @@ meson setup "$BUILD" \ -Db_lundef=false # build fuzzers -ninja -v -C "$BUILD" test/fuzz/{fuzz_round_trip,fuzz_compression,fuzz_decompression} +ninja -v -C "$BUILD" test/fuzz/{fuzz_round_trip,fuzz_compression,fuzz_decompression,fuzz_cmp_tool} find "$BUILD/test/fuzz" -maxdepth 1 -executable -type f -exec cp "{}" "$OUT" \; #TODO prepare corps diff --git a/CHANGELOG.md b/CHANGELOG.md index 443309e2fd35f0bf3c6cfc2c32c7a814f69885a1..0b7ac16c4fc67379306822757177a3e7ad6912da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.15] - 22-01-2025 +### Added +- add fuzzer for cmp_tool +### Changed +- error handling is now consistent with the --binary option when model file size does not match original data size + ## [0.14] - 16-01-2025 ### Added - check for model file size mismatch errors diff --git a/meson.build b/meson.build index f91a1b79c444a392bcb75d6ab9c78f08ef3bd1c3..9604d70d3d013797ea62840f169a4f0162b06da2 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('cmp_tool', 'c', - version : '0.14', + version : '0.15', meson_version : '>= 0.63', license : 'GPL-2.0', default_options : [ diff --git a/programs/cmp_guess.c b/programs/cmp_guess.c index c76d1da39660bc2686d307b793deef727e931194..7c0f7fca365ba3bdc4d2993881c7bb3eb52697d3 100644 --- a/programs/cmp_guess.c +++ b/programs/cmp_guess.c @@ -35,6 +35,13 @@ #define CMP_GUESS_MAX_CAL_STEPS 20274 +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +/* Redefine (f)printf to do nothing */ +__extension__ +#define printf(...) do {} while (0) +#define fprintf(...) do {} while (0) +#endif + /* how often the model is updated before it is rested */ static int num_model_updates = CMP_GUESS_N_MODEL_UPDATE_DEF; diff --git a/programs/cmp_io.c b/programs/cmp_io.c index 8dc453d699a5c105b339ba9aaeca1d1dbbee8dfe..32eb9967aebdcbaf3a8124cf5ac34ccb8524bf1a 100644 --- a/programs/cmp_io.c +++ b/programs/cmp_io.c @@ -39,6 +39,14 @@ #include <leon_inttypes.h> +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +/* Redefine (f)printf to do nothing */ +__extension__ +#define printf(...) do {} while (0) +#define fprintf(...) do {} while (0) +#endif + + /* directory to convert from data_type to string */ static const struct { enum cmp_data_type data_type; @@ -78,7 +86,7 @@ static const struct { * @param program_name name of the program */ -void print_help(const char *program_name) +void print_help(const char *program_name MAYBE_UNUSED) { printf("usage: %s [options] [<argument>]\n", program_name); printf("General Options:\n"); @@ -284,9 +292,11 @@ int write_data_to_file(const void *buf, uint32_t buf_size, const char *output_pr } if (flags & CMP_IO_VERBOSE_EXTRA && !(flags & CMP_IO_BINARY)) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION printf("\n"); fwrite(output_file_data, 1, output_file_size, stdout); printf("\n"); +#endif } free(tmp_buf); @@ -491,6 +501,12 @@ int case_insensitive_compare(const char *s1, const char *s2) { size_t i; + if(s1 == NULL) + abort(); + + if(s2 == NULL) + abort(); + for (i = 0; ; ++i) { unsigned int x1 = (unsigned char)s1[i]; unsigned int x2 = (unsigned char)s2[i]; @@ -1305,7 +1321,7 @@ static __inline ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t b nptr++; } if (*nptr != '\0') { - fprintf(stderr, "%s: %s: Warning: The file may contain more data than specified by the samples or cmp_size parameter.\n", + fprintf(stderr, "%s: %s: Warning: The file may contain more data than read from it.\n", PROGRAM_NAME, file_name); } @@ -1731,8 +1747,8 @@ int cmp_cfg_fo_file(const struct rdcu_cfg *rcfg, const char *output_prefix, * @returns 0 on success, error otherwise */ -int cmp_info_to_file(const struct cmp_info *info, const char *output_prefix, - int add_ap_pars) +int cmp_info_to_file(const struct cmp_info *info MAYBE_UNUSED, + const char *output_prefix, int add_ap_pars) { FILE *fp = open_file(output_prefix, ".info"); diff --git a/programs/cmp_tool.c b/programs/cmp_tool.c index 3d0e7f2b9ad386d9255fd93c4308ca5999010f6e..20e2fb90bb8b218e345c860b82f95512b47205ca 100644 --- a/programs/cmp_tool.c +++ b/programs/cmp_tool.c @@ -19,6 +19,7 @@ * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 */ +#include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -27,8 +28,9 @@ #include <errno.h> #include <getopt.h> +#include <cmp_tool-config.h> + #include "cmp_support.h" -#include "cmp_tool-config.h" #include "cmp_io.h" #include "cmp_icu.h" #include "cmp_chunk.h" @@ -145,6 +147,45 @@ static uint32_t model_id = DEFAULT_MODEL_ID; static uint32_t model_counter; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define CMP_MAIN cmp_tool_main +/* Redefine (f)printf to do nothing */ +__extension__ +#define printf(...) do {} while (0) +#define fprintf(...) do {} while (0) +#define fflush(...) do {} while (0) + +int CMP_MAIN(int argc, char **argv); + +/** + * @brief callable main function + * Resets the global variables and calls the cmp_tool main function. + * + * @param argc argument count + * @param argv argument vector + * + * @returns EXIT_SUCCESS on success, EXIT_FAILURE on error. + */ + +int testable_cmp_tool_main(int argc, char **argv) +{ + output_prefix = DEFAULT_OUTPUT_PREFIX; + add_rdcu_pars = 0; + rdcu_pkt_mode = 0; + last_info_file_name = NULL; + io_flags = 0; + include_cmp_header = 1; + model_id = DEFAULT_MODEL_ID; + model_counter = 0; + + optind = 0; + return CMP_MAIN(argc, argv); +} +#else +#define CMP_MAIN main +#endif + + /** * @brief This is the main function of the compression / decompression tool * @@ -155,7 +196,7 @@ static uint32_t model_counter; * @returns EXIT_SUCCESS on success, EXIT_FAILURE on error */ -int main(int argc, char **argv) +int CMP_MAIN(int argc, char **argv) { int opt; int error; @@ -188,7 +229,7 @@ int main(int argc, char **argv) /* show help if no arguments are provided */ if (argc < 2) { print_help(program_name); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } while ((opt = getopt_long(argc, argv, "abc:d:hi:m:no:vV", long_options, @@ -209,8 +250,7 @@ int main(int argc, char **argv) break; case 'h': /* --help */ print_help(argv[0]); - exit(EXIT_SUCCESS); - break; + return EXIT_SUCCESS; case 'i': info_file_name = optarg; include_cmp_header = 0; @@ -231,8 +271,7 @@ int main(int argc, char **argv) break; case 'V': /* --version */ printf("%s version %s\n", PROGRAM_NAME, CMP_TOOL_VERSION); - exit(EXIT_SUCCESS); - break; + return EXIT_SUCCESS; case DIFF_CFG_OPTION: print_diff_cfg = 1; break; @@ -256,24 +295,23 @@ int main(int argc, char **argv) break; case MODEL_ID: if (atoui32("model_id", optarg, &model_id)) - return -1; + return EXIT_FAILURE; if (model_counter > UINT16_MAX) { fprintf(stderr, "%s: Error: model id value to large.\n", PROGRAM_NAME); - return -1; + return EXIT_FAILURE; } break; case MODEL_COUTER: if (atoui32("model_counter", optarg, &model_counter)) - return -1; + return EXIT_FAILURE; if (model_counter > UINT8_MAX) { fprintf(stderr, "%s: Error: model counter value to large.\n", PROGRAM_NAME); - return -1; + return EXIT_FAILURE; } break; default: print_help(program_name); - exit(EXIT_FAILURE); - break; + return EXIT_FAILURE; } } argc -= optind; @@ -284,7 +322,7 @@ int main(int argc, char **argv) if (argc > 2) { printf("%s: To many arguments.\n", PROGRAM_NAME); print_help(argv[0]); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } if (argc > 0) { @@ -293,7 +331,7 @@ int main(int argc, char **argv) else { printf("You can define the data file using either the -d option or the first argument, but not both.\n"); print_help(program_name); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } } if (argc > 1) { @@ -302,14 +340,14 @@ int main(int argc, char **argv) else { printf("You can define the model file using either the -m option or the second argument, but not both.\n"); print_help(program_name); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } } #else if (argc > 0) { printf("%s: To many arguments.\n", PROGRAM_NAME); print_help(argv[0]); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } #endif @@ -317,14 +355,14 @@ int main(int argc, char **argv) if (print_model_cfg && print_diff_cfg) { fprintf(stderr, "%s: Cannot use -n, --model_cfg and -diff_cfg together.\n", PROGRAM_NAME); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } if (print_model_cfg) cmp_cfg_create_default(&rcfg, MODEL_CFG); else cmp_cfg_create_default(&rcfg, DIFF_CFG); cmp_cfg_print(&rcfg, add_rdcu_pars); - exit(EXIT_SUCCESS); + return EXIT_SUCCESS; } { @@ -342,15 +380,14 @@ int main(int argc, char **argv) } if (!data_file_name) { - fprintf(stderr, "%s: No data file (-d option) specified.\n", - PROGRAM_NAME); - exit(EXIT_FAILURE); + fprintf(stderr, "%s: No data file (-d option) specified.\n", PROGRAM_NAME); + return EXIT_FAILURE; } if (!cfg_file_name && !info_file_name && !guess_operation && !include_cmp_header) { fprintf(stderr, "%s: No configuration file (-c option) or decompression information file (-i option) specified.\n", PROGRAM_NAME); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } @@ -500,6 +537,19 @@ int main(int argc, char **argv) else model_size = cmp_ent_get_original_size(decomp_entity); + size = read_file_data(model_file_name, cmp_type, NULL, + model_size, io_flags); + if (size < 0) + goto fail; + if (size < (ssize_t)model_size) { + fprintf(stderr, "%s: %s: Error: The files do not contain enough data. Expected: 0x%x, has 0x%x.\n", + PROGRAM_NAME, model_file_name, model_size, (uint32_t)size); + goto fail; + } + if (size != (ssize_t)model_size) { + fprintf(stderr, "%s: %s: Error: Model file size does not match original data size.\n", PROGRAM_NAME, model_file_name); + goto fail; + } input_model_buf = malloc(model_size); if (!input_model_buf) { @@ -511,10 +561,7 @@ int main(int argc, char **argv) model_size, io_flags); if (size < 0) goto fail; - if (size != (ssize_t)model_size) { - fprintf(stderr, "%s: %s: Error: Model file size does not match original data size.\n", PROGRAM_NAME, model_file_name); - goto fail; - } + printf("DONE\n"); @@ -561,7 +608,7 @@ int main(int argc, char **argv) free(decomp_entity); free(input_model_buf); - exit(EXIT_SUCCESS); + return EXIT_SUCCESS; fail: printf("FAILED\n"); @@ -570,7 +617,7 @@ fail: free(decomp_entity); free(input_model_buf); - exit(EXIT_FAILURE); + return EXIT_FAILURE; } @@ -584,7 +631,7 @@ static int guess_cmp_pars(struct rdcu_cfg *rcfg, struct cmp_par *chunk_par, { int error; uint32_t cmp_size_bit; - double cr; + double cr MAYBE_UNUSED; enum cmp_data_type data_type; char *endptr; int guess_level; @@ -603,7 +650,7 @@ static int guess_cmp_pars(struct rdcu_cfg *rcfg, struct cmp_par *chunk_par, printf("Search for a good set of compression parameters (level: %d) ... ", guess_level); fflush(stdout); - if (!case_insensitive_compare(guess_option, "rdcu")) { + if (guess_option && !case_insensitive_compare(guess_option, "rdcu")) { if (add_rdcu_pars) data_type = DATA_TYPE_IMAGETTE_ADAPTIVE; else @@ -612,7 +659,7 @@ static int guess_cmp_pars(struct rdcu_cfg *rcfg, struct cmp_par *chunk_par, rcfg->cmp_mode = CMP_GUESS_DEF_MODE_MODEL; else rcfg->cmp_mode = CMP_GUESS_DEF_MODE_DIFF; - } else if (!case_insensitive_compare(guess_option, "chunk")) { + } else if (guess_option && !case_insensitive_compare(guess_option, "chunk")) { data_type = DATA_TYPE_CHUNK; } else { data_type = DATA_TYPE_IMAGETTE; diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py index 3f7e00418dabaa60380f71183369920a23caf977..e0e3fbe7afe1f4399dd4b1185e7d3347c3b6e24c 100755 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -1331,7 +1331,7 @@ def test_model_fiel_erros(): del_file(output_prefix+"_upmodel.dat") -def test_decmp_model_fiel_original_size_miss_match(): +def test_decmp_model_fiel_original_size_miss_match_binary(): cmp_data = b'8000000d000029000004097ce800cbd5097ce800cbfe00010108d01001000000001001001110078700' to_large_model = b'111111111111' # should be 4 byte large in normal case output_prefix = 'model_file_to_large' @@ -1356,6 +1356,37 @@ def test_decmp_model_fiel_original_size_miss_match(): finally: del_file(cmp_data_file_name) del_file(model_file_name) + del_file(output_prefix+'.dat') + del_file(output_prefix+'_upmodel.dat') + + +def test_decmp_model_fiel_original_size_miss_match_binary(): + cmp_data = '8000000d000029000004097ce800cbd5097ce800cbfe00010108d01001000000001001001110078700' + to_large_model = '111111111111' # should be 4 byte large in normal case + output_prefix = 'model_file_to_large' + cmp_data_file_name = 'cmp_data.cmp' + model_file_name = 'to_large_model.dat' + + try: + with open(cmp_data_file_name, 'w', encoding='utf-8') as f: + f.write(cmp_data) + + with open(model_file_name, 'w', encoding='utf-8') as f: + f.write(to_large_model) + + returncode, stdout, stderr = call_cmp_tool( + " -d "+cmp_data_file_name + " -m " + model_file_name + " -o "+output_prefix) + assert(returncode == EXIT_FAILURE) + assert(stdout == CMP_START_STR_DECMP + + "Importing compressed data file %s ... DONE\n" % (cmp_data_file_name) + + "Importing model file %s ... FAILED\n" % (model_file_name)) + assert(stderr == "cmp_tool: %s: Error: Model file size does not match original data size.\n" % (model_file_name)) + + finally: + del_file(cmp_data_file_name) + del_file(model_file_name) + del_file(output_prefix+'.dat') + del_file(output_prefix+'_upmodel.dat') def test_rdcu_pkt(): diff --git a/test/fuzz/fuzz_cmp_tool.c b/test/fuzz/fuzz_cmp_tool.c new file mode 100644 index 0000000000000000000000000000000000000000..0fab4999cde1249af7f19a45fb44695950d02748 --- /dev/null +++ b/test/fuzz/fuzz_cmp_tool.c @@ -0,0 +1,150 @@ +/** + * @file fuzz_cmp_tool.c + * @date 2025 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * @brief chunk compression fuzz target + * + * This fuzzer tests the compression functionality with random data/model and + * parameters. It uses a random portion of the input data for parameter + * generation, while the rest is used for compression. + */ + + +#include <stdint.h> +#include <limits.h> +#include <stdint.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> + +#include "fuzz_helpers.h" +#include "fuzz_data_producer.h" + +#define MAX_ARG_COUNT 32 +#define MAX_ARG_SIZE 64 + + +int testable_cmp_tool_main(int argc, char **argv); + + +static void add_argument_with_file(char **argv, int index, const char *flag, const char *file) +{ + if (index > 0) { /* zero is revert for program name */ + size_t flag_len = strlen(flag); + size_t file_len = strlen(file); + + FUZZ_ASSERT(flag_len + file_len < MAX_ARG_SIZE); + memcpy(argv[index], flag, flag_len); + memcpy(argv[index]+flag_len, file, file_len + 1); + } +} + + +static int gen_argv(FUZZ_dataProducer_t *producer, char **argv, const char *data_file, + const char *model_file, const char *cfg_file, const char *info_file, + const uint8_t *other_arguments, size_t o_args_size) +{ + int argc = FUZZ_dataProducer_int32Range(producer, 1, MAX_ARG_COUNT); + int i, end; + + /* Add optional arguments no the end so they have higher priority */ + end = argc-1; + /* TODO: How to clean up written stuff by the cmp_tool? */ + add_argument_with_file(argv, end--, "-o", FUZZ_TMP_DIR "/fuzz-output-cmp_tool"); + if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) + add_argument_with_file(argv, end--, "-d", data_file); + if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) + add_argument_with_file(argv, end--, "-m", model_file); + if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) + add_argument_with_file(argv, end--, "-c", cfg_file); + if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) + add_argument_with_file(argv, end--, "-i", info_file); + + for (i = 0; i < end; i++) { + size_t s = FUZZ_dataProducer_uint32Range(producer, 0, MAX_ARG_SIZE-1); + + if (s > o_args_size) + s = o_args_size; + memcpy(argv[i], other_arguments, s); + argv[i][s] = '\0'; + other_arguments += s; + o_args_size -= s; + } + + return argc; +} + + + +static char *get_file(FUZZ_dataProducer_t *producer, const uint8_t **src, uint32_t *size) +{ + uint32_t file_size = FUZZ_dataProducer_uint32Range(producer, 0, *size); + char *file = FUZZ_buf_to_file(*src, file_size); + *src += file_size; + *size -= file_size; + return file; +} + + +int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size) +{ + static char **argv; + /* + * Give a random portion of src data to the producer, to use for + * parameter generation. The rest will be used for the file content and + * arguments + */ + FUZZ_dataProducer_t *producer = FUZZ_dataProducer_create(src, size); + uint32_t size32 = (uint32_t)FUZZ_dataProducer_reserveDataPrefix(producer); + const char *data_file = get_file(producer, &src, &size32); + const char *model_file = get_file(producer, &src, &size32); + const char *info_file = get_file(producer, &src, &size32); + const char *cfg_file = get_file(producer, &src, &size32); + const uint8_t *other_arguments = src; + + int argc; + + FUZZ_ASSERT(size < UINT32_MAX); + + if (argv == NULL) { + static const char program_name[] = "cmp_tool_fuzz"; + char *data; + size_t i; + + argv = FUZZ_malloc(sizeof(*argv) * MAX_ARG_COUNT); + data = FUZZ_malloc(sizeof(*data) * MAX_ARG_COUNT * MAX_ARG_SIZE); + memset(data, 0, sizeof(*data) * MAX_ARG_COUNT * MAX_ARG_SIZE); + for (i = 0; i < MAX_ARG_COUNT; i++) + argv[i] = &data[i*MAX_ARG_SIZE]; + + FUZZ_ASSERT(sizeof(program_name) <= MAX_ARG_SIZE); + memcpy(argv[0], program_name, sizeof(program_name)); + } + + argc = gen_argv(producer, argv, data_file, model_file, cfg_file, + info_file, other_arguments, size32); + + /* for(int i = 1; i < argc; i++) */ + /* printf("%s\n", argv[i]); */ + + testable_cmp_tool_main(argc, argv); + + FUZZ_delete_file(data_file); + FUZZ_delete_file(model_file); + FUZZ_delete_file(info_file); + FUZZ_delete_file(cfg_file); + + FUZZ_dataProducer_free(producer); + + return 0; +} diff --git a/test/fuzz/fuzz_data_producer.h b/test/fuzz/fuzz_data_producer.h index cdccd16a5a916ec8d1326a0be0fb7e378ba846f5..c5d4891a02f90315dc16122b08c51b396222b80e 100644 --- a/test/fuzz/fuzz_data_producer.h +++ b/test/fuzz/fuzz_data_producer.h @@ -29,13 +29,21 @@ #ifndef FUZZ_DATA_PRODUCER_H #define FUZZ_DATA_PRODUCER_H + #include <stddef.h> #include <stdint.h> -#include <stdio.h> #include <stdlib.h> #include <cmp_chunk.h> + +#ifdef __APPLE__ +# define FUZZ_TMP_DIR "/tmp" +#else +# define FUZZ_TMP_DIR "/dev/shm" +#endif + + /* Struct used for maintaining the state of the data */ typedef struct FUZZ_dataProducer_s FUZZ_dataProducer_t; @@ -47,14 +55,14 @@ void FUZZ_dataProducer_free(FUZZ_dataProducer_t *producer); /* Returns value between [min, max] */ uint32_t FUZZ_dataProducer_uint32Range(FUZZ_dataProducer_t *producer, uint32_t min, - uint32_t max); + uint32_t max); /* Returns a uint32 value */ uint32_t FUZZ_dataProducer_uint32(FUZZ_dataProducer_t *producer); /* Returns a signed value between [min, max] */ int32_t FUZZ_dataProducer_int32Range(FUZZ_dataProducer_t *producer, - int32_t min, int32_t max); + int32_t min, int32_t max); /* Provides compression parameters */ void FUZZ_dataProducer_cmp_par(FUZZ_dataProducer_t *producer, struct cmp_par *cmp_par); @@ -69,12 +77,16 @@ void FUZZ_dataProducer_rollBack(FUZZ_dataProducer_t *producer, size_t remainingB int FUZZ_dataProducer_empty(FUZZ_dataProducer_t *producer); /* Restricts the producer to only the last newSize bytes of data. -If newSize > current data size, nothing happens. Returns the number of bytes -the producer won't use anymore, after contracting. */ + * If newSize > current data size, nothing happens. Returns the number of bytes + * the producer won't use anymore, after contracting. + */ size_t FUZZ_dataProducer_contract(FUZZ_dataProducer_t *producer, size_t newSize); -/* Restricts the producer to use only the last X bytes of data, where X is - a random number in the interval [0, data_size]. Returns the size of the - remaining data the producer won't use anymore (the prefix). */ +/* Restricts the producer to use only the last X bytes of data, where X is a + * random number in the interval [0, data_size]. Returns the size of the + * remaining data the producer won't use anymore (the prefix). + */ size_t FUZZ_dataProducer_reserveDataPrefix(FUZZ_dataProducer_t *producer); + + #endif // FUZZ_DATA_PRODUCER_H diff --git a/test/fuzz/fuzz_helpers.c b/test/fuzz/fuzz_helpers.c index 160b153e6f4ac69db6cf5411e567bb09c1bb91e1..55b6cdf5f89964be695b03d7c57b1ba1fc6249b1 100644 --- a/test/fuzz/fuzz_helpers.c +++ b/test/fuzz/fuzz_helpers.c @@ -8,13 +8,29 @@ * You may select, at your option, one of the above-listed licenses. */ +/* + * Modifications made by + * @author Dominik Loidolt (dominik.loidolt@univie.ac.at) + * @date 2025 + * + * - add FUZZ_buf_to_file + * - add FUZZ_delete_file + * + * Modifications are also licensed under the same license for consistency + */ + /** * Helper functions for fuzzing. */ +#include <stddef.h> #include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> #include "fuzz_helpers.h" +#include "fuzz_data_producer.h" void *FUZZ_malloc(size_t size) @@ -27,3 +43,45 @@ void *FUZZ_malloc(size_t size) } return NULL; } + + +int FUZZ_delete_file(const char *path_name) +{ + int const ret = unlink(path_name); + + FUZZ_ASSERT(ret != -1); + + free((void *)path_name); + + return ret; +} + + +char *FUZZ_buf_to_file(const uint8_t *buf, size_t size) +{ + int fd, ret_close; + size_t pos = 0; + + char *path_name = strdup(FUZZ_TMP_DIR "/fuzz-XXXXXX"); + + FUZZ_ASSERT(path_name != NULL); + + fd = mkstemp(path_name); + FUZZ_ASSERT_MSG(fd != 1, path_name); + + while (pos < size) { + ssize_t bytes_written = write(fd, &buf[pos], size - pos); + + if (bytes_written == -1 && errno == EINTR) + continue; + + FUZZ_ASSERT(bytes_written != -1); + + pos += (size_t)bytes_written; + } + + ret_close = close(fd); + FUZZ_ASSERT(ret_close != 1); + + return path_name; +} diff --git a/test/fuzz/fuzz_helpers.h b/test/fuzz/fuzz_helpers.h index bdaa672c81e9f63dad8d5fe4119be0c085128ecf..dee423e47580e431c5671302a58d3bd2a765e720 100644 --- a/test/fuzz/fuzz_helpers.h +++ b/test/fuzz/fuzz_helpers.h @@ -16,6 +16,7 @@ #define FUZZ_HELPERS_H #include <stdio.h> +#include <stdint.h> #ifdef __cplusplus extern "C" { @@ -28,14 +29,17 @@ extern "C" { /** * Asserts for fuzzing that are always enabled. */ -#define FUZZ_ASSERT_MSG(cond, msg) \ - ((cond) ? (void)0 \ - : (fprintf(stderr, "%s: %u: Assertion: `%s' failed. %s\n", __FILE__, \ - __LINE__, FUZZ_QUOTE(cond), (msg)), \ - abort())) +#define FUZZ_ASSERT_MSG(cond, msg) \ + ((cond) ? (void)0 \ + : (fprintf(stderr, "%s: %u: Assertion: `%s' failed. %s\n", __FILE__, \ + __LINE__, FUZZ_QUOTE(cond), (msg)), \ + abort())) #define FUZZ_ASSERT(cond) FUZZ_ASSERT_MSG((cond), ""); -void* FUZZ_malloc(size_t size); +void *FUZZ_malloc(size_t size); + +char *FUZZ_buf_to_file(const uint8_t *buf, size_t size); +int FUZZ_delete_file(const char *path_name); #ifdef __cplusplus } diff --git a/test/fuzz/getopt_long_quiet.c b/test/fuzz/getopt_long_quiet.c new file mode 100644 index 0000000000000000000000000000000000000000..6b5e03c8e525468ab5124d66f471502ad58c9e31 --- /dev/null +++ b/test/fuzz/getopt_long_quiet.c @@ -0,0 +1,608 @@ +/* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* include <err.h> */ +__extension__ +#define warnx(format, ...) (void)0 +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ + +#if 1 +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ +#endif + +#ifdef REPLACE_GETOPT +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +static char EMSG[] = ""; + +#ifdef GNU_COMPATIBLE +#define NO_PREFIX (-1) +#define D_PREFIX 0 +#define DD_PREFIX 1 +#define W_PREFIX 2 +#endif + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ +#ifdef GNU_COMPATIBLE +static int dash_prefix = NO_PREFIX; +static const char gnuoptchar[] = "invalid option -- %c"; + +static const char recargstring[] = "option `%s%s' requires an argument"; +static const char ambig[] = "option `%s%.*s' is ambiguous"; +static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; +static const char illoptstring[] = "unrecognized option `%s%s'"; +#else +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptstring[] = "unknown option -- %s"; +#endif + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; +#ifdef GNU_COMPATIBLE + const char *current_dash; +#endif + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = place; +#ifdef GNU_COMPATIBLE + switch (dash_prefix) { + case D_PREFIX: + current_dash = "-"; + break; + case DD_PREFIX: + current_dash = "--"; + break; + case W_PREFIX: + current_dash = "-W "; + break; + default: + current_dash = ""; + break; + } +#endif + match = -1; + exact_match = 0; + second_partial_match = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != + long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; +#ifdef GNU_COMPATIBLE + return (BADCH); +#else + return (BADARG); +#endif + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1 || optreset) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || +#ifdef GNU_COMPATIBLE + place[1] == '\0') { +#else + (place[1] == '\0' && strchr(options, '-') == NULL)) { +#endif + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; +#ifdef GNU_COMPATIBLE + dash_prefix = D_PREFIX; +#endif + if (*place == '-') { + place++; /* --foo long option */ + if (*place == '\0') + return (BADARG); /* malformed option */ +#ifdef GNU_COMPATIBLE + dash_prefix = DD_PREFIX; +#endif + } else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too, flags); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; +#ifdef GNU_COMPATIBLE + if (PRINT_ERROR) + warnx(posixly_correct ? illoptchar : gnuoptchar, + optchar); +#else + if (PRINT_ERROR) + warnx(illoptchar, optchar); +#endif + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; +#ifdef GNU_COMPATIBLE + dash_prefix = W_PREFIX; +#endif + optchar = parse_long_options(nargv, options, long_options, + idx, 0, flags); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +#ifdef REPLACE_GETOPT +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} +#endif /* REPLACE_GETOPT */ + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} diff --git a/test/fuzz/meson.build b/test/fuzz/meson.build index c9a73c9a8f05bc30add39690e8c983ea75bd9113..7959b738ab9503397ef83bb5cea1047070bb40b6 100644 --- a/test/fuzz/meson.build +++ b/test/fuzz/meson.build @@ -3,17 +3,26 @@ if get_option('fuzzer').disabled() endif fuzz_common = files('fuzz_data_producer.c', 'fuzz_helpers.c') -fuzz_targets = ['fuzz_compression.c', 'fuzz_round_trip.c', 'fuzz_decompression.c'] +fuzz_targets = ['fuzz_compression.c', 'fuzz_round_trip.c', 'fuzz_decompression.c', 'fuzz_cmp_tool.c'] add_languages('cpp', native: false) # libFuzzingEngine needs c++ +prodir = include_directories('../../programs') foreach target : fuzz_targets file_name = target target_name = file_name.split('.').get(0) + if target == 'fuzz_cmp_tool.c' + extra_files = [cmp_tool_src, files('getopt_long_quiet.c')] + elif target == 'fuzz_round_trip.c' + extra_files = [chunk_round_trip] + else + extra_files = [] + endif + fuzz_exe = executable(target_name, - fuzz_common, chunk_round_trip, file_name, - include_directories : incdir, + fuzz_common, file_name, extra_files, + include_directories : [incdir, prodir], link_with : [cmp_lib], link_args : get_option('fuzzer_ldflags'), link_language : 'cpp' # libFuzzingEngine needs c++