diff --git a/fuzzing/.gitignore b/fuzzing/.gitignore index c82a262..520b91f 100644 --- a/fuzzing/.gitignore +++ b/fuzzing/.gitignore @@ -1 +1,2 @@ afl-build +libfuzzer-build diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index fdd7126..5a72afd 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -5,23 +5,26 @@ if (ENABLE_FUZZING) message(FATAL_ERROR "Couldn't find afl-fuzz.") endif() + option(ENABLE_LIBFUZZER "Enable fuzzing with libfuzzer (only works with llvm 5 which hasn't been release at this point)" Off) + if (ENABLE_LIBFUZZER) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=fuzzer") + endif() + + add_library(fuzz-target fuzz-target.c) + target_link_libraries(fuzz-target "${CJSON_LIB}") + add_executable(afl-main afl.c) - target_link_libraries(afl-main "${CJSON_LIB}") + target_link_libraries(afl-main fuzz-target) if (NOT ENABLE_SANITIZERS) message(FATAL_ERROR "Enable sanitizers with -DENABLE_SANITIZERS=On to do fuzzing.") endif() - option(ENABLE_FUZZING_PRINT "Fuzz printing functions together with parser." On) - set(fuzz_print_parameter "no") - if (ENABLE_FUZZING_PRINT) - set(fuzz_print_parameter "yes") - endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error") add_custom_target(afl - COMMAND "${AFL_FUZZ}" -i "${CMAKE_CURRENT_SOURCE_DIR}/inputs" -o "${CMAKE_CURRENT_BINARY_DIR}/findings" -x "${CMAKE_CURRENT_SOURCE_DIR}/json.dict" -- "${CMAKE_CURRENT_BINARY_DIR}/afl-main" "@@" "${fuzz_print_parameter}" + COMMAND "${AFL_FUZZ}" -i "${CMAKE_CURRENT_SOURCE_DIR}/inputs" -o "${CMAKE_CURRENT_BINARY_DIR}/findings" -x "${CMAKE_CURRENT_SOURCE_DIR}/json.dict" -- "${CMAKE_CURRENT_BINARY_DIR}/afl-main" "@@" DEPENDS afl-main) diff --git a/fuzzing/afl.c b/fuzzing/afl.c index 036ed14..2215828 100644 --- a/fuzzing/afl.c +++ b/fuzzing/afl.c @@ -24,54 +24,71 @@ #include #include -#include "../cJSON.h" +#include "fuzz-target.h" -static char *read_file(const char *filename) +static char *read_file(const char *filename, size_t *size) { FILE *file = NULL; long length = 0; char *content = NULL; size_t read_chars = 0; + if (size == NULL) + { + goto fail; + } + /* open in read binary mode */ file = fopen(filename, "rb"); if (file == NULL) { - goto cleanup; + goto fail; } /* get the length */ if (fseek(file, 0, SEEK_END) != 0) { - goto cleanup; + goto fail; } length = ftell(file); if (length < 0) { - goto cleanup; + goto fail; } if (fseek(file, 0, SEEK_SET) != 0) { - goto cleanup; + goto fail; } /* allocate content buffer */ content = (char*)malloc((size_t)length + sizeof("")); if (content == NULL) { - goto cleanup; + goto fail; } /* read the file into memory */ read_chars = fread(content, sizeof(char), (size_t)length, file); if ((long)read_chars != length) { - free(content); - content = NULL; - goto cleanup; + goto fail; } content[read_chars] = '\0'; + *size = read_chars + sizeof(""); + + goto cleanup; + +fail: + if (size != NULL) + { + *size = 0; + } + if (content != NULL) + { + free(content); + content = NULL; + } cleanup: if (file != NULL) @@ -85,92 +102,50 @@ cleanup: int main(int argc, char** argv) { const char *filename = NULL; - cJSON *item = NULL; char *json = NULL; int status = EXIT_FAILURE; char *printed_json = NULL; - if ((argc < 2) || (argc > 3)) + if (argc != 2) { printf("Usage:\n"); - printf("%s input_file [enable_printing]\n", argv[0]); + printf("%s input_file\n", argv[0]); printf("\t input_file: file containing the test data\n"); - printf("\t enable_printing: print after parsing, 'yes' or 'no', defaults to 'no'\n"); goto cleanup; } filename = argv[1]; -#if __AFL_HAVE_MANUAL_CONTROL +#if defined(__AFL_HAVE_MANUAL_CONTROL) && __AFL_HAVE_MANUAL_CONTROL while (__AFL_LOOP(1000)) { +#else + { #endif - status = EXIT_SUCCESS; + size_t size = 0; + status = EXIT_SUCCESS; - json = read_file(filename); - if ((json == NULL) || (json[0] == '\0') || (json[1] == '\0')) - { - status = EXIT_FAILURE; - goto cleanup; - } - item = cJSON_Parse(json + 2); - if (item == NULL) - { - goto cleanup; - } - - if ((argc == 3) && (strncmp(argv[2], "yes", 3) == 0)) - { - int do_format = 0; - if (json[1] == 'f') - { - do_format = 1; - } - - if (json[0] == 'b') - { - /* buffered printing */ - printed_json = cJSON_PrintBuffered(item, 1, do_format); - } - else - { - /* unbuffered printing */ - if (do_format) - { - printed_json = cJSON_Print(item); - } - else - { - printed_json = cJSON_PrintUnformatted(item); - } - } - if (printed_json == NULL) + json = read_file(filename, &size); + if ((json == NULL) || (json[0] == '\0') || (json[1] == '\0')) { status = EXIT_FAILURE; goto cleanup; } - printf("%s\n", printed_json); - } -cleanup: - if (item != NULL) - { - cJSON_Delete(item); - item = NULL; + LLVMFuzzerTestOneInput(json, size); + + cleanup: + if (json != NULL) + { + free(json); + json = NULL; + } + if (printed_json != NULL) + { + free(printed_json); + printed_json = NULL; + } } - if (json != NULL) - { - free(json); - json = NULL; - } - if (printed_json != NULL) - { - free(printed_json); - printed_json = NULL; - } -#if __AFL_HAVE_MANUAL_CONTROL - } -#endif return status; } diff --git a/fuzzing/fuzz-target.c b/fuzzing/fuzz-target.c new file mode 100644 index 0000000..057a296 --- /dev/null +++ b/fuzzing/fuzz-target.c @@ -0,0 +1,129 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include +#include + +#include "fuzz-target.h" +#include "../cJSON.h" + +static void minify(const unsigned char *data, size_t size) +{ + unsigned char *copied_data = (unsigned char*)malloc(size); + if (copied_data == NULL) + { + return; + } + + memcpy(copied_data, data, size); + + cJSON_Minify((char*)copied_data); + + free(copied_data); + + return; +} + +static void printing(cJSON *json, unsigned char format_setting, unsigned char buffered_setting) +{ + unsigned char *printed = NULL; + + if (buffered_setting == '1') + { + printed = (unsigned char*)cJSON_PrintBuffered(json, 1, (format_setting == '1')); + } + else + { + if (format_setting == '1') + { + printed = (unsigned char*)cJSON_Print(json); + } + else + { + printed = (unsigned char*)cJSON_PrintUnformatted(json); + } + } + + if (printed != NULL) + { + free(printed); + } +} + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) +{ + unsigned char minify_setting = '\0'; /* minify instead of parsing */ + unsigned char require_zero_setting = '\0'; /* zero termination required */ + unsigned char format_setting = '\0'; /* formatted printing */ + unsigned char buffered_setting = '\0'; /* buffered printing */ + const size_t data_offset = 4; + + cJSON *json = NULL; + + /* don't work with NULL or without mode selector */ + if ((data == NULL) || (size < data_offset)) + { + return 0; + } + + /* get configuration from the beginning of the test case */ + minify_setting = data[0]; + require_zero_setting = data[1]; + format_setting = data[2]; + buffered_setting = data[3]; + + /* check if settings are valid */ + if ((minify_setting != '0') && (minify_setting != '1')) + { + return 0; + } + if ((require_zero_setting != '0') && (require_zero_setting != '1')) + { + return 0; + } + if ((format_setting != '0') && (format_setting != '1')) + { + return 0; + } + if ((buffered_setting != '0') && (buffered_setting != '1')) + { + return 0; + } + + if (minify_setting == '1') + { + minify(data + data_offset, size); + return 0; + } + + json = cJSON_ParseWithOpts((const char*)data + data_offset, NULL, (require_zero_setting == '1')); + if (json == NULL) + { + return 0; + } + + printing(json, format_setting, buffered_setting); + + free(json); + + return 0; +} diff --git a/fuzzing/fuzz-target.h b/fuzzing/fuzz-target.h new file mode 100644 index 0000000..8b3115d --- /dev/null +++ b/fuzzing/fuzz-target.h @@ -0,0 +1,30 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include + +#ifndef CJSON_FUZZ_TARGET +#define CJSON_FUZZ_TARGET + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); + +#endif /* CJSON_FUZZ_TARGET */ diff --git a/fuzzing/inputs/test1 b/fuzzing/inputs/test1 index 6a0c0d7..c88024f 100644 --- a/fuzzing/inputs/test1 +++ b/fuzzing/inputs/test1 @@ -1,4 +1,4 @@ -bf{ +0111{ "glossary": { "title": "example glossary", "GlossDiv": { diff --git a/fuzzing/inputs/test10 b/fuzzing/inputs/test10 index 01e9a82..5852697 100644 --- a/fuzzing/inputs/test10 +++ b/fuzzing/inputs/test10 @@ -1 +1 @@ -bf["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] +0111["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] diff --git a/fuzzing/inputs/test11 b/fuzzing/inputs/test11 index 818c6e0..686d079 100644 --- a/fuzzing/inputs/test11 +++ b/fuzzing/inputs/test11 @@ -1,4 +1,4 @@ -bf{ +0111{ "name": "Jack (\"Bee\") Nimble", "format": {"type": "rect", "width": 1920, diff --git a/fuzzing/inputs/test2 b/fuzzing/inputs/test2 index 3fdf8cb..04aa455 100644 --- a/fuzzing/inputs/test2 +++ b/fuzzing/inputs/test2 @@ -1,4 +1,4 @@ -bf{"menu": { +0111{"menu": { "id": "file", "value": "File", "popup": { diff --git a/fuzzing/inputs/test3 b/fuzzing/inputs/test3 index 7143163..d1e0a07 100644 --- a/fuzzing/inputs/test3 +++ b/fuzzing/inputs/test3 @@ -1,4 +1,4 @@ -bf{"widget": { +0111{"widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", diff --git a/fuzzing/inputs/test3.bu b/fuzzing/inputs/test3.bu index 6fc93d3..e58848c 100644 --- a/fuzzing/inputs/test3.bu +++ b/fuzzing/inputs/test3.bu @@ -1,4 +1,4 @@ -bu{"widget": { +0101{"widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", diff --git a/fuzzing/inputs/test3.uf b/fuzzing/inputs/test3.uf index d48df61..6b983b7 100644 --- a/fuzzing/inputs/test3.uf +++ b/fuzzing/inputs/test3.uf @@ -1,4 +1,4 @@ -uf{"widget": { +0110{"widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", diff --git a/fuzzing/inputs/test3.uu b/fuzzing/inputs/test3.uu index ad6ae54..7e4482b 100644 --- a/fuzzing/inputs/test3.uu +++ b/fuzzing/inputs/test3.uu @@ -1,4 +1,4 @@ -uu{"widget": { +0100{"widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", diff --git a/fuzzing/inputs/test4 b/fuzzing/inputs/test4 index e24ae9b..ca248f9 100644 --- a/fuzzing/inputs/test4 +++ b/fuzzing/inputs/test4 @@ -1,4 +1,4 @@ -bf{"web-app": { +0111{"web-app": { "servlet": [ { "servlet-name": "cofaxCDS", diff --git a/fuzzing/inputs/test5 b/fuzzing/inputs/test5 index f6cc84e..814e7e8 100644 --- a/fuzzing/inputs/test5 +++ b/fuzzing/inputs/test5 @@ -1,4 +1,4 @@ -bf{"menu": { +0111{"menu": { "header": "SVG Viewer", "items": [ {"id": "Open"}, diff --git a/fuzzing/inputs/test6 b/fuzzing/inputs/test6 index af27975..e637a40 100644 --- a/fuzzing/inputs/test6 +++ b/fuzzing/inputs/test6 @@ -1,4 +1,4 @@ -bf +0111 diff --git a/fuzzing/inputs/test7 b/fuzzing/inputs/test7 index 4a3c0b7..ce7cd3c 100644 --- a/fuzzing/inputs/test7 +++ b/fuzzing/inputs/test7 @@ -1,4 +1,4 @@ -bf[ +0111[ { "precision": "zip", "Latitude": 37.7668, diff --git a/fuzzing/inputs/test8 b/fuzzing/inputs/test8 index 3ffe570..aed7bd0 100644 --- a/fuzzing/inputs/test8 +++ b/fuzzing/inputs/test8 @@ -1,4 +1,4 @@ -bf{ +0111{ "Image": { "Width": 800, "Height": 600, diff --git a/fuzzing/inputs/test9 b/fuzzing/inputs/test9 index 28c9033..a99c4eb 100644 --- a/fuzzing/inputs/test9 +++ b/fuzzing/inputs/test9 @@ -1,4 +1,4 @@ -bf[ +0111[ [0, -1, 0], [1, 0, 0], [0, 0, 1] diff --git a/fuzzing/libfuzzer.sh b/fuzzing/libfuzzer.sh new file mode 100755 index 0000000..3c81917 --- /dev/null +++ b/fuzzing/libfuzzer.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +mkdir -p libfuzzer-build || exit 1 +cd libfuzzer-build || exit 1 +#cleanup +rm -r -- * + +CC=clang cmake ../.. -DENABLE_FUZZING=On -DENABLE_SANITIZERS=On -DBUILD_SHARED_LIBS=Off -DCMAKE_BUILD_TYPE=Debug -DENABLE_LIBFUZZER=On +make fuzz-target