From 1c23b640499c1539df0bd30b833d31b50eac442a Mon Sep 17 00:00:00 2001 From: Nick Terrell Date: Mon, 25 Sep 2017 11:27:33 -0700 Subject: [PATCH] [fuzz] fuzz.py can minimize and zip corpora * "minimize" minimizes the corpora into an output directory. * "zip" zips up the minimized corpora, which are ready to deploy. --- tests/fuzz/fuzz.py | 165 +++++++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 51 deletions(-) diff --git a/tests/fuzz/fuzz.py b/tests/fuzz/fuzz.py index 0ce201cd..8c381ecf 100755 --- a/tests/fuzz/fuzz.py +++ b/tests/fuzz/fuzz.py @@ -82,6 +82,35 @@ def tmpdir(): shutil.rmtree(dirpath, ignore_errors=True) +def parse_targets(in_targets): + targets = set() + for target in in_targets: + if not target: + continue + if target == 'all': + targets = targets.union(TARGETS) + elif target in TARGETS: + targets.add(target) + else: + raise RuntimeError('{} is not a valid target'.format(target)) + return list(targets) + + +def targets_parser(args, description): + parser = argparse.ArgumentParser(prog=args.pop(0), description=description) + parser.add_argument( + 'TARGET', + nargs='*', + type=str, + help='Fuzz target(s) to build {{{}}}'.format(', '.join(ALL_TARGETS))) + args, extra = parser.parse_known_args(args) + args.extra = extra + + args.TARGET = parse_targets(args.TARGET) + + return args + + def parse_env_flags(args, flags): """ Look for flags set by environment variables. @@ -424,36 +453,42 @@ def libfuzzer_parser(args): if args.TARGET and args.TARGET not in TARGETS: raise RuntimeError('{} is not a valid target'.format(args.TARGET)) - if not args.corpora: - args.corpora = abs_join(CORPORA_DIR, args.TARGET) - if not args.artifact: - args.artifact = abs_join(CORPORA_DIR, '{}-crash'.format(args.TARGET)) - if not args.seed: - args.seed = abs_join(CORPORA_DIR, '{}-seed'.format(args.TARGET)) - return args -def libfuzzer(args): - try: - args = libfuzzer_parser(args) - except Exception as e: - print(e) - return 1 - target = abs_join(FUZZ_DIR, args.TARGET) +def libfuzzer(target, corpora=None, artifact=None, seed=None, extra_args=None): + if corpora is None: + corpora = abs_join(CORPORA_DIR, target) + if artifact is None: + artifact = abs_join(CORPORA_DIR, '{}-crash'.format(target)) + if seed is None: + seed = abs_join(CORPORA_DIR, '{}-seed'.format(target)) + if extra_args is None: + extra_args = [] - corpora = [create(args.corpora)] - artifact = create(args.artifact) - seed = check(args.seed) + target = abs_join(FUZZ_DIR, target) + + corpora = [create(corpora)] + artifact = create(artifact) + seed = check(seed) corpora += [artifact] if seed is not None: corpora += [seed] cmd = [target, '-artifact_prefix={}/'.format(artifact)] - cmd += corpora + args.extra + cmd += corpora + extra_args print(' '.join(cmd)) - subprocess.call(cmd) + subprocess.check_call(cmd) + + +def libfuzzer_cmd(args): + try: + args = libfuzzer_parser(args) + except Exception as e: + print(e) + return 1 + libfuzzer(args.TARGET, args.corpora, args.artifact, args.seed, args.extra) return 0 @@ -518,39 +553,15 @@ def afl(args): return 0 -def regression_parser(args): - description = """ - Runs one or more regression tests. - The fuzzer should have been built with with - LIB_FUZZING_ENGINE='libregression.a'. - Takes input from CORPORA. - """ - parser = argparse.ArgumentParser(prog=args.pop(0), description=description) - parser.add_argument( - 'TARGET', - nargs='*', - type=str, - help='Fuzz target(s) to build {{{}}}'.format(', '.join(ALL_TARGETS))) - args = parser.parse_args(args) - - targets = set() - for target in args.TARGET: - if not target: - continue - if target == 'all': - targets = targets.union(TARGETS) - elif target in TARGETS: - targets.add(target) - else: - raise RuntimeError('{} is not a valid target'.format(target)) - args.TARGET = list(targets) - - return args - - def regression(args): try: - args = regression_parser(args) + description = """ + Runs one or more regression tests. + The fuzzer should have been built with with + LIB_FUZZING_ENGINE='libregression.a'. + Takes input from CORPORA. + """ + args = targets_parser(args, description) except Exception as e: print(e) return 1 @@ -673,6 +684,52 @@ def gen(args): return 0 +def minimize(args): + try: + description = """ + Runs a libfuzzer fuzzer with -merge=1 to build a minimal corpus in + TARGET_seed_corpus. All extra args are passed to libfuzzer. + """ + args = targets_parser(args, description) + except Exception as e: + print(e) + return 1 + + for target in args.TARGET: + # Merge the corpus + anything else into the seed_corpus + corpus = abs_join(CORPORA_DIR, target) + seed_corpus = abs_join(CORPORA_DIR, "{}_seed_corpus".format(target)) + extra_args = [corpus, "-merge=1"] + args.extra + libfuzzer(target, corpora=seed_corpus, extra_args=extra_args) + seeds = set(os.listdir(seed_corpus)) + # Copy all crashes directly into the seed_corpus if not already present + crashes = abs_join(CORPORA_DIR, '{}-crash'.format(target)) + for crash in os.listdir(crashes): + if crash not in seeds: + shutil.copy(abs_join(crashes, crash), seed_corpus) + seeds.add(crash) + + +def zip_cmd(args): + try: + description = """ + Zips up the seed corpus. + """ + args = targets_parser(args, description) + except Exception as e: + print(e) + return 1 + + for target in args.TARGET: + # Zip the seed_corpus + seed_corpus = abs_join(CORPORA_DIR, "{}_seed_corpus".format(target)) + seeds = [abs_join(seed_corpus, f) for f in os.listdir(seed_corpus)] + zip_file = "{}.zip".format(seed_corpus) + cmd = ["zip", "-q", "-j", "-9", zip_file] + print(' '.join(cmd + [abs_join(seed_corpus, '*')])) + subprocess.check_call(cmd + seeds) + + def short_help(args): name = args[0] print("Usage: {} [OPTIONS] COMMAND [ARGS]...\n".format(name)) @@ -690,6 +747,8 @@ def help(args): print("\tafl\t\tRun an AFL fuzzer") print("\tregression\tRun a regression test") print("\tgen\t\tGenerate a seed corpus for a fuzzer") + print("\tminimize\tMinimize the test corpora") + print("\tzip\t\tZip the minimized corpora up") def main(): @@ -705,13 +764,17 @@ def main(): if command == "build": return build(args) if command == "libfuzzer": - return libfuzzer(args) + return libfuzzer_cmd(args) if command == "regression": return regression(args) if command == "afl": return afl(args) if command == "gen": return gen(args) + if command == "minimize": + return minimize(args) + if command == "zip": + return zip_cmd(args) short_help(args) print("Error: No such command {} (pass -h for help)".format(command)) return 1