diff --git a/tests/fuzzing/fuzz.nims b/tests/fuzzing/fuzz.nims index daa1aba..2032c4e 100644 --- a/tests/fuzzing/fuzz.nims +++ b/tests/fuzzing/fuzz.nims @@ -1,36 +1,7 @@ -import strformat, strutils +import ./fuzz_helpers -# Dependencies: -# - afl fuzzing: afl and gcc or clang/llvm -# - libFuzzer fuzzing: libFuzzer and clang/llvm -# - in afl experimental modes clang/llvm is also required - -# TODO: -# - switch clang / gcc option for afl -# - afl init and persistent modes -# - parallel fuzzing -# - custom generate test cases from this script? -# - rerun testcases option (or create tests from failed cases)? -# - better cmd line parsing & and more options - -const aflGcc = "--cc=gcc " & - "--gcc.exe=afl-gcc " & - "--gcc.linkerexe=afl-gcc" -const aflClang = "--cc=clang " & - "--clang.exe=afl-clang " & - "--clang.linkerexe=afl-clang" -const aflClangFast = "--cc=clang " & - "--clang.exe=afl-clang-fast " & - "--clang.linkerexe=afl-clang-fast" -const libFuzzerClang = "--cc=clang " & - "--passC='-fsanitize=fuzzer,address' " & - "--passL='-fsanitize=fuzzer,address'" -# Can also test in debug mode obviously, but might be slower -# Can turn on more logging, in case of libFuzzer it will get very verbose though -const defaultFlags = "-d:release -d:chronicles_log_level=fatal " & - "--hints:off --warnings:off --verbosity:0" -const inputDir = "input" # dir for input samples -const resultsDir = "results" # results dir (for afl) +# TODO: get this some nice cmd line options when confutils works for nimscript +# or if we want to put this in a nim application instead of script if paramCount() < 3: echo "Usage: nim fuzz.nims FUZZER TARGET" @@ -45,45 +16,13 @@ if not fileExists(targetPath): echo "Target file does not exist" quit 1 -let splitFile = targetPath.rsplit("/", 1) -let workDir = splitFile[0] -let targetNimFile = splitFile[1] -var target = targetNimFile -target.removeSuffix(".nim") -cd workDir - case fuzzer of "afl": - let aflOptions = &"-d:afl -d:noSignalHandler {aflGcc}" - let compileCmd = &"nim c {defaultFlags} {aflOptions} {target}" - exec compileCmd - - if not dirExists(inputDir): - # create a input dir with one 0 file for afl - mkDir(inputDir) - withDir inputDir: exec "echo '0' > test" - - var fuzzCmd: string - # if there is an output dir already, continue fuzzing from previous run - if not dirExists(resultsDir): - fuzzCmd = &"afl-fuzz -i {inputDir} -o {resultsDir} -M fuzzer01 -- ./{target}" - else: - fuzzCmd = &"afl-fuzz -i - -o {resultsDir} -M fuzzer01 -- ./{target}" - exec fuzzCmd - + runFuzzer(targetPath, afl) of "libFuzzer": - let libFuzzerOptions = &"--noMain {libFuzzerClang}" - let compileCmd = &"nim c {defaultFlags} {libFuzzerOptions} {target}" - exec compileCmd - - if not dirExists(inputDir): - # libFuzzer is OK with empty input dir - mkDir(inputDir) - - # TODO: could use {resultsDir} of afl here also to reuse as corpus - exec &"./{target} {inputDir}" + runFuzzer(targetPath, libFuzzer) else: echo "Invalid fuzzer option: ", fuzzer echo "Fuzzer options are afl or libFuzzer" - quit 1 \ No newline at end of file + quit 1 diff --git a/tests/fuzzing/fuzz_helpers.nim b/tests/fuzzing/fuzz_helpers.nim new file mode 100644 index 0000000..8af34f7 --- /dev/null +++ b/tests/fuzzing/fuzz_helpers.nim @@ -0,0 +1,101 @@ +import strformat, strutils + +# Dependencies: +# - afl fuzzing: afl and gcc or clang/llvm +# - libFuzzer fuzzing: libFuzzer and clang/llvm +# - in afl experimental modes clang/llvm is also required + +# TODO: +# - switch clang / gcc option for afl +# - afl init and persistent modes +# - parallel fuzzing options +# - custom generate test cases from this script? +# - rerun testcases option (or create tests from failed cases) +# - currently not cross platform +# - ... + +const + aflGcc = "--cc=gcc " & + "--gcc.exe=afl-gcc " & + "--gcc.linkerexe=afl-gcc" + aflClang = "--cc=clang " & + "--clang.exe=afl-clang " & + "--clang.linkerexe=afl-clang" + aflClangFast = "--cc=clang " & + "--clang.exe=afl-clang-fast " & + "--clang.linkerexe=afl-clang-fast" + libFuzzerClang = "--cc=clang " & + "--passC='-fsanitize=fuzzer,address' " & + "--passL='-fsanitize=fuzzer,address'" +# Can also test in debug mode obviously, but might be slower +# Can turn on more logging, in case of libFuzzer it will get very verbose though + defaultFlags = "-d:release -d:chronicles_log_level=fatal "# & + # "--hints:off --warnings:off --verbosity:0" + +type + Fuzzer* = enum + afl, + libFuzzer + + Compiler* = enum + gcc = aflGcc, + clang = aflClang, + clangFast = aflClangFast + +proc aflCompile*(target: string, c: Compiler) = + let aflOptions = &"-d:standalone -d:noSignalHandler {$c}" + let compileCmd = &"nim c {defaultFlags} {aflOptions} {target}" + exec compileCmd + +proc aflExec*(target: string, inputDir: string, resultsDir: string, + cleanStart = false) = + if not dirExists(inputDir): + # create a input dir with one 0 file for afl + mkDir(inputDir) + withDir inputDir: exec "echo '0' > test" + + var fuzzCmd: string + # if there is an output dir already, continue fuzzing from previous run + if (not dirExists(resultsDir)) or cleanStart: + fuzzCmd = &"afl-fuzz -i {inputDir} -o {resultsDir} -M fuzzer01 -- ./{target}" + else: + fuzzCmd = &"afl-fuzz -i - -o {resultsDir} -M fuzzer01 -- ./{target}" + exec fuzzCmd + +proc libFuzzerCompile*(target: string) = + let libFuzzerOptions = &"--noMain {libFuzzerClang}" + let compileCmd = &"nim c {defaultFlags} {libFuzzerOptions} {target}" + exec compileCmd + +proc libFuzzerExec*(target: string, corpusDir: string) = + if not dirExists(corpusDir): + # libFuzzer is OK when starting with empty corpus dir + mkDir(corpusDir) + + exec &"./{target} {corpusDir}" + +proc getDir*(path: string): string = + # TODO: This is not platform friendly at all. + let splitFile = path.rsplit("/", 1) + result = splitFile[0] + +proc getTarget*(path: string): string = + # TODO: error handling + result = path + result.removeSuffix(".nim") + +proc runFuzzer*(targetPath: string, fuzzer: Fuzzer) = + let + path = getDir(targetPath) + target = getTarget(targetPath) + case fuzzer + of afl: + aflCompile(targetPath, gcc) + aflExec(target, path & "/input", path & "/results") + + of libFuzzer: + libFuzzerCompile(targetPath) + # Note: Lets not mix afl input with libFuzzer corpus default. This can have + # consequences on speed for afl. Better to look into merging afl results & + # libFuzzer corpus. + libFuzzerExec(target, path & "/corpus")