Changeset View
Changeset View
Standalone View
Standalone View
test/fuzz/test_runner.py
Show First 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | parser.add_argument( | ||||
'target', | 'target', | ||||
nargs='*', | nargs='*', | ||||
help='The target(s) to run. Default is to run all targets.', | help='The target(s) to run. Default is to run all targets.', | ||||
) | ) | ||||
parser.add_argument( | parser.add_argument( | ||||
'--m_dir', | '--m_dir', | ||||
help='Merge inputs from this directory into the seed_dir. Needs /target subdirectory.', | help='Merge inputs from this directory into the seed_dir. Needs /target subdirectory.', | ||||
) | ) | ||||
parser.add_argument( | |||||
'-g', | |||||
'--generate', | |||||
action='store_true', | |||||
help='Create new corpus seeds (or extend the existing ones) by running' | |||||
' the given targets for a finite number of times. Outputs them to' | |||||
' the passed seed_dir.' | |||||
) | |||||
args = parser.parse_args() | args = parser.parse_args() | ||||
# Set up logging | # Set up logging | ||||
logging.basicConfig( | logging.basicConfig( | ||||
format='%(message)s', | format='%(message)s', | ||||
level=int(args.loglevel) if args.loglevel.isdigit( | level=int(args.loglevel) if args.loglevel.isdigit( | ||||
) else args.loglevel.upper(), | ) else args.loglevel.upper(), | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | def main(): | ||||
test_list_selection.sort() | test_list_selection.sort() | ||||
logging.info( | logging.info( | ||||
"{} of {} detected fuzz target(s) selected: {}".format( | "{} of {} detected fuzz target(s) selected: {}".format( | ||||
len(test_list_selection), | len(test_list_selection), | ||||
len(test_list_all), | len(test_list_all), | ||||
" ".join(test_list_selection))) | " ".join(test_list_selection))) | ||||
if not args.generate: | |||||
test_list_seedless = [] | test_list_seedless = [] | ||||
for t in test_list_selection: | for t in test_list_selection: | ||||
corpus_path = os.path.join(args.seed_dir, t) | corpus_path = os.path.join(args.seed_dir, t) | ||||
if not os.path.exists(corpus_path) or len( | if not os.path.exists(corpus_path) or len( | ||||
os.listdir(corpus_path)) == 0: | os.listdir(corpus_path)) == 0: | ||||
test_list_seedless.append(t) | test_list_seedless.append(t) | ||||
test_list_seedless.sort() | test_list_seedless.sort() | ||||
if test_list_seedless: | if test_list_seedless: | ||||
logging.info( | logging.info( | ||||
"Fuzzing harnesses lacking a seed corpus: {}".format( | "Fuzzing harnesses lacking a seed corpus: {}".format( | ||||
" ".join(test_list_seedless) | " ".join(test_list_seedless) | ||||
) | ) | ||||
) | ) | ||||
logging.info( | logging.info( | ||||
"Please consider adding a fuzz seed corpus at https://github.com/Bitcoin-ABC/qa-assets") | "Please consider adding a fuzz seed corpus at https://github.com/Bitcoin-ABC/qa-assets") | ||||
try: | try: | ||||
help_output = subprocess.run( | help_output = subprocess.run( | ||||
args=[ | args=[ | ||||
os.path.join(test_dir, test_list_selection[0]), | os.path.join(test_dir, test_list_selection[0]), | ||||
'-help=1', | '-help=1', | ||||
], | ], | ||||
timeout=20, | timeout=20, | ||||
check=True, | check=True, | ||||
stderr=subprocess.PIPE, | stderr=subprocess.PIPE, | ||||
universal_newlines=True, | universal_newlines=True, | ||||
).stderr | ).stderr | ||||
if "libFuzzer" not in help_output: | if "libFuzzer" not in help_output: | ||||
logging.error("Must be built with libFuzzer") | logging.error("Must be built with libFuzzer") | ||||
sys.exit(1) | sys.exit(1) | ||||
except subprocess.TimeoutExpired: | except subprocess.TimeoutExpired: | ||||
logging.error( | logging.error( | ||||
"subprocess timed out: Currently only libFuzzer is supported") | "subprocess timed out: Currently only libFuzzer is supported") | ||||
sys.exit(1) | sys.exit(1) | ||||
with ThreadPoolExecutor(max_workers=args.par) as fuzz_pool: | with ThreadPoolExecutor(max_workers=args.par) as fuzz_pool: | ||||
if args.generate: | |||||
return generate_corpus_seeds( | |||||
fuzz_pool=fuzz_pool, | |||||
test_dir=test_dir, | |||||
seed_dir=args.seed_dir, | |||||
targets=test_list_selection, | |||||
) | |||||
if args.m_dir: | if args.m_dir: | ||||
merge_inputs( | merge_inputs( | ||||
fuzz_pool=fuzz_pool, | fuzz_pool=fuzz_pool, | ||||
corpus=args.seed_dir, | corpus=args.seed_dir, | ||||
test_list=test_list_selection, | test_list=test_list_selection, | ||||
test_dir=test_dir, | test_dir=test_dir, | ||||
merge_dir=args.m_dir, | merge_dir=args.m_dir, | ||||
) | ) | ||||
return | return | ||||
run_once( | run_once( | ||||
fuzz_pool=fuzz_pool, | fuzz_pool=fuzz_pool, | ||||
corpus=args.seed_dir, | corpus=args.seed_dir, | ||||
test_list=test_list_selection, | test_list=test_list_selection, | ||||
test_dir=test_dir, | test_dir=test_dir, | ||||
use_valgrind=args.valgrind, | use_valgrind=args.valgrind, | ||||
) | ) | ||||
def generate_corpus_seeds(*, fuzz_pool, test_dir, seed_dir, targets): | |||||
"""Generates new corpus seeds. | |||||
Run {targets} without input, and outputs the generated corpus seeds to | |||||
{seed_dir}. | |||||
""" | |||||
logging.info("Generating corpus seeds to {}".format(seed_dir)) | |||||
def job(command): | |||||
logging.debug("Running '{}'\n".format(" ".join(command))) | |||||
logging.debug("Command '{}' output:\n'{}'\n".format( | |||||
' '.join(command), | |||||
subprocess.run(command, check=True, stderr=subprocess.PIPE, | |||||
universal_newlines=True).stderr | |||||
)) | |||||
futures = [] | |||||
for target in targets: | |||||
target_seed_dir = os.path.join(seed_dir, target) | |||||
os.makedirs(target_seed_dir, exist_ok=True) | |||||
command = [ | |||||
os.path.join(test_dir, target), | |||||
"-runs=100000", | |||||
target_seed_dir, | |||||
] | |||||
futures.append(fuzz_pool.submit(job, command)) | |||||
for future in as_completed(futures): | |||||
future.result() | |||||
def merge_inputs(*, fuzz_pool, corpus, test_list, test_dir, merge_dir): | def merge_inputs(*, fuzz_pool, corpus, test_list, test_dir, merge_dir): | ||||
logging.info( | logging.info( | ||||
"Merge the inputs in the passed dir into the seed_dir. Passed dir {}".format(merge_dir)) | "Merge the inputs in the passed dir into the seed_dir. Passed dir {}".format(merge_dir)) | ||||
jobs = [] | jobs = [] | ||||
for t in test_list: | for t in test_list: | ||||
args = [ | args = [ | ||||
os.path.join(test_dir, t), | os.path.join(test_dir, t), | ||||
'-merge=1', | '-merge=1', | ||||
▲ Show 20 Lines • Show All 70 Lines • Show Last 20 Lines |