[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.
dev
Nick Terrell 2017-09-25 11:27:33 -07:00
parent 14946af10c
commit 1c23b64049
1 changed files with 114 additions and 51 deletions

View File

@ -82,6 +82,35 @@ def tmpdir():
shutil.rmtree(dirpath, ignore_errors=True) 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): def parse_env_flags(args, flags):
""" """
Look for flags set by environment variables. Look for flags set by environment variables.
@ -424,36 +453,42 @@ def libfuzzer_parser(args):
if args.TARGET and args.TARGET not in TARGETS: if args.TARGET and args.TARGET not in TARGETS:
raise RuntimeError('{} is not a valid target'.format(args.TARGET)) 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 return args
def libfuzzer(args): def libfuzzer(target, corpora=None, artifact=None, seed=None, extra_args=None):
try: if corpora is None:
args = libfuzzer_parser(args) corpora = abs_join(CORPORA_DIR, target)
except Exception as e: if artifact is None:
print(e) artifact = abs_join(CORPORA_DIR, '{}-crash'.format(target))
return 1 if seed is None:
target = abs_join(FUZZ_DIR, args.TARGET) seed = abs_join(CORPORA_DIR, '{}-seed'.format(target))
if extra_args is None:
extra_args = []
corpora = [create(args.corpora)] target = abs_join(FUZZ_DIR, target)
artifact = create(args.artifact)
seed = check(args.seed) corpora = [create(corpora)]
artifact = create(artifact)
seed = check(seed)
corpora += [artifact] corpora += [artifact]
if seed is not None: if seed is not None:
corpora += [seed] corpora += [seed]
cmd = [target, '-artifact_prefix={}/'.format(artifact)] cmd = [target, '-artifact_prefix={}/'.format(artifact)]
cmd += corpora + args.extra cmd += corpora + extra_args
print(' '.join(cmd)) 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 return 0
@ -518,39 +553,15 @@ def afl(args):
return 0 return 0
def regression_parser(args): def regression(args):
try:
description = """ description = """
Runs one or more regression tests. Runs one or more regression tests.
The fuzzer should have been built with with The fuzzer should have been built with with
LIB_FUZZING_ENGINE='libregression.a'. LIB_FUZZING_ENGINE='libregression.a'.
Takes input from CORPORA. Takes input from CORPORA.
""" """
parser = argparse.ArgumentParser(prog=args.pop(0), description=description) args = targets_parser(args, 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)
except Exception as e: except Exception as e:
print(e) print(e)
return 1 return 1
@ -673,6 +684,52 @@ def gen(args):
return 0 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): def short_help(args):
name = args[0] name = args[0]
print("Usage: {} [OPTIONS] COMMAND [ARGS]...\n".format(name)) print("Usage: {} [OPTIONS] COMMAND [ARGS]...\n".format(name))
@ -690,6 +747,8 @@ def help(args):
print("\tafl\t\tRun an AFL fuzzer") print("\tafl\t\tRun an AFL fuzzer")
print("\tregression\tRun a regression test") print("\tregression\tRun a regression test")
print("\tgen\t\tGenerate a seed corpus for a fuzzer") 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(): def main():
@ -705,13 +764,17 @@ def main():
if command == "build": if command == "build":
return build(args) return build(args)
if command == "libfuzzer": if command == "libfuzzer":
return libfuzzer(args) return libfuzzer_cmd(args)
if command == "regression": if command == "regression":
return regression(args) return regression(args)
if command == "afl": if command == "afl":
return afl(args) return afl(args)
if command == "gen": if command == "gen":
return gen(args) return gen(args)
if command == "minimize":
return minimize(args)
if command == "zip":
return zip_cmd(args)
short_help(args) short_help(args)
print("Error: No such command {} (pass -h for help)".format(command)) print("Error: No such command {} (pass -h for help)".format(command))
return 1 return 1