From 67de65504c7e561595c544250796ef300a2f13fe Mon Sep 17 00:00:00 2001 From: Alex Zanegin <10143657+azanegin@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:38:37 +0000 Subject: [PATCH 1/2] luzer: rm fuzzer internals cov instrumentation Before this commit, most internal functions were marked with attributes to protect them from Address Sanitizer. This was meant clang still instrumented code with coverage collection, slowing down hot path AND unstabilizing fuzzing process by damaging real target coverage. Fixes #11 --- CHANGELOG.md | 4 ++++ luzer/macros.h | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c40732f..2182432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Integration with libFuzzer's `FuzzedDataProvider`. - Examples with tests. - Documentation with usecases, API etc. + +### Fixed +- Interfering coverage instrumentation of fuzzer internals (#11) + diff --git a/luzer/macros.h b/luzer/macros.h index 984ed68..aa11e02 100644 --- a/luzer/macros.h +++ b/luzer/macros.h @@ -36,6 +36,20 @@ #define NO_SANITIZE_MEMORY #endif // __has_attribute -#define NO_SANITIZE NO_SANITIZE_ADDRESS NO_SANITIZE_MEMORY +// https://clang.llvm.org/docs/SanitizerCoverage.html#disabling-instrumentation-with-attribute-no-sanitize-coverage +#ifdef __has_feature + +#if __has_feature(coverage_sanitizer) +#define NO_SANITIZE_COVERAGE __attribute__((no_sanitize("coverage"))) +#else // __has_feature(coverage_sanitizer) +#warning "compiler does not provide 'coverage_sanitizer' feature" +#define NO_SANITIZE_COVERAGE +#endif // __has_feature(coverage_sanitizer) + +#else // __has_feature +#warning "compiler does not provide __has_feature, can't check for sanitization" +#endif // __has_feature + +#define NO_SANITIZE NO_SANITIZE_ADDRESS NO_SANITIZE_MEMORY NO_SANITIZE_COVERAGE #endif // LUZER_MACROS_H_ From a0e1cc82fb50d1e5de31aabe0e88214ff1a19944 Mon Sep 17 00:00:00 2001 From: forzafedor Date: Fri, 29 Dec 2023 13:33:22 +0300 Subject: [PATCH 2/2] luzer: fix parse args from lua & added support for command line arguments for libfuzzer Fixes #13 --- CHANGELOG.md | 2 + README.md | 2 +- luzer/CMakeLists.txt | 1 + luzer/luzer.c | 54 +---------- luzer/luzer.h | 6 +- luzer/luzer_args.c | 212 +++++++++++++++++++++++++++++++++++++++++++ luzer/luzer_args.h | 7 ++ 7 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 luzer/luzer_args.c create mode 100644 luzer/luzer_args.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c40732f..da3c4c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Integration with libFuzzer's `FuzzedDataProvider`. - Examples with tests. - Documentation with usecases, API etc. +- Support for command line arguments for libfuzzer. +- Environment variable to disable parsing of command line arguments for libfuzzer - `LUZER_NOT_USE_CLI_ARGS_FOR_LF`. diff --git a/README.md b/README.md index d1e4812..3e65ff6 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ local function TestOneInput(buf) end end -luzer.Fuzz(TestOneInput) +luzer.Fuzz(TestOneInput, nil, {}) ``` 3. Start the fuzzer using the fuzz target diff --git a/luzer/CMakeLists.txt b/luzer/CMakeLists.txt index 785a9e2..70a3732 100644 --- a/luzer/CMakeLists.txt +++ b/luzer/CMakeLists.txt @@ -23,6 +23,7 @@ set(LUZER_SOURCES luzer.c fuzzed_data_provider.cc tracer.c counters.c + luzer_args.c ${CMAKE_CURRENT_BINARY_DIR}/version.c) add_library(${CMAKE_PROJECT_NAME} SHARED ${LUZER_SOURCES}) diff --git a/luzer/luzer.c b/luzer/luzer.c index e6e94d3..e1e1660 100644 --- a/luzer/luzer.c +++ b/luzer/luzer.c @@ -24,6 +24,7 @@ #include "macros.h" #include "tracer.h" #include "version.h" +#include "luzer_args.h" #include "luzer.h" #define TEST_ONE_INPUT_FUNC "luzer_test_one_input" @@ -327,57 +328,10 @@ load_custom_mutator_lib(void) { NO_SANITIZE static int luaL_fuzz(lua_State *L) { - if (lua_istable(L, -1) == 0) { - luaL_error(L, "opts is not a table"); - } - lua_pushnil(L); + char **argv = NULL; + int argc = 0; - /* Processing a table with options. */ - int argc = 0; - char **argv = malloc(1 * sizeof(char*)); - if (!argv) - luaL_error(L, "not enough memory"); - const char *corpus_path = NULL; - while (lua_next(L, -2) != 0) { - char **argvp = realloc(argv, sizeof(char*) * (argc + 1)); - if (argvp == NULL) { - free(argv); - luaL_error(L, "not enough memory"); - } - const char *key = lua_tostring(L, -2); - const char *value = lua_tostring(L, -1); - if (strcmp(key, "corpus") != 0) { - size_t arg_len = strlen(key) + strlen(value) + 3; - char *arg = calloc(arg_len, sizeof(char)); - if (!arg) - luaL_error(L, "not enough memory"); - snprintf(arg, arg_len, "-%s=%s", key, value); - argvp[argc] = arg; - argc++; - } else { - corpus_path = strdup(value); - } - lua_pop(L, 1); - argv = argvp; - } - if (corpus_path) { - argv[argc] = (char*)corpus_path; - argc++; - } - if (argc == 0) { - argv[argc] = ""; - argc++; - } - argv[argc] = NULL; - lua_pop(L, 1); - -#ifdef DEBUG - char **p = argv; - while(*p++) { - if (*p) - DEBUG_PRINT("libFuzzer arg - '%s'\n", *p); - } -#endif /* DEBUG */ + get_fuzz_args(L, &argv, &argc); /* Processing a function with custom mutator. */ if (!lua_isnil(L, -1) && (lua_isfunction(L, -1) == 1)) { diff --git a/luzer/luzer.h b/luzer/luzer.h index 2346690..0401fa6 100644 --- a/luzer/luzer.h +++ b/luzer/luzer.h @@ -1,5 +1,5 @@ -#ifndef LUZER_MACROS_H_ -#define LUZER_MACROS_H_ +#ifndef LUZER_H_ +#define LUZER_H_ #ifdef __cplusplus extern "C" { @@ -10,4 +10,4 @@ int luaL_mutate(lua_State *L); } /* extern "C" */ #endif -#endif // LUZER_MACROS_H_ +#endif // LUZER_H_ diff --git a/luzer/luzer_args.c b/luzer/luzer_args.c new file mode 100644 index 0000000..6a80b57 --- /dev/null +++ b/luzer/luzer_args.c @@ -0,0 +1,212 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright 2022-2023, Sergey Bronnikov + */ + +#include +#include +#include + +#include "luzer_args.h" +#include "macros.h" + +#define ENV_NOT_USE_CLI_ARGS "LUZER_NOT_USE_CLI_ARGS_FOR_LF" + +#define LUA_CORPUS_FLAG "-corpus" + +/* Structure for convenient argument parsing. */ +struct Luzer_args { + char **argv; + int argc; +}; + +NO_SANITIZE static bool +is_flag_in_args(struct Luzer_args *f_args, const char *key) { + if (!f_args || !f_args->argv || f_args->argc <= 0) + return false; + char search_flag[strlen(key) + 3]; + snprintf(search_flag, strlen(key) + 3, "-%s=", key); + for (int i = 0; i <= f_args->argc; i++) { + if (strncmp(f_args->argv[i], search_flag, strlen(search_flag)) == 0) + return true; + } + return false; +} + +NO_SANITIZE static int +get_args_from_cli(lua_State *L, struct Luzer_args *cli_args) { + lua_getglobal(L, "arg"); + + cli_args->argv = malloc(1 * sizeof(char*)); + if (!cli_args->argv) + return -1; + + lua_pushnil(L); + + char* not_use_cli_args = getenv(ENV_NOT_USE_CLI_ARGS); + cli_args->argc = 0; + while (lua_next(L, -2) != 0) { + const char *value = lua_tostring(L, -1); + const int key = lua_tointeger(L, -2); + lua_pop(L, 1); + if (key < 0) + continue; + + const char *arg = strdup(value); + if (!arg) + return -1; + + if (key > 0 && (not_use_cli_args == NULL || !strncmp(not_use_cli_args, "0", 1))) { + cli_args->argc++; + char **argvp = realloc(cli_args->argv, sizeof(char*) * (cli_args->argc + 1)); + if (argvp == NULL) + return -1; + cli_args->argv = argvp; + cli_args->argv[cli_args->argc] = (char*)arg; + } else { + cli_args->argv[0] = (char*)arg; + } + } + lua_pop(L, 1); + return 0; +} + +NO_SANITIZE static int +get_args_from_table(lua_State *L, struct Luzer_args *table_args, struct Luzer_args *cli_args) { + if (lua_istable(L, -1) == 0) + return -2; + + lua_pushnil(L); + + /* Processing a table with options. */ + table_args->argc = 0; + while (lua_next(L, -2) != 0) { + const char *key = lua_tostring(L, -2); + const char *value = lua_tostring(L, -1); + lua_pop(L, 1); + if (is_flag_in_args(cli_args, key)) + continue; + + if (table_args->argc > 0) { + char **argvp = realloc(table_args->argv, sizeof(char*) * (table_args->argc + 1)); + if (argvp == NULL) + return -1; + table_args->argv = argvp; + } else { + table_args->argv = malloc(1 * sizeof(char*)); + if (!table_args->argv) + return -1; + } + size_t arg_len = strlen(key) + strlen(value) + 3; + char *arg = calloc(arg_len, sizeof(char)); + if (!arg) + return -1; + snprintf(arg, arg_len, "-%s=%s", key, value); + table_args->argv[table_args->argc] = arg; + table_args->argc++; + } + lua_pop(L, 1); + return 0; +} + +NO_SANITIZE static int +merge_args(struct Luzer_args *cli_args, struct Luzer_args *table_args, struct Luzer_args *total_args) { + total_args->argc = (cli_args->argc + table_args->argc + 1); + total_args->argv = malloc(sizeof(char*) * (total_args->argc + 1)); + if (!cli_args->argv) + return -1; + + /* Program name on zero index. */ + total_args->argv[0] = cli_args->argv[0]; + + int cur_pos_arg = 1; + char *corpus_path = NULL; + for (int i = 0; i < table_args->argc; i++) { + if (strncmp(table_args->argv[i], LUA_CORPUS_FLAG, strlen(LUA_CORPUS_FLAG)) == 0) { + int corpus_path_len = strlen(table_args->argv[i]) - strlen(LUA_CORPUS_FLAG); + corpus_path = malloc(corpus_path_len * sizeof(char*)); + if (!corpus_path) + return -1; + memcpy(corpus_path, &table_args->argv[i][strlen(LUA_CORPUS_FLAG) + 1], corpus_path_len); + free(table_args->argv[i]); + table_args->argv[i] = NULL; + } else { + total_args->argv[cur_pos_arg] = table_args->argv[i]; + cur_pos_arg++; + } + } + for (int i = 1; i <= cli_args->argc; i++) { + total_args->argv[cur_pos_arg] = cli_args->argv[i]; + cur_pos_arg++; + } + if (corpus_path) { + total_args->argv[cur_pos_arg] = corpus_path; + cur_pos_arg++; + } + total_args->argv[total_args->argc] = NULL; + + if (table_args->argv) + free(table_args->argv); + + if (cli_args->argv) + free(cli_args->argv); + + return 0; +} + +NO_SANITIZE static void +free_args(struct Luzer_args args) { + if (!args.argv) + return; + for (int i = 0; i < args.argc; i++) { + if (args.argv[i]) + free(args.argv[i]); + } + free(args.argv); +} + +NO_SANITIZE int +get_fuzz_args(lua_State *L, char ***argv, int *argc) { + struct Luzer_args total_args = { .argv = NULL, .argc = 0}; + struct Luzer_args cli_args = { .argv = NULL, .argc = 0 }; + struct Luzer_args table_args = { .argv = NULL, .argc = 0 }; + + int result = -1; + result = get_args_from_cli(L, &cli_args); + if (result != 0) { + free_args(cli_args); + luaL_error(L, "failed parsing fuzz args. not enough memory"); + } + + /* If flag in cli and lua is duplicated, then flag from lua is ignored. */ + result = get_args_from_table(L, &table_args, &cli_args); + if (result != 0) { + free_args(table_args); + free_args(cli_args); + if (result == -2) + luaL_error(L, "failed parsing fuzz args. last argument is not a table"); + luaL_error(L, "failed parsing fuzz args. not enough memory"); + } + + result = merge_args(&cli_args, &table_args, &total_args); + if (result != 0) { + free_args(table_args); + free_args(cli_args); + free_args(total_args); + luaL_error(L, "failed parsing fuzz args. not enough memory"); + } + + *argv = total_args.argv; + *argc = total_args.argc; + +#ifdef DEBUG + char **p = *argv; + while(*p++) { + if (*p) + DEBUG_PRINT("libFuzzer arg - '%s'\n", *p); + } +#endif /* DEBUG */ + + return 0; +} diff --git a/luzer/luzer_args.h b/luzer/luzer_args.h new file mode 100644 index 0000000..12fd048 --- /dev/null +++ b/luzer/luzer_args.h @@ -0,0 +1,7 @@ +#ifndef LUZER_ARGS_H_ +#define LUZER_ARGS_H_ + +int get_fuzz_args(lua_State *L, char ***argv, int *argc); + +#endif // LUZER_ARGS_H_ +