diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf1ea8..41c5b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,3 +81,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## 20230522 - Fix error in generator - Add generation for pugi::char_t *& + +## 20230711 +- Support generation fuzz driver for Natch data: https://github.com/thientc/Futag-tests/tree/main/Natch + +## 20230807 +- Optimize ConsumerBuilder +- Add example for context-generation https://github.com/thientc/Futag-tests/tree/main/json-c-contexts \ No newline at end of file diff --git a/futag-work.png b/futag-work.png index 15333bc..eaab03e 100644 Binary files a/futag-work.png and b/futag-work.png differ diff --git a/src/Checkers/lib/FutagAnalyzer.cpp b/src/Checkers/lib/FutagAnalyzer.cpp index 0ba04a8..805d37f 100644 --- a/src/Checkers/lib/FutagAnalyzer.cpp +++ b/src/Checkers/lib/FutagAnalyzer.cpp @@ -2,7 +2,7 @@ * @file FutagAnalyzer.cpp * @author Tran Chi Thien (thientcgithub@gmail.com) * @brief - * @version 2.0.4 + * @version 2.0.5 * @date 2023-04-17 * * @copyright Copyright (c) 2023 diff --git a/src/Checkers/lib/FutagConsumerAnalyzer.cpp b/src/Checkers/lib/FutagConsumerAnalyzer.cpp index 3a96a1d..bb80eee 100644 --- a/src/Checkers/lib/FutagConsumerAnalyzer.cpp +++ b/src/Checkers/lib/FutagConsumerAnalyzer.cpp @@ -2,7 +2,7 @@ * @file FutagConsumerAnalyzer.cpp * @author Tran Chi Thien * @brief - * @version 2.0.4 + * @version 2.0.5 * @date 2023-04-17 * * @copyright Copyright (c) 2023 @@ -516,6 +516,10 @@ void FutagConsumerAnalyzer::AnalyzeVisitedFunctionDecl( llvm::outs() << "[Futag]: Print contexts\n"; // Build the CFG of current function CFG *cfg = Mgr.getCFG(func); + //To speed up the analyzer for normal computer, we analyze only simple functions with number of block in CFG <= 30 + if(cfg->size() > 30) { + return ; + } if (!cfg) { llvm::outs() << "-- Empty CFG for function: " << func->getNameAsString() << "\n"; diff --git a/src/clang/lib/Futag/Basic.cpp b/src/clang/lib/Futag/Basic.cpp index 1ac3811..56434e8 100755 --- a/src/clang/lib/Futag/Basic.cpp +++ b/src/clang/lib/Futag/Basic.cpp @@ -13,7 +13,7 @@ * a tool of ISP RAS * ************************************************ * - * @version 2.0.4 + * @version 2.0.5 * @date 2023-04-17 * * @copyright This file is distributed under the GPL v3 license diff --git a/src/clang/lib/Futag/ConsumerFinder.cpp b/src/clang/lib/Futag/ConsumerFinder.cpp index e3dcb76..4a75704 100644 --- a/src/clang/lib/Futag/ConsumerFinder.cpp +++ b/src/clang/lib/Futag/ConsumerFinder.cpp @@ -2,7 +2,7 @@ * @file ConsumerFinder.cpp * @author Tran Chi Thien * @brief This file contains functions for analyzing consumer program - * @version 2.0.4 + * @version 2.0.5 * @date 2023-04-17 * * @copyright Copyright (c) 2023 diff --git a/src/clang/lib/Futag/MatchFinder.cpp b/src/clang/lib/Futag/MatchFinder.cpp index 6743fc0..14fa67e 100644 --- a/src/clang/lib/Futag/MatchFinder.cpp +++ b/src/clang/lib/Futag/MatchFinder.cpp @@ -2,7 +2,7 @@ * @file MatchFinder.cpp * @author Tran Chi Thien * @brief - * @version 2.0.4 + * @version 2.0.5 * @date 2023-04-17 * * @copyright Copyright (c) 2023 diff --git a/src/python/futag-package/README.md b/src/python/futag-package/README.md index 286b138..4ea6177 100644 --- a/src/python/futag-package/README.md +++ b/src/python/futag-package/README.md @@ -20,7 +20,7 @@ This python package is for building library, generating and compiling fuzz-drive ## 1. Install ```bash -pip install dist/futag-2.0.4.tar.gz +pip install dist/futag-2.0.5.tar.gz ``` ## 2. Preprocessor diff --git a/src/python/futag-package/dist/futag-2.0.4-py3-none-any.whl b/src/python/futag-package/dist/futag-2.0.4-py3-none-any.whl deleted file mode 100644 index 96b7bee..0000000 Binary files a/src/python/futag-package/dist/futag-2.0.4-py3-none-any.whl and /dev/null differ diff --git a/src/python/futag-package/dist/futag-2.0.4.tar.gz b/src/python/futag-package/dist/futag-2.0.4.tar.gz deleted file mode 100644 index 5cf6a1f..0000000 Binary files a/src/python/futag-package/dist/futag-2.0.4.tar.gz and /dev/null differ diff --git a/src/python/futag-package/dist/futag-2.0.5-py3-none-any.whl b/src/python/futag-package/dist/futag-2.0.5-py3-none-any.whl new file mode 100644 index 0000000..f5c7083 Binary files /dev/null and b/src/python/futag-package/dist/futag-2.0.5-py3-none-any.whl differ diff --git a/src/python/futag-package/dist/futag-2.0.5.tar.gz b/src/python/futag-package/dist/futag-2.0.5.tar.gz new file mode 100644 index 0000000..89caab9 Binary files /dev/null and b/src/python/futag-package/dist/futag-2.0.5.tar.gz differ diff --git a/src/python/futag-package/setup.cfg b/src/python/futag-package/setup.cfg index a081c30..14a6b1b 100644 --- a/src/python/futag-package/setup.cfg +++ b/src/python/futag-package/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = futag -version = 2.0.4 +version = 2.0.5 author = Futag-team of ISP RAS author_email = thientcgithub@gmail.com description = Python package of Futag diff --git a/src/python/futag-package/setup.py b/src/python/futag-package/setup.py index 7024d57..05d163a 100644 --- a/src/python/futag-package/setup.py +++ b/src/python/futag-package/setup.py @@ -2,7 +2,7 @@ setup( name='futag', - version='2.0.4', + version='2.0.5', author='Futag-team of ISP RAS', author_email='thientcgithub@gmail.com', packages=['futag'], diff --git a/src/python/futag-package/src/futag.egg-info/PKG-INFO b/src/python/futag-package/src/futag.egg-info/PKG-INFO index 23dbc9b..e31a2f1 100644 --- a/src/python/futag-package/src/futag.egg-info/PKG-INFO +++ b/src/python/futag-package/src/futag.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: futag -Version: 2.0.4 +Version: 2.0.5 Summary: Futag tools for creating fuzz targets of software library Home-page: https://github.com/ispras/Futag/tree/main/src/python/futag-package Author: Futag-team of ISP RAS @@ -36,7 +36,7 @@ This python package is for building library, generating and compiling fuzz-drive ## 1. Install ```bash -pip install dist/futag-2.0.4.tar.gz +pip install dist/futag-2.0.5.tar.gz ``` ## 2. Preprocessor diff --git a/src/python/futag-package/src/futag/fuzzer.py b/src/python/futag-package/src/futag/fuzzer.py index 13934e1..c397658 100644 --- a/src/python/futag-package/src/futag/fuzzer.py +++ b/src/python/futag-package/src/futag/fuzzer.py @@ -578,7 +578,9 @@ def fuzz(self, extra_param: str = ""): for func_dir in generated_functions: self.backtraces = [] fuzz_driver_dirs = [x for x in func_dir.iterdir() if x.is_dir()] + # print(func_dir.as_posix()) for dir in fuzz_driver_dirs: + # print(dir.as_posix()) for x in [t for t in dir.glob("*.out") if t.is_file()]: print("\n-- [Futag] FUZZING driver: " + x.stem + "... \n") my_env = os.environ.copy() @@ -676,20 +678,831 @@ def fuzz(self, extra_param: str = ""): llvm_cov.as_posix(), "report", x.as_posix(), - "-instr-profile=" + x.as_posix() + ".profdata", + "-instr-profile", + x.as_posix() + ".profdata", + "--object", + x.as_posix(), + ] + if self.debug: + print(" ".join(llvm_cov_report)) + p = run(llvm_cov_report) + + # llvm_cov_show = [ + # llvm_cov.as_posix(), + # "show", + # x.as_posix(), + # "-instr-profile=" + x.as_posix() + ".profdata", + # ] + llvm_cov_show = [ + llvm_cov.as_posix(), + "show", + x.as_posix(), + "-format=html", + "-instr-profile", + x.as_posix() + ".profdata", + "--object", + x.as_posix(), + ] + + # cov_filename = x.as_posix() + ".cov" + # cov_file = open(cov_filename, "w") + # p = Popen( + # llvm_cov_show, + # stdout=cov_file, + # stderr=PIPE, + # universal_newlines=True, + # env=my_env, + # ) + # output, errors = p.communicate() + # cov_file.close() + cov_filename = x.as_posix() + ".html" + cov_file = open(cov_filename, "w") + p = Popen( + llvm_cov_show, + stdout=cov_file, + stderr=PIPE, + universal_newlines=True, + env=my_env, + ) + output, errors = p.communicate() + cov_file.close() + if self.coverage: + profdata_files = [x.as_posix() for x in self.fuzz_driver_path.glob("**/*.profraw") if x.is_file()] + object_list = [x.as_posix()[:-8] for x in self.fuzz_driver_path.glob("**/*.profraw") if x.is_file()] + object_files =[] + for o in object_list: + object_files += ["-object", o] + + llvm_profdata = self.futag_llvm_package / "bin/llvm-profdata" + llvm_profdata_command = [ + llvm_profdata.as_posix(), + "merge", + "-sparse" + ] + profdata_files + [ + "-o", + (self.fuzz_driver_path / "futag-fuzz-result.profdata").as_posix(), + ] + if self.debug: + print(" ".join(llvm_profdata_command)) + p = call( + llvm_profdata_command, + stdout=PIPE, + stderr=PIPE, + ) + + llvm_cov = self.futag_llvm_package / "bin/llvm-cov" + llvm_cov_report = [ + llvm_cov.as_posix(), + "report", + ]+ object_files + [ + "-instr-profile=" + (self.fuzz_driver_path / "futag-fuzz-result.profdata").as_posix() + ] + if self.debug: + print(" ".join(llvm_cov_report)) + cov_report_filename = (self.fuzz_driver_path / "futag-coverage-report.txt").as_posix() + cov_report_file = open(cov_report_filename, "w") + p = Popen( + llvm_cov_report, + stdout=cov_report_file, + stderr=PIPE, + ) + + + llvm_cov_show = [ + llvm_cov.as_posix(), + "show", + "-format=html", + "-instr-profile=" + (self.fuzz_driver_path / "futag-fuzz-result.profdata").as_posix(), + ] + object_files + + cov_filename = (self.fuzz_driver_path / "futag-coverage-result.html").as_posix() + cov_file = open(cov_filename, "w") + p = Popen( + llvm_cov_show, + stdout=cov_file, + stderr=PIPE, + ) + if self.debug: + print(" ".join(llvm_cov_show)) + + template_file = self.futag_llvm_package / "svres-tmpl/svres.tmpl" + warning_info_text = "" + warning_info_path = Path.cwd().absolute() / "warning_info.svres" + warning_info_ex_text = "" + warning_info_ex_path = Path.cwd().absolute() / "warning_info_ex.svres" + + if warning_info_path.exists() and warning_info_ex_path.exists(): + with open("warning_info.svres", "r") as warning_info: + warning_info_text = warning_info.read() + with open("warning_info_ex.svres", "r") as warning_info_ex: + warning_info_ex_text = warning_info_ex.read() + with template_file.open() as tmpl: + lines = tmpl.read() + lines = lines.replace("WARNING_INFO", warning_info_text) + lines = lines.replace( + "WARNINGINFO_EXPLAINATION", warning_info_ex_text) + warning_info_path.unlink() + warning_info_ex_path.unlink() + with open((self.fuzz_driver_path / "futag.svres").as_posix(), "w") as svres: + svres.write(lines) + print("-- [Futag] Please import file ", (self.fuzz_driver_path / + "futag.svres").as_posix(), " to Svace project to view result!") + print("============ FINISH ============") + +class NatchFuzzer: + """Futag Fuzzer for Natch""" + + def __init__(self, futag_llvm_package: str, fuzz_driver_path: str = FUZZ_DRIVER_PATH, debug: bool = False, gdb: bool = False, svres: bool = False, fork: int = 1, totaltime: int = 300, timeout: int = 10, memlimit: int = 2048, coverage: bool = False, leak: bool = False, introspect: bool = False): + """_summary_ + + Args: + futag_llvm_package (str): path to the futag llvm package (with binaries, scripts, etc) + fuzz_driver_path (str, optional): location of fuzz-drivers, default "futag-fuzz-drivers". Defaults to FUZZ_DRIVER_PATH. + debug (bool, optional): print debug infomation while fuzzing, default False. Defaults to False. + gdb (bool, optional): debug crashes with GDB, default False. Defaults to False. + svres (bool, optional): generate svres file for Svace (if you have Svace), default False. Defaults to False. + fork (int, optional): fork mode of libFuzzer (https://llvm.org/docs/LibFuzzer.html#fork-mode). Defaults to 1 - no fork mode. + totaltime (int, optional): total time of fuzzing one fuzz-driver, default 300 seconds. Defaults to 300. + timeout (int, optional): if an fuzz-drive takes longer than this timeout, the process is treated as a failure case. Defaults to 10. + memlimit (int, optional): option for rss_limit_mb of libFuzzer - Memory usage limit in Mb, 0 - disable the limit. Defaults to 2048. + coverage (bool, optional): option for showing coverage of fuzzing. Defaults to False. + leak (bool, optional): detecting memory leak, default False. Defaults to False. + introspect (bool, optional): option for integrate with fuzz-introspector (to be add soon). Defaults to False. + """ + + self.futag_llvm_package = futag_llvm_package + self.fuzz_driver_path = fuzz_driver_path + + if Path(self.futag_llvm_package).exists(): + self.futag_llvm_package = Path(self.futag_llvm_package).absolute() + else: + sys.exit(INVALID_FUTAG_PATH) + + if Path(self.fuzz_driver_path).exists(): + self.fuzz_driver_path = Path(self.fuzz_driver_path).absolute() + else: + sys.exit(INVALID_FUZZ_DRIVER_PATH) + + self.svres = svres + self.leak = leak + self.debug = debug + self.gdb = gdb + if self.gdb and which("gdb") is None: + sys.exit(GDB_NOT_FOUND) + + self.fork = fork + self.timeout = timeout + self.totaltime = totaltime + self.memlimit = memlimit + self.coverage = coverage + self.introspect = introspect + self.backtraces = [] # backtraces list + # Set for backtrace's hashes. If current backtrace's hash is not in set then add this backtrace to backtraces list, otherwise this backtrace will be passed + self.backtrace_hashes = ( + set() + ) + + def __get_id_from_error(self, error_string): + error_id = 0 + for c in error_string: + error_id += ord(c) + return str(error_id) + + def __Printer(self, data): + sys.stdout.write("\r\x1b[K" + data.__str__()) + sys.stdout.flush() + + def __futag_escape(self, str): + str = str.replace("&", "&") + str = str.replace("<", "<") + str = str.replace(">", ">") + str = str.replace('"', """) + str = str.replace("\n", " ") + return str + + def __get_backtrace_hash(self, backtrace): + ''' + # Format of backtrace: + # backtrace= { + # "warnClass" : warnClass, + # "warnID": md5(warnClass+msg), + # "msg" : msg, + # "crash_line" : crash_line, + # "crash_file" : crash_file, + # "role_traces" : [{ + # "role": role, + # "stack": { + # "function": trace.group(2), + # "file": trace.group(3), + # "location" : { + # "line": location.group(1), + # "col" : location.group(2) + # }, + # "info" : "" + # } + # }] + # } + # + # HASH = warnID + role_traces["stack"]["file"] + role_traces["stack"]["location"]["line"] + role_traces["stack"]["file"]["col"] + ''' + input_str = "" + for r in backtrace["role_traces"]: + for s in r["stack"]: + input_str += ( + str(s["file"]) + str(s["location"]["line"]) + + str(s["location"]["col"]) + ) + return hash(str(backtrace["warnID"]) + input_str) + + def __libFuzzerLog_parser(self, fuzz_driver: str, libFuzzer_log: str, gdb: bool = False): + """_summary_ + + Args: + fuzz_driver (str): path to the fuzz-driver + libFuzzer_log (str): path of libFuzzer log + gdb (bool, optional): option for parsing with GDB. Defaults to False. + """ + + # Thank https://regex101.com/ + # match_error = "^==\d*==ERROR: (\w*): (.*)$" + match_error = "^==\d*==ERROR: (\w*): (.*) on.*$" + match_libFuzzer = "^==\d*== ERROR: (\w*): (.*)$" + match_summary = "^SUMMARY: \w*: (.*)$" + match_traceback = ( + "^ *#(\d*) \d.\w* in ([\w:_\[\]()&<> *,]*) ([\/\w\d\-._]*):(\d*:?\d*)$" + ) + match_tracepass = "^ *#(\d*) \d.\w* in ([\w:_\[\]()&<> *,]*) ([\(\)+\/\w\d\-._]*)$" + match_location = "(\d*):(\d*)" + match_exc_trace = "^.*\/llvm-11.1.0\/.*$" + match_exc_trace2 = "^.*libc-start.c.*$" + match_exc_trace3 = "^.*compiler-rt/lib/.*$" + match_exc_trace4 = "^.*LLVMFuzzerTestOneInput.*$" + # match_artifacts = "^artifact_prefix.*Test unit written to (.*)$" + match_artifacts = "^Running: (.*)$" + match_oom = "out-of-memory" + + backtrace = {} + parsing_error = False + stack = [] + info = "" + warnClass = "" + msg = "" + role_traces = [] + role = "" + crash_file = "" + crash_line = 0 + artifact_file = "" + with open(libFuzzer_log, "r", errors="ignore") as f: + lines = f.readlines() + if self.gdb: + print("-- [Futag] crash log:\n", "".join(lines)) + for l in lines: + artifact = re.match(match_artifacts, l) + if artifact: + artifact_file = artifact.group(1) + error = re.match(match_error, l) + # if not error: + # error = re.match(match_libFuzzer, l) + if error: + parsing_error = True + warnClass = error.group(1) + msg = error.group(2) + continue + summary = re.match(match_summary, l) + if summary: + parsing_error = False + if role_traces: + backtrace = { + "warnClass": warnClass, + "warnID": self.__get_id_from_error( + warnClass + msg + crash_file + str(crash_line) + ), + "msg": msg, + "crash_line": crash_line, + "crash_file": crash_file, + "role_traces": role_traces, + } + crash_file = "" + crash_line = 0 + role_traces = [] + if parsing_error: + trace = re.match(match_traceback, l) + if trace: + if re.match(match_exc_trace, l): + continue + if re.match(match_exc_trace2, l): + continue + if re.match(match_exc_trace3, l): + continue + # if re.match(match_exc_trace4, l): + # continue + location = re.match(match_location, trace.group(4)) + if location: + if not crash_line: + crash_line = location.group(1) + location = {"line": location.group( + 1), "col": location.group(2)} + else: + location = {"line": trace.group(4), "col": "0"} + if not crash_line: + crash_line = trace.group(4) + if not crash_file: + crash_file = trace.group(3) + stack.insert( + 0, + { + "function": trace.group(2), + "file": trace.group(3), + "location": location, + "info": "", + }, + ) + info = "Next: " + else: + if re.match(match_tracepass, l): + continue + empty_line = re.match("^$", l) + if not empty_line: + role = l + else: + if stack: + role_traces.append({"role": role, "stack": stack}) + stack = [] + role = "" + if not backtrace: + return + if gdb: + """ + Execute gdb for 3 times: + - First time for setting breakpoints and output all args, variables + - Second time for getting type of args, variables + - Third time for getting value + """ + + match_variable = "^([a-zA-Z_0-9]*) = .*$" + match_empty = "^(.*) = 0x[0-9]$" + match_full_ff = "^(.*) = 0x[0-9]$" + match_error_gdb = "^([a-zA-Z_0-9]*) = .*(' + ) + + for r in backtrace["role_traces"]: + loc_info = "" + for s in r["stack"]: + loc_info += ( + '' + ) + curren_explanation += ( + '' + + loc_info + + "" + ) + with open("warning_info_ex.svres", "a") as warning_info_ex: + warning_info_ex.write( + '' + + curren_explanation + + '.comment.statusDefault' + ) + os.system("rm -f values_*") + os.system("rm -f types_*") + os.system("rm -f trace_*") + + def fuzz(self, extra_param: str = ""): + """ helper for automatic fuzzing + + Args: + extra_param (str, optional): Extra params for fuzzing. Defaults to "". + """ + symbolizer = self.futag_llvm_package / "bin/llvm-symbolizer" + generated_functions = [ + x for x in (self.fuzz_driver_path / "succeeded").iterdir() if x.is_dir()] + # for dir in generated_functions: + for func_dir in generated_functions: + self.backtraces = [] + fuzz_driver_dirs = [x for x in func_dir.iterdir() if x.is_dir()] + # print(func_dir.as_posix()) + for dir in fuzz_driver_dirs: + # print(dir.as_posix()) + for x in [t for t in dir.glob("*.out") if t.is_file()]: + print("\n-- [Futag] FUZZING driver: " + x.stem + "... \n") + my_env = os.environ.copy() + if not self.leak: + my_env["ASAN_OPTIONS"] = "detect_leaks=0" + + my_env["ASAN_SYMBOLIZER_PATH"] = symbolizer.as_posix() + if self.coverage: + my_env["LLVM_PROFILE_FILE"] = x.as_posix() + ".profraw" + if self.fork > 1: + # 1. Execute binary with -fork=4 -ignore_crashes=1 -max_total_time=10 + # 2. Find all crash-* leak-* ... in artifact folder + # 3. Execute binary with these artifacts and save to log + # 4. With received log, parse to get traceback + # 5. Debug with GDB + execute_command = [ + x.as_posix(), + (x.parents[3]/ "Natch_corpus" / x.parents[1].stem.replace("anonymous_", "")).as_posix(), + "-fork=" + str(self.fork), + "-ignore_crashes=1", + "-timeout=" + str(self.timeout), + "-rss_limit_mb=" + str(self.memlimit), + "-max_total_time=" + str(self.totaltime), + "-artifact_prefix=" + dir.as_posix() + "/", + ] + else: + execute_command = [ + x.as_posix(), + (x.parents[3]/ "Natch_corpus" / x.parents[1].stem.replace("anonymous_", "")).as_posix(), + "-timeout=" + str(self.timeout), + "-rss_limit_mb=" + str(self.memlimit), + "-max_total_time=" + str(self.totaltime), + "-artifact_prefix=" + dir.as_posix() + "/", + ] + if extra_param: + execute_command = execute_command + extra_param.split(" ") + if self.debug: + print("-- [Futag] FUZZING command:" + + " ".join(execute_command)) + p = call( + execute_command, + stdout=PIPE, + stderr=PIPE, + universal_newlines=True, + env=my_env, + ) + + # 2. Find all crash-* leak-* ... in artifact folder + crashes_files = [x for x in dir.glob( + "**/crash-*") if x.is_file()] + for cr in crashes_files: + getlog_command = [x.as_posix(), cr.as_posix()] + crashlog_filename = dir.as_posix() + "/" + cr.stem + ".log" + crashlog_file = open(crashlog_filename, "w") + p = Popen( + getlog_command, + stdout=PIPE, + stderr=crashlog_file, + universal_newlines=True, + env=my_env, + ) + output, errors = p.communicate() + crashlog_file.close() + if self.gdb: + print( + "-- [Futag]: Parsing crashes with GDB: ", x.as_posix()) + self.__libFuzzerLog_parser( + x.as_posix(), crashlog_filename, True) + else: + print( + "-- [Futag]: Parsing crash without GDB: ", x.as_posix()) + self.__libFuzzerLog_parser( + x.as_posix(), crashlog_filename, False) + + if self.coverage: + llvm_profdata = self.futag_llvm_package / "bin/llvm-profdata" + llvm_profdata_command = [ + llvm_profdata.as_posix(), + "merge", + "-sparse", + x.as_posix() + ".profraw", + "-o", + x.as_posix() + ".profdata", + ] + if self.debug: + print(" ".join(llvm_profdata_command)) + p = call( + llvm_profdata_command, + stdout=PIPE, + stderr=PIPE, + universal_newlines=True, + env=my_env, + ) + + llvm_cov = self.futag_llvm_package / "bin/llvm-cov" + llvm_cov_report = [ + llvm_cov.as_posix(), + "report", + x.as_posix(), + "-instr-profile", + x.as_posix() + ".profdata", + "--object", + x.as_posix(), ] if self.debug: print(" ".join(llvm_cov_report)) p = run(llvm_cov_report) + # llvm_cov_show = [ + # llvm_cov.as_posix(), + # "show", + # x.as_posix(), + # "-instr-profile=" + x.as_posix() + ".profdata", + # ] llvm_cov_show = [ llvm_cov.as_posix(), "show", x.as_posix(), - "-instr-profile=" + x.as_posix() + ".profdata", + "-format=html", + "-instr-profile", + x.as_posix() + ".profdata", + "--object", + x.as_posix(), ] - cov_filename = x.as_posix() + ".cov" + # cov_filename = x.as_posix() + ".cov" + # cov_file = open(cov_filename, "w") + # p = Popen( + # llvm_cov_show, + # stdout=cov_file, + # stderr=PIPE, + # universal_newlines=True, + # env=my_env, + # ) + # output, errors = p.communicate() + # cov_file.close() + cov_filename = x.as_posix() + ".html" cov_file = open(cov_filename, "w") p = Popen( llvm_cov_show, @@ -781,4 +1594,5 @@ def fuzz(self, extra_param: str = ""): svres.write(lines) print("-- [Futag] Please import file ", (self.fuzz_driver_path / "futag.svres").as_posix(), " to Svace project to view result!") - print("============ FINISH ============") \ No newline at end of file + print("============ FINISH ============") + diff --git a/src/python/futag-package/src/futag/generator.py b/src/python/futag-package/src/futag/generator.py index c1857c9..40cbe7d 100644 --- a/src/python/futag-package/src/futag/generator.py +++ b/src/python/futag-package/src/futag/generator.py @@ -27,6 +27,7 @@ # import shutil from distutils.dir_util import copy_tree + class Generator: """Futag Generator""" @@ -83,7 +84,6 @@ def __init__(self, futag_llvm_package: str, library_root: str, target_type: int sys.exit(INVALID_TARGET_TYPE) self.target_type = target_type - if pathlib.Path(self.futag_llvm_package).exists(): self.futag_llvm_package = pathlib.Path( @@ -92,10 +92,10 @@ def __init__(self, futag_llvm_package: str, library_root: str, target_type: int sys.exit(INVALID_FUTAG_PATH) if self.target_type == LIBFUZZER: - if not pathlib.Path(self.futag_llvm_package/ "bin/clang").exists(): + if not pathlib.Path(self.futag_llvm_package / "bin/clang").exists(): sys.exit(INVALID_FUTAG_PATH) else: - if not pathlib.Path(self.futag_llvm_package/ "AFLplusplus/usr/local/bin/afl-clang-fast").exists(): + if not pathlib.Path(self.futag_llvm_package / "AFLplusplus/usr/local/bin/afl-clang-fast").exists(): sys.exit(INVALID_FUTAG_PATH) if pathlib.Path(self.library_root).exists(): @@ -188,7 +188,7 @@ def __get_compile_command(self, file): "location": command["directory"] } else: - if file.split(".")[-1] == "c": + if file.split(".")[-1] == "c": return { "compiler": "CC", "command": "", @@ -301,13 +301,16 @@ def __gen_builtin(self, param_name, gen_type_info): return { "gen_lines": [ "//GEN_BUILTIN\n", - gen_type_info["type_name"].replace("(anonymous namespace)::","") + " " + param_name + ";\n", + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + " " + param_name + ";\n", "memcpy(&"+param_name+", futag_pos, sizeof(" + - gen_type_info["type_name"].replace("(anonymous namespace)::","") + "));\n", - "futag_pos += sizeof(" + gen_type_info["type_name"].replace("(anonymous namespace)::","") + ");\n" + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + "));\n", + "futag_pos += sizeof(" + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + ");\n" ], "gen_free": [], - "buffer_size": ["sizeof(" + gen_type_info["type_name"].replace("(anonymous namespace)::","")+")"] + "buffer_size": ["sizeof(" + gen_type_info["type_name"].replace("(anonymous namespace)::", "")+")"] } def __gen_strsize(self, param_name, param_type, dyn_size_idx, array_name): @@ -339,14 +342,18 @@ def __gen_cstring(self, param_name, gen_type_info, dyn_cstring_size_idx): gen_lines = [ "//GEN_CSTRING1\n", - gen_type_info["base_type_name"] + " " + ref_name + \ - " = (" + gen_type_info["base_type_name"] + \ - ") malloc((dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "] + 1)* sizeof(char));\n", + gen_type_info["base_type_name"] + " " + ref_name + + " = (" + gen_type_info["base_type_name"] + + ") malloc((dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "] + 1)* sizeof(char));\n", "memset(" + ref_name + - ", 0, dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "] + 1);\n", + ", 0, dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "] + 1);\n", "memcpy(" + ref_name + - ", futag_pos, dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "]);\n", - "futag_pos += dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "];\n", + ", futag_pos, dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "]);\n", + "futag_pos += dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "];\n", ] if (gen_type_info["local_qualifier"]): gen_lines += [gen_type_info["type_name"] + @@ -362,7 +369,7 @@ def __gen_cstring(self, param_name, gen_type_info, dyn_cstring_size_idx): ], "buffer_size": [] } - + def __gen_wstring(self, param_name, gen_type_info, dyn_wstring_size_idx): """Declare and assign value for a C string type @@ -380,14 +387,18 @@ def __gen_wstring(self, param_name, gen_type_info, dyn_wstring_size_idx): gen_lines = [ "//GEN_WSTRING\n", - gen_type_info["base_type_name"] + " " + ref_name + \ - " = (" + gen_type_info["base_type_name"] + \ - ") malloc((dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "] + 1)* sizeof(wchar_t));\n", + gen_type_info["base_type_name"] + " " + ref_name + + " = (" + gen_type_info["base_type_name"] + + ") malloc((dyn_wstring_size[" + str(dyn_wstring_size_idx - + 1) + "] + 1)* sizeof(wchar_t));\n", "memset(" + ref_name + - ", 0, (dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "] + 1)* sizeof(wchar_t));\n", + ", 0, (dyn_wstring_size[" + str(dyn_wstring_size_idx - + 1) + "] + 1)* sizeof(wchar_t));\n", "memcpy(" + ref_name + - ", futag_pos, dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t));\n", - "futag_pos += dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t);\n", + ", futag_pos, dyn_wstring_size[" + + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t));\n", + "futag_pos += dyn_wstring_size[" + + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t);\n", ] if (gen_type_info["local_qualifier"]): gen_lines += [gen_type_info["type_name"] + @@ -427,8 +438,10 @@ def __gen_cxxstring(self, param_name, gen_type_info, dyn_cxxstring_size_idx): return { "gen_lines": [ gen_type_info["type_name"] + " " + param_name + - "(futag_pos, dyn_cxxstring_size[" + str(dyn_cxxstring_size_idx - 1) + "]); \n", - "futag_pos += dyn_cxxstring_size[" + str(dyn_cxxstring_size_idx - 1) + "];\n", + "(futag_pos, dyn_cxxstring_size[" + + str(dyn_cxxstring_size_idx - 1) + "]); \n", + "futag_pos += dyn_cxxstring_size[" + + str(dyn_cxxstring_size_idx - 1) + "];\n", ], "gen_free": [], "buffer_size": [] @@ -580,7 +593,7 @@ def __gen_struct(self, struct_name, struct, gen_info): self.dyn_cstring_size_idx += 1 curr_gen = self.__gen_cstring( curr_name, gen_type_info, self.dyn_cstring_size_idx) - #reinit value of curr_name to send reference of string + # reinit value of curr_name to send reference of string # curr_name = "&" + curr_name # string_prefix buffer_size += curr_gen["buffer_size"] gen_lines += curr_gen["gen_lines"] @@ -719,9 +732,11 @@ def __gen_class(self, param_name, class_record): def __gen_input_file(self, param_name, gen_type_info): cur_gen_free = [" " + x for x in self.gen_free] if gen_type_info["gen_type"] == GEN_CSTRING: - line = "const char* " + param_name + " = \"futag_input_file_" + str(self.file_idx - 1) + "\";\n" + line = "const char* " + param_name + \ + " = \"futag_input_file_" + str(self.file_idx - 1) + "\";\n" elif gen_type_info["gen_type"] == GEN_WSTRING: - line = "const wchar_t * " + param_name + " = L\"futag_input_file_" + str(self.file_idx - 1) + "\";\n" + line = "const wchar_t * " + param_name + \ + " = L\"futag_input_file_" + str(self.file_idx - 1) + "\";\n" else: return { "gen_lines": [], @@ -759,7 +774,8 @@ def __gen_file_descriptor(self, param_name, gen_type_info): "const char* " + param_name + "_tmp" + str(self.file_idx) + " = \"futag_input_file_" + str(self.file_idx - 1) + "\";\n", "FILE * fp_" + str(self.file_idx - 1) + - " = fopen(" + param_name + "_tmp" + str(self.file_idx) + ",\"w\");\n", + " = fopen(" + param_name + "_tmp" + + str(self.file_idx) + ",\"w\");\n", "if (fp_" + str(self.file_idx - 1) + " == NULL) {\n", ] gen_lines += cur_gen_free @@ -770,7 +786,9 @@ def __gen_file_descriptor(self, param_name, gen_type_info): "], fp_" + str(self.file_idx - 1) + ");\n", "fclose(fp_" + str(self.file_idx - 1) + ");\n", "futag_pos += file_size[" + str(self.file_idx - 1) + "];\n", - gen_type_info["type_name"] + " " + param_name + "= open(" + param_name + "_tmp" + str(self.file_idx) + ", O_RDWR);\n" + gen_type_info["type_name"] + " " + param_name + + "= open(" + param_name + "_tmp" + + str(self.file_idx) + ", O_RDWR);\n" ] gen_free = ["close(" + param_name + ");\n"] return { @@ -864,7 +882,8 @@ def __gen_var_function(self, func_param_name: str, func): if gen_type_info["gen_type"] == GEN_BUILTIN: this_gen_size = False if arg["param_usage"] in ["FILE_DESCRIPTOR"]: - curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "fd_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_file_descriptor( curr_name, gen_type_info) @@ -903,28 +922,32 @@ def __gen_var_function(self, func_param_name: str, func): if gen_type_info["gen_type"] == GEN_CSTRING: # GEN FILE NAME OR # GEN STRING if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: - curr_name = "f_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_input_file( curr_name, gen_type_info) else: - curr_name = "str_" + curr_name + str(self.dyn_cstring_size_idx) # string_prefix + curr_name = "str_" + curr_name + \ + str(self.dyn_cstring_size_idx) # string_prefix self.dyn_cstring_size_idx += 1 curr_gen = self.__gen_cstring( curr_name, gen_type_info, self.dyn_cstring_size_idx) gen_dict["buffer_size"] += curr_gen["buffer_size"] gen_dict["gen_lines"] += curr_gen["gen_lines"] gen_dict["gen_free"] += curr_gen["gen_free"] - + if gen_type_info["gen_type"] == GEN_REFSTRING: # GEN FILE NAME OR # GEN STRING if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: - curr_name = "f_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_input_file( curr_name, gen_type_info) else: - curr_name = "str_" + curr_name + str(self.dyn_cstring_size_idx) # string_prefix + curr_name = "str_" + curr_name + \ + str(self.dyn_cstring_size_idx) # string_prefix self.dyn_cstring_size_idx += 1 curr_gen = self.__gen_cstring( curr_name, gen_type_info, self.dyn_cstring_size_idx) @@ -932,16 +955,18 @@ def __gen_var_function(self, func_param_name: str, func): gen_dict["buffer_size"] += curr_gen["buffer_size"] gen_dict["gen_lines"] += curr_gen["gen_lines"] gen_dict["gen_free"] += curr_gen["gen_free"] - + if gen_type_info["gen_type"] == GEN_WSTRING: # GEN FILE NAME OR # GEN STRING if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: - curr_name = "f_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_input_file( curr_name, gen_type_info) else: - curr_name = "str_" + curr_name + str(self.dyn_wstring_size_idx) # string_prefix + curr_name = "str_" + curr_name + \ + str(self.dyn_wstring_size_idx) # string_prefix self.dyn_wstring_size_idx += 1 curr_gen = self.__gen_wstring( curr_name, gen_type_info, self.dyn_wstring_size_idx) @@ -951,14 +976,16 @@ def __gen_var_function(self, func_param_name: str, func): if gen_type_info["gen_type"] == GEN_CXXSTRING: - if (arg["param_name"] in ["filename", "file", "filepath", "path"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1 or arg["param_name"].find('path') != -1) and len(arg["gen_list"]) == 1: - curr_name = "f_" + curr_name + str(self.file_idx) # string_prefix + if (arg["param_name"] in ["filename", "file", "filepath", "path"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1 or arg["param_name"].find('path') != -1) and len(arg["gen_list"]) == 1: + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_input_file( curr_name, gen_type_info) else: - curr_name = "str_" + curr_name + str(self.dyn_cxxstring_size_idx) # string_prefix + curr_name = "str_" + curr_name + \ + str(self.dyn_cxxstring_size_idx) # string_prefix self.dyn_cxxstring_size_idx += 1 curr_gen = self.__gen_cxxstring( curr_name, gen_type_info, self.dyn_cxxstring_size_idx) @@ -1046,7 +1073,8 @@ def __gen_var_function(self, func_param_name: str, func): class_name = found_parent["qname"] if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: function_call = " //declare the RECORD and call constructor\n" - function_call +=" " + class_name.replace("::(anonymous namespace)", "") + func_param_name + "("+ ",".join(param_list)+");\n" + function_call += " " + class_name.replace( + "::(anonymous namespace)", "") + func_param_name + "(" + ",".join(param_list)+");\n" else: # Find default constructor # TODO: add code for other constructors @@ -1059,10 +1087,13 @@ def __gen_var_function(self, func_param_name: str, func): if not found_default_constructor: self.gen_this_function = False function_call = " //declare the RECORD first\n" - function_call += " " + class_name.replace("::(anonymous namespace)", "") + " " + func_param_name + ";\n" + function_call += " " + \ + class_name.replace( + "::(anonymous namespace)", "") + " " + func_param_name + ";\n" # call the method function_call += " //METHOD CALL\n" - function_call += " " + func_param_name + "." + func["name"]+"("+ ",".join(param_list)+");\n" + function_call += " " + func_param_name + "." + \ + func["name"]+"(" + ",".join(param_list)+");\n" else: function_call = "//GEN_VAR_FUNCTION\n " + func["return_type"] + " " + func_param_name + \ @@ -1122,9 +1153,10 @@ def __wrapper_file(self, func): "msg": "Error: File closed!" } return { - "file": f, - "msg": "Successed: " + full_path + " created!" - } + "file": f, + "msg": "Successed: " + full_path + " created!" + } + def __anonymous_wrapper_file(self, func): # if anonymous: @@ -1162,11 +1194,12 @@ def __anonymous_wrapper_file(self, func): file_name = filename + \ str(file_index) + "." + self.target_extension - full_path_destination = (filepath / filename / dir_name / file_name).as_posix() + full_path_destination = ( + filepath / filename / dir_name / file_name).as_posix() with open(source_path, 'r') as s: source_file = s.read() d = open(full_path_destination, "w") - d.write("//"+func["hash"]+ "\n") + d.write("//"+func["hash"] + "\n") d.write(source_file) d.close() f = open(full_path_destination, 'a') @@ -1174,7 +1207,6 @@ def __anonymous_wrapper_file(self, func): return None return f - def __log_file(self, func, anonymous: bool = False): if anonymous: filename = func["name"] @@ -1234,9 +1266,12 @@ def __retrieve_old_values(self, old_values): self.buffer_size = copy.copy(old_values["buffer_size"]) self.gen_lines = copy.copy(old_values["gen_lines"]) self.gen_free = copy.copy(old_values["gen_free"]) - self.dyn_cstring_size_idx = copy.copy(old_values["dyn_cstring_size_idx"]) - self.dyn_cxxstring_size_idx = copy.copy(old_values["dyn_cxxstring_size_idx"]) - self.dyn_wstring_size_idx = copy.copy(old_values["dyn_wstring_size_idx"]) + self.dyn_cstring_size_idx = copy.copy( + old_values["dyn_cstring_size_idx"]) + self.dyn_cxxstring_size_idx = copy.copy( + old_values["dyn_cxxstring_size_idx"]) + self.dyn_wstring_size_idx = copy.copy( + old_values["dyn_wstring_size_idx"]) self.var_function_idx = copy.copy(old_values["var_function_idx"]) self.param_list = copy.copy(old_values["param_list"]) self.curr_func_log = copy.copy(old_values["curr_func_log"]) @@ -1244,7 +1279,6 @@ def __retrieve_old_values(self, old_values): self.gen_this_function = copy.copy(old_values["gen_this_function"]) self.param_list = copy.copy(old_values["param_list"]) - def __gen_anonymous_function(self, func, param_id) -> bool: malloc_free = [ "unsigned char *", @@ -1279,7 +1313,7 @@ def __gen_anonymous_function(self, func, param_id) -> bool: print(CANNOT_CREATE_WRAPPER_FILE, func["qname"]) return False print(WRAPPER_FILE_CREATED, f.name) - + for line in self.__gen_header(func["location"]["fullpath"]): f.write("// " + line) f.write('\n') @@ -1294,13 +1328,16 @@ def __gen_anonymous_function(self, func, param_id) -> bool: else: f.write(AFLPLUSPLUS_PREFIX) - buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + " + " + str(self.file_idx) + buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + \ + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + \ + " + " + str(self.file_idx) if self.buffer_size: buffer_check += " + " + " + ".join(self.buffer_size) f.write(" if (Fuzz_Size < " + buffer_check + ") return 0;\n") if self.dyn_cstring_size_idx > 0: - f.write(" size_t dyn_cstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cstring_size[" + str(self.dyn_cstring_size_idx) + "];\n") @@ -1308,7 +1345,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_cstring_buffer == 0) dyn_cstring_size[0] = dyn_cstring_buffer; \n") - f.write(" else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") + f.write( + " else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") f.write(" size_t remain = dyn_cstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cstring_size_idx) + " - 1; i++){\n") @@ -1326,7 +1364,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: " //end of generation random array of dynamic string sizes\n") if self.dyn_wstring_size_idx > 0: - f.write(" size_t dyn_wstring_buffer = (size_t) ((Fuzz_Size + sizeof(wchar_t) - (" + buffer_check +" )))/sizeof(wchar_t);\n") + f.write(" size_t dyn_wstring_buffer = (size_t) ((Fuzz_Size + sizeof(wchar_t) - (" + + buffer_check + " )))/sizeof(wchar_t);\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_wstring_size[" + str(self.dyn_wstring_size_idx) + "];\n") @@ -1334,7 +1373,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_wstring_buffer == 0) dyn_wstring_size[0] = dyn_wstring_buffer; \n") - f.write(" else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") + f.write( + " else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") f.write(" size_t remain = dyn_wstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_wstring_size_idx) + " - 1; i++){\n") @@ -1350,9 +1390,10 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" dyn_wstring_size[0] = dyn_wstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") - + if self.dyn_cxxstring_size_idx > 0: - f.write(" size_t dyn_cxxstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cxxstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cxxstring_size[" + str(self.dyn_cxxstring_size_idx) + "];\n") @@ -1360,7 +1401,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_cxxstring_buffer == 0) dyn_cxxstring_size[0] = dyn_cxxstring_buffer; \n") - f.write(" else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") + f.write( + " else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") f.write(" size_t remain = dyn_cxxstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cxxstring_size_idx) + " - 1; i++){\n") @@ -1373,12 +1415,14 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write( " dyn_cxxstring_size[" + str(self.dyn_cxxstring_size_idx) + " - 1] = dyn_cxxstring_buffer - remain;\n") else: - f.write(" dyn_cxxstring_size[0] = dyn_cxxstring_buffer;\n") + f.write( + " dyn_cxxstring_size[0] = dyn_cxxstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") if self.file_idx > 0: - f.write(" size_t file_buffer = (size_t) ((Fuzz_Size + "+ str(self.file_idx) + " - (" + buffer_check +" )));\n") + f.write(" size_t file_buffer = (size_t) ((Fuzz_Size + " + + str(self.file_idx) + " - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic file sizes\n") f.write(" size_t file_size[" + str(self.file_idx) + "];\n") @@ -1408,12 +1452,13 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" " + line) f.write(" //" + func["qname"]) - + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: class_name = found_parent["qname"] if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: f.write(" //declare the RECORD and call constructor\n") - f.write(" " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") else: # Find default constructor # TODO: add code for other constructors @@ -1429,7 +1474,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.close() return False f.write(" //declare the RECORD first\n") - f.write(" " + class_name.replace("::(anonymous namespace)", "") + " futag_target;\n") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target;\n") # call the method f.write(" //METHOD CALL\n") f.write(" futag_target." + func["name"]+"(") @@ -1439,7 +1485,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: f.write(" " + func["return_type"] + " futag_target = " + func["qname"] + "(") else: - f.write(" " + func["qname"].replace("::(anonymous namespace)", "") + "(") + f.write( + " " + func["qname"].replace("::(anonymous namespace)", "") + "(") param_list = [] for arg in self.param_list: @@ -1482,14 +1529,15 @@ def __gen_anonymous_function(self, func, param_id) -> bool: if gen_type_info["gen_type"] == GEN_BUILTIN: this_gen_size = False if curr_param["param_usage"] in ["FILE_DESCRIPTOR"]: - curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "fd_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_file_descriptor( curr_name, gen_type_info) self.__append_gen_dict(curr_gen) break # GEN STRING SIZE - + elif param_id > 0 and (func["params"][param_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING] or curr_param["param_usage"] == "SIZE_FIELD"): if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: dyn_size_idx = 0 @@ -1507,13 +1555,20 @@ def __gen_anonymous_function(self, func, param_id) -> bool: curr_gen = self.__gen_strsize( curr_name, curr_param["param_type"], dyn_size_idx, array_name) self.__append_gen_dict(curr_gen) - this_gen_size = True + this_gen_size = True break if not this_gen_size: curr_name = "b_" + curr_name # builtin_prefix curr_gen = self.__gen_builtin(curr_name, gen_type_info) self.__append_gen_dict(curr_gen) + if gen_type_info["gen_type"] == GEN_CFILE: + # GEN FILE NAME OR # GEN STRING + curr_name = "fc_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + if gen_type_info["gen_type"] == GEN_CSTRING: # GEN FILE NAME OR # GEN STRING if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: @@ -1530,7 +1585,7 @@ def __gen_anonymous_function(self, func, param_id) -> bool: self.__append_gen_dict(curr_gen) if gen_type_info["gen_type"] == GEN_REFSTRING: - print ("!!!GEN_REFSTRING\n\n\n") + print("!!!GEN_REFSTRING\n\n\n") # GEN FILE NAME OR # GEN STRING if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: curr_name = "f_" + curr_name # string_prefix @@ -1665,7 +1720,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -1696,8 +1752,10 @@ def __gen_anonymous_function(self, func, param_id) -> bool: self.var_function_idx += 1 self.gen_lines += ["\n"] self.param_list += [curr_name] - curr_gen = self.__gen_var_function(curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -1746,7 +1804,8 @@ def __gen_anonymous_function(self, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -1773,7 +1832,6 @@ def __gen_anonymous_function(self, func, param_id) -> bool: param_id += 1 self.__gen_anonymous_function(func, param_id) - def __gen_target_function(self, func, param_id) -> bool: malloc_free = [ "unsigned char *", @@ -1806,14 +1864,15 @@ def __gen_target_function(self, func, param_id) -> bool: return False # generate file name wrapper_result = self.__wrapper_file(func) - print("Generating fuzzing-wapper for function ", func["qname"], ": ") + print("Generating fuzzing-wapper for function ", + func["qname"], ": ") print("-- ", wrapper_result["msg"]) if not wrapper_result["file"]: self.gen_this_function = False return False f = wrapper_result["file"] - - f.write("//"+func["hash"]+ "\n") + + f.write("//"+func["hash"] + "\n") for line in self.__gen_header(func["location"]["fullpath"]): f.write(line) f.write('\n') @@ -1828,15 +1887,37 @@ def __gen_target_function(self, func, param_id) -> bool: else: f.write(AFLPLUSPLUS_PREFIX) - # buffer_check = " if (Fuzz_Size < " + \ - # str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) +" + str(self.dyn_cxxstring_size_idx) + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + " + " + str(self.file_idx) - buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + " + " + str(self.file_idx) + # buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + \ + # "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + \ + # " + " + str(self.file_idx) + buffer_check_list =[] + wchar_t_check = "" + if self.dyn_wstring_size_idx > 0: + wchar_t_check = str(self.dyn_wstring_size_idx) + " * sizeof(wchar_t)" + buffer_check_list.append(wchar_t_check) + dyn_cstring_check = "" + if self.dyn_cstring_size_idx > 0: + dyn_cstring_check = str(self.dyn_cstring_size_idx) + " * sizeof(char)" + buffer_check_list.append(dyn_cstring_check) + file_idx_check = "" + if self.file_idx > 0: + file_idx_check = str(self.file_idx) + buffer_check_list.append(file_idx_check) + + buffer_check = "" if self.buffer_size: - buffer_check += " + " + " + ".join(self.buffer_size) + if buffer_check_list: + buffer_check = "+".join(buffer_check_list) + " + " + " + ".join(self.buffer_size) + else: + buffer_check = " + ".join(self.buffer_size) + else: + buffer_check = "+".join(buffer_check_list) + f.write(" if (Fuzz_Size < " + buffer_check + ") return 0;\n") if self.dyn_cstring_size_idx > 0: - f.write(" size_t dyn_cstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cstring_buffer = (size_t) (Fuzz_Size + "+ str(self.dyn_cstring_size_idx) +"*sizeof(char) - (" + buffer_check + " ));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cstring_size[" + str(self.dyn_cstring_size_idx) + "];\n") @@ -1844,7 +1925,8 @@ def __gen_target_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_cstring_buffer == 0) dyn_cstring_size[0] = dyn_cstring_buffer; \n") - f.write(" else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") + f.write( + " else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") f.write(" size_t remain = dyn_cstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cstring_size_idx) + " - 1; i++){\n") @@ -1862,7 +1944,8 @@ def __gen_target_function(self, func, param_id) -> bool: " //end of generation random array of dynamic string sizes\n") if self.dyn_wstring_size_idx > 0: - f.write(" size_t dyn_wstring_buffer = (size_t) ((Fuzz_Size + sizeof(wchar_t) - (" + buffer_check +" )))/sizeof(wchar_t);\n") + f.write(" size_t dyn_wstring_buffer = (size_t) (Fuzz_Size + " + str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) - (" + + buffer_check + " ))/sizeof(wchar_t);\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_wstring_size[" + str(self.dyn_wstring_size_idx) + "];\n") @@ -1870,7 +1953,8 @@ def __gen_target_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_wstring_buffer == 0) dyn_wstring_size[0] = dyn_wstring_buffer; \n") - f.write(" else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") + f.write( + " else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") f.write(" size_t remain = dyn_wstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_wstring_size_idx) + " - 1; i++){\n") @@ -1886,9 +1970,10 @@ def __gen_target_function(self, func, param_id) -> bool: f.write(" dyn_wstring_size[0] = dyn_wstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") - + if self.dyn_cxxstring_size_idx > 0: - f.write(" size_t dyn_cxxstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cxxstring_buffer = (size_t) (Fuzz_Size - (" + buffer_check + " ));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cxxstring_size[" + str(self.dyn_cxxstring_size_idx) + "];\n") @@ -1896,7 +1981,8 @@ def __gen_target_function(self, func, param_id) -> bool: f.write(" srand(time(NULL));\n") f.write( " if(dyn_cxxstring_buffer == 0) dyn_cxxstring_size[0] = dyn_cxxstring_buffer; \n") - f.write(" else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") + f.write( + " else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") f.write(" size_t remain = dyn_cxxstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cxxstring_size_idx) + " - 1; i++){\n") @@ -1909,12 +1995,14 @@ def __gen_target_function(self, func, param_id) -> bool: f.write( " dyn_cxxstring_size[" + str(self.dyn_cxxstring_size_idx) + " - 1] = dyn_cxxstring_buffer - remain;\n") else: - f.write(" dyn_cxxstring_size[0] = dyn_cxxstring_buffer;\n") + f.write( + " dyn_cxxstring_size[0] = dyn_cxxstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") if self.file_idx > 0: - f.write(" size_t file_buffer = (size_t) ((Fuzz_Size + "+ str(self.file_idx) + " - (" + buffer_check +" )));\n") + f.write(" size_t file_buffer = (size_t) (Fuzz_Size + " + + str(self.file_idx) + " - (" + buffer_check + " ));\n") f.write(" //generate random array of dynamic file sizes\n") f.write(" size_t file_size[" + str(self.file_idx) + "];\n") @@ -1947,7 +2035,8 @@ def __gen_target_function(self, func, param_id) -> bool: class_name = found_parent["qname"] if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: f.write(" //declare the RECORD and call constructor\n") - f.write(" " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") else: # Find default constructor # TODO: add code for other constructors @@ -2016,14 +2105,15 @@ def __gen_target_function(self, func, param_id) -> bool: if gen_type_info["gen_type"] == GEN_BUILTIN: this_gen_size = False if curr_param["param_usage"] in ["FILE_DESCRIPTOR"]: - curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "fd_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_file_descriptor( curr_name, gen_type_info) self.__append_gen_dict(curr_gen) break # GEN STRING SIZE - + elif param_id > 0 and (func["params"][param_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING] or curr_param["param_usage"] == "SIZE_FIELD"): if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: dyn_size_idx = 0 @@ -2041,7 +2131,7 @@ def __gen_target_function(self, func, param_id) -> bool: curr_gen = self.__gen_strsize( curr_name, curr_param["param_type"], dyn_size_idx, array_name) self.__append_gen_dict(curr_gen) - this_gen_size = True + this_gen_size = True break if not this_gen_size: curr_name = "b_" + curr_name # builtin_prefix @@ -2198,7 +2288,8 @@ def __gen_target_function(self, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -2229,8 +2320,10 @@ def __gen_target_function(self, func, param_id) -> bool: self.var_function_idx += 1 self.gen_lines += ["\n"] self.param_list += [curr_name] - curr_gen = self.__gen_var_function(curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -2279,7 +2372,8 @@ def __gen_target_function(self, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -2518,14 +2612,15 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par f for f in self.target_library['functions'] if f['qname'].replace(":", "_") == func_dir.name] if not len(search_curr_func): search_curr_func = [ - f for f in self.target_library['functions'] if 'anonymous_' + f['name'].replace(":", "_") == func_dir.name] + f for f in self.target_library['functions'] if 'anonymous_' + f['name'].replace(":", "_") == func_dir.name] if not len(search_curr_func): continue current_func = search_curr_func[0] func_file_location = current_func["location"]["fullpath"] compiler_info = self.__get_compile_command(func_file_location) - - include_subdir = [] # List of Pathlib (-I parameters) in compile command. + + # List of Pathlib (-I parameters) in compile command. + include_subdir = [] if os.path.exists(compiler_info["location"]): current_location = os.getcwd() @@ -2594,14 +2689,16 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par fuzz_driver_dirs = [x for x in func_dir.iterdir() if x.is_dir()] for dir in fuzz_driver_dirs: # for target_src in [t for t in dir.glob("*"+self.target_extension) if t.is_file()]: - for target_src in [t for t in dir.glob("*") if t.is_file() and t.suffix in [".c", ".cc", ".cpp"]]: + for target_src in [t for t in dir.glob("*") if t.is_file() and t.suffix in [".c", ".cc", ".cpp", ".log"]]: target_path = dir.as_posix() + "/" + target_src.stem + ".out" error_path = dir.as_posix() + "/" + target_src.stem + ".err" generated_targets += 1 if self.target_type == LIBFUZZER: - compiler_cmd = [compiler_path.as_posix()] + compiler_flags_libFuzzer.split(" ") + current_include + ["-I" + x for x in extra_include.split(" ") if x.strip() ] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_libFuzzer.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") else: - compiler_cmd = [compiler_path.as_posix()] + compiler_flags_aflplusplus.split(" ") + current_include + ["-I" + x for x in extra_include.split(" ") if x.strip() ] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_aflplusplus.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") compile_cmd_list.append({ "compiler_cmd": compiler_cmd, @@ -2634,7 +2731,8 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par ((self.succeeded_path / dir.parents[1].name)).mkdir(parents=True, exist_ok=True) # shutil.move(dir.parents[0].as_posix(), (self.succeeded_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) - copy_tree(dir.parents[0].as_posix(), (self.succeeded_path / dir.parents[1].name).as_posix()) + copy_tree(dir.parents[0].as_posix( + ), (self.succeeded_path / dir.parents[1].name / dir.parents[0].name).as_posix()) if keep_failed: failed_tree = set() @@ -2648,12 +2746,13 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par if target not in failed_tree: failed_tree.add(target) for dir in failed_tree: - if dir.parents[0] not in succeeded_dir : + if dir.parents[0] not in succeeded_dir: if not (self.failed_path / dir.parents[1].name).exists(): ((self.failed_path / - dir.parents[1].name)).mkdir(parents=True, exist_ok=True) + dir.parents[1].name)).mkdir(parents=True, exist_ok=True) # shutil.move(dir.parents[0].as_posix(), (self.failed_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) - copy_tree(dir.parents[0].as_posix(),(self.failed_path / dir.parents[1].name).as_posix()) + copy_tree(dir.parents[0].as_posix( + ), (self.failed_path / dir.parents[1].name / dir.parents[0].name).as_posix()) else: delete_folder(self.failed_path) if not keep_original: @@ -2664,7 +2763,7 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par + str(len(compiled_targets_list)) + " fuzz-driver(s)\n" ) - + def gen_targets_from_callstack(self, target): found_function = None for func in self.target_library["functions"]: @@ -2709,9 +2808,9 @@ def __init__(self, futag_llvm_package: str, library_root: str, target_type: int self.consumer_contexts = None self.total_context = [] self.header = [] - + self.gen_anonymous = False - self.max_wrappers = 10 + self.max_wrappers = 10 self.gen_this_function = True self.gen_lines = [] self.buffer_size = [] @@ -2845,7 +2944,7 @@ def __get_compile_command(self, file): "location": command["directory"] } else: - if file.split(".")[-1] == "c": + if file.split(".")[-1] == "c": return { "compiler": "CC", "command": "", @@ -2996,7 +3095,8 @@ def __gen_cstring(self, param_name, gen_type_info, dyn_cstring_size_idx): malloc = gen_type_info["base_type_name"] + " " + ref_name + \ " = (" + gen_type_info["base_type_name"] + \ - ") malloc(dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "] + 1);\n" + ") malloc(dyn_cstring_size[" + \ + str(dyn_cstring_size_idx - 1) + "] + 1);\n" if "wchar_t" in gen_type_info["base_type_name"]: malloc = gen_type_info["base_type_name"] + " " + ref_name + \ " = (" + gen_type_info["base_type_name"] + \ @@ -3006,10 +3106,13 @@ def __gen_cstring(self, param_name, gen_type_info, dyn_cstring_size_idx): "//GEN_CSTRING\n", malloc, "memset(" + ref_name + - ", 0, dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "] + 1);\n", + ", 0, dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "] + 1);\n", "memcpy(" + ref_name + - ", futag_pos, dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "] );\n", - "futag_pos += dyn_cstring_size[" + str(dyn_cstring_size_idx - 1) + "];\n", + ", futag_pos, dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "] );\n", + "futag_pos += dyn_cstring_size[" + + str(dyn_cstring_size_idx - 1) + "];\n", ] if (gen_type_info["local_qualifier"]): gen_lines += [gen_type_info["type_name"] + @@ -3043,14 +3146,18 @@ def __gen_wstring(self, param_name, gen_type_info, dyn_wstring_size_idx): gen_lines = [ "//GEN_WSTRING\n", - gen_type_info["base_type_name"] + " " + ref_name + \ - " = (" + gen_type_info["base_type_name"] + \ - ") malloc((dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "] + 1)* sizeof(wchar_t));\n", + gen_type_info["base_type_name"] + " " + ref_name + + " = (" + gen_type_info["base_type_name"] + + ") malloc((dyn_wstring_size[" + str(dyn_wstring_size_idx - + 1) + "] + 1)* sizeof(wchar_t));\n", "memset(" + ref_name + - ", 0, (dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "] + 1)* sizeof(wchar_t));\n", + ", 0, (dyn_wstring_size[" + str(dyn_wstring_size_idx - + 1) + "] + 1)* sizeof(wchar_t));\n", "memcpy(" + ref_name + - ", futag_pos, dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t));\n", - "futag_pos += dyn_wstring_size[" + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t);\n", + ", futag_pos, dyn_wstring_size[" + + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t));\n", + "futag_pos += dyn_wstring_size[" + + str(dyn_wstring_size_idx - 1) + "]* sizeof(wchar_t);\n", ] if (gen_type_info["local_qualifier"]): gen_lines += [gen_type_info["type_name"] + @@ -3067,7 +3174,6 @@ def __gen_wstring(self, param_name, gen_type_info, dyn_wstring_size_idx): "buffer_size": [] } - def __gen_cxxstring(self, param_name, gen_type_info, dyn_cxxstring_size_idx): """Declare and assign value for a C++ string type @@ -3091,8 +3197,10 @@ def __gen_cxxstring(self, param_name, gen_type_info, dyn_cxxstring_size_idx): return { "gen_lines": [ gen_type_info["type_name"] + " " + param_name + - "(futag_pos, dyn_cxxstring_size[" + str(dyn_cxxstring_size_idx - 1) + "]); \n", - "futag_pos += dyn_cxxstring_size[" + str(dyn_cxxstring_size_idx - 1) + "];\n", + "(futag_pos, dyn_cxxstring_size[" + + str(dyn_cxxstring_size_idx - 1) + "]); \n", + "futag_pos += dyn_cxxstring_size[" + + str(dyn_cxxstring_size_idx - 1) + "];\n", ], "gen_free": [], "buffer_size": [] @@ -3131,7 +3239,8 @@ def __gen_enum(self, enum_record, param_name, gen_type_info, compiler_info, anon "_enum_index, futag_pos, sizeof(unsigned int));\n", # "enum " + enum_name + " " + param_name + " = static_cast(" + param_name + "_enum_index % " + str(enum_length) + ");\n", + ">(" + param_name + "_enum_index % " + \ + str(enum_length) + ");\n", "futag_pos += sizeof(unsigned int);\n" ], "gen_free": [], @@ -3406,7 +3515,7 @@ def __gen_input_file(self, param_name, gen_type_info): "gen_free": [], "buffer_size": [] } - + def __gen_file_descriptor(self, param_name, gen_type_info): if not "" in self.header: self.header += [""] @@ -3416,7 +3525,8 @@ def __gen_file_descriptor(self, param_name, gen_type_info): "const char* " + param_name + "_tmp" + str(self.file_idx) + " = \"futag_input_file_" + str(self.file_idx - 1) + "\";\n", "FILE * fp_" + str(self.file_idx - 1) + - " = fopen(" + param_name + "_tmp" + str(self.file_idx) + ",\"w\");\n", + " = fopen(" + param_name + "_tmp" + + str(self.file_idx) + ",\"w\");\n", "if (fp_" + str(self.file_idx - 1) + " == NULL) {\n", ] gen_lines += cur_gen_free @@ -3427,7 +3537,9 @@ def __gen_file_descriptor(self, param_name, gen_type_info): "], fp_" + str(self.file_idx - 1) + ");\n", "fclose(fp_" + str(self.file_idx - 1) + ");\n", "futag_pos += file_size[" + str(self.file_idx - 1) + "];\n", - gen_type_info["type_name"] + " " + param_name + "= open(" + param_name + "_tmp" + str(self.file_idx) + ", O_RDWR);\n" + gen_type_info["type_name"] + " " + param_name + + "= open(" + param_name + "_tmp" + + str(self.file_idx) + ", O_RDWR);\n" ] gen_free = ["close(" + param_name + ");\n"] return { @@ -3436,7 +3548,6 @@ def __gen_file_descriptor(self, param_name, gen_type_info): "buffer_size": [] } - def __search_in_typedefs(self, type_name, typedefs): # Are there multiple type definitions for the same data type??? result = None @@ -3522,7 +3633,8 @@ def __gen_var_function(self, func_param_name: str, func): if gen_type_info["gen_type"] == GEN_BUILTIN: this_gen_size = False if arg["param_usage"] in ["FILE_DESCRIPTOR"]: - curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "fd_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_file_descriptor( curr_name, gen_type_info) @@ -3743,9 +3855,9 @@ def __wrapper_file(self, func): "msg": "Error: File closed!" } return { - "file": f, - "msg": "Successed: " + full_path + " created!" - } + "file": f, + "msg": "Successed: " + full_path + " created!" + } def __log_file(self, func, anonymous: bool = False): if anonymous: @@ -3806,9 +3918,12 @@ def __retrieve_old_values(self, old_values): self.buffer_size = copy.copy(old_values["buffer_size"]) self.gen_lines = copy.copy(old_values["gen_lines"]) self.gen_free = copy.copy(old_values["gen_free"]) - self.dyn_cstring_size_idx = copy.copy(old_values["dyn_cstring_size_idx"]) - self.dyn_cxxstring_size_idx = copy.copy(old_values["dyn_cxxstring_size_idx"]) - self.dyn_wstring_size_idx = copy.copy(old_values["dyn_wstring_size_idx"]) + self.dyn_cstring_size_idx = copy.copy( + old_values["dyn_cstring_size_idx"]) + self.dyn_cxxstring_size_idx = copy.copy( + old_values["dyn_cxxstring_size_idx"]) + self.dyn_wstring_size_idx = copy.copy( + old_values["dyn_wstring_size_idx"]) self.var_function_idx = copy.copy(old_values["var_function_idx"]) self.param_list = copy.copy(old_values["param_list"]) self.curr_func_log = copy.copy(old_values["curr_func_log"]) @@ -3859,7 +3974,9 @@ def __gen_target_function(self, call, func, param_id) -> bool: if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: gen_lines.append( " //declare the RECORD and call constructor\n") - func_call += " " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(" + func_call += " " + \ + class_name.replace( + "::(anonymous namespace)", "") + " futag_target" + "(" func_call += ",".join(param_list) func_call += ");\n" gen_lines.append(func_call) @@ -3966,7 +4083,8 @@ def __gen_target_function(self, call, func, param_id) -> bool: # GEN STRING SIZE this_gen_size = False if curr_param["param_usage"] in ["FILE_DESCRIPTOR"]: - curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + curr_name = "fd_" + curr_name + \ + str(self.file_idx) # string_prefix self.file_idx += 1 curr_gen = self.__gen_file_descriptor( curr_name, gen_type_info) @@ -4147,7 +4265,8 @@ def __gen_target_function(self, call, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -4181,7 +4300,8 @@ def __gen_target_function(self, call, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -4230,7 +4350,8 @@ def __gen_target_function(self, call, func, param_id) -> bool: self.param_list += [curr_name] curr_gen = self.__gen_var_function( curr_name, curr_return_func["function"]) - self.__add_header(self.__get_function_header(func["location"]["fullpath"])) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) self.__append_gen_dict(curr_gen) #!!!call recursive param_id += 1 @@ -4451,9 +4572,11 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par error_path = dir.as_posix() + "/" + target_src.stem + ".err" generated_targets += 1 if self.target_type == LIBFUZZER: - compiler_cmd = [compiler_path.as_posix()] + compiler_flags_libFuzzer.split(" ") + current_include + ["-I" + x for x in extra_include.split(" ") if x.strip() ] + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_libFuzzer.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") else: - compiler_cmd = [compiler_path.as_posix()] + compiler_flags_aflplusplus.split(" ") + current_include + ["-I" + x for x in extra_include.split(" ") if x.strip() ] + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_aflplusplus.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") compile_cmd_list.append({ "compiler_cmd": compiler_cmd, @@ -4488,7 +4611,7 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par # shutil.move(dir.parents[0].as_posix( # ), (self.succeeded_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) copy_tree(dir.parents[0].as_posix( - ), (self.succeeded_path / dir.parents[1].name).as_posix()) + ), (self.succeeded_path / dir.parents[1].name / dir.parents[0].name).as_posix()) if keep_failed: failed_tree = set() @@ -4502,14 +4625,14 @@ def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_par if target not in failed_tree: failed_tree.add(target) for dir in failed_tree: - if dir.parents[0] not in succeeded_dir : + if dir.parents[0] not in succeeded_dir: if not (self.failed_path / dir.parents[1].name).exists(): ((self.failed_path / - dir.parents[1].name)).mkdir(parents=True, exist_ok=True) + dir.parents[1].name)).mkdir(parents=True, exist_ok=True) # shutil.move(dir.parents[0].as_posix( # ), (self.failed_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) copy_tree(dir.parents[0].as_posix( - ), (self.failed_path / dir.parents[1].name).as_posix()) + ), (self.failed_path / dir.parents[1].name / dir.parents[0].name).as_posix()) else: delete_folder(self.failed_path) @@ -4630,13 +4753,16 @@ def __gen_context_wrapper(self, func): else: f.write(AFLPLUSPLUS_PREFIX) - buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + " + " + str(self.file_idx) + buffer_check = str(self.dyn_wstring_size_idx) + "*sizeof(wchar_t) + " + str(self.dyn_cxxstring_size_idx) + \ + "*sizeof(char) + " + str(self.dyn_cstring_size_idx) + \ + " + " + str(self.file_idx) if self.buffer_size: buffer_check += " + " + " + ".join(self.buffer_size) f.write(" if (Fuzz_Size < " + buffer_check + ") return 0;\n") if self.dyn_cstring_size_idx > 0: - f.write(" size_t dyn_cstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cstring_size[" + str(self.dyn_cstring_size_idx) + "];\n") @@ -4644,7 +4770,8 @@ def __gen_context_wrapper(self, func): f.write(" srand(time(NULL));\n") f.write( " if(dyn_cstring_buffer == 0) dyn_cstring_size[0] = dyn_cstring_buffer; \n") - f.write(" else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") + f.write( + " else dyn_cstring_size[0] = rand() % dyn_cstring_buffer; \n") f.write(" size_t remain = dyn_cstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cstring_size_idx) + " - 1; i++){\n") @@ -4662,7 +4789,8 @@ def __gen_context_wrapper(self, func): " //end of generation random array of dynamic string sizes\n") if self.dyn_wstring_size_idx > 0: - f.write(" size_t dyn_wstring_buffer = (size_t) ((Fuzz_Size + sizeof(wchar_t) - (" + buffer_check +" )))/sizeof(wchar_t);\n") + f.write(" size_t dyn_wstring_buffer = (size_t) ((Fuzz_Size + sizeof(wchar_t) - (" + + buffer_check + " )))/sizeof(wchar_t);\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_wstring_size[" + str(self.dyn_wstring_size_idx) + "];\n") @@ -4670,7 +4798,8 @@ def __gen_context_wrapper(self, func): f.write(" srand(time(NULL));\n") f.write( " if(dyn_wstring_buffer == 0) dyn_wstring_size[0] = dyn_wstring_buffer; \n") - f.write(" else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") + f.write( + " else dyn_wstring_size[0] = rand() % dyn_wstring_buffer; \n") f.write(" size_t remain = dyn_wstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_wstring_size_idx) + " - 1; i++){\n") @@ -4686,9 +4815,10 @@ def __gen_context_wrapper(self, func): f.write(" dyn_wstring_size[0] = dyn_wstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") - + if self.dyn_cxxstring_size_idx > 0: - f.write(" size_t dyn_cxxstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check +" )));\n") + f.write( + " size_t dyn_cxxstring_buffer = (size_t) ((Fuzz_Size + sizeof(char) - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic string sizes\n") f.write(" size_t dyn_cxxstring_size[" + str(self.dyn_cxxstring_size_idx) + "];\n") @@ -4696,7 +4826,8 @@ def __gen_context_wrapper(self, func): f.write(" srand(time(NULL));\n") f.write( " if(dyn_cxxstring_buffer == 0) dyn_cxxstring_size[0] = dyn_cxxstring_buffer; \n") - f.write(" else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") + f.write( + " else dyn_cxxstring_size[0] = rand() % dyn_cxxstring_buffer; \n") f.write(" size_t remain = dyn_cxxstring_size[0];\n") f.write(" for(size_t i = 1; i< " + str(self.dyn_cxxstring_size_idx) + " - 1; i++){\n") @@ -4712,9 +4843,10 @@ def __gen_context_wrapper(self, func): f.write(" dyn_cxxstring_size[0] = dyn_cxxstring_buffer;\n") f.write( " //end of generation random array of dynamic string sizes\n") - + if self.file_idx > 0: - f.write(" size_t file_buffer = (size_t) ((Fuzz_Size + "+ str(self.file_idx) + " - (" + buffer_check +" )));\n") + f.write(" size_t file_buffer = (size_t) ((Fuzz_Size + " + + str(self.file_idx) + " - (" + buffer_check + " )));\n") f.write(" //generate random array of dynamic file sizes\n") f.write(" size_t file_size[" + str(self.file_idx) + "];\n") @@ -4781,3 +4913,2557 @@ def gen_context(self, max_wrappers: int = 10): break self.__gen_context_wrapper(func) + + +class NatchGenerator: + """Futag Generator for Natch""" + + def __init__(self, futag_llvm_package: str, library_root: str, json_file: str, target_type: int = LIBFUZZER, output_path=FUZZ_DRIVER_PATH, build_path=BUILD_PATH, install_path=INSTALL_PATH): + """ Constructor of Generator class. + + Args: + futag_llvm_package (str): path to the futag-llvm package (with binaries, scripts, etc.). + library_root (str): path to the library root. + json_file (str, optional): path to the JSON file from Natch. + target_type (int, optional): format of fuzz-drivers (LIBFUZZER or AFLPLUSPLUS). Defaults to LIBFUZZER. + output_path (_type_, optional): where to save fuzz-drivers, if this path exists, Futag will delete it and create new one. Defaults to FUZZ_DRIVER_PATH. + build_path (_type_, optional): path to the build directory. Defaults to BUILD_PATH. + install_path (_type_, optional): path to the install directory. Defaults to INSTALL_PATH. + + Raises: + ValueError: INVALID_TARGET_TYPE: Invalid the type of target. + ValueError: INVALID_FUTAG_PATH: Invalid path of futag-llvm. + ValueError: INVALID_LIBPATH: Invalid path of library. + ValueError: INVALID_ANALYSIS_FILE: Invalid path to analysis result file. + ValueError: INVALID_BUILPATH: Invalid path to the library build path. + ValueError: INVALID_INSTALLPATH: Invalid path to the library install path. + """ + + self.output_path = None # Path for saving fuzzing drivers + self.tmp_output_path = None # Path for saving fuzzing drivers + self.json_file = json_file + self.futag_llvm_package = futag_llvm_package + self.library_root = library_root + self.target_library = None + self.header = [] + + self.gen_anonymous = False + self.max_wrappers = 10 + self.gen_this_function = True + self.gen_lines = [] + self.buffer_size = [] + self.gen_free = [] + self.dyn_cstring_size_idx = 0 + self.dyn_cxxstring_size_idx = 0 + self.dyn_wstring_size_idx = 0 + self.file_idx = 0 + self.curr_function = None + self.curr_func_log = "" + self.curr_gen_string = -1 + self.param_list = [] + self.var_function_idx = 0 + + # save the list of generated function for debugging + self.target_extension = "" + self.result_report = {} + + if (target_type > 1 or target_type < 0): + sys.exit(INVALID_TARGET_TYPE) + + self.target_type = target_type + + if pathlib.Path(self.futag_llvm_package).exists(): + self.futag_llvm_package = pathlib.Path( + self.futag_llvm_package).absolute() + else: + sys.exit(INVALID_FUTAG_PATH) + + if self.target_type == LIBFUZZER: + if not pathlib.Path(self.futag_llvm_package / "bin/clang").exists(): + sys.exit(INVALID_FUTAG_PATH) + else: + if not pathlib.Path(self.futag_llvm_package / "AFLplusplus/usr/local/bin/afl-clang-fast").exists(): + sys.exit(INVALID_FUTAG_PATH) + + if pathlib.Path(self.library_root).exists(): + self.library_root = pathlib.Path(self.library_root).absolute() + else: + sys.exit(INVALID_LIBPATH) + + if not pathlib.Path(json_file).exists(): + sys.exit(INVALID_NATCH_JSON) + else: + self.json_file = pathlib.Path(json_file).absolute() + + if (self.library_root / ANALYSIS_FILE_PATH).exists(): + f = open((self.library_root / ANALYSIS_FILE_PATH).as_posix()) + self.target_library = json.load(f) + tmp_output_path = "." + output_path + # create directory for function targets if not exists + # TODO: set option for deleting + if (self.library_root / output_path).exists(): + delete_folder(self.library_root / output_path) + if (self.library_root / tmp_output_path).exists(): + delete_folder(self.library_root / tmp_output_path) + + simple_functions = [] + if self.target_library["functions"]: + for f_iter in self.target_library["functions"]: + if f_iter["is_simple"]: + simple_functions.append(f_iter) + self.simple_functions = simple_functions + (self.library_root / output_path).mkdir(parents=True, exist_ok=True) + (self.library_root / tmp_output_path).mkdir(parents=True, exist_ok=True) + self.output_path = (self.library_root / output_path).absolute() + self.tmp_output_path = ( + self.library_root / tmp_output_path).absolute() + + succeeded_path = self.output_path / "succeeded" + if not succeeded_path.exists(): + (succeeded_path).mkdir(parents=True, exist_ok=True) + self.succeeded_path = succeeded_path + + failed_path = self.output_path / "failed" + if not failed_path.exists(): + (failed_path).mkdir(parents=True, exist_ok=True) + self.failed_path = failed_path + else: + sys.exit(INVALID_ANALYSIS_FILE) + + if not (self.library_root / build_path).exists(): + sys.exit(INVALID_BUILPATH) + self.build_path = self.library_root / build_path + + if not (self.library_root / install_path).exists(): + sys.exit(INVALID_INSTALLPATH) + self.install_path = self.library_root / install_path + + Natch_corpus_path = self.output_path / "Natch_corpus" + if not Natch_corpus_path.exists(): + (Natch_corpus_path).mkdir(parents=True, exist_ok=True) + self.Natch_corpus_path = Natch_corpus_path + + def parse_values(self): + print(self.Natch_corpus_path.as_posix()) + natch_values = json.load(open(self.json_file.as_posix())) + if not natch_values: + raise ValueError(COULD_NOT_PARSE_NATCH_CALLSTACK) + function_name_list = set() + target_functions = [] + for function in natch_values: + add_arg_list = False + if not function["Function name"] in function_name_list: + (self.Natch_corpus_path / + function["Function name"]).mkdir(parents=True, exist_ok=True) + function_name_list.add(function["Function name"]) + add_arg_list = True + + index = 0 + blob_name = "blob" + str(index) + while ((self.Natch_corpus_path / function["Function name"] / blob_name).exists()): + index += 1 + blob_name = "blob" + str(index) + arguments = [] + print("-- Parsing data of function " + function["Function name"]) + with open((self.Natch_corpus_path / function["Function name"] / blob_name).as_posix(), "wb") as f: + print(" [*] writing seed file: " + (self.Natch_corpus_path / + function["Function name"] / blob_name).as_posix() + "...") + for arg in function["Arguments"]: + arguments.append(arg["Type"]) + if (arg["Type"] in ["char *", "const char *", "unsigned char *", "const unsigned char *", "const char *&"]): + # print(len(arg["Value"])) + f.write((len(arg["Value"])).to_bytes( + 4, byteorder='big')) + f.write(arg["Value"].encode()) + else: + # f.write(arg["Value"]).to_bytes(arg["Size"], byteorder='big') + if (not type(arg["Value"]) is int): + f.write(int(arg["Value"]).to_bytes( + 8, byteorder='big')) + else: + f.write(arg["Value"].to_bytes(8, byteorder='big')) + if add_arg_list: + target_functions.append( + { + "name": function["Function name"], + "args": arguments + }) + self.target_functions = target_functions + + def __get_compile_command(self, file): + """ Get the compile command of given file + + Args: + file (FILE): given file + + Returns: + dict(compiler, command, file, location): dict consists of compiler type, compile command, file name, and file location. + """ + if (self.build_path / "compile_commands.json").exists(): + compile_commands = self.build_path / "compile_commands.json" + commands = json.load(open(compile_commands.as_posix())) + for command in commands: + if pathlib.Path(command["file"]) == pathlib.Path(file): + compiler = command["command"].split(" ")[0].split("/")[-1] + if compiler == "cc" or compiler == "clang" or compiler == "gcc": + return { + "compiler": "CC", + "command": command["command"], + "file": command["file"], + "location": command["directory"] + } + else: + return { + "compiler": "CXX", + "command": command["command"], + "file": command["file"], + "location": command["directory"] + } + return { + "compiler": "CXX", + "command": command["command"], + "file": command["file"], + "location": command["directory"] + } + else: + if file.split(".")[-1] == "c": + return { + "compiler": "CC", + "command": "", + "file": file, + "location": "" + } + else: + return { + "compiler": "CXX", + "command": "", + "file": file, + "location": "" + } + return { + "compiler": "CC", + "command": "", + "file": file, + "location": "" + } + + def __gen_header(self, target_function_name): + """ Generate header for the target function + + Args: + target_function_name (string): the target function name. + + Returns: + list: list of included header. + """ + + defaults = ["stdio.h", "stddef.h", "time.h", + "stdlib.h", "string.h", "stdint.h"] + compiled_files = self.target_library["compiled_files"] + included_headers = [] + found = False + for f in compiled_files: + if f["filename"] == target_function_name: + found = True + for header in f["headers"]: + if not header[1:-1] in defaults: + included_headers.append(header) + break + if not found: + short_filename = target_function_name.split('/')[-1] + for f in compiled_files: + if f["filename"].split('/')[-1] == short_filename: + found = True + for header in f["headers"]: + if not header[1:-1] in defaults: + included_headers.append(header) + break + include_lines = [] + for i in defaults: + include_lines.append("#include <" + i + ">\n") + for i in included_headers: + include_lines.append("#include " + i + "\n") + if self.header: + for i in self.header: + if i not in included_headers: + include_lines.append("#include " + i + "\n") + return include_lines + + def __get_function_header(self, func_location): + """ Generate header for the target function + + Args: + func_location (string): function location. + + Returns: + list: list of included header. + """ + defaults = ["stdio.h", "stddef.h", "time.h", + "stdlib.h", "string.h", "stdint.h"] + compiled_files = self.target_library["compiled_files"] + included_headers = [] + found = False + for f in compiled_files: + if f["filename"] == func_location: + found = True + for header in f["headers"]: + if not header[1:-1] in defaults: + included_headers.append(header) + break + if not found: + short_filename = func_location.split('/')[-1] + for f in compiled_files: + if f["filename"].split('/')[-1] == short_filename: + found = True + for header in f["headers"]: + if not header[1:-1] in defaults: + included_headers.append(header) + break + return included_headers + + def __add_header(self, function_headers): + for h in function_headers: + if h not in self.header: + self.header.append(h) + + def __gen_builtin(self, param_name, gen_type_info): + """Declare and assign value for a builtin type + + Args: + param_name (str): parameter's name + gen_type_info (dict): information of parameter's type + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + return { + "gen_lines": [ + "//GEN_BUILTIN\n", + "if (Fuzz_Size_remain < sizeof(" + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + ") return 0;\n", + "Fuzz_Size_remain = Fuzz_Size_remain - sizeof(" + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + ");\n", + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + " " + param_name + ";\n", + "memcpy(&"+param_name+", futag_pos, sizeof(" + + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + "));\n", + "futag_pos += sizeof(" + gen_type_info["type_name"].replace( + "(anonymous namespace)::", "") + ");\n" + ], + "gen_free": [], + "buffer_size": ["sizeof(" + gen_type_info["type_name"].replace("(anonymous namespace)::", "")+")"] + } + + def __gen_strsize(self, param_name, param_type, dyn_size_idx, array_name): + return { + "gen_lines": [ + "//GEN_SIZE\n", + param_type + " " + param_name + + " = (" + param_type + + ") " + array_name + "[" + str(dyn_size_idx - 1) + "];\n", + ], + "gen_free": [], + "buffer_size": [] + } + + def __gen_cstring(self, param_name, gen_type_info, dyn_cstring_size_idx): + """Declare and assign value for a C string type + + Args: + param_name (str): parameter's name + gen_type_info (dict): information of parameter's type + dyn_cstring_size_idx (int): id of dynamic cstring size + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + ref_name = param_name + if (gen_type_info["local_qualifier"]): + ref_name = "r" + ref_name + + gen_lines = [ + "//GEN_CSTRING1\n", + + "//Read length of str\n", + "if (Fuzz_Size_remain < sizeof(int)) return 0;\n", + "Fuzz_Size_remain -= sizeof(int);\n", + "int futag_str_size;\n" + "memcpy(&futag_str_size, futag_pos, sizeof(int));\n", + "futag_pos += sizeof(int);\n", + "if (Fuzz_Size_remain < (size_t)futag_str_size) return 0;\n", + "Fuzz_Size_remain = Fuzz_Size_remain - (size_t)futag_str_size;\n", + + gen_type_info["base_type_name"] + " " + ref_name + + " = (" + gen_type_info["base_type_name"] + + ") malloc((futag_str_size + 1)* sizeof(char));\n", + "memset(" + ref_name + + ", 0, futag_str_size + 1);\n", + "memcpy(" + ref_name + + ", futag_pos, futag_str_size);\n", + "futag_pos += futag_str_size;\n", + ] + if (gen_type_info["local_qualifier"]): + gen_lines += [gen_type_info["type_name"] + + " " + param_name + " = " + ref_name + ";\n"] + + return { + "gen_lines": gen_lines, + "gen_free": [ + "if (" + ref_name + ") {\n", + " free(" + ref_name + ");\n", + " " + ref_name + " = NULL;\n", + "}\n" + ], + "buffer_size": [] + } + + def __gen_wstring(self, param_name, gen_type_info, dyn_wstring_size_idx): + """Declare and assign value for a C string type + + Args: + param_name (str): parameter's name + gen_type_info (dict): information of parameter's type + dyn_wstring_size_idx (int): id of dynamic wstring size + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + ref_name = param_name + if (gen_type_info["local_qualifier"]): + ref_name = "r" + ref_name + + gen_lines = [ + "//GEN_WSTRING\n", + "//Read length of str\n", + "if (Fuzz_Size_remain < sizeof(int)) return 0;\n", + "Fuzz_Size_remain -= sizeof(int);\n", + "int futag_str_size;\n" + "memcpy(&futag_str_size, futag_pos, sizeof(int));\n", + "futag_pos += sizeof(int);\n", + "if (Fuzz_Size_remain < (size_t)futag_str_size) return 0;\n", + "Fuzz_Size_remain = Fuzz_Size_remain - (size_t)futag_str_size;\n", + + gen_type_info["base_type_name"] + " " + ref_name + + " = (" + gen_type_info["base_type_name"] + + ") malloc(futag_str_size + sizeof(wchar_t));\n", + "memset(" + ref_name + + ", 0, (int)((futag_str_size + sizeof(wchar_t))/4));\n", + "memcpy(" + ref_name + ", futag_pos, futag_str_size);\n", + "futag_pos += futag_str_size;\n", + ] + if (gen_type_info["local_qualifier"]): + gen_lines += [gen_type_info["type_name"] + + " " + param_name + " = " + ref_name + ";\n"] + + return { + "gen_lines": gen_lines, + "gen_free": [ + "if (" + ref_name + ") {\n", + " free(" + ref_name + ");\n", + " " + ref_name + " = NULL;\n", + "}\n" + ], + "buffer_size": [] + } + + def __gen_cxxstring(self, param_name, gen_type_info, dyn_cxxstring_size_idx): + """Declare and assign value for a C++ string type + + Args: + param_name (str): parameter's name + gen_type_info (dict): information of parameter's type for initializing + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + ref_name = param_name + if (gen_type_info["local_qualifier"]): + ref_name = "r" + ref_name + gen_lines = [ + "//GEN_CXXSTRING\n", + ] + if (gen_type_info["local_qualifier"]): + gen_lines += [gen_type_info["type_name"] + + " " + param_name + " = " + ref_name + ";\n"] + + return { + "gen_lines": [ + "//Read length of str\n", + "if (Fuzz_Size_remain < sizeof(int)) return 0;\n", + "Fuzz_Size_remain -= sizeof(int);\n", + "int futag_str_size;\n", + "memcpy(&futag_str_size, futag_pos, sizeof(int));\n", + "futag_pos += sizeof(int);\n", + "if (Fuzz_Size_remain < (size_t)futag_str_size) return 0;\n", + "Fuzz_Size_remain = Fuzz_Size_remain - (size_t)futag_str_size;\n", + + gen_type_info["type_name"] + " " + param_name + + "(futag_pos, futag_str_size); \n", + "futag_pos += futag_str_size;\n", + ], + "gen_free": [], + "buffer_size": [] + } + + def __gen_enum(self, enum_record, param_name, gen_type_info, compiler_info, anonymous: bool = False): + + if anonymous: + enum_name = enum_record["name"] + else: + enum_name = enum_record["qname"] + + enum_length = len(enum_record["enum_values"]) + enum_name = gen_type_info["type_name"] + if compiler_info["compiler"] == "CC": + return { + "gen_lines": [ + "//GEN_ENUM\n", + "unsigned int " + param_name + "_enum_index; \n", + "memcpy(&" + param_name + + "_enum_index, futag_pos, sizeof(unsigned int));\n", + enum_name + " " + param_name + " = " + + param_name + "_enum_index % " + + str(enum_length) + ";\n" + ], + "gen_free": [], + "buffer_size": ["sizeof(unsigned int)"] + } + else: + return { + "gen_lines": [ + "//GEN_ENUM\n", + "unsigned int " + param_name + "_enum_index; \n", + "memcpy(&" + param_name + + "_enum_index, futag_pos, sizeof(unsigned int));\n", + # "enum " + enum_name + " " + param_name + " = static_cast(" + param_name + "_enum_index % " + str(enum_length) + ");\n" + ], + "gen_free": [], + "buffer_size": ["sizeof(unsigned int)"] + } + + def __gen_array(self, param_name, gen_type_info): + return { + "gen_lines": [ + "//GEN_ARRAY\n", + gen_type_info["type_name"] + " " + param_name + " = (" + gen_type_info["type_name"] + ") " + + "malloc(sizeof(" + gen_type_info["base_type_name"] + + ") * " + str(gen_type_info["length"]) + ");\n", + "memcpy(" + param_name + ", futag_pos, " + str( + gen_type_info["length"]) + " * sizeof(" + gen_type_info["base_type_name"] + "));\n", + "futag_pos += " + + str(gen_type_info["length"]) + " * sizeof(" + + gen_type_info["base_type_name"] + ");\n" + ], + "gen_free": [ + "if (" + param_name + ") {\n", + " free( " + param_name + ");\n", + " " + param_name + " = NULL;\n", + "}\n" + ], + "buffer_size": [str(gen_type_info["length"]) + " * sizeof(" + gen_type_info["base_type_name"] + ")"] + } + + def __gen_void(self, param_name): + return { + "gen_lines": [ + "//GEN_VOID\n", + "const char *" + param_name + "= NULL; \n", + ], + "gen_free": [], + "buffer_size": [] + } + + def __gen_qualifier(self, param_name, prev_param_name, gen_type_info): + return { + "gen_lines": [ + "//GEN_QUALIFIED\n", + gen_type_info["type_name"] + " " + + param_name + " = " + prev_param_name + ";\n" + ], + "gen_free": [], + "buffer_size": [] + } + + def __gen_pointer(self, param_name, prev_param_name, gen_type_info): + return { + "gen_lines": [ + "//GEN_POINTER\n", + gen_type_info["type_name"].replace("(anonymous namespace)::", "") + " " + param_name + + " = & " + prev_param_name + ";\n" + ], + "gen_free": [], + "buffer_size": [] + } + + def __gen_struct(self, struct_name, struct, gen_info): + gen_lines = [gen_info["type_name"] + " " + struct_name + ";\n"] + gen_free = [] + buffer_size = [] + field_id = 0 + + for field in struct["fields"]: + curr_name = field["field_name"] + for gen_type_info in field["gen_list"]: + if gen_type_info["gen_type"] == GEN_BUILTIN: + this_gen_size = False + # if field_id > 0 and (struct["fields"][field_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING]): + # if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: + # dyn_size_idx = 0 + # array_name = "" + # if struct["fields"][field_id - 1]["gen_list"][0]["gen_type"] == GEN_CSTRING: + # dyn_size_idx = self.dyn_cstring_size_idx + # array_name = "dyn_cstring_size" + # elif struct["fields"][field_id - 1]["gen_list"][0]["gen_type"] == GEN_WSTRING: + # dyn_size_idx = self.dyn_wstring_size_idx + # array_name = "dyn_wstring_size" + # else: + # dyn_size_idx = self.dyn_cxxstring_size_idx + # array_name = "dyn_cxxstring_size" + # curr_name = "sz_" + curr_name # size_prefix + # curr_gen = self.__gen_strsize( + # curr_name, gen_type_info["type_name"], dyn_size_idx, array_name) + # buffer_size += curr_gen["buffer_size"] + # gen_lines += curr_gen["gen_lines"] + # gen_free += curr_gen["gen_free"] + # this_gen_size = True # with break, we may not need this variable :) + # break + + if not this_gen_size: + curr_name = "b_" + curr_name # builtin_prefix + curr_gen = self.__gen_builtin(curr_name, gen_type_info) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_CSTRING: + curr_name = "strc_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_REFSTRING: + curr_name = "strc_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + # reinit value of curr_name to send reference of string + # curr_name = "&" + curr_name # string_prefix + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_WSTRING: + curr_name = "strc_" + curr_name # string_prefix + self.dyn_wstring_size_idx += 1 + curr_gen = self.__gen_wstring( + curr_name, gen_type_info, self.dyn_wstring_size_idx) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_CXXSTRING: + curr_name = "strcxx_" + curr_name # string_prefix + self.dyn_cxxstring_size_idx += 1 + curr_gen = self.__gen_cxxstring( + curr_name, gen_type_info, self.dyn_cxxstring_size_idx) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_ENUM: # GEN_ENUM + curr_name = "e_" + curr_name # enum_prefix + found_enum = None + # search in enum list of analysis result: + for enum in self.target_library["enums"]: + if len(gen_type_info["type_name"].split(" ")) > 1: + if enum["qname"] == gen_type_info["type_name"].split(" ")[ + 1]: + found_enum = enum + break + else: + if enum["qname"] == gen_type_info["type_name"]: + found_enum = enum + break + if not found_enum: + # search in typedef list of analysis result: + for typedef in self.target_library["typedefs"]: + if typedef["name"] == gen_type_info["type_name"]: + enum_hash = typedef["type_source_hash"] + for enum in self.target_library["enums"]: + if enum["hash"] == enum_hash: + found_enum = enum + break + if not found_enum: + self.curr_func_log += f"- Can not generate for object: {str(gen_type_info)}\n" + self.gen_this_function = False + else: + compiler_info = self.__get_compile_command( + self.curr_function["location"]["fullpath"]) + curr_gen = self.__gen_enum( + found_enum, curr_name, gen_type_info, compiler_info, self.gen_anonymous) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_ARRAY: # GEN_ARRAY + curr_name = "a_" + curr_name # array_prefix + curr_gen = self.__gen_array(curr_name, gen_type_info) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_QUALIFIER: + curr_name = "q_" + curr_name # qualifier_prefix + curr_gen = self.__gen_qualifier( + curr_name, prev_param_name, gen_type_info) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_POINTER: + curr_name = "p_" + curr_name # pointer_prefix + curr_gen = self.__gen_pointer( + curr_name, prev_param_name, gen_type_info) + buffer_size += curr_gen["buffer_size"] + gen_lines += curr_gen["gen_lines"] + gen_free += curr_gen["gen_free"] + + prev_param_name = curr_name + gen_lines += [struct_name + "." + + field["field_name"] + " = " + curr_name + ";\n"] + field_id += 1 + + return { + "gen_lines": gen_lines, + "gen_free": gen_free, + "buffer_size": buffer_size + } + + def __gen_union(self, param_name, class_record, gen_type_info): + """Declare and assign value for a union type + + Args: + param_name (str): _description_ + gen_type_info (dict): information of parameter's type for initializing + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + return { + "gen_lines": [ + "//GEN_UNION\n", + gen_type_info["type_name"] + " " + param_name + ";\n", + "memcpy(&"+param_name+", futag_pos, sizeof(" + + gen_type_info["type_name"] + "));\n", + "futag_pos += sizeof(" + gen_type_info["type_name"] + ");\n" + ], + "gen_free": [], + "buffer_size": ["sizeof(" + gen_type_info["type_name"] + ")"] + } + + def __gen_class(self, param_name, class_record): + """Declare and assign value for a class type + + Args: + param_name (str): _description_ + gen_type_info (dict): information of parameter's type for initializing + + Returns: + dict: (gen_lines, gen_free, buffer_size) + """ + result = [] + constructors = [c for c in self.target_library["functions"] if c['parent_hash'] == class_record['hash'] + and c['is_simple'] and c["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]] + + # if class has default constructor, then return this constructor + for c in constructors: + if c['is_simple'] and c["func_type"] == FUNC_DEFAULT_CONSTRUCTOR: + result.append(self.__gen_var_function(param_name, c)) + + return result + + def __gen_input_file(self, param_name, gen_type_info): + cur_gen_free = [" " + x for x in self.gen_free] + if gen_type_info["gen_type"] == GEN_CSTRING: + line = "const char* " + param_name + \ + " = \"futag_input_file_" + str(self.file_idx - 1) + "\";\n" + elif gen_type_info["gen_type"] == GEN_WSTRING: + line = "const wchar_t * " + param_name + \ + " = L\"futag_input_file_" + str(self.file_idx - 1) + "\";\n" + else: + return { + "gen_lines": [], + "gen_free": [], + "buffer_size": [] + } + gen_lines = [ + "//GEN_INPUT_FILE\n", + line, + "FILE * fp_" + str(self.file_idx - 1) + + " = fopen(" + param_name + ",\"w\");\n", + "if (fp_" + str(self.file_idx - 1) + " == NULL) {\n", + ] + gen_lines += cur_gen_free + gen_lines += [ + " return 0;\n", + "}\n", + "fwrite(futag_pos, 1, file_size[" + str(self.file_idx - 1) + + "], fp_" + str(self.file_idx - 1) + ");\n", + "fclose(fp_" + str(self.file_idx - 1) + ");\n", + "futag_pos += file_size[" + str(self.file_idx - 1) + "];\n" + ] + return { + "gen_lines": gen_lines, + "gen_free": [], + "buffer_size": [] + } + + def __gen_file_descriptor(self, param_name, gen_type_info): + if not "" in self.header: + self.header += [""] + cur_gen_free = [" " + x for x in self.gen_free] + gen_lines = [ + "//GEN_FILE_DESCRIPTOR\n", + "const char* " + param_name + "_tmp" + str(self.file_idx) + " = \"futag_input_file_" + + str(self.file_idx - 1) + "\";\n", + "FILE * fp_" + str(self.file_idx - 1) + + " = fopen(" + param_name + "_tmp" + + str(self.file_idx) + ",\"w\");\n", + "if (fp_" + str(self.file_idx - 1) + " == NULL) {\n", + ] + gen_lines += cur_gen_free + gen_lines += [ + " return 0;\n", + "}\n", + "fwrite(futag_pos, 1, file_size[" + str(self.file_idx - 1) + + "], fp_" + str(self.file_idx - 1) + ");\n", + "fclose(fp_" + str(self.file_idx - 1) + ");\n", + "futag_pos += file_size[" + str(self.file_idx - 1) + "];\n", + gen_type_info["type_name"] + " " + param_name + + "= open(" + param_name + "_tmp" + + str(self.file_idx) + ", O_RDWR);\n" + ] + gen_free = ["close(" + param_name + ");\n"] + return { + "gen_lines": gen_lines, + "gen_free": gen_free, + "buffer_size": [] + } + + def __search_in_typedefs(self, type_name, typedefs): + # Are there multiple type definitions for the same data type??? + result = None + for td in typedefs: + if td["underlying_type"] == type_name: + return td + return result + + def __search_return_types(self, param_gen_list, curr_function, function_lists): + result = [] + for f in function_lists: + gen_list = [] + # To avoid infinite loop, we search only function with different name + if f["qname"] == curr_function["qname"]: + continue + # Search only simple function with the same return type + + compiler_info = self.__get_compile_command( + curr_function["location"]["fullpath"]) + compiler = compiler_info["compiler"] + + if f["gen_return_type"] and f["gen_return_type"][0]["type_name"] == param_gen_list[0]["type_name"] and f["is_simple"]: + if (compiler != "CXX" and f["storage_class"] != SC_STATIC) or (compiler == "CXX" and not f["access_type"] in [AS_PROTECTED, AS_PRIVATE]): + f_gen_list_length = len(f["gen_return_type"]) + param_gen_list_length = len(param_gen_list) + min_length = f_gen_list_length if f_gen_list_length < param_gen_list_length else param_gen_list_length + iter = 0 + while iter < min_length and f["gen_return_type"][iter]["type_name"] == param_gen_list[iter]["type_name"]: + iter += 1 + + last_iter = iter + while iter < f_gen_list_length: + curr_gen_field = f["gen_return_type"][iter] + if curr_gen_field["gen_type"] == GEN_POINTER: + # curr_gen_field["gen_type"] = GEN_VARADDR + curr_gen_field["gen_type_name"] = "_VAR_ADDRESS" + else: + curr_gen_field["gen_type"] == GEN_UNKNOWN + + gen_list.append(curr_gen_field) + iter += 1 + + iter = last_iter + while iter < param_gen_list_length: + gen_list.append(param_gen_list[iter]) + iter += 1 + + result.append({ + "function": f, + "gen_list": gen_list, + }) + return result + + def __append_gen_dict(self, curr_gen): + if curr_gen: + self.buffer_size += curr_gen["buffer_size"] + self.gen_lines += curr_gen["gen_lines"] + self.gen_free += curr_gen["gen_free"] + + def __gen_var_function(self, func_param_name: str, func): + """ Initialize for argument of function call """ + # curr_dyn_size = 0 + param_list = [] + curr_gen_string = -1 + gen_dict = { + "gen_lines": [], + "gen_free": [], + "buffer_size": [], + } + if not self.gen_anonymous and "(anonymous namespace)" in func["qname"]: + self.gen_this_function = False + return gen_dict + param_id = 0 + for arg in func["params"]: + if len(arg["gen_list"]) > 1: + curr_name = "_" + str(self.var_function_idx) + \ + "_" + arg["param_name"] + else: + curr_name = curr_name = str( + self.var_function_idx) + "_" + arg["param_name"] + prev_param_name = curr_name + for gen_type_info in arg["gen_list"]: + if gen_type_info["gen_type"] == GEN_BUILTIN: + this_gen_size = False + # if arg["param_usage"] in ["FILE_DESCRIPTOR"]: + # curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + # self.file_idx += 1 + # curr_gen = self.__gen_file_descriptor( + # curr_name, gen_type_info) + # gen_dict["buffer_size"] += curr_gen["buffer_size"] + # gen_dict["gen_lines"] += curr_gen["gen_lines"] + # gen_dict["gen_free"] += curr_gen["gen_free"] + # break + # elif param_id > 0 and (func["params"][param_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING] or arg["param_usage"] == "SIZE_FIELD"): + # if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: + # dyn_size_idx = 0 + # array_name = "" + # if func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_CSTRING: + # dyn_size_idx = self.dyn_cstring_size_idx + # array_name = "dyn_cstring_size" + # elif func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_WSTRING: + # dyn_size_idx = self.dyn_wstring_size_idx + # array_name = "dyn_wstring_size" + # else: + # dyn_size_idx = self.dyn_cxxstring_size_idx + # array_name = "dyn_cxxstring_size" + # curr_name = "sz_" + curr_name # size_prefix + # curr_gen = self.__gen_strsize( + # curr_name, arg["param_type"], dyn_size_idx, array_name) + # gen_dict["buffer_size"] += curr_gen["buffer_size"] + # gen_dict["gen_lines"] += curr_gen["gen_lines"] + # gen_dict["gen_free"] += curr_gen["gen_free"] + # this_gen_size = True # with break, we may not need this variable :) + # break + if not this_gen_size: + curr_name = "b_" + curr_name # builtin_prefix + curr_gen = self.__gen_builtin(curr_name, gen_type_info) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_CSTRING: + # GEN FILE NAME OR # GEN STRING + if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + curr_name = "str_" + curr_name + \ + str(self.dyn_cstring_size_idx) # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_REFSTRING: + # GEN FILE NAME OR # GEN STRING + if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + curr_name = "str_" + curr_name + \ + str(self.dyn_cstring_size_idx) # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + # curr_name = "&" + curr_name + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_WSTRING: + # GEN FILE NAME OR # GEN STRING + if (arg["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or arg["param_name"] in ["filename", "file", "filepath"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1) and len(arg["gen_list"]) == 1: + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + curr_name = "str_" + curr_name + \ + str(self.dyn_wstring_size_idx) # string_prefix + self.dyn_wstring_size_idx += 1 + curr_gen = self.__gen_wstring( + curr_name, gen_type_info, self.dyn_wstring_size_idx) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_CXXSTRING: + + if (arg["param_name"] in ["filename", "file", "filepath", "path"] or arg["param_name"].find('file') != -1 or arg["param_name"].find('File') != -1 or arg["param_name"].find('path') != -1) and len(arg["gen_list"]) == 1: + curr_name = "f_" + curr_name + \ + str(self.file_idx) # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + + curr_name = "str_" + curr_name + \ + str(self.dyn_cxxstring_size_idx) # string_prefix + self.dyn_cxxstring_size_idx += 1 + curr_gen = self.__gen_cxxstring( + curr_name, gen_type_info, self.dyn_cxxstring_size_idx) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_ENUM: # GEN_ENUM + + curr_name = "e_" + curr_name # enum_prefix + found_enum = None + # search in enum list of analysis result: + for enum in self.target_library["enums"]: + if len(gen_type_info["type_name"].split(" ")) > 1: + if enum["qname"] == gen_type_info["type_name"].split(" ")[ + 1]: + found_enum = enum + break + else: + if enum["qname"] == gen_type_info["type_name"]: + found_enum = enum + break + if not found_enum: + # search in typedef list of analysis result: + for typedef in self.target_library["typedefs"]: + if typedef["name"] == gen_type_info["type_name"]: + enum_hash = typedef["type_source_hash"] + for enum in self.target_library["enums"]: + if enum["hash"] == enum_hash: + found_enum = enum + break + if not found_enum: + self.curr_func_log += f"- Can not generate for enum: {str(gen_type_info)}\n" + self.gen_this_function = False + else: + compiler_info = self.__get_compile_command( + func["location"]["fullpath"]) + curr_gen = self.__gen_enum( + found_enum, curr_name, gen_type_info, compiler_info, self.gen_anonymous) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_ARRAY: # GEN_ARRAY + curr_name = "a_" + curr_name # array_prefix + curr_gen = self.__gen_array(curr_name, gen_type_info) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_VOID: + curr_name = "a_" + curr_name # void_prefix + self.curr_func_log += f"- Can not generate for object of void type: {str(gen_type_info)}\n" + self.gen_this_function = False + + if gen_type_info["gen_type"] == GEN_QUALIFIER: + curr_name = "q_" + curr_name # qualifier_prefix + curr_gen = self.__gen_qualifier( + curr_name, prev_param_name, gen_type_info) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + + if gen_type_info["gen_type"] == GEN_POINTER: + curr_name = "p_" + curr_name # qualifier_prefix + curr_gen = self.__gen_pointer( + curr_name, prev_param_name, gen_type_info) + gen_dict["buffer_size"] += curr_gen["buffer_size"] + gen_dict["gen_lines"] += curr_gen["gen_lines"] + gen_dict["gen_free"] += curr_gen["gen_free"] + prev_param_name = curr_name + + param_id += 1 + param_list.append(curr_name) + + found_parent = None + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + # Find parent class + for r in self.target_library["records"]: + if r["hash"] == func["parent_hash"]: + found_parent = r + break + if not found_parent: + self.gen_this_function = False + class_name = found_parent["qname"] + if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + function_call = " //declare the RECORD and call constructor\n" + function_call += " " + class_name.replace( + "::(anonymous namespace)", "") + func_param_name + "(" + ",".join(param_list)+");\n" + else: + # Find default constructor + # TODO: add code for other constructors + found_default_constructor = False + for fu in self.target_library["functions"]: + if fu["parent_hash"] == func["parent_hash"] and fu["func_type"] == FUNC_DEFAULT_CONSTRUCTOR: + found_default_constructor = True + + # TODO: add code for other constructors!!! + if not found_default_constructor: + self.gen_this_function = False + function_call = " //declare the RECORD first\n" + function_call += " " + \ + class_name.replace( + "::(anonymous namespace)", "") + " " + func_param_name + ";\n" + # call the method + function_call += " //METHOD CALL\n" + function_call += " " + func_param_name + "." + \ + func["name"]+"(" + ",".join(param_list)+");\n" + + else: + function_call = "//GEN_VAR_FUNCTION\n " + func["return_type"] + " " + func_param_name + \ + " = " + func["qname"] + \ + "(" + ",".join(param_list)+");\n" + + gen_dict["gen_lines"] += [function_call] + return gen_dict + + def __wrapper_file(self, func): + + # if anonymous: + # filename = func["name"] + # filepath = self.tmp_output_path / "anonymous" + # else: + filename = func["qname"].replace(":", "_") + filepath = self.tmp_output_path + + self.target_extension = func["location"]["fullpath"].split(".")[-1] + file_index = 1 + + # qname = func["qname"] + if len(filename) > 250: + return { + "file": None, + "msg": "Error: File name is too long (>250 characters)!" + } + dir_name = filename + str(file_index) + + if not (filepath / filename).exists(): + (filepath / filename).mkdir(parents=True, exist_ok=True) + + # Each variant of fuzz-driver will be save in separated directory + # inside the directory of function + + while (filepath / filename / dir_name).exists(): + file_index += 1 + dir_name = filename + str(file_index) + if file_index > self.max_wrappers: + break + + if file_index > self.max_wrappers: + return { + "file": None, + "msg": "Warning: exeeded maximum number of generated fuzzing-wrappers for each function!" + } + (filepath / filename / dir_name).mkdir(parents=True, exist_ok=True) + + file_name = filename + \ + str(file_index) + "." + self.target_extension + + full_path = (filepath / filename / dir_name / file_name).as_posix() + f = open(full_path, 'w') + if f.closed: + return { + "file": None, + "msg": "Error: File closed!" + } + return { + "file": f, + "msg": "Successed: " + full_path + " created!" + } + + def __anonymous_wrapper_file(self, func): + + # if anonymous: + # filename = func["name"] + # filepath = self.tmp_output_path / "anonymous" + # else: + source_path = func["location"]["fullpath"] + filename = "anonymous_" + func["name"].replace(":", "_") + filepath = self.tmp_output_path + + self.target_extension = func["location"]["fullpath"].split(".")[-1] + file_index = 1 + + # qname = func["qname"] + if len(filename) > 250: + return None + dir_name = filename + str(file_index) + + if not (filepath / filename).exists(): + (filepath / filename).mkdir(parents=True, exist_ok=True) + + # Each variant of fuzz-driver will be save in separated directory + # inside the directory of function + + while (filepath / filename / dir_name).exists(): + file_index += 1 + dir_name = filename + str(file_index) + if file_index > self.max_wrappers: + break + + if file_index > self.max_wrappers: + return None + (filepath / filename / dir_name).mkdir(parents=True, exist_ok=True) + + file_name = filename + \ + str(file_index) + "." + self.target_extension + + full_path_destination = ( + filepath / filename / dir_name / file_name).as_posix() + with open(source_path, 'r') as s: + source_file = s.read() + d = open(full_path_destination, "w") + d.write("//"+func["hash"] + "\n") + d.write(source_file) + d.close() + f = open(full_path_destination, 'a') + if f.closed: + return None + return f + + def __log_file(self, func, anonymous: bool = False): + if anonymous: + filename = func["name"] + filepath = self.tmp_output_path / "anonymous" + else: + filename = func["qname"] + filepath = self.tmp_output_path + + file_index = 1 + + # qname = func["qname"] + if len(filename) > 250: + return None + dir_name = filename + str(file_index) + + if not (filepath / filename).exists(): + (filepath / filename).mkdir(parents=True, exist_ok=True) + + # Each variant of fuzz-driver will be save in separated directory + # inside the directory of function + + while (filepath / filename / dir_name).exists(): + file_index += 1 + dir_name = filename + str(file_index) + if file_index > self.max_wrappers: + break + + if file_index > self.max_wrappers: + return None + (filepath / filename / dir_name).mkdir(parents=True, exist_ok=True) + + file_name = filename + str(file_index) + ".log" + + full_path = (filepath / filename / dir_name / file_name).as_posix() + f = open(full_path, 'w') + if f.closed: + return None + return f + + def __save_old_values(self): + return { + "buffer_size": copy.copy(self.buffer_size), + "gen_lines": copy.copy(self.gen_lines), + "gen_free": copy.copy(self.gen_free), + "dyn_cstring_size_idx": copy.copy(self.dyn_cstring_size_idx), + "dyn_cxxstring_size_idx": copy.copy(self.dyn_cxxstring_size_idx), + "dyn_wstring_size_idx": copy.copy(self.dyn_wstring_size_idx), + "var_function_idx": copy.copy(self.var_function_idx), + "param_list": copy.copy(self.param_list), + "curr_func_log": copy.copy(self.curr_func_log), + "file_idx": copy.copy(self.file_idx), + "gen_this_function": copy.copy(self.gen_this_function), + "param_list": copy.copy(self.param_list) + } + + def __retrieve_old_values(self, old_values): + self.buffer_size = copy.copy(old_values["buffer_size"]) + self.gen_lines = copy.copy(old_values["gen_lines"]) + self.gen_free = copy.copy(old_values["gen_free"]) + self.dyn_cstring_size_idx = copy.copy( + old_values["dyn_cstring_size_idx"]) + self.dyn_cxxstring_size_idx = copy.copy( + old_values["dyn_cxxstring_size_idx"]) + self.dyn_wstring_size_idx = copy.copy( + old_values["dyn_wstring_size_idx"]) + self.var_function_idx = copy.copy(old_values["var_function_idx"]) + self.param_list = copy.copy(old_values["param_list"]) + self.curr_func_log = copy.copy(old_values["curr_func_log"]) + self.file_idx = copy.copy(old_values["file_idx"]) + self.gen_this_function = copy.copy(old_values["gen_this_function"]) + self.param_list = copy.copy(old_values["param_list"]) + + def __gen_anonymous_function(self, func, param_id) -> bool: + malloc_free = [ + "unsigned char *", + "char *", + "wchar_t *" + ] + if param_id == len(func['params']): + found_parent = None + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + # Find parent class + for r in self.target_library["records"]: + if r["hash"] == func["parent_hash"]: + found_parent = r + break + if not found_parent: + self.gen_this_function = False + + # If there is no buffer - return! + if (not len(self.buffer_size) and not self.dyn_cstring_size_idx and not self.dyn_cxxstring_size_idx and not self.dyn_wstring_size_idx and not self.file_idx) or not self.gen_this_function: + log = self.__log_file(func, self.gen_anonymous) + if not log: + print(CANNOT_CREATE_LOG_FILE, func["qname"]) + else: + self.curr_func_log = f"Log for function: {func['qname']}\n{self.curr_func_log}" + log.write(self.curr_func_log) + log.close() + return False + # generate file name + f = self.__anonymous_wrapper_file(func) + if not f: + self.gen_this_function = False + print(CANNOT_CREATE_WRAPPER_FILE, func["qname"]) + return False + print(WRAPPER_FILE_CREATED, f.name) + + for line in self.__gen_header(func["location"]["fullpath"]): + f.write("// " + line) + f.write('\n') + compiler_info = self.__get_compile_command( + func["location"]["fullpath"]) + + if self.target_type == LIBFUZZER: + if compiler_info["compiler"] == "CC": + f.write(LIBFUZZER_PREFIX_C) + else: + f.write(LIBFUZZER_PREFIX_CXX) + else: + f.write(AFLPLUSPLUS_PREFIX) + + f.write(" size_t Fuzz_Size_remain = Fuzz_Size;\n") + f.write(" uint8_t * futag_pos = Fuzz_Data;\n") + for line in self.gen_lines: + f.write(" " + line) + + f.write(" //" + func["qname"]) + + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + class_name = found_parent["qname"] + if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + f.write(" //declare the RECORD and call constructor\n") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") + else: + # Find default constructor + # TODO: add code for other constructors + found_default_constructor = False + for fu in self.target_library["functions"]: + if fu["parent_hash"] == func["parent_hash"] and fu["func_type"] == FUNC_DEFAULT_CONSTRUCTOR: + found_default_constructor = True + + # TODO: add code for other constructors!!! + if not found_default_constructor: + self.gen_this_function = False + os.unlink(f.name) + f.close() + return False + f.write(" //declare the RECORD first\n") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target;\n") + # call the method + f.write(" //METHOD CALL\n") + f.write(" futag_target." + func["name"]+"(") + else: + f.write(" //FUNCTION_CALL\n") + if func["return_type"] in malloc_free: + f.write(" " + func["return_type"] + + " futag_target = " + func["qname"] + "(") + else: + f.write( + " " + func["qname"].replace("::(anonymous namespace)", "") + "(") + + param_list = [] + for arg in self.param_list: + param_list.append(arg + " ") + f.write(",".join(param_list)) + f.write(");\n") + # !attempting free on address which was not malloc()-ed + + if func["return_type"] in malloc_free: + f.write(" if(futag_target){\n") + f.write(" free(futag_target);\n") + f.write(" futag_target = NULL;\n") + f.write(" }\n") + + f.write(" //FREE\n") + for line in self.gen_free: + f.write(" " + line) + if self.target_type == LIBFUZZER: + f.write(LIBFUZZER_SUFFIX) + else: + f.write(AFLPLUSPLUS_SUFFIX) + f.close() + return True + + curr_param = func["params"][param_id] + if len(curr_param["gen_list"]) > 1: + curr_name = "_" + curr_param["param_name"] + else: + curr_name = curr_param["param_name"] + prev_param_name = curr_name + gen_curr_param = True + + curr_gen = {} + if len(curr_param["gen_list"]) == 0: + self.gen_this_function = False + return False + if curr_param["gen_list"][0]["gen_type"] in [GEN_BUILTIN, GEN_CSTRING, GEN_WSTRING, GEN_REFSTRING, GEN_CXXSTRING, GEN_ENUM, GEN_ARRAY, GEN_UNION, GEN_INPUT_FILE, GEN_OUTPUT_FILE, GEN_QUALIFIER, GEN_POINTER]: + for gen_type_info in curr_param["gen_list"]: + prev_param_name = curr_name + if gen_type_info["gen_type"] == GEN_BUILTIN: + this_gen_size = False + # if curr_param["param_usage"] in ["FILE_DESCRIPTOR"]: + # curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + # self.file_idx += 1 + # curr_gen = self.__gen_file_descriptor( + # curr_name, gen_type_info) + # self.__append_gen_dict(curr_gen) + # break + # # GEN STRING SIZE + + # elif param_id > 0 and (func["params"][param_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING] or curr_param["param_usage"] == "SIZE_FIELD"): + # if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: + # dyn_size_idx = 0 + # array_name = "" + # if func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_CSTRING: + # dyn_size_idx = self.dyn_cstring_size_idx + # array_name = "dyn_cstring_size" + # elif func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_WSTRING: + # dyn_size_idx = self.dyn_wstring_size_idx + # array_name = "dyn_wstring_size" + # else: + # dyn_size_idx = self.dyn_cxxstring_size_idx + # array_name = "dyn_cxxstring_size" + # curr_name = "sz_" + curr_name # size_prefix + # curr_gen = self.__gen_strsize( + # curr_name, curr_param["param_type"], dyn_size_idx, array_name) + # self.__append_gen_dict(curr_gen) + # this_gen_size = True + # break + if not this_gen_size: + curr_name = "b_" + curr_name # builtin_prefix + curr_gen = self.__gen_builtin(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_CSTRING: + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN STRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_REFSTRING: + print("!!!GEN_REFSTRING\n\n\n") + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN_REFSTRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + # curr_name = "&" + curr_name + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_WSTRING: + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN STRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_wstring_size_idx += 1 + curr_gen = self.__gen_wstring( + curr_name, gen_type_info, self.dyn_wstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_CXXSTRING: + curr_name = "str_" + curr_name # string_prefix + self.dyn_cxxstring_size_idx += 1 + curr_gen = self.__gen_cxxstring( + curr_name, gen_type_info, self.dyn_cxxstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_ENUM: # GEN_ENUM + curr_name = "e_" + curr_name # enum_prefix + found_enum = None + # search in enum list of analysis result: + for enum in self.target_library["enums"]: + if len(gen_type_info["type_name"].split(" ")) > 1: + if enum["qname"] == gen_type_info["type_name"].split(" ")[ + 1]: + found_enum = enum + break + else: + if enum["qname"] == gen_type_info["type_name"]: + found_enum = enum + break + if not found_enum: + # search in typedef list of analysis result: + for typedef in self.target_library["typedefs"]: + if typedef["name"] == gen_type_info["type_name"]: + enum_hash = typedef["type_source_hash"] + for enum in self.target_library["enums"]: + if enum["hash"] == enum_hash: + found_enum = enum + break + if not found_enum: + self.curr_func_log += f"- Can not generate for enum: {str(gen_type_info)}\n" + gen_curr_param = False + else: + compiler_info = self.__get_compile_command( + func["location"]["fullpath"]) + curr_gen = self.__gen_enum( + found_enum, curr_name, gen_type_info, compiler_info, self.gen_anonymous) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_UNION: + curr_name = "u_" + curr_name # union_prefix + curr_gen = self.__gen_union(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_ARRAY: # GEN_ARRAY + curr_name = "a_" + curr_name # array_prefix + curr_gen = self.__gen_array(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_QUALIFIER: + curr_name = "q_" + curr_name # qualifier_prefix + curr_gen = self.__gen_qualifier( + curr_name, prev_param_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_POINTER: + curr_name = "p_" + curr_name # qualifier_prefix + curr_gen = self.__gen_pointer( + curr_name, prev_param_name, gen_type_info) + self.__append_gen_dict(curr_gen) + prev_param_name = curr_name + if not gen_curr_param: + self.gen_this_function = False + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.__gen_anonymous_function(func, param_id) + + else: + if curr_param["gen_list"][0]["gen_type"] == GEN_STRUCT: + # 1. Search for function call that generate struct type + # 2. If not found, find in typdef the derived type of current struct and then take the action of 1. + # 3. If not found, find the struct definition, check if the struct is simple and manual generate + + curr_name = "s_" + curr_name # struct_prefix + # A variable of structure type can be initialized with other functions. + result_search_return_type = self.__search_return_types( + curr_param["gen_list"], func, self.target_library['functions']) + + if not result_search_return_type: + # A struct type may be defined with different name through typdef + result_search_typedefs = self.__search_in_typedefs( + curr_param["gen_list"][0]["type_name"], self.target_library['typedefs']) + if result_search_typedefs: + typedef_gen_list = [{ + "base_type_name": result_search_typedefs["underlying_type"], + "gen_type": GEN_STRUCT, + "gen_type_name": "_STRUCT", + "length": 0, + "local_qualifier": "", + "type_name": result_search_typedefs["name"] + }] + # Search typedef in return type of functions + result_search_typdef_return_type = self.__search_return_types( + typedef_gen_list, func, self.target_library['functions']) + if result_search_typdef_return_type: + old_values = self.__save_old_values() + for curr_return_func in result_search_typdef_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_anonymous_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + else: + found_struct = None + for record in self.target_library["records"]: + if len(curr_param["gen_list"][0]["type_name"].split(" ")) > 1 and record["type"] == STRUCT_RECORD and record["name"] == curr_param["gen_list"][0]["type_name"].split(" ")[1] and record["is_simple"]: + found_struct = record + break + if found_struct: + curr_gen = self.__gen_struct( + curr_name, record, curr_param["gen_list"][0]) + self.__append_gen_dict(curr_gen) + else: + _tmp = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(_tmp)}. Could not find function call to generate this struct!\n" + gen_curr_param = False + else: + _tmp = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(_tmp)}. Could not create function call to generate this struct, and the definition of struct not found!\n" + gen_curr_param = False + else: + old_values = self.__save_old_values() + for curr_return_func in result_search_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_anonymous_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + + if curr_param["gen_list"][0]["gen_type"] == GEN_CLASS: + # 1. Search for function call that generate class type + # 2. If not found, try to generate class through constructor/default constructor + + curr_name = "c_" + curr_name # struct_prefix + # A variable of structure type can be initialized with other functions. + result_search_return_type = self.__search_return_types( + curr_param["gen_list"], func, self.target_library['functions']) + + if not result_search_return_type: + found_class = None + for record in self.target_library["records"]: + if record["type"] == CLASS_RECORD and record["name"] == curr_param["gen_list"][0]["type_name"]: + found_class = record + break + if found_class: + curr_gen_list = self.__gen_class( + curr_name, found_class) + old_values = self.__save_old_values() + for curr_gen in curr_gen_list: + self.__append_gen_dict(curr_gen) + #!!!call recursive + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.var_function_idx += 1 + self.__gen_anonymous_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + else: + gen_type_info = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(gen_type_info)}. Could not find function call to generate this class!\n" + gen_curr_param = False + else: + old_values = self.__save_old_values() + for curr_return_func in result_search_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_anonymous_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + + if curr_param["gen_list"][0]["gen_type"] in [GEN_INCOMPLETE, GEN_VOID, GEN_FUNCTION, GEN_UNKNOWN]: + gen_type_info = curr_param["gen_list"][0] + self.curr_func_log += f"- Can not generate for object: {str(gen_type_info)}\n" + gen_curr_param = False + + # if gen_type_info["gen_type"] == GEN_VOID: + # curr_name = "a_" + curr_name # void_prefix + # self.curr_func_log += f"- Can not generate for object: {str(gen_type_info)}\n" + # gen_curr_param = False + # # curr_gen = self.__gen_void(curr_name) + # # self.__append_gen_dict(curr_gen) + + if not gen_curr_param: + self.gen_this_function = False + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.__gen_anonymous_function(func, param_id) + + def __gen_target_function(self, func, param_id) -> bool: + malloc_free = [ + "unsigned char *", + "char *", + ] + + if param_id == len(func['params']): + if not self.gen_anonymous and "(anonymous namespace)" in func["qname"]: + self.curr_func_log = f"This function is in anonymous namespace!" + self.gen_this_function = False + found_parent = None + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + # Find parent class + for r in self.target_library["records"]: + if r["hash"] == func["parent_hash"]: + found_parent = r + break + if not found_parent: + self.gen_this_function = False + + # If there is no buffer - return! + if (not len(self.buffer_size) and not self.dyn_cstring_size_idx and not self.dyn_cxxstring_size_idx and not self.dyn_wstring_size_idx and not self.file_idx) or not self.gen_this_function: + log = self.__log_file(func, self.gen_anonymous) + if not log: + print(CANNOT_CREATE_LOG_FILE, func["qname"]) + else: + self.curr_func_log = f"Log for function: {func['qname']}\n{self.curr_func_log}" + log.write(self.curr_func_log) + log.close() + return False + # generate file name + wrapper_result = self.__wrapper_file(func) + print("Generating fuzzing-wapper for function ", + func["qname"], ": ") + print("-- ", wrapper_result["msg"]) + if not wrapper_result["file"]: + self.gen_this_function = False + return False + f = wrapper_result["file"] + + f.write("//"+func["hash"] + "\n") + for line in self.__gen_header(func["location"]["fullpath"]): + f.write(line) + f.write('\n') + compiler_info = self.__get_compile_command( + func["location"]["fullpath"]) + + if self.target_type == LIBFUZZER: + if compiler_info["compiler"] == "CC": + f.write(LIBFUZZER_PREFIX_C) + else: + f.write(LIBFUZZER_PREFIX_CXX) + else: + f.write(AFLPLUSPLUS_PREFIX) + + f.write(" size_t Fuzz_Size_remain = Fuzz_Size;;\n") + f.write(" uint8_t * futag_pos = Fuzz_Data;\n") + for line in self.gen_lines: + f.write(" " + line) + + if func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + class_name = found_parent["qname"] + if func["func_type"] in [FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR]: + f.write(" //declare the RECORD and call constructor\n") + f.write( + " " + class_name.replace("::(anonymous namespace)", "") + " futag_target" + "(") + else: + # Find default constructor + # TODO: add code for other constructors + found_default_constructor = False + for fu in self.target_library["functions"]: + if fu["parent_hash"] == func["parent_hash"] and fu["func_type"] == FUNC_DEFAULT_CONSTRUCTOR: + found_default_constructor = True + + # TODO: add code for other constructors!!! + if not found_default_constructor: + self.gen_this_function = False + os.unlink(f.name) + f.close() + return False + f.write(" //declare the RECORD first\n") + f.write(" " + class_name + " futag_target;\n") + # call the method + f.write(" //METHOD CALL\n") + f.write(" futag_target." + func["name"]+"(") + else: + f.write(" //FUNCTION_CALL\n") + if func["return_type"] in malloc_free: + f.write(" " + func["return_type"] + + " futag_target = " + func["qname"] + "(") + else: + f.write(" " + func["qname"] + "(") + + param_list = [] + for arg in self.param_list: + param_list.append(arg + " ") + f.write(",".join(param_list)) + f.write(");\n") + # !attempting free on address which was not malloc()-ed + + if func["return_type"] in malloc_free: + f.write(" if(futag_target){\n") + f.write(" free(futag_target);\n") + f.write(" futag_target = NULL;\n") + f.write(" }\n") + + f.write(" //FREE\n") + for line in self.gen_free: + f.write(" " + line) + if self.target_type == LIBFUZZER: + f.write(LIBFUZZER_SUFFIX) + else: + f.write(AFLPLUSPLUS_SUFFIX) + f.close() + return True + + curr_param = func["params"][param_id] + if len(curr_param["gen_list"]) > 1: + curr_name = "_" + curr_param["param_name"] + else: + curr_name = curr_param["param_name"] + prev_param_name = curr_name + gen_curr_param = True + + curr_gen = {} + if len(curr_param["gen_list"]) == 0: + self.gen_this_function = False + return False + if curr_param["gen_list"][0]["gen_type"] in [GEN_BUILTIN, GEN_CSTRING, GEN_WSTRING, GEN_REFSTRING, GEN_CXXSTRING, GEN_ENUM, GEN_ARRAY, GEN_UNION, GEN_INPUT_FILE, GEN_OUTPUT_FILE, GEN_QUALIFIER, GEN_POINTER]: + for gen_type_info in curr_param["gen_list"]: + prev_param_name = curr_name + if gen_type_info["gen_type"] == GEN_BUILTIN: + this_gen_size = False + # if curr_param["param_usage"] in ["FILE_DESCRIPTOR"]: + # curr_name = "fd_" + curr_name + str(self.file_idx) # string_prefix + # self.file_idx += 1 + # curr_gen = self.__gen_file_descriptor( + # curr_name, gen_type_info) + # self.__append_gen_dict(curr_gen) + # break + # # GEN STRING SIZE + + # elif param_id > 0 and (func["params"][param_id - 1]["gen_list"][0]["gen_type"] in [GEN_CSTRING, GEN_WSTRING, GEN_CXXSTRING] or curr_param["param_usage"] == "SIZE_FIELD"): + # if gen_type_info["type_name"] in ["size_t", "unsigned char", "char", "int", "unsigned", "unsigned int", "short", "unsigned short", "short int", "unsigned short int"]: + # dyn_size_idx = 0 + # array_name = "" + # if func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_CSTRING: + # dyn_size_idx = self.dyn_cstring_size_idx + # array_name = "dyn_cstring_size" + # elif func["params"][param_id - 1]["gen_list"][0]["gen_type"] == GEN_WSTRING: + # dyn_size_idx = self.dyn_wstring_size_idx + # array_name = "dyn_wstring_size" + # else: + # dyn_size_idx = self.dyn_cxxstring_size_idx + # array_name = "dyn_cxxstring_size" + # curr_name = "sz_" + curr_name # size_prefix + # curr_gen = self.__gen_strsize( + # curr_name, curr_param["param_type"], dyn_size_idx, array_name) + # self.__append_gen_dict(curr_gen) + # this_gen_size = True + # break + if not this_gen_size: + curr_name = "b_" + curr_name # builtin_prefix + curr_gen = self.__gen_builtin(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_CSTRING: + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN STRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_REFSTRING: + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN STRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_cstring_size_idx += 1 + curr_gen = self.__gen_cstring( + curr_name, gen_type_info, self.dyn_cstring_size_idx) + # curr_name = "&" + curr_name + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_WSTRING: + # GEN FILE NAME OR # GEN STRING + if (curr_param["param_usage"] in ["FILE_PATH_READ", "FILE_PATH_WRITE", "FILE_PATH_RW", "FILE_PATH"] or curr_param["param_name"] in ["filename", "file", "filepath"] or curr_param["param_name"].find('file') != -1 or curr_param["param_name"].find('File') != -1) and len(curr_param["gen_list"]) == 1: + curr_name = "f_" + curr_name # string_prefix + self.file_idx += 1 + curr_gen = self.__gen_input_file( + curr_name, gen_type_info) + else: + # GEN STRING + curr_name = "str_" + curr_name # string_prefix + self.dyn_wstring_size_idx += 1 + curr_gen = self.__gen_wstring( + curr_name, gen_type_info, self.dyn_wstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_CXXSTRING: + curr_name = "str_" + curr_name # string_prefix + self.dyn_cxxstring_size_idx += 1 + curr_gen = self.__gen_cxxstring( + curr_name, gen_type_info, self.dyn_cxxstring_size_idx) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_ENUM: # GEN_ENUM + curr_name = "e_" + curr_name # enum_prefix + found_enum = None + # search in enum list of analysis result: + for enum in self.target_library["enums"]: + if len(gen_type_info["type_name"].split(" ")) > 1: + if enum["qname"] == gen_type_info["type_name"].split(" ")[ + 1]: + found_enum = enum + break + else: + if enum["qname"] == gen_type_info["type_name"]: + found_enum = enum + break + if not found_enum: + # search in typedef list of analysis result: + for typedef in self.target_library["typedefs"]: + if typedef["name"] == gen_type_info["type_name"]: + enum_hash = typedef["type_source_hash"] + for enum in self.target_library["enums"]: + if enum["hash"] == enum_hash: + found_enum = enum + break + if not found_enum: + self.curr_func_log += f"- Can not generate for enum: {str(gen_type_info)}\n" + gen_curr_param = False + else: + compiler_info = self.__get_compile_command( + func["location"]["fullpath"]) + curr_gen = self.__gen_enum( + found_enum, curr_name, gen_type_info, compiler_info, self.gen_anonymous) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_UNION: + curr_name = "u_" + curr_name # union_prefix + curr_gen = self.__gen_union(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_ARRAY: # GEN_ARRAY + curr_name = "a_" + curr_name # array_prefix + curr_gen = self.__gen_array(curr_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_QUALIFIER: + curr_name = "q_" + curr_name # qualifier_prefix + curr_gen = self.__gen_qualifier( + curr_name, prev_param_name, gen_type_info) + self.__append_gen_dict(curr_gen) + + if gen_type_info["gen_type"] == GEN_POINTER: + curr_name = "p_" + curr_name # qualifier_prefix + curr_gen = self.__gen_pointer( + curr_name, prev_param_name, gen_type_info) + self.__append_gen_dict(curr_gen) + prev_param_name = curr_name + if not gen_curr_param: + self.gen_this_function = False + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.__gen_target_function(func, param_id) + + else: + if curr_param["gen_list"][0]["gen_type"] == GEN_STRUCT: + # 1. Search for function call that generate struct type + # 2. If not found, find in typdef the derived type of current struct and then take the action of 1. + # 3. If not found, find the struct definition, check if the struct is simple and manual generate + + curr_name = "s_" + curr_name # struct_prefix + # A variable of structure type can be initialized with other functions. + result_search_return_type = self.__search_return_types( + curr_param["gen_list"], func, self.target_library['functions']) + + if not result_search_return_type: + # A struct type may be defined with different name through typdef + result_search_typedefs = self.__search_in_typedefs( + curr_param["gen_list"][0]["type_name"], self.target_library['typedefs']) + if result_search_typedefs: + typedef_gen_list = [{ + "base_type_name": result_search_typedefs["underlying_type"], + "gen_type": GEN_STRUCT, + "gen_type_name": "_STRUCT", + "length": 0, + "local_qualifier": "", + "type_name": result_search_typedefs["name"] + }] + # Search typedef in return type of functions + result_search_typdef_return_type = self.__search_return_types( + typedef_gen_list, func, self.target_library['functions']) + if result_search_typdef_return_type: + old_values = self.__save_old_values() + for curr_return_func in result_search_typdef_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_target_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + else: + found_struct = None + for record in self.target_library["records"]: + if len(curr_param["gen_list"][0]["type_name"].split(" ")) > 1 and record["type"] == STRUCT_RECORD and record["name"] == curr_param["gen_list"][0]["type_name"].split(" ")[1] and record["is_simple"]: + found_struct = record + break + if found_struct: + curr_gen = self.__gen_struct( + curr_name, record, curr_param["gen_list"][0]) + self.__append_gen_dict(curr_gen) + else: + _tmp = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(_tmp)}. Could not find function call to generate this struct!\n" + gen_curr_param = False + else: + _tmp = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(_tmp)}. Could not create function call to generate this struct, and the definition of struct not found!\n" + gen_curr_param = False + else: + old_values = self.__save_old_values() + for curr_return_func in result_search_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_target_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + + if curr_param["gen_list"][0]["gen_type"] == GEN_CLASS: + # 1. Search for function call that generate class type + # 2. If not found, try to generate class through constructor/default constructor + + curr_name = "c_" + curr_name # struct_prefix + # A variable of structure type can be initialized with other functions. + result_search_return_type = self.__search_return_types( + curr_param["gen_list"], func, self.target_library['functions']) + + if not result_search_return_type: + found_class = None + for record in self.target_library["records"]: + if record["type"] == CLASS_RECORD and record["name"] == curr_param["gen_list"][0]["type_name"]: + found_class = record + break + if found_class: + curr_gen_list = self.__gen_class( + curr_name, found_class) + old_values = self.__save_old_values() + for curr_gen in curr_gen_list: + self.__append_gen_dict(curr_gen) + #!!!call recursive + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.var_function_idx += 1 + self.__gen_target_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + else: + gen_type_info = curr_param["gen_list"][0] + self.curr_func_log += f"- Could not generate for object: {str(gen_type_info)}. Could not find function call to generate this class!\n" + gen_curr_param = False + else: + old_values = self.__save_old_values() + for curr_return_func in result_search_return_type: + self.var_function_idx += 1 + self.gen_lines += ["\n"] + self.param_list += [curr_name] + curr_gen = self.__gen_var_function( + curr_name, curr_return_func["function"]) + self.__add_header(self.__get_function_header( + func["location"]["fullpath"])) + self.__append_gen_dict(curr_gen) + #!!!call recursive + param_id += 1 + self.__gen_target_function(func, param_id) + param_id -= 1 + self.__retrieve_old_values(old_values) + + if curr_param["gen_list"][0]["gen_type"] in [GEN_INCOMPLETE, GEN_VOID, GEN_FUNCTION, GEN_UNKNOWN]: + gen_type_info = curr_param["gen_list"][0] + self.curr_func_log += f"- Can not generate for object: {str(gen_type_info)}\n" + gen_curr_param = False + + # if gen_type_info["gen_type"] == GEN_VOID: + # curr_name = "a_" + curr_name # void_prefix + # self.curr_func_log += f"- Can not generate for object: {str(gen_type_info)}\n" + # gen_curr_param = False + # # curr_gen = self.__gen_void(curr_name) + # # self.__append_gen_dict(curr_gen) + + if not gen_curr_param: + self.gen_this_function = False + self.gen_lines += ["\n"] + self.param_list += [curr_name] + param_id += 1 + self.__gen_target_function(func, param_id) + + def gen_targets(self, anonymous: bool = False, max_wrappers: int = 10): + """ + Parameters + ---------- + anonymous: bool + option for generating fuzz-targets of non-public functions, default to False. + """ + self.gen_anonymous = anonymous + self.max_wrappers = max_wrappers + C_generated_function = [] + C_unknown_function = [] + Cplusplus_usual_class_method = [] + Cplusplus_static_class_method = [] + Cplusplus_anonymous_class_method = [] + for target in self.target_functions: + for func in self.target_library["functions"]: + if target["name"] != func["name"]: + continue + # For C + if func["access_type"] == AS_NONE and func["fuzz_it"] and func["storage_class"] < 2 and (func["parent_hash"] == ""): + print( + "-- [Futag] Try to generate fuzz-driver for function: ", func["name"], "...") + C_generated_function.append(func["name"]) + self.gen_this_function = True + self.header = [] + self.buffer_size = [] + self.gen_lines = [] + self.gen_free = [] + self.dyn_cstring_size_idx = 0 + self.dyn_wstring_size_idx = 0 + self.dyn_cxxstring_size_idx = 0 + self.file_idx = 0 + self.var_function_idx = 0 + self.param_list = [] + self.curr_function = func + self.curr_func_log = "" + if "(anonymous" in func["qname"]: + self.__gen_anonymous_function(func, 0) + else: + self.__gen_target_function(func, 0) + # self.__gen_target_function(func, 0) + + # For C++, Declare object of class and then call the method + if func["access_type"] == AS_PUBLIC and func["fuzz_it"] and func["func_type"] in [FUNC_CXXMETHOD, FUNC_CONSTRUCTOR, FUNC_DEFAULT_CONSTRUCTOR, FUNC_GLOBAL, FUNC_STATIC] and (not "::operator" in func["qname"]): + Cplusplus_usual_class_method.append(func["qname"]) + print( + "-- [Futag] Try to generate fuzz-driver for class method: ", func["name"], "...") + self.gen_this_function = True + self.header = [] + self.buffer_size = [] + self.gen_lines = [] + self.gen_free = [] + self.dyn_cstring_size_idx = 0 + self.dyn_wstring_size_idx = 0 + self.dyn_cxxstring_size_idx = 0 + self.file_idx = 0 + self.var_function_idx = 0 + self.param_list = [] + self.curr_function = func + self.curr_func_log = "" + if "(anonymous" in func["qname"]: + self.__gen_anonymous_function(func, 0) + else: + self.__gen_target_function(func, 0) + + # For C++, Call the static function of class without declaring object + if func["access_type"] in [AS_NONE, AS_PUBLIC] and func["fuzz_it"] and func["func_type"] in [FUNC_CXXMETHOD, FUNC_GLOBAL, FUNC_STATIC] and func["storage_class"] == SC_STATIC: + self.gen_this_function = True + self.header = [] + self.buffer_size = [] + self.gen_lines = [] + self.gen_free = [] + self.dyn_cstring_size_idx = 0 + self.dyn_wstring_size_idx = 0 + self.dyn_cxxstring_size_idx = 0 + self.file_idx = 0 + self.var_function_idx = 0 + self.param_list = [] + self.curr_function = func + self.curr_func_log = "" + if (not "(anonymous namespace)" in func["qname"]) and (not "::operator" in func["qname"]): + Cplusplus_static_class_method.append(func["qname"]) + if "(anonymous" in func["qname"]: + self.__gen_anonymous_function(func, 0) + + # We dont generate for static function of C + if func["func_type"] == FUNC_UNKNOW_RECORD and func["storage_class"] == 2: + C_unknown_function.append(func["qname"]) + + self.result_report = { + "C_generated_functions": C_generated_function, + "Cplusplus_static_class_methods": Cplusplus_static_class_method, + "Cplusplus_usual_class_methods": Cplusplus_usual_class_method, + "Cplusplus_anonymous_class_methods": Cplusplus_anonymous_class_method, + "C_unknown_functions": C_unknown_function + } + json.dump(self.result_report, open( + (self.build_path / "result-report.json").as_posix(), "w")) + + def compile_driver_worker(self, bgen_args): + with open(bgen_args["error_path"], "w") as error_log_file: + p = Popen( + bgen_args["compiler_cmd"], + stdout=PIPE, + stderr=error_log_file, + universal_newlines=True, + ) + + target_file = open(bgen_args["source_path"], "a") + + target_file.write("\n// Compile database: \n") + target_file.write("/*\n") + target_file.write( + "command: " + bgen_args["compiler_info"]['command'] + "\n") + target_file.write("location: " + + bgen_args["compiler_info"]['location'] + "\n") + target_file.write("file: " + bgen_args["compiler_info"]['file']) + target_file.write("\n*/\n") + + new_compiler_cmd = [] + compiler_cmd = bgen_args["compiler_cmd"] + target_file.write("\n// Compile command:") + target_file.write("\n/* \n") + output, errors = p.communicate() + if p.returncode: + print(" ".join(bgen_args["compiler_cmd"])) + print("\n-- [Futag] ERROR on target ", + bgen_args["target_name"], "\n") + for c in compiler_cmd: + if c.find(self.tmp_output_path.as_posix()) >= 0: + new_compiler_cmd.append( + c.replace(self.tmp_output_path.as_posix(), self.failed_path.as_posix())) + else: + new_compiler_cmd.append(c) + + else: + print("-- [Futag] Fuzz-driver ", + bgen_args["target_name"], " was compiled successfully!") + for c in compiler_cmd: + if c.find(self.tmp_output_path.as_posix()) >= 0: + new_compiler_cmd.append( + c.replace(self.tmp_output_path.as_posix(), self.succeeded_path.as_posix())) + else: + new_compiler_cmd.append(c) + + target_file.write(" ".join(new_compiler_cmd)) + target_file.write("\n */\n") + + error_log_file = open(bgen_args["error_path"], "r") + if error_log_file: + target_file.write("\n// Error log:") + target_file.write("\n/* \n") + target_file.write("".join(error_log_file.readlines())) + error_log_file.close() + target_file.write("\n */\n") + target_file.close() + + def compile_targets(self, workers: int = 4, keep_failed: bool = False, extra_params: str = "", extra_include: str = "", extra_dynamiclink: str = "", flags: str = "", coverage: bool = False, keep_original: bool = False): + """_summary_ + + Args: + workers (int, optional): number of processes for compiling. Defaults to 4. + keep_failed (bool, optional): option for saving not compiled fuzz-targets. Defaults to False. + extra_params (str, optional): option for adding parameters while compiling. Defaults to "". + extra_include (str, optional): option for adding included directories while compiling. Defaults to "". + extra_dynamiclink (str, optional): option for adding dynamic libraries while compiling. Defaults to "". + flags (str, optional): flags for compiling fuzz-drivers. Defaults to "-fsanitize=address -g -O0". + coverage (bool, optional): option for adding coverage flag. Defaults to False. + keep_original (bool, optional): option for keeping .futag-fuzz-drivers. Defaults to False. + """ + + # include_subdir = self.target_library["header_dirs"] + # include_subdir = include_subdir + [x.parents[0].as_posix() for x in (self.build_path).glob("**/*.h")] + [x.parents[0].as_posix() for x in (self.build_path).glob("**/*.hpp")] + [self.build_path.as_posix()] + + # if (self.install_path / "include").exists(): + # include_subdir = include_subdir + [x.parents[0].as_posix() for x in (self.install_path / "include").glob("**/*.h")] + [x.parents[0].as_posix() for x in (self.install_path / "include").glob("**/*.hpp")] + # include_subdir = list(set(include_subdir)) + if not flags: + if coverage: + compiler_flags_aflplusplus = COMPILER_FLAGS + " " + \ + COMPILER_COVERAGE_FLAGS + " " + DEBUG_FLAGS + " -fPIE" + compiler_flags_libFuzzer = FUZZ_COMPILER_FLAGS + " " +\ + COMPILER_COVERAGE_FLAGS + " " + DEBUG_FLAGS + else: + compiler_flags_aflplusplus = COMPILER_FLAGS + " " + DEBUG_FLAGS + " -fPIE " + compiler_flags_libFuzzer = FUZZ_COMPILER_FLAGS + " " + DEBUG_FLAGS + else: + compiler_flags_aflplusplus = flags + compiler_flags_libFuzzer = flags + if coverage: + compiler_flags_aflplusplus = COMPILER_COVERAGE_FLAGS + \ + " " + compiler_flags_aflplusplus + compiler_flags_libFuzzer = COMPILER_COVERAGE_FLAGS + " " + compiler_flags_libFuzzer + + generated_functions = [ + x for x in self.tmp_output_path.iterdir() if x.is_dir()] + + generated_targets = 0 + + compile_cmd_list = [] + static_lib = [] + target_lib = [u for u in (self.library_root).glob( + "**/*.a") if u.is_file()] + if target_lib: + static_lib = ["-Wl,--start-group"] + for t in target_lib: + static_lib.append(t.as_posix()) + static_lib.append("-Wl,--end-group") + + for func_dir in generated_functions: + # Extract compiler cwd, to resolve relative includes + search_curr_func = [ + f for f in self.target_library['functions'] if f['qname'].replace(":", "_") == func_dir.name] + if not len(search_curr_func): + search_curr_func = [ + f for f in self.target_library['functions'] if 'anonymous_' + f['name'].replace(":", "_") == func_dir.name] + if not len(search_curr_func): + continue + current_func = search_curr_func[0] + func_file_location = current_func["location"]["fullpath"] + compiler_info = self.__get_compile_command(func_file_location) + + # List of Pathlib (-I parameters) in compile command. + include_subdir = [] + + if os.path.exists(compiler_info["location"]): + current_location = os.getcwd() + os.chdir(compiler_info["location"]) + for iter in compiler_info["command"].split(" "): + if iter[0:2] == "-I": + if pathlib.Path(iter[2:]).exists(): + include_subdir.append( + "-I" + pathlib.Path(iter[2:]).absolute().as_posix() + "/") + os.chdir(current_location) + + if not "-fPIE" in compiler_flags_aflplusplus: + compiler_flags_aflplusplus += " -fPIE" + + if not "-ferror-limit=1" in compiler_flags_libFuzzer: + compiler_flags_libFuzzer += " -ferror-limit=1" + + compiler_path = "" + if self.target_type == LIBFUZZER: + if compiler_info["compiler"] == "CC": + compiler_path = self.futag_llvm_package / "bin/clang" + else: + compiler_path = self.futag_llvm_package / "bin/clang++" + else: + if compiler_info["compiler"] == "CC": + compiler_path = self.futag_llvm_package / \ + "AFLplusplus/usr/local/bin/afl-clang-fast" + else: + compiler_path = self.futag_llvm_package / \ + "AFLplusplus/usr/local/bin/afl-clang-fast++" + + current_func_compilation_opts = "" + compilation_opts = "" + + for compiled_file in self.target_library["compiled_files"]: + if func_file_location == compiled_file["filename"]: + compilation_opts = compiled_file["compiler_opts"] + current_func_compilation_opts = compilation_opts.split(' ') + # Extract all include locations from compilation options + include_paths: List[pathlib.Path] = map( + pathlib.Path, + map( + current_func_compilation_opts.__getitem__, + [i + 1 for i, + x in enumerate(current_func_compilation_opts) if x == '-I'] + )) + + resolved_include_paths: List[pathlib.Path] = [] + for include_path in include_paths: + if include_path.is_absolute(): + resolved_include_paths.append(include_path) + else: + # Resolve relative include paths (e.g. in this case: -I.. -I.) + resolved_include_paths.append( + pathlib.Path(include_path).absolute()) + + current_include = [] + if not include_subdir: + if resolved_include_paths: + for i in resolved_include_paths: + current_include.append("-I" + i.as_posix() + "/") + else: + for i in include_subdir: + current_include.append(i) + + fuzz_driver_dirs = [x for x in func_dir.iterdir() if x.is_dir()] + for dir in fuzz_driver_dirs: + # for target_src in [t for t in dir.glob("*"+self.target_extension) if t.is_file()]: + for target_src in [t for t in dir.glob("*") if t.is_file() and t.suffix in [".c", ".cc", ".cpp"]]: + target_path = dir.as_posix() + "/" + target_src.stem + ".out" + error_path = dir.as_posix() + "/" + target_src.stem + ".err" + generated_targets += 1 + if self.target_type == LIBFUZZER: + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_libFuzzer.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + else: + compiler_cmd = [compiler_path.as_posix()] + compiler_flags_aflplusplus.split(" ") + current_include + ["-I" + x for x in extra_include.split( + " ") if x.strip()] + extra_params.split(" ") + [target_src.as_posix()] + ["-o"] + [target_path] + static_lib + extra_dynamiclink.split(" ") + + compile_cmd_list.append({ + "compiler_cmd": compiler_cmd, + "target_name": target_src.stem, + "error_path": error_path, + "source_path": target_src.as_posix(), + "binary_path": target_path, + "compiler_info": compiler_info, + }) + with Pool(workers) as p: + p.map(self.compile_driver_worker, compile_cmd_list) + + # Extract the results of compilation + + compiled_targets_list = [ + x for x in self.tmp_output_path.glob("**/*.out") if x.is_file()] + print("-- [Futag] collecting result ...") + + succeeded_tree = set() + succeeded_dir = set() + # for compiled_target in compiled_targets_list: + # if compiled_target.parents[0].as_posix() not in succeeded_tree: + # succeeded_tree.add(compiled_target.parents[0].as_posix()) + for compiled_target in compiled_targets_list: + if compiled_target not in succeeded_tree: + succeeded_tree.add(compiled_target) + succeeded_dir.add(compiled_target.parents[0]) + for dir in succeeded_tree: + if not (self.succeeded_path / dir.parents[1].name).exists(): + ((self.succeeded_path / + dir.parents[1].name)).mkdir(parents=True, exist_ok=True) + # shutil.move(dir.parents[0].as_posix(), (self.succeeded_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) + copy_tree(dir.parents[0].as_posix( + ), (self.succeeded_path / dir.parents[1].name / dir.parents[0].name).as_posix()) + if keep_failed: + failed_tree = set() + not_compiled_targets_list = [ + x for x in self.tmp_output_path.glob("**/*.cc") if x.is_file()] + not_compiled_targets_list = not_compiled_targets_list + [ + x for x in self.tmp_output_path.glob("**/*.c") if x.is_file()] + not_compiled_targets_list = not_compiled_targets_list + [ + x for x in self.tmp_output_path.glob("**/*.cpp") if x.is_file()] + for target in not_compiled_targets_list: + if target not in failed_tree: + failed_tree.add(target) + for dir in failed_tree: + if dir.parents[0] not in succeeded_dir: + if not (self.failed_path / dir.parents[1].name).exists(): + ((self.failed_path / + dir.parents[1].name)).mkdir(parents=True, exist_ok=True) + # shutil.move(dir.parents[0].as_posix(), (self.failed_path / dir.parents[1].name).as_posix(), copy_function=shutil.copytree) + copy_tree(dir.parents[0].as_posix( + ), (self.failed_path / dir.parents[1].name / dir.parents[0].name).as_posix()) + else: + delete_folder(self.failed_path) + if not keep_original: + delete_folder(self.tmp_output_path) + + print( + "-- [Futag] Result of compiling: " + + str(len(compiled_targets_list)) + + " fuzz-driver(s)\n" + ) + + def gen_targets_from_callstack(self, target): + found_function = None + for func in self.target_library["functions"]: + # if func["qname"] == target["qname"] and func["location"]["line"] == target["location"]["line"]and func["location"]["line"]["file"]== target["location"]["line"]: + if func["qname"] == target["qname"]: + found_function = func + self.__gen_target_function(func, 0) + if not found_function: + sys.exit("Function \"%s\" not found in library!" % target["qname"]) diff --git a/src/python/futag-package/src/futag/preprocessor.py b/src/python/futag-package/src/futag/preprocessor.py index 78dd8d6..54f8303 100644 --- a/src/python/futag-package/src/futag/preprocessor.py +++ b/src/python/futag-package/src/futag/preprocessor.py @@ -174,7 +174,7 @@ def build_cmake(self) -> bool: "cmake", f"-DLLVM_CONFIG_PATH={(self.futag_llvm_package / 'bin/llvm-config').as_posix()}", f"-DCMAKE_INSTALL_PREFIX={self.install_path.as_posix()}", - # f"-DCMAKE_EXPORT_COMPILE_COMMANDS=1", + f"-DCMAKE_EXPORT_COMPILE_COMMANDS=1", f"-B{(self.build_path).as_posix()}", f"-S{self.library_root.as_posix()}" ] @@ -240,7 +240,7 @@ def build_cmake(self) -> bool: f"-DCMAKE_C_COMPILER={(self.futag_llvm_package / 'bin/clang').as_posix()}", f"-DCMAKE_C_FLAGS='{self.flags}'", f"-B{(self.build_path).as_posix()}", - f"-S{self.library_root.as_posix()}" + f"-S{self.library_root.as_posix()}",f"-DCMAKE_EXPORT_COMPILE_COMMANDS=1" ] # my_env["CC"] = (self.futag_llvm_package / 'bin/clang').as_posix() @@ -980,7 +980,7 @@ def build_cmake(self) -> bool: "cplusplus", "cmake", f"-DLLVM_CONFIG_PATH={(self.futag_llvm_package / 'bin/llvm-config').as_posix()}", - # f"-DCMAKE_EXPORT_COMPILE_COMMANDS=1", + f"-DCMAKE_EXPORT_COMPILE_COMMANDS=1", f"-B{(self.build_path).as_posix()}", f"-S{self.consumer_root.as_posix()}" ] @@ -1028,11 +1028,11 @@ def build_cmake(self) -> bool: universal_newlines=True, env=my_env) print(LIB_ANALYZING_COMMAND, " ".join(p.args)) output, errors = p.communicate() - if p.returncode: - print(errors) - sys.exit(LIB_ANALYZING_FAILED) - else: - print(LIB_ANALYZING_SUCCEEDED) + # if p.returncode: + # print(errors) + # sys.exit(LIB_ANALYZING_FAILED) + # else: + # print(LIB_ANALYZING_SUCCEEDED) os.chdir(curr_dir) return True @@ -1110,11 +1110,11 @@ def build_configure(self) -> bool: print(LIB_ANALYZING_COMMAND, " ".join(p.args)) output, errors = p.communicate() - if p.returncode: - print(errors) - sys.exit(LIB_ANALYZING_FAILED) - else: - print(LIB_ANALYZING_SUCCEEDED) + # if p.returncode: + # print(errors) + # sys.exit(LIB_ANALYZING_FAILED) + # else: + # print(LIB_ANALYZING_SUCCEEDED) os.chdir(curr_dir) return True diff --git a/src/python/futag-package/src/futag/sysmsg.py b/src/python/futag-package/src/futag/sysmsg.py index e3f7118..5bd77a6 100644 --- a/src/python/futag-package/src/futag/sysmsg.py +++ b/src/python/futag-package/src/futag/sysmsg.py @@ -38,6 +38,7 @@ INVALID_ANALYSIS_FILE = "-- [Futag]: Incorrect path to analysis result file" INVALID_CONTEXT_FILE_PATH = "-- [Futag]: Incorrect path to consumer context file" INVALID_LIBPATH = "-- [Futag]: Incorrect path to the library root" +INVALID_NATCH_JSON = "-- [Futag]: Incorrect path to JSON file from Natch." INVALID_CONSUMER_PATH = "-- [Futag]: Incorrect path to the consumer program" INVALID_DB_FILEPATH = "-- [Futag]: analysis result of testing library not found" INVALID_BUILPATH = "-- [Futag]: Incorrect path to the library build path" @@ -69,7 +70,7 @@ INVALID_TARGET_TYPE = "-- [Futag] Error: Unknown type of fuzz-driver for generating!" # message for Natch -COULD_NOT_PARSE_NATCH_CALLSTACK = "-- [Futag] Error: Could not parse file!" +COULD_NOT_PARSE_NATCH_CALLSTACK = "-- [Futag] Error: Could not parse JSON file!" # messages for GENERATOR