From f852c7b38508cfb2050d7cab091364f7683b6013 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 21 Dec 2022 12:48:01 +0000 Subject: [PATCH] infra: make it possible to do a full introspector run (#9243) Make it possible to do a full run of introspector locally. This will make it a lot easier for users to integrate it into the fuzzer building workflow. To trigger, just run: `python3 infra/helper.py introspector PROJ_NAME` Other example commands: `python3 infra/helper.py introspector --public-corpora PROJ_NAME` : will download the latest public corpus for project PROJ_NAME and use that when collecting coverage `python3 infra/helper.py introspector --seconds=X PROJ_NAME`: will run the fuzzers for X seconds for corpus collection `python3 infra/helper.py introspector PROJ_NAME LOCAL_PATH` will do the introspector run using the LOCAL_PATH as source code folder (for testing modifications) Ref: https://github.com/ossf/fuzz-introspector/issues/587 Signed-off-by: David Korczynski --- infra/helper.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/infra/helper.py b/infra/helper.py index d1adf7c1c948..72bc8ec40e11 100755 --- a/infra/helper.py +++ b/infra/helper.py @@ -188,6 +188,8 @@ def main(): # pylint: disable=too-many-branches,too-many-return-statements result = run_fuzzer(args) elif args.command == 'coverage': result = coverage(args) + elif args.command == 'introspector': + result = introspector(args) elif args.command == 'reproduce': result = reproduce(args) elif args.command == 'shell': @@ -353,6 +355,33 @@ def get_parser(): # pylint: disable=too-many-statements _add_external_project_args(coverage_parser) _add_architecture_args(coverage_parser) + introspector_parser = subparsers.add_parser( + 'introspector', + help='Run a complete end-to-end run of ' + 'fuzz introspector. This involves (1) ' + 'building the fuzzers with ASAN; (2) ' + 'running all fuzzers; (3) building ' + 'fuzzers with coverge; (4) extracting ' + 'coverage; (5) building fuzzers using ' + 'introspector') + introspector_parser.add_argument('project', help='name of the project') + introspector_parser.add_argument('--seconds', + help='number of seconds to run fuzzers', + default=10) + introspector_parser.add_argument('source_path', + help='path of local source', + nargs='?') + introspector_parser.add_argument( + '--public-corpora', + help='if specified, will use public corpora for code coverage', + default=False, + action='store_true') + introspector_parser.add_argument( + '--private-corpora', + help='if specified, will use private corpora', + default=False, + action='store_true') + download_corpora_parser = subparsers.add_parser( 'download_corpora', help='Download all corpora for a project.') download_corpora_parser.add_argument('--fuzz-target', @@ -1131,6 +1160,108 @@ def coverage(args): return result +def _introspector_prepare_corpus(args): + """Helper function for introspector runs to generate corpora.""" + parser = get_parser() + # Generate corpus, either by downloading or running fuzzers. + if args.private_corpora or args.public_corpora: + corpora_command = ['download_corpora'] + if args.public_corpora: + corpora_command.append('--public') + corpora_command.append(args.project.name) + if not download_corpora(parse_args(parser, corpora_command)): + logging.error('Failed to download corpora') + return False + else: + fuzzer_targets = _get_fuzz_targets(args.project) + for fuzzer_name in fuzzer_targets: + # Make a corpus directory. + fuzzer_corpus_dir = args.project.corpus + f'/{fuzzer_name}' + if not os.path.isdir(fuzzer_corpus_dir): + os.makedirs(fuzzer_corpus_dir) + run_fuzzer_command = [ + 'run_fuzzer', '--sanitizer', 'address', '--corpus-dir', + fuzzer_corpus_dir, args.project.name, fuzzer_name + ] + + parsed_args = parse_args(parser, run_fuzzer_command) + parsed_args.fuzzer_args = [ + f'-max_total_time={args.seconds}', '-detect_leaks=0' + ] + # Continue even if run command fails, because we do not have 100% + # accuracy in fuzz target detection, i.e. we might try to run something + # that is not a target. + run_fuzzer(parsed_args) + return True + + +def introspector(args): + """Runs a complete end-to-end run of introspector.""" + parser = get_parser() + + args_to_append = [] + if args.source_path: + args_to_append.append(_get_absolute_path(args.source_path)) + + # Build fuzzers with ASAN. + build_fuzzers_command = [ + 'build_fuzzers', '--sanitizer=address', args.project.name + ] + args_to_append + if not build_fuzzers(parse_args(parser, build_fuzzers_command)): + logging.error('Failed to build project with ASAN') + return False + + if not _introspector_prepare_corpus(args): + return False + + # Build code coverage. + build_fuzzers_command = [ + 'build_fuzzers', '--sanitizer=coverage', args.project.name + ] + args_to_append + if not build_fuzzers(parse_args(parser, build_fuzzers_command)): + logging.error('Failed to build project with coverage instrumentation') + return False + + # Collect coverage. + coverage_command = [ + 'coverage', '--no-corpus-download', '--port', '', args.project.name + ] + if not coverage(parse_args(parser, coverage_command)): + logging.error('Failed to extract coverage') + return False + + # Build introspector. + build_fuzzers_command = [ + 'build_fuzzers', '--sanitizer=introspector', args.project.name + ] + args_to_append + if not build_fuzzers(parse_args(parser, build_fuzzers_command)): + logging.error('Failed to build project with introspector') + return False + + introspector_dst = os.path.join(args.project.out, "introspector-report") + if os.path.isdir(introspector_dst): + os.rmdir(introspector_dst) + shutil.copytree(os.path.join(args.project.out, "inspector"), introspector_dst) + + # Copy the coverage reports into the introspector report. + dst_cov_report = os.path.join(introspector_dst, "covreport") + shutil.copytree(os.path.join(args.project.out, "report"), dst_cov_report) + + # Copy per-target coverage reports + src_target_cov_report = os.path.join(args.project.out, "report_target") + for target_cov_dir in os.listdir(src_target_cov_report): + dst_target_cov_report = os.path.join(dst_cov_report, target_cov_dir) + shutil.copytree(os.path.join(src_target_cov_report, target_cov_dir), + dst_target_cov_report) + + logging.info('Introspector run complete. Report in %s', introspector_dst) + logging.info( + 'To browse the report, run: `python3 -m http.server 8008 --directory %s`' + 'and navigate to localhost:8008/fuzz_report.html in your browser', + introspector_dst) + return True + + def run_fuzzer(args): """Runs a fuzzer in the container.""" if not check_project_exists(args.project):