diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a2264137..28c6f9a62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,16 @@ if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") source_format_enable(0.77) endif() +if(ENABLE_FUZZ_TARGETS) + set(FUZZER "AFL" CACHE STRING "fuzzer type") + if(FUZZER STREQUAL "LibFuzzer") + if (NOT CMAKE_C_COMPILER_ID STREQUAL "Clang") + message(FATAL_ERROR "LibFuzzer works only with clang") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") + endif() +endif() + # check regex compatibility check_include_file("regex.h" LY_HAVE_REGEX_H) if(LY_HAVE_REGEX_H) @@ -498,16 +508,8 @@ endif() if(ENABLE_TESTS) enable_testing() add_subdirectory(tests) -endif() - -if(ENABLE_FUZZ_TARGETS) - set(FUZZER "AFL" CACHE STRING "fuzzer type") - if(FUZZER STREQUAL "LibFuzzer") - if (NOT CMAKE_C_COMPILER_ID STREQUAL "Clang") - message(FATAL_ERROR "LibFuzzer works only with clang") - endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") - endif() +elseif(ENABLE_FUZZ_TARGETS AND NOT WIN32) + add_subdirectory(tests/fuzz) endif() # create coverage target for generating coverage reports diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 99906166b..d14e444e7 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_FUZZ_TARGETS) - set(fuzz_targets lys_parse_mem lyd_parse_mem_xml lyd_parse_mem_json yang_parse_module) + set(fuzz_targets lys_parse_mem lyd_parse_mem_xml lyd_parse_mem_json lyd_parse_lyb lyd_parse_schema_mount_xml yang_parse_module) if(FUZZER STREQUAL "AFL") foreach(target_name IN LISTS fuzz_targets) @@ -9,15 +9,15 @@ if(ENABLE_FUZZ_TARGETS) else() foreach(target_name IN LISTS fuzz_targets) add_executable(${target_name}_fuzz_harness ${target_name}.c) - set_source_files_properties(${target_name}.c PROPERTIES COMPILE_FLAGS "-fsanitize=fuzzer") - target_link_libraries(${target_name}_fuzz_harness yang "-fsanitize=fuzzer") + set_source_files_properties(${target_name}.c PROPERTIES COMPILE_FLAGS "-fsanitize=fuzzer,address,undefined") + target_link_libraries(${target_name}_fuzz_harness yang "-fsanitize=fuzzer,address,undefined") endforeach() endif() endif() if(ENABLE_TESTS) add_executable(fuzz_regression_test fuzz_regression_test.c) - set(fuzz_regression_tests lys_parse_mem lyd_parse_mem_xml lyd_parse_mem_json) + set(fuzz_regression_tests lys_parse_mem lyd_parse_mem_xml lyd_parse_mem_json lyd_parse_schema_mount_xml) foreach(target_name IN LISTS fuzz_regression_tests) file(COPY ${CMAKE_SOURCE_DIR}/tests/fuzz/corpus/${target_name} DESTINATION ${CMAKE_BINARY_DIR}/tests/fuzz/) add_executable(regress_fuzz_${target_name} ${target_name}.c main.c) diff --git a/tests/fuzz/corpus/lyd_parse_schema_mount_xml/valid-mounted-leaf b/tests/fuzz/corpus/lyd_parse_schema_mount_xml/valid-mounted-leaf new file mode 100644 index 000000000..009543605 --- /dev/null +++ b/tests/fuzz/corpus/lyd_parse_schema_mount_xml/valid-mounted-leaf @@ -0,0 +1 @@ +1 diff --git a/tests/fuzz/lyd_parse_lyb.c b/tests/fuzz/lyd_parse_lyb.c new file mode 100644 index 000000000..2dd902f7f --- /dev/null +++ b/tests/fuzz/lyd_parse_lyb.c @@ -0,0 +1,56 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include "libyang.h" + +int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) +{ + struct ly_ctx *ctx = NULL; + struct lyd_node *tree = NULL; + FILE *input = NULL; + static bool log = false; + const char *schema = + "module fuzz-lyb {namespace urn:tests:fuzz-lyb;prefix fl;" + "container c {leaf s {type string;} leaf u16 {type uint16;}" + "leaf-list bits {type bits {bit zero; bit one;}}}}"; + + if (!log) { + ly_log_options(0); + log = true; + } + + if (ly_ctx_new(LY_SRC_DIR "/modules", 0, &ctx) != LY_SUCCESS) { + return 0; + } + + if (lys_parse_mem(ctx, schema, LYS_IN_YANG, NULL) != LY_SUCCESS) { + goto cleanup; + } + + input = tmpfile(); + if (!input) { + goto cleanup; + } + + if (len && (fwrite(buf, 1, len, input) != len)) { + goto cleanup; + } + fflush(input); + if (lseek(fileno(input), 0, SEEK_SET) == -1) { + goto cleanup; + } + + lyd_parse_data_fd(ctx, fileno(input), LYD_LYB, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree); + +cleanup: + lyd_free_all(tree); + if (input) { + fclose(input); + } + ly_ctx_destroy(ctx); + return 0; +} diff --git a/tests/fuzz/lyd_parse_mem_json.c b/tests/fuzz/lyd_parse_mem_json.c index c2f3e9813..739ea81a8 100644 --- a/tests/fuzz/lyd_parse_mem_json.c +++ b/tests/fuzz/lyd_parse_mem_json.c @@ -62,8 +62,7 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) err = ly_ctx_new(LY_SRC_DIR "/modules", 0, &ctx); if (err != LY_SUCCESS) { - fprintf(stderr, "Failed to create context\n"); - exit(EXIT_FAILURE); + return 0; } lys_parse_mem(ctx, schema_a, LYS_IN_YANG, NULL); @@ -71,12 +70,14 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) data = malloc(len + 1); if (data == NULL) { - return 0; + goto cleanup; } memcpy(data, buf, len); data[len] = 0; lyd_parse_data_mem(ctx, data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, &tree); + +cleanup: lyd_free_all(tree); ly_ctx_destroy(ctx); diff --git a/tests/fuzz/lyd_parse_mem_xml.c b/tests/fuzz/lyd_parse_mem_xml.c index e317fde47..0ef5481dc 100644 --- a/tests/fuzz/lyd_parse_mem_xml.c +++ b/tests/fuzz/lyd_parse_mem_xml.c @@ -62,8 +62,7 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) err = ly_ctx_new(LY_SRC_DIR "/modules", 0, &ctx); if (err != LY_SUCCESS) { - fprintf(stderr, "Failed to create context\n"); - exit(EXIT_FAILURE); + return 0; } lys_parse_mem(ctx, schema_a, LYS_IN_YANG, NULL); @@ -71,12 +70,14 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) data = malloc(len + 1); if (data == NULL) { - return 0; + goto cleanup; } memcpy(data, buf, len); data[len] = 0; lyd_parse_data_mem(ctx, data, LYD_XML, 0, LYD_VALIDATE_PRESENT, &tree); + +cleanup: lyd_free_all(tree); ly_ctx_destroy(ctx); diff --git a/tests/fuzz/lyd_parse_schema_mount_xml.c b/tests/fuzz/lyd_parse_schema_mount_xml.c new file mode 100644 index 000000000..d52c065b0 --- /dev/null +++ b/tests/fuzz/lyd_parse_schema_mount_xml.c @@ -0,0 +1,170 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include "libyang.h" + +static const char mounted_module[] = + "module fuzz-sm-leaf {" + "namespace \"urn:tests:fuzz-sm-leaf\";" + "prefix fsl;" + "leaf lfl {type uint16;}" + "leaf txt {type string;}" + "}"; + +static const char mount_module[] = + "module fuzz-sm {" + "yang-version 1.1;" + "namespace \"urn:tests:fuzz-sm\";" + "prefix fsm;" + "import ietf-yang-schema-mount {prefix sm;}" + "container root {sm:mount-point \"root\";}" + "}"; + +static const char ext_data_xml[] = + "" + "fuzz-setfuzz-sm-leaf" + "urn:tests:fuzz-sm-leaf" + "1" + "" + "1" + "" + "fuzz-sm" + ""; + +static const char mount_module_dir_template[] = "/tmp/libyang-sm-fuzz.XXXXXX"; +static char mount_module_dir[sizeof mount_module_dir_template]; +static ly_bool mount_module_dir_ready; + +static void +remove_mount_module_dir(void) +{ + char path[sizeof mount_module_dir + sizeof "/fuzz-sm-leaf.yang"]; + + if (!mount_module_dir_ready) { + return; + } + + snprintf(path, sizeof path, "%s/fuzz-sm-leaf.yang", mount_module_dir); + unlink(path); + rmdir(mount_module_dir); + mount_module_dir[0] = 0; + mount_module_dir_ready = 0; +} + +static LY_ERR +prepare_mount_module_dir(void) +{ + char path[sizeof mount_module_dir + sizeof "/fuzz-sm-leaf.yang"]; + FILE *f; + + if (mount_module_dir_ready) { + return LY_SUCCESS; + } + + memcpy(mount_module_dir, mount_module_dir_template, sizeof mount_module_dir); + if (!mkdtemp(mount_module_dir)) { + mount_module_dir[0] = 0; + return LY_ESYS; + } + mount_module_dir_ready = 1; + atexit(remove_mount_module_dir); + + snprintf(path, sizeof path, "%s/fuzz-sm-leaf.yang", mount_module_dir); + f = fopen(path, "w"); + if (!f) { + remove_mount_module_dir(); + return LY_ESYS; + } + + if (fputs(mounted_module, f) == EOF) { + fclose(f); + remove_mount_module_dir(); + return LY_ESYS; + } + if (fclose(f) == EOF) { + remove_mount_module_dir(); + return LY_ESYS; + } + + return LY_SUCCESS; +} + +static LY_ERR +fuzz_ext_data_clb(const struct lysc_ext_instance *ext, const struct lyd_node *parent, void *user_data, + void **ext_data, ly_bool *ext_data_free) +{ + struct lyd_node *data = NULL; + static ly_bool recursive_call; + LY_ERR ret = LY_SUCCESS; + + (void)ext; + (void)parent; + + *ext_data = NULL; + *ext_data_free = 0; + + if (recursive_call) { + return LY_SUCCESS; + } + + recursive_call = 1; + ret = lyd_parse_data_mem(user_data, ext_data_xml, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data); + recursive_call = 0; + if (ret) { + lyd_free_all(data); + return ret; + } + + *ext_data = data; + *ext_data_free = 1; + return LY_SUCCESS; +} + +int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) +{ + struct ly_ctx *ctx = NULL; + struct lyd_node *tree = NULL; + char *data = NULL; + static bool log = false; + + if (!log) { + ly_log_options(0); + log = true; + } + + if (prepare_mount_module_dir() != LY_SUCCESS) { + return 0; + } + + if (ly_ctx_new(LY_SRC_DIR "/modules", 0, &ctx) != LY_SUCCESS) { + return 0; + } + if (ly_ctx_set_searchdir(ctx, mount_module_dir) != LY_SUCCESS) { + goto cleanup; + } + if (lys_parse_mem(ctx, mount_module, LYS_IN_YANG, NULL) != LY_SUCCESS) { + goto cleanup; + } + + ly_ctx_set_ext_data_clb(ctx, fuzz_ext_data_clb, ctx); + + data = malloc(len + 1); + if (!data) { + goto cleanup; + } + memcpy(data, buf, len); + data[len] = 0; + + lyd_parse_data_mem(ctx, data, LYD_XML, 0, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, &tree); + +cleanup: + lyd_free_all(tree); + free(data); + ly_ctx_destroy(ctx); + return 0; +} diff --git a/tests/fuzz/lys_parse_mem.c b/tests/fuzz/lys_parse_mem.c index 6f8d87b40..739861380 100644 --- a/tests/fuzz/lys_parse_mem.c +++ b/tests/fuzz/lys_parse_mem.c @@ -18,12 +18,12 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) err = ly_ctx_new(LY_SRC_DIR "/modules", 0, &ctx); if (err != LY_SUCCESS) { - fprintf(stderr, "Failed to create context\n"); - exit(EXIT_FAILURE); + return 0; } data = malloc(len + 1); if (data == NULL) { + ly_ctx_destroy(ctx); return 0; } diff --git a/tests/fuzz/yang_parse_module.c b/tests/fuzz/yang_parse_module.c index e9fbe8b42..7cd90387d 100644 --- a/tests/fuzz/yang_parse_module.c +++ b/tests/fuzz/yang_parse_module.c @@ -25,7 +25,7 @@ int LLVMFuzzerTestOneInput(uint8_t const *buf, size_t len) data = malloc(len + 1); if (data == NULL) { - fprintf(stderr, "Out of memory\n"); + ly_ctx_destroy(ctx); return 0; } memcpy(data, buf, len);