From 2f3bbb0dd2e38422ccafb0e634d226f063094b31 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 20 Jan 2025 17:18:42 +0100 Subject: [PATCH] Refactor cmp_tool fuzzing setup --- programs/cmp_io.c | 17 +- programs/cmp_tool.c | 59 ++-- test/fuzz/fuzz_cmp_tool.c | 340 ++++--------------- test/fuzz/fuzz_helpers.c | 61 ++++ test/fuzz/fuzz_helpers.h | 16 +- test/fuzz/getopt_long_quiet.c | 606 ++++++++++++++++++++++++++++++++++ test/fuzz/meson.build | 10 +- 7 files changed, 796 insertions(+), 313 deletions(-) create mode 100644 test/fuzz/getopt_long_quiet.c diff --git a/programs/cmp_io.c b/programs/cmp_io.c index c6a1bbe..8cad7b0 100644 --- a/programs/cmp_io.c +++ b/programs/cmp_io.c @@ -38,9 +38,14 @@ #include <cmp_data_types.h> #include <leon_inttypes.h> - /* Redefine printf to do nothing */ - #define printf(...) - #define fprintf(...) + +#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 { @@ -81,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"); @@ -1734,8 +1739,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 2867c5a..22082b8 100644 --- a/programs/cmp_tool.c +++ b/programs/cmp_tool.c @@ -40,9 +40,6 @@ #include "cmp_entity.h" #include "rdcu_pkt_to_file.h" - /* Redefine printf to do nothing */ - #define printf(...) - #define fprintf(...) #define BUFFER_LENGTH_DEF_FAKTOR 2 @@ -150,6 +147,44 @@ 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) + +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; + + optreset = 1; + return CMP_MAIN(argc, argv); +} +#else +#define CMP_MAIN main +#endif + + /** * @brief This is the main function of the compression / decompression tool * @@ -160,7 +195,7 @@ static uint32_t model_counter; * @returns EXIT_SUCCESS on success, EXIT_FAILURE on error */ -int my_main(int argc, char **argv) +int CMP_MAIN(int argc, char **argv) { int opt; int error; @@ -584,20 +619,6 @@ fail: return EXIT_FAILURE; } -int testable_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 = 1; - return my_main(argc, argv); -} /** * @brief find a good set of compression parameters for a given dataset @@ -609,7 +630,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; diff --git a/test/fuzz/fuzz_cmp_tool.c b/test/fuzz/fuzz_cmp_tool.c index 944ad6c..00437af 100644 --- a/test/fuzz/fuzz_cmp_tool.c +++ b/test/fuzz/fuzz_cmp_tool.c @@ -30,247 +30,65 @@ #include "fuzz_helpers.h" #include "fuzz_data_producer.h" -/* #ifndef TEST */ -/* #define MAIN main */ -/* #else */ -/* #endif */ -/* #include "../../programs/cmp_tool.c" */ +#define MAX_ARG_COUNT 32 +#define MAX_ARG_SIZE 64 +int testable_cmp_tool_main(int argc, char **argv); - - - -#include <stdio.h> - - - - -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -/* #include "fuzz_utils.h" */ - -int ignore_stdout_and_stderr(void) -{ - int fd = open("/dev/null", O_WRONLY); - int ret = 0; - - if (fd == -1) { - warn("open(\"/dev/null\") failed"); - return -1; - } - - if (dup2(fd, STDOUT_FILENO) == -1) { - warn("failed to redirect stdout to /dev/null\n"); - ret = -1; - } - -#if 1 - if (dup2(fd, STDERR_FILENO) == -1) { - warn("failed to redirect stderr to /dev/null\n"); - ret = -1; - } -#endif - - if (close(fd) == -1) { - warn("close"); - ret = -1; - } - - return ret; -} - -int delete_file(const char *pathname) -{ - int ret = unlink(pathname); - - if (ret == -1) { - warn("failed to delete \"%s\"", pathname); - } - - free((void *)pathname); - - return ret; -} - -char *buf_to_file(const uint8_t *buf, size_t size) +static void add_argument_with_file(char **argv, int index, const char *flag, const char *file) { - int fd; - size_t pos = 0; - - char *pathname = strdup("/tmp/fuzz-XXXXXX"); - - if (pathname == NULL) { - return NULL; - } - - fd = mkstemp(pathname); - if (fd == -1) { - warn("mkstemp(\"%s\")", pathname); - free(pathname); - return NULL; - } - - while (pos < size) { - int nbytes = write(fd, &buf[pos], size - pos); - - if (nbytes <= 0) { - if (nbytes == -1 && errno == EINTR) { - continue; - } - warn("write"); - goto err; - } - pos += nbytes; - } - - if (close(fd) == -1) { - warn("close"); - goto err; - } - - return pathname; - -err: - delete_file(pathname); - FUZZ_ASSERT(1); - return NULL; -} - -#include <unistd.h> -#include <limits.h> - -#define FILE_ARG_SIZE 50 - + if (index > 0) { /* zero is revert for program name */ + size_t flag_len = strlen(flag); + size_t file_len = strlen(file); -static void add_argument_with_file_intern(char **argv, int index, const char *flag, const char *file) -{ - if (index >= 0) { - argv[index] = FUZZ_malloc(FILE_ARG_SIZE); - memcpy(argv[index], flag, strlen(flag) + 1); - strcat(argv[index], 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 void add_program_name(char **argv, const char *name) -{ - add_argument_with_file_intern(argv, 0, name, ""); -} - -static void add_argument_with_file(char **argv, int index, const char *flag, const char *file) -{ - if (index > 0) - add_argument_with_file_intern(argv, index, flag, file); -} - -char **gen_argv(FUZZ_dataProducer_t *producer, int *argc, char *data_file, - char *model_file, char *cfg_file, char *info_file, - const uint8_t *other_arguments, size_t o_args_size) +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) { - char **argv; - long max_arg = sysconf(_SC_ARG_MAX); - long line_max = sysconf(_SC_LINE_MAX) - 1; - int i = 0; + int argc = FUZZ_dataProducer_int32Range(producer, 1, MAX_ARG_COUNT); + int i, end; - FUZZ_ASSERT(max_arg > 1); - FUZZ_ASSERT(max_arg <= INT_MAX); - FUZZ_ASSERT(line_max > 0); - FUZZ_ASSERT(line_max <= UINT32_MAX); - - *argc = FUZZ_dataProducer_int32Range(producer, 1, (int32_t)max_arg); - argv = FUZZ_malloc((size_t)(*argc) * sizeof(argv)); - - /* set program name */ - add_program_name(argv, "cmp_tool_fuzz"); - /* set the file at thend the have a higher priotirty */ - i = *argc-1; - - add_argument_with_file(argv, i--, "-o /tmp/fuzz-output-cmp_tool", ""); + /* Add optional arguments no the end so they have higher priority */ + end = argc-1; + add_argument_with_file(argv, end--, "-o", "/tmp/fuzz-output-cmp_tool"); if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) - add_argument_with_file(argv, i--, "-d", data_file); + add_argument_with_file(argv, end--, "-d", data_file); if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) - add_argument_with_file(argv, i--, "-m", model_file); + add_argument_with_file(argv, end--, "-m", model_file); if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) - add_argument_with_file(argv, i--, "-c", cfg_file); + add_argument_with_file(argv, end--, "-c", cfg_file); if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) - add_argument_with_file(argv, i--, "-i", info_file); + 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); - while (i > 0) { - size_t s = FUZZ_dataProducer_uint32Range(producer, 0, (uint32_t)line_max); - - if (o_args_size < s) + if (s > o_args_size) s = o_args_size; - argv[i] = FUZZ_malloc(s+1); memcpy(argv[i], other_arguments, s); - argv[i--][s] = '\0'; + argv[i][s] = '\0'; other_arguments += s; o_args_size -= s; } - return argv; + return argc; } -void free_argv(int argc, char **argv) -{ - int i; - - for (i = 0; i < argc; ++i) { - free(argv[i]); - argv[i] = NULL; - } - free(argv); -} - - - -int testable_main(int argc, char **argv); -#if 0 -int main(void) -{ - int argc = 0; - char **argv = NULL; - FUZZ_dataProducer_t *producer = FUZZ_dataProducer_create(NULL, 0); - char data_file[] = "data-file"; - char model_file[] = "model-file"; - char cfg_file[] = "cfg-file"; - uint8_t other_arguments[] = "other-stuf"; - - argv = gen_argv(producer, &argc, data_file, model_file, cfg_file, - other_arguments, sizeof(other_arguments)); - FUZZ_ASSERT(argv != NULL); - FUZZ_ASSERT(argc == 11); - FUZZ_ASSERT(!strcmp(argv[0], "cmp_tool_fuzz")); - FUZZ_ASSERT(!strcmp(argv[10], "-o /tmp/fuzz-output-cmp_tool")); - FUZZ_ASSERT(!strcmp(argv[9], "-ddata-file")); - FUZZ_ASSERT(!strcmp(argv[8], "-mmodel-file")); - FUZZ_ASSERT(!strcmp(argv[7], "-ccfg-file")); - FUZZ_ASSERT(!strcmp(argv[6], "oth")); - FUZZ_ASSERT(!strcmp(argv[5], "er-")); - FUZZ_ASSERT(!strcmp(argv[4], "stu")); - FUZZ_ASSERT(!strcmp(argv[3], "f")); - FUZZ_ASSERT(!strcmp(argv[2], "")); - FUZZ_ASSERT(!strcmp(argv[1], "")); - - /* testable_main(argc, argv); */ - free_argv(argc, argv); - FUZZ_dataProducer_free(producer); - return 0; -} -#endif -char *get_file(FUZZ_dataProducer_t *producer, const uint8_t **src, uint32_t *size) +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 = buf_to_file(*src, file_size); + char *file = FUZZ_buf_to_file(*src, file_size); *src += file_size; *size -= file_size; return file; @@ -279,92 +97,52 @@ char *get_file(FUZZ_dataProducer_t *producer, const uint8_t **src, uint32_t *siz 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); - /* Create an array to hold the input arguments */ - int argc = 0; - char **argv = NULL; - char *data_file = get_file(producer, &src, &size32); - char *model_file = get_file(producer, &src, &size32); - char *info_file = get_file(producer, &src, &size32); - char *cfg_file = get_file(producer, &src, &size32); + 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; - FUZZ_ASSERT(size < UINT32_MAX); - - argv = gen_argv(producer, &argc, data_file, model_file, cfg_file, info_file, - other_arguments, size32); + int argc; + FUZZ_ASSERT(size < UINT32_MAX); -#if 0 - /* Give a random portion of src data to the producer, to use for - parameter generation. The rest will be used for data/model */ - FUZZ_dataProducer_t *producer = FUZZ_dataProducer_create(src, size); - - size = FUZZ_dataProducer_reserveDataPrefix(producer); - FUZZ_ASSERT(size <= UINT32_MAX); + if (argv == NULL) { + static const char program_name[] = "cmp_tool_fuzz"; + char *data; + size_t i; - /* spilt data to compressed data and model data */ - ent_size = FUZZ_dataProducer_uint32Range(producer, 0, (uint32_t)size); - model_of_data_size = FUZZ_dataProducer_uint32Range(producer, 0, (uint32_t)size-ent_size); + argv = FUZZ_malloc(sizeof(*argv) * MAX_ARG_COUNT); + data = FUZZ_malloc(sizeof(*data) * MAX_ARG_COUNT * MAX_ARG_SIZE); + for (i = 0; i < MAX_ARG_COUNT; i++) + argv[i] = &data[i*MAX_ARG_SIZE]; - if (ent_size) - ent = (const struct cmp_entity *)src; - if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) - model_of_data = src + ent_size; - else - model_of_data = NULL; + 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); -#endif - /* ignore_stdout_and_stderr(); */ /* for(int i = 1; i < argc; i++) */ /* printf("%s\n", argv[i]); */ - testable_main(argc, argv); + testable_cmp_tool_main(argc, argv); - delete_file(data_file); - delete_file(model_file); - delete_file(info_file); - delete_file(cfg_file); - - free_argv(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; } - -#if 0 -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> - -extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); -__attribute__((weak)) extern int LLVMFuzzerInitialize(int *argc, char ***argv); -int main(int argc, char **argv) -{ - fprintf(stderr, "StandaloneFuzzTargetMain: running %d inputs\n", argc - 1); - if (LLVMFuzzerInitialize) - LLVMFuzzerInitialize(&argc, &argv); - for (int i = 1; i < argc; i++) { - fprintf(stderr, "Running: %s\n", argv[i]); - FILE *f = fopen(argv[i], "r"); - - assert(f); - fseek(f, 0, SEEK_END); - size_t len = ftell(f); - - fseek(f, 0, SEEK_SET); - unsigned char *buf = (unsigned char *)malloc(len); - size_t n_read = fread(buf, 1, len, f); - - fclose(f); - assert(n_read == len); - LLVMFuzzerTestOneInput(buf, len); - free(buf); - fprintf(stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); - } -} -#endif diff --git a/test/fuzz/fuzz_helpers.c b/test/fuzz/fuzz_helpers.c index 160b153..7a150b6 100644 --- a/test/fuzz/fuzz_helpers.c +++ b/test/fuzz/fuzz_helpers.c @@ -8,11 +8,26 @@ * 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" @@ -27,3 +42,49 @@ 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; + +#ifdef __APPLE__ + char *path_name = strdup("/tmp/fuzz-XXXXXX"); +#else + char *path_name = strdup("/dev/shm/fuzz-XXXXXX"); +#endif + + 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 bdaa672..dee423e 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 0000000..5cf6a55 --- /dev/null +++ b/test/fuzz/getopt_long_quiet.c @@ -0,0 +1,606 @@ +/* $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> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ + +#if 0 /* we prefer to keep our getopt(3) */ +#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 a5cca1b..7959b73 100644 --- a/test/fuzz/meson.build +++ b/test/fuzz/meson.build @@ -12,8 +12,16 @@ 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, cmp_tool_src, + fuzz_common, file_name, extra_files, include_directories : [incdir, prodir], link_with : [cmp_lib], link_args : get_option('fuzzer_ldflags'), -- GitLab