1*01826a49SYabin Cui#!/usr/bin/env python 2*01826a49SYabin Cui 3*01826a49SYabin Cui# ################################################################ 4*01826a49SYabin Cui# Copyright (c) Meta Platforms, Inc. and affiliates. 5*01826a49SYabin Cui# All rights reserved. 6*01826a49SYabin Cui# 7*01826a49SYabin Cui# This source code is licensed under both the BSD-style license (found in the 8*01826a49SYabin Cui# LICENSE file in the root directory of this source tree) and the GPLv2 (found 9*01826a49SYabin Cui# in the COPYING file in the root directory of this source tree). 10*01826a49SYabin Cui# You may select, at your option, one of the above-listed licenses. 11*01826a49SYabin Cui# ########################################################################## 12*01826a49SYabin Cui 13*01826a49SYabin Cuiimport argparse 14*01826a49SYabin Cuiimport contextlib 15*01826a49SYabin Cuiimport os 16*01826a49SYabin Cuiimport re 17*01826a49SYabin Cuiimport shlex 18*01826a49SYabin Cuiimport shutil 19*01826a49SYabin Cuiimport subprocess 20*01826a49SYabin Cuiimport sys 21*01826a49SYabin Cuiimport tempfile 22*01826a49SYabin Cui 23*01826a49SYabin Cui 24*01826a49SYabin Cuidef abs_join(a, *p): 25*01826a49SYabin Cui return os.path.abspath(os.path.join(a, *p)) 26*01826a49SYabin Cui 27*01826a49SYabin Cui 28*01826a49SYabin Cuiclass InputType(object): 29*01826a49SYabin Cui RAW_DATA = 1 30*01826a49SYabin Cui COMPRESSED_DATA = 2 31*01826a49SYabin Cui DICTIONARY_DATA = 3 32*01826a49SYabin Cui 33*01826a49SYabin Cui 34*01826a49SYabin Cuiclass FrameType(object): 35*01826a49SYabin Cui ZSTD = 1 36*01826a49SYabin Cui BLOCK = 2 37*01826a49SYabin Cui 38*01826a49SYabin Cui 39*01826a49SYabin Cuiclass TargetInfo(object): 40*01826a49SYabin Cui def __init__(self, input_type, frame_type=FrameType.ZSTD): 41*01826a49SYabin Cui self.input_type = input_type 42*01826a49SYabin Cui self.frame_type = frame_type 43*01826a49SYabin Cui 44*01826a49SYabin Cui 45*01826a49SYabin Cui# Constants 46*01826a49SYabin CuiFUZZ_DIR = os.path.abspath(os.path.dirname(__file__)) 47*01826a49SYabin CuiCORPORA_DIR = abs_join(FUZZ_DIR, 'corpora') 48*01826a49SYabin CuiTARGET_INFO = { 49*01826a49SYabin Cui 'simple_round_trip': TargetInfo(InputType.RAW_DATA), 50*01826a49SYabin Cui 'stream_round_trip': TargetInfo(InputType.RAW_DATA), 51*01826a49SYabin Cui 'block_round_trip': TargetInfo(InputType.RAW_DATA, FrameType.BLOCK), 52*01826a49SYabin Cui 'simple_decompress': TargetInfo(InputType.COMPRESSED_DATA), 53*01826a49SYabin Cui 'stream_decompress': TargetInfo(InputType.COMPRESSED_DATA), 54*01826a49SYabin Cui 'block_decompress': TargetInfo(InputType.COMPRESSED_DATA, FrameType.BLOCK), 55*01826a49SYabin Cui 'dictionary_round_trip': TargetInfo(InputType.RAW_DATA), 56*01826a49SYabin Cui 'dictionary_decompress': TargetInfo(InputType.COMPRESSED_DATA), 57*01826a49SYabin Cui 'zstd_frame_info': TargetInfo(InputType.COMPRESSED_DATA), 58*01826a49SYabin Cui 'simple_compress': TargetInfo(InputType.RAW_DATA), 59*01826a49SYabin Cui 'dictionary_loader': TargetInfo(InputType.DICTIONARY_DATA), 60*01826a49SYabin Cui 'raw_dictionary_round_trip': TargetInfo(InputType.RAW_DATA), 61*01826a49SYabin Cui 'dictionary_stream_round_trip': TargetInfo(InputType.RAW_DATA), 62*01826a49SYabin Cui 'decompress_dstSize_tooSmall': TargetInfo(InputType.RAW_DATA), 63*01826a49SYabin Cui 'fse_read_ncount': TargetInfo(InputType.RAW_DATA), 64*01826a49SYabin Cui 'sequence_compression_api': TargetInfo(InputType.RAW_DATA), 65*01826a49SYabin Cui 'seekable_roundtrip': TargetInfo(InputType.RAW_DATA), 66*01826a49SYabin Cui 'huf_round_trip': TargetInfo(InputType.RAW_DATA), 67*01826a49SYabin Cui 'huf_decompress': TargetInfo(InputType.RAW_DATA), 68*01826a49SYabin Cui 'decompress_cross_format': TargetInfo(InputType.RAW_DATA), 69*01826a49SYabin Cui 'generate_sequences': TargetInfo(InputType.RAW_DATA), 70*01826a49SYabin Cui} 71*01826a49SYabin CuiTARGETS = list(TARGET_INFO.keys()) 72*01826a49SYabin CuiALL_TARGETS = TARGETS + ['all'] 73*01826a49SYabin CuiFUZZ_RNG_SEED_SIZE = 4 74*01826a49SYabin Cui 75*01826a49SYabin Cui# Standard environment variables 76*01826a49SYabin CuiCC = os.environ.get('CC', 'cc') 77*01826a49SYabin CuiCXX = os.environ.get('CXX', 'c++') 78*01826a49SYabin CuiCPPFLAGS = os.environ.get('CPPFLAGS', '') 79*01826a49SYabin CuiCFLAGS = os.environ.get('CFLAGS', '-O3') 80*01826a49SYabin CuiCXXFLAGS = os.environ.get('CXXFLAGS', CFLAGS) 81*01826a49SYabin CuiLDFLAGS = os.environ.get('LDFLAGS', '') 82*01826a49SYabin CuiMFLAGS = os.environ.get('MFLAGS', '-j') 83*01826a49SYabin CuiTHIRD_PARTY_SEQ_PROD_OBJ = os.environ.get('THIRD_PARTY_SEQ_PROD_OBJ', '') 84*01826a49SYabin Cui 85*01826a49SYabin Cui# Fuzzing environment variables 86*01826a49SYabin CuiLIB_FUZZING_ENGINE = os.environ.get('LIB_FUZZING_ENGINE', 'libregression.a') 87*01826a49SYabin CuiAFL_FUZZ = os.environ.get('AFL_FUZZ', 'afl-fuzz') 88*01826a49SYabin CuiDECODECORPUS = os.environ.get('DECODECORPUS', 89*01826a49SYabin Cui abs_join(FUZZ_DIR, '..', 'decodecorpus')) 90*01826a49SYabin CuiZSTD = os.environ.get('ZSTD', abs_join(FUZZ_DIR, '..', '..', 'zstd')) 91*01826a49SYabin Cui 92*01826a49SYabin Cui# Sanitizer environment variables 93*01826a49SYabin CuiMSAN_EXTRA_CPPFLAGS = os.environ.get('MSAN_EXTRA_CPPFLAGS', '') 94*01826a49SYabin CuiMSAN_EXTRA_CFLAGS = os.environ.get('MSAN_EXTRA_CFLAGS', '') 95*01826a49SYabin CuiMSAN_EXTRA_CXXFLAGS = os.environ.get('MSAN_EXTRA_CXXFLAGS', '') 96*01826a49SYabin CuiMSAN_EXTRA_LDFLAGS = os.environ.get('MSAN_EXTRA_LDFLAGS', '') 97*01826a49SYabin Cui 98*01826a49SYabin Cui 99*01826a49SYabin Cuidef create(r): 100*01826a49SYabin Cui d = os.path.abspath(r) 101*01826a49SYabin Cui if not os.path.isdir(d): 102*01826a49SYabin Cui os.makedirs(d) 103*01826a49SYabin Cui return d 104*01826a49SYabin Cui 105*01826a49SYabin Cui 106*01826a49SYabin Cuidef check(r): 107*01826a49SYabin Cui d = os.path.abspath(r) 108*01826a49SYabin Cui if not os.path.isdir(d): 109*01826a49SYabin Cui return None 110*01826a49SYabin Cui return d 111*01826a49SYabin Cui 112*01826a49SYabin Cui 113*01826a49SYabin Cui@contextlib.contextmanager 114*01826a49SYabin Cuidef tmpdir(): 115*01826a49SYabin Cui dirpath = tempfile.mkdtemp() 116*01826a49SYabin Cui try: 117*01826a49SYabin Cui yield dirpath 118*01826a49SYabin Cui finally: 119*01826a49SYabin Cui shutil.rmtree(dirpath, ignore_errors=True) 120*01826a49SYabin Cui 121*01826a49SYabin Cui 122*01826a49SYabin Cuidef parse_targets(in_targets): 123*01826a49SYabin Cui targets = set() 124*01826a49SYabin Cui for target in in_targets: 125*01826a49SYabin Cui if not target: 126*01826a49SYabin Cui continue 127*01826a49SYabin Cui if target == 'all': 128*01826a49SYabin Cui targets = targets.union(TARGETS) 129*01826a49SYabin Cui elif target in TARGETS: 130*01826a49SYabin Cui targets.add(target) 131*01826a49SYabin Cui else: 132*01826a49SYabin Cui raise RuntimeError('{} is not a valid target'.format(target)) 133*01826a49SYabin Cui return list(targets) 134*01826a49SYabin Cui 135*01826a49SYabin Cui 136*01826a49SYabin Cuidef targets_parser(args, description): 137*01826a49SYabin Cui parser = argparse.ArgumentParser(prog=args.pop(0), description=description) 138*01826a49SYabin Cui parser.add_argument( 139*01826a49SYabin Cui 'TARGET', 140*01826a49SYabin Cui nargs='*', 141*01826a49SYabin Cui type=str, 142*01826a49SYabin Cui help='Fuzz target(s) to build {{{}}}'.format(', '.join(ALL_TARGETS))) 143*01826a49SYabin Cui args, extra = parser.parse_known_args(args) 144*01826a49SYabin Cui args.extra = extra 145*01826a49SYabin Cui 146*01826a49SYabin Cui args.TARGET = parse_targets(args.TARGET) 147*01826a49SYabin Cui 148*01826a49SYabin Cui return args 149*01826a49SYabin Cui 150*01826a49SYabin Cui 151*01826a49SYabin Cuidef parse_env_flags(args, flags): 152*01826a49SYabin Cui """ 153*01826a49SYabin Cui Look for flags set by environment variables. 154*01826a49SYabin Cui """ 155*01826a49SYabin Cui san_flags = ','.join(re.findall('-fsanitize=((?:[a-z]+,?)+)', flags)) 156*01826a49SYabin Cui nosan_flags = ','.join(re.findall('-fno-sanitize=((?:[a-z]+,?)+)', flags)) 157*01826a49SYabin Cui 158*01826a49SYabin Cui def set_sanitizer(sanitizer, default, san, nosan): 159*01826a49SYabin Cui if sanitizer in san and sanitizer in nosan: 160*01826a49SYabin Cui raise RuntimeError('-fno-sanitize={s} and -fsanitize={s} passed'. 161*01826a49SYabin Cui format(s=sanitizer)) 162*01826a49SYabin Cui if sanitizer in san: 163*01826a49SYabin Cui return True 164*01826a49SYabin Cui if sanitizer in nosan: 165*01826a49SYabin Cui return False 166*01826a49SYabin Cui return default 167*01826a49SYabin Cui 168*01826a49SYabin Cui san = set(san_flags.split(',')) 169*01826a49SYabin Cui nosan = set(nosan_flags.split(',')) 170*01826a49SYabin Cui 171*01826a49SYabin Cui args.asan = set_sanitizer('address', args.asan, san, nosan) 172*01826a49SYabin Cui args.msan = set_sanitizer('memory', args.msan, san, nosan) 173*01826a49SYabin Cui args.ubsan = set_sanitizer('undefined', args.ubsan, san, nosan) 174*01826a49SYabin Cui 175*01826a49SYabin Cui args.sanitize = args.asan or args.msan or args.ubsan 176*01826a49SYabin Cui 177*01826a49SYabin Cui return args 178*01826a49SYabin Cui 179*01826a49SYabin Cui 180*01826a49SYabin Cuidef compiler_version(cc, cxx): 181*01826a49SYabin Cui """ 182*01826a49SYabin Cui Determines the compiler and version. 183*01826a49SYabin Cui Only works for clang and gcc. 184*01826a49SYabin Cui """ 185*01826a49SYabin Cui cc_version_bytes = subprocess.check_output([cc, "--version"]) 186*01826a49SYabin Cui cxx_version_bytes = subprocess.check_output([cxx, "--version"]) 187*01826a49SYabin Cui compiler = None 188*01826a49SYabin Cui version = None 189*01826a49SYabin Cui print("{} --version:\n{}".format(cc, cc_version_bytes.decode('ascii'))) 190*01826a49SYabin Cui if b'clang' in cc_version_bytes: 191*01826a49SYabin Cui assert(b'clang' in cxx_version_bytes) 192*01826a49SYabin Cui compiler = 'clang' 193*01826a49SYabin Cui elif b'gcc' in cc_version_bytes or b'GCC' in cc_version_bytes: 194*01826a49SYabin Cui assert(b'gcc' in cxx_version_bytes or b'g++' in cxx_version_bytes) 195*01826a49SYabin Cui compiler = 'gcc' 196*01826a49SYabin Cui if compiler is not None: 197*01826a49SYabin Cui version_regex = b'([0-9]+)\.([0-9]+)\.([0-9]+)' 198*01826a49SYabin Cui version_match = re.search(version_regex, cc_version_bytes) 199*01826a49SYabin Cui version = tuple(int(version_match.group(i)) for i in range(1, 4)) 200*01826a49SYabin Cui return compiler, version 201*01826a49SYabin Cui 202*01826a49SYabin Cui 203*01826a49SYabin Cuidef overflow_ubsan_flags(cc, cxx): 204*01826a49SYabin Cui compiler, version = compiler_version(cc, cxx) 205*01826a49SYabin Cui if compiler == 'gcc' and version < (8, 0, 0): 206*01826a49SYabin Cui return ['-fno-sanitize=signed-integer-overflow'] 207*01826a49SYabin Cui if compiler == 'gcc' or (compiler == 'clang' and version >= (5, 0, 0)): 208*01826a49SYabin Cui return ['-fno-sanitize=pointer-overflow'] 209*01826a49SYabin Cui return [] 210*01826a49SYabin Cui 211*01826a49SYabin Cui 212*01826a49SYabin Cuidef build_parser(args): 213*01826a49SYabin Cui description = """ 214*01826a49SYabin Cui Cleans the repository and builds a fuzz target (or all). 215*01826a49SYabin Cui Many flags default to environment variables (default says $X='y'). 216*01826a49SYabin Cui Options that aren't enabling features default to the correct values for 217*01826a49SYabin Cui zstd. 218*01826a49SYabin Cui Enable sanitizers with --enable-*san. 219*01826a49SYabin Cui For regression testing just build. 220*01826a49SYabin Cui For libFuzzer set LIB_FUZZING_ENGINE and pass --enable-coverage. 221*01826a49SYabin Cui For AFL set CC and CXX to AFL's compilers and set 222*01826a49SYabin Cui LIB_FUZZING_ENGINE='libregression.a'. 223*01826a49SYabin Cui """ 224*01826a49SYabin Cui parser = argparse.ArgumentParser(prog=args.pop(0), description=description) 225*01826a49SYabin Cui parser.add_argument( 226*01826a49SYabin Cui '--lib-fuzzing-engine', 227*01826a49SYabin Cui dest='lib_fuzzing_engine', 228*01826a49SYabin Cui type=str, 229*01826a49SYabin Cui default=LIB_FUZZING_ENGINE, 230*01826a49SYabin Cui help=('The fuzzing engine to use e.g. /path/to/libFuzzer.a ' 231*01826a49SYabin Cui "(default: $LIB_FUZZING_ENGINE='{})".format(LIB_FUZZING_ENGINE))) 232*01826a49SYabin Cui 233*01826a49SYabin Cui fuzz_group = parser.add_mutually_exclusive_group() 234*01826a49SYabin Cui fuzz_group.add_argument( 235*01826a49SYabin Cui '--enable-coverage', 236*01826a49SYabin Cui dest='coverage', 237*01826a49SYabin Cui action='store_true', 238*01826a49SYabin Cui help='Enable coverage instrumentation (-fsanitize-coverage)') 239*01826a49SYabin Cui fuzz_group.add_argument( 240*01826a49SYabin Cui '--enable-fuzzer', 241*01826a49SYabin Cui dest='fuzzer', 242*01826a49SYabin Cui action='store_true', 243*01826a49SYabin Cui help=('Enable clang fuzzer (-fsanitize=fuzzer). When enabled ' 244*01826a49SYabin Cui 'LIB_FUZZING_ENGINE is ignored') 245*01826a49SYabin Cui ) 246*01826a49SYabin Cui 247*01826a49SYabin Cui parser.add_argument( 248*01826a49SYabin Cui '--enable-asan', dest='asan', action='store_true', help='Enable UBSAN') 249*01826a49SYabin Cui parser.add_argument( 250*01826a49SYabin Cui '--enable-ubsan', 251*01826a49SYabin Cui dest='ubsan', 252*01826a49SYabin Cui action='store_true', 253*01826a49SYabin Cui help='Enable UBSAN') 254*01826a49SYabin Cui parser.add_argument( 255*01826a49SYabin Cui '--disable-ubsan-pointer-overflow', 256*01826a49SYabin Cui dest='ubsan_pointer_overflow', 257*01826a49SYabin Cui action='store_false', 258*01826a49SYabin Cui help='Disable UBSAN pointer overflow check (known failure)') 259*01826a49SYabin Cui parser.add_argument( 260*01826a49SYabin Cui '--enable-msan', dest='msan', action='store_true', help='Enable MSAN') 261*01826a49SYabin Cui parser.add_argument( 262*01826a49SYabin Cui '--enable-msan-track-origins', dest='msan_track_origins', 263*01826a49SYabin Cui action='store_true', help='Enable MSAN origin tracking') 264*01826a49SYabin Cui parser.add_argument( 265*01826a49SYabin Cui '--msan-extra-cppflags', 266*01826a49SYabin Cui dest='msan_extra_cppflags', 267*01826a49SYabin Cui type=str, 268*01826a49SYabin Cui default=MSAN_EXTRA_CPPFLAGS, 269*01826a49SYabin Cui help="Extra CPPFLAGS for MSAN (default: $MSAN_EXTRA_CPPFLAGS='{}')". 270*01826a49SYabin Cui format(MSAN_EXTRA_CPPFLAGS)) 271*01826a49SYabin Cui parser.add_argument( 272*01826a49SYabin Cui '--msan-extra-cflags', 273*01826a49SYabin Cui dest='msan_extra_cflags', 274*01826a49SYabin Cui type=str, 275*01826a49SYabin Cui default=MSAN_EXTRA_CFLAGS, 276*01826a49SYabin Cui help="Extra CFLAGS for MSAN (default: $MSAN_EXTRA_CFLAGS='{}')".format( 277*01826a49SYabin Cui MSAN_EXTRA_CFLAGS)) 278*01826a49SYabin Cui parser.add_argument( 279*01826a49SYabin Cui '--msan-extra-cxxflags', 280*01826a49SYabin Cui dest='msan_extra_cxxflags', 281*01826a49SYabin Cui type=str, 282*01826a49SYabin Cui default=MSAN_EXTRA_CXXFLAGS, 283*01826a49SYabin Cui help="Extra CXXFLAGS for MSAN (default: $MSAN_EXTRA_CXXFLAGS='{}')". 284*01826a49SYabin Cui format(MSAN_EXTRA_CXXFLAGS)) 285*01826a49SYabin Cui parser.add_argument( 286*01826a49SYabin Cui '--msan-extra-ldflags', 287*01826a49SYabin Cui dest='msan_extra_ldflags', 288*01826a49SYabin Cui type=str, 289*01826a49SYabin Cui default=MSAN_EXTRA_LDFLAGS, 290*01826a49SYabin Cui help="Extra LDFLAGS for MSAN (default: $MSAN_EXTRA_LDFLAGS='{}')". 291*01826a49SYabin Cui format(MSAN_EXTRA_LDFLAGS)) 292*01826a49SYabin Cui parser.add_argument( 293*01826a49SYabin Cui '--enable-sanitize-recover', 294*01826a49SYabin Cui dest='sanitize_recover', 295*01826a49SYabin Cui action='store_true', 296*01826a49SYabin Cui help='Non-fatal sanitizer errors where possible') 297*01826a49SYabin Cui parser.add_argument( 298*01826a49SYabin Cui '--debug', 299*01826a49SYabin Cui dest='debug', 300*01826a49SYabin Cui type=int, 301*01826a49SYabin Cui default=1, 302*01826a49SYabin Cui help='Set DEBUGLEVEL (default: 1)') 303*01826a49SYabin Cui parser.add_argument( 304*01826a49SYabin Cui '--force-memory-access', 305*01826a49SYabin Cui dest='memory_access', 306*01826a49SYabin Cui type=int, 307*01826a49SYabin Cui default=0, 308*01826a49SYabin Cui help='Set MEM_FORCE_MEMORY_ACCESS (default: 0)') 309*01826a49SYabin Cui parser.add_argument( 310*01826a49SYabin Cui '--fuzz-rng-seed-size', 311*01826a49SYabin Cui dest='fuzz_rng_seed_size', 312*01826a49SYabin Cui type=int, 313*01826a49SYabin Cui default=4, 314*01826a49SYabin Cui help='Set FUZZ_RNG_SEED_SIZE (default: 4)') 315*01826a49SYabin Cui parser.add_argument( 316*01826a49SYabin Cui '--disable-fuzzing-mode', 317*01826a49SYabin Cui dest='fuzzing_mode', 318*01826a49SYabin Cui action='store_false', 319*01826a49SYabin Cui help='Do not define FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION') 320*01826a49SYabin Cui parser.add_argument( 321*01826a49SYabin Cui '--enable-stateful-fuzzing', 322*01826a49SYabin Cui dest='stateful_fuzzing', 323*01826a49SYabin Cui action='store_true', 324*01826a49SYabin Cui help='Reuse contexts between runs (makes reproduction impossible)') 325*01826a49SYabin Cui parser.add_argument( 326*01826a49SYabin Cui '--custom-seq-prod', 327*01826a49SYabin Cui dest='third_party_seq_prod_obj', 328*01826a49SYabin Cui type=str, 329*01826a49SYabin Cui default=THIRD_PARTY_SEQ_PROD_OBJ, 330*01826a49SYabin Cui help='Path to an object file with symbols for fuzzing your sequence producer plugin.') 331*01826a49SYabin Cui parser.add_argument( 332*01826a49SYabin Cui '--cc', 333*01826a49SYabin Cui dest='cc', 334*01826a49SYabin Cui type=str, 335*01826a49SYabin Cui default=CC, 336*01826a49SYabin Cui help="CC (default: $CC='{}')".format(CC)) 337*01826a49SYabin Cui parser.add_argument( 338*01826a49SYabin Cui '--cxx', 339*01826a49SYabin Cui dest='cxx', 340*01826a49SYabin Cui type=str, 341*01826a49SYabin Cui default=CXX, 342*01826a49SYabin Cui help="CXX (default: $CXX='{}')".format(CXX)) 343*01826a49SYabin Cui parser.add_argument( 344*01826a49SYabin Cui '--cppflags', 345*01826a49SYabin Cui dest='cppflags', 346*01826a49SYabin Cui type=str, 347*01826a49SYabin Cui default=CPPFLAGS, 348*01826a49SYabin Cui help="CPPFLAGS (default: $CPPFLAGS='{}')".format(CPPFLAGS)) 349*01826a49SYabin Cui parser.add_argument( 350*01826a49SYabin Cui '--cflags', 351*01826a49SYabin Cui dest='cflags', 352*01826a49SYabin Cui type=str, 353*01826a49SYabin Cui default=CFLAGS, 354*01826a49SYabin Cui help="CFLAGS (default: $CFLAGS='{}')".format(CFLAGS)) 355*01826a49SYabin Cui parser.add_argument( 356*01826a49SYabin Cui '--cxxflags', 357*01826a49SYabin Cui dest='cxxflags', 358*01826a49SYabin Cui type=str, 359*01826a49SYabin Cui default=CXXFLAGS, 360*01826a49SYabin Cui help="CXXFLAGS (default: $CXXFLAGS='{}')".format(CXXFLAGS)) 361*01826a49SYabin Cui parser.add_argument( 362*01826a49SYabin Cui '--ldflags', 363*01826a49SYabin Cui dest='ldflags', 364*01826a49SYabin Cui type=str, 365*01826a49SYabin Cui default=LDFLAGS, 366*01826a49SYabin Cui help="LDFLAGS (default: $LDFLAGS='{}')".format(LDFLAGS)) 367*01826a49SYabin Cui parser.add_argument( 368*01826a49SYabin Cui '--mflags', 369*01826a49SYabin Cui dest='mflags', 370*01826a49SYabin Cui type=str, 371*01826a49SYabin Cui default=MFLAGS, 372*01826a49SYabin Cui help="Extra Make flags (default: $MFLAGS='{}')".format(MFLAGS)) 373*01826a49SYabin Cui parser.add_argument( 374*01826a49SYabin Cui 'TARGET', 375*01826a49SYabin Cui nargs='*', 376*01826a49SYabin Cui type=str, 377*01826a49SYabin Cui help='Fuzz target(s) to build {{{}}}'.format(', '.join(ALL_TARGETS)) 378*01826a49SYabin Cui ) 379*01826a49SYabin Cui args = parser.parse_args(args) 380*01826a49SYabin Cui args = parse_env_flags(args, ' '.join( 381*01826a49SYabin Cui [args.cppflags, args.cflags, args.cxxflags, args.ldflags])) 382*01826a49SYabin Cui 383*01826a49SYabin Cui # Check option sanity 384*01826a49SYabin Cui if args.msan and (args.asan or args.ubsan): 385*01826a49SYabin Cui raise RuntimeError('MSAN may not be used with any other sanitizers') 386*01826a49SYabin Cui if args.msan_track_origins and not args.msan: 387*01826a49SYabin Cui raise RuntimeError('--enable-msan-track-origins requires MSAN') 388*01826a49SYabin Cui if args.sanitize_recover and not args.sanitize: 389*01826a49SYabin Cui raise RuntimeError('--enable-sanitize-recover but no sanitizers used') 390*01826a49SYabin Cui 391*01826a49SYabin Cui return args 392*01826a49SYabin Cui 393*01826a49SYabin Cui 394*01826a49SYabin Cuidef build(args): 395*01826a49SYabin Cui try: 396*01826a49SYabin Cui args = build_parser(args) 397*01826a49SYabin Cui except Exception as e: 398*01826a49SYabin Cui print(e) 399*01826a49SYabin Cui return 1 400*01826a49SYabin Cui # The compilation flags we are setting 401*01826a49SYabin Cui targets = args.TARGET 402*01826a49SYabin Cui cc = args.cc 403*01826a49SYabin Cui cxx = args.cxx 404*01826a49SYabin Cui cppflags = shlex.split(args.cppflags) 405*01826a49SYabin Cui cflags = shlex.split(args.cflags) 406*01826a49SYabin Cui ldflags = shlex.split(args.ldflags) 407*01826a49SYabin Cui cxxflags = shlex.split(args.cxxflags) 408*01826a49SYabin Cui mflags = shlex.split(args.mflags) 409*01826a49SYabin Cui # Flags to be added to both cflags and cxxflags 410*01826a49SYabin Cui common_flags = [ 411*01826a49SYabin Cui '-Werror', 412*01826a49SYabin Cui '-Wno-error=declaration-after-statement', 413*01826a49SYabin Cui '-Wno-error=c++-compat', 414*01826a49SYabin Cui '-Wno-error=deprecated' # C files are sometimes compiled with CXX 415*01826a49SYabin Cui ] 416*01826a49SYabin Cui 417*01826a49SYabin Cui cppflags += [ 418*01826a49SYabin Cui '-DDEBUGLEVEL={}'.format(args.debug), 419*01826a49SYabin Cui '-DMEM_FORCE_MEMORY_ACCESS={}'.format(args.memory_access), 420*01826a49SYabin Cui '-DFUZZ_RNG_SEED_SIZE={}'.format(args.fuzz_rng_seed_size), 421*01826a49SYabin Cui ] 422*01826a49SYabin Cui 423*01826a49SYabin Cui # Set flags for options 424*01826a49SYabin Cui assert not (args.fuzzer and args.coverage) 425*01826a49SYabin Cui if args.coverage: 426*01826a49SYabin Cui common_flags += [ 427*01826a49SYabin Cui '-fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp' 428*01826a49SYabin Cui ] 429*01826a49SYabin Cui if args.fuzzer: 430*01826a49SYabin Cui common_flags += ['-fsanitize=fuzzer'] 431*01826a49SYabin Cui args.lib_fuzzing_engine = '' 432*01826a49SYabin Cui 433*01826a49SYabin Cui mflags += ['LIB_FUZZING_ENGINE={}'.format(args.lib_fuzzing_engine)] 434*01826a49SYabin Cui 435*01826a49SYabin Cui if args.sanitize_recover: 436*01826a49SYabin Cui recover_flags = ['-fsanitize-recover=all'] 437*01826a49SYabin Cui else: 438*01826a49SYabin Cui recover_flags = ['-fno-sanitize-recover=all'] 439*01826a49SYabin Cui if args.sanitize: 440*01826a49SYabin Cui common_flags += recover_flags 441*01826a49SYabin Cui 442*01826a49SYabin Cui if args.msan: 443*01826a49SYabin Cui msan_flags = ['-fsanitize=memory'] 444*01826a49SYabin Cui if args.msan_track_origins: 445*01826a49SYabin Cui msan_flags += ['-fsanitize-memory-track-origins'] 446*01826a49SYabin Cui common_flags += msan_flags 447*01826a49SYabin Cui # Append extra MSAN flags (it might require special setup) 448*01826a49SYabin Cui cppflags += [args.msan_extra_cppflags] 449*01826a49SYabin Cui cflags += [args.msan_extra_cflags] 450*01826a49SYabin Cui cxxflags += [args.msan_extra_cxxflags] 451*01826a49SYabin Cui ldflags += [args.msan_extra_ldflags] 452*01826a49SYabin Cui 453*01826a49SYabin Cui if args.asan: 454*01826a49SYabin Cui common_flags += ['-fsanitize=address'] 455*01826a49SYabin Cui 456*01826a49SYabin Cui if args.ubsan: 457*01826a49SYabin Cui ubsan_flags = ['-fsanitize=undefined'] 458*01826a49SYabin Cui if not args.ubsan_pointer_overflow: 459*01826a49SYabin Cui ubsan_flags += overflow_ubsan_flags(cc, cxx) 460*01826a49SYabin Cui common_flags += ubsan_flags 461*01826a49SYabin Cui 462*01826a49SYabin Cui if args.stateful_fuzzing: 463*01826a49SYabin Cui cppflags += ['-DSTATEFUL_FUZZING'] 464*01826a49SYabin Cui 465*01826a49SYabin Cui if args.third_party_seq_prod_obj: 466*01826a49SYabin Cui cppflags += ['-DFUZZ_THIRD_PARTY_SEQ_PROD'] 467*01826a49SYabin Cui mflags += ['THIRD_PARTY_SEQ_PROD_OBJ={}'.format(args.third_party_seq_prod_obj)] 468*01826a49SYabin Cui 469*01826a49SYabin Cui if args.fuzzing_mode: 470*01826a49SYabin Cui cppflags += ['-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION'] 471*01826a49SYabin Cui 472*01826a49SYabin Cui if args.lib_fuzzing_engine == 'libregression.a': 473*01826a49SYabin Cui targets = ['libregression.a'] + targets 474*01826a49SYabin Cui 475*01826a49SYabin Cui # Append the common flags 476*01826a49SYabin Cui cflags += common_flags 477*01826a49SYabin Cui cxxflags += common_flags 478*01826a49SYabin Cui 479*01826a49SYabin Cui # Prepare the flags for Make 480*01826a49SYabin Cui cc_str = "CC={}".format(cc) 481*01826a49SYabin Cui cxx_str = "CXX={}".format(cxx) 482*01826a49SYabin Cui cppflags_str = "CPPFLAGS={}".format(' '.join(cppflags)) 483*01826a49SYabin Cui cflags_str = "CFLAGS={}".format(' '.join(cflags)) 484*01826a49SYabin Cui cxxflags_str = "CXXFLAGS={}".format(' '.join(cxxflags)) 485*01826a49SYabin Cui ldflags_str = "LDFLAGS={}".format(' '.join(ldflags)) 486*01826a49SYabin Cui 487*01826a49SYabin Cui # Print the flags 488*01826a49SYabin Cui print('MFLAGS={}'.format(' '.join(mflags))) 489*01826a49SYabin Cui print(cc_str) 490*01826a49SYabin Cui print(cxx_str) 491*01826a49SYabin Cui print(cppflags_str) 492*01826a49SYabin Cui print(cflags_str) 493*01826a49SYabin Cui print(cxxflags_str) 494*01826a49SYabin Cui print(ldflags_str) 495*01826a49SYabin Cui 496*01826a49SYabin Cui # Clean and build 497*01826a49SYabin Cui clean_cmd = ['make', 'clean'] + mflags 498*01826a49SYabin Cui print(' '.join(clean_cmd)) 499*01826a49SYabin Cui subprocess.check_call(clean_cmd) 500*01826a49SYabin Cui build_cmd = [ 501*01826a49SYabin Cui 'make', 502*01826a49SYabin Cui '-j', 503*01826a49SYabin Cui cc_str, 504*01826a49SYabin Cui cxx_str, 505*01826a49SYabin Cui cppflags_str, 506*01826a49SYabin Cui cflags_str, 507*01826a49SYabin Cui cxxflags_str, 508*01826a49SYabin Cui ldflags_str, 509*01826a49SYabin Cui ] + mflags + targets 510*01826a49SYabin Cui print(' '.join(build_cmd)) 511*01826a49SYabin Cui subprocess.check_call(build_cmd) 512*01826a49SYabin Cui return 0 513*01826a49SYabin Cui 514*01826a49SYabin Cui 515*01826a49SYabin Cuidef libfuzzer_parser(args): 516*01826a49SYabin Cui description = """ 517*01826a49SYabin Cui Runs a libfuzzer binary. 518*01826a49SYabin Cui Passes all extra arguments to libfuzzer. 519*01826a49SYabin Cui The fuzzer should have been build with LIB_FUZZING_ENGINE pointing to 520*01826a49SYabin Cui libFuzzer.a. 521*01826a49SYabin Cui Generates output in the CORPORA directory, puts crashes in the ARTIFACT 522*01826a49SYabin Cui directory, and takes extra input from the SEED directory. 523*01826a49SYabin Cui To merge AFL's output pass the SEED as AFL's output directory and pass 524*01826a49SYabin Cui '-merge=1'. 525*01826a49SYabin Cui """ 526*01826a49SYabin Cui parser = argparse.ArgumentParser(prog=args.pop(0), description=description) 527*01826a49SYabin Cui parser.add_argument( 528*01826a49SYabin Cui '--corpora', 529*01826a49SYabin Cui type=str, 530*01826a49SYabin Cui help='Override the default corpora dir (default: {})'.format( 531*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET'))) 532*01826a49SYabin Cui parser.add_argument( 533*01826a49SYabin Cui '--artifact', 534*01826a49SYabin Cui type=str, 535*01826a49SYabin Cui help='Override the default artifact dir (default: {})'.format( 536*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET-crash'))) 537*01826a49SYabin Cui parser.add_argument( 538*01826a49SYabin Cui '--seed', 539*01826a49SYabin Cui type=str, 540*01826a49SYabin Cui help='Override the default seed dir (default: {})'.format( 541*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET-seed'))) 542*01826a49SYabin Cui parser.add_argument( 543*01826a49SYabin Cui 'TARGET', 544*01826a49SYabin Cui type=str, 545*01826a49SYabin Cui help='Fuzz target(s) to build {{{}}}'.format(', '.join(TARGETS))) 546*01826a49SYabin Cui args, extra = parser.parse_known_args(args) 547*01826a49SYabin Cui args.extra = extra 548*01826a49SYabin Cui 549*01826a49SYabin Cui if args.TARGET and args.TARGET not in TARGETS: 550*01826a49SYabin Cui raise RuntimeError('{} is not a valid target'.format(args.TARGET)) 551*01826a49SYabin Cui 552*01826a49SYabin Cui return args 553*01826a49SYabin Cui 554*01826a49SYabin Cui 555*01826a49SYabin Cuidef libfuzzer(target, corpora=None, artifact=None, seed=None, extra_args=None): 556*01826a49SYabin Cui if corpora is None: 557*01826a49SYabin Cui corpora = abs_join(CORPORA_DIR, target) 558*01826a49SYabin Cui if artifact is None: 559*01826a49SYabin Cui artifact = abs_join(CORPORA_DIR, '{}-crash'.format(target)) 560*01826a49SYabin Cui if seed is None: 561*01826a49SYabin Cui seed = abs_join(CORPORA_DIR, '{}-seed'.format(target)) 562*01826a49SYabin Cui if extra_args is None: 563*01826a49SYabin Cui extra_args = [] 564*01826a49SYabin Cui 565*01826a49SYabin Cui target = abs_join(FUZZ_DIR, target) 566*01826a49SYabin Cui 567*01826a49SYabin Cui corpora = [create(corpora)] 568*01826a49SYabin Cui artifact = create(artifact) 569*01826a49SYabin Cui seed = check(seed) 570*01826a49SYabin Cui 571*01826a49SYabin Cui corpora += [artifact] 572*01826a49SYabin Cui if seed is not None: 573*01826a49SYabin Cui corpora += [seed] 574*01826a49SYabin Cui 575*01826a49SYabin Cui cmd = [target, '-artifact_prefix={}/'.format(artifact)] 576*01826a49SYabin Cui cmd += corpora + extra_args 577*01826a49SYabin Cui print(' '.join(cmd)) 578*01826a49SYabin Cui subprocess.check_call(cmd) 579*01826a49SYabin Cui 580*01826a49SYabin Cui 581*01826a49SYabin Cuidef libfuzzer_cmd(args): 582*01826a49SYabin Cui try: 583*01826a49SYabin Cui args = libfuzzer_parser(args) 584*01826a49SYabin Cui except Exception as e: 585*01826a49SYabin Cui print(e) 586*01826a49SYabin Cui return 1 587*01826a49SYabin Cui libfuzzer(args.TARGET, args.corpora, args.artifact, args.seed, args.extra) 588*01826a49SYabin Cui return 0 589*01826a49SYabin Cui 590*01826a49SYabin Cui 591*01826a49SYabin Cuidef afl_parser(args): 592*01826a49SYabin Cui description = """ 593*01826a49SYabin Cui Runs an afl-fuzz job. 594*01826a49SYabin Cui Passes all extra arguments to afl-fuzz. 595*01826a49SYabin Cui The fuzzer should have been built with CC/CXX set to the AFL compilers, 596*01826a49SYabin Cui and with LIB_FUZZING_ENGINE='libregression.a'. 597*01826a49SYabin Cui Takes input from CORPORA and writes output to OUTPUT. 598*01826a49SYabin Cui Uses AFL_FUZZ as the binary (set from flag or environment variable). 599*01826a49SYabin Cui """ 600*01826a49SYabin Cui parser = argparse.ArgumentParser(prog=args.pop(0), description=description) 601*01826a49SYabin Cui parser.add_argument( 602*01826a49SYabin Cui '--corpora', 603*01826a49SYabin Cui type=str, 604*01826a49SYabin Cui help='Override the default corpora dir (default: {})'.format( 605*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET'))) 606*01826a49SYabin Cui parser.add_argument( 607*01826a49SYabin Cui '--output', 608*01826a49SYabin Cui type=str, 609*01826a49SYabin Cui help='Override the default AFL output dir (default: {})'.format( 610*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET-afl'))) 611*01826a49SYabin Cui parser.add_argument( 612*01826a49SYabin Cui '--afl-fuzz', 613*01826a49SYabin Cui type=str, 614*01826a49SYabin Cui default=AFL_FUZZ, 615*01826a49SYabin Cui help='AFL_FUZZ (default: $AFL_FUZZ={})'.format(AFL_FUZZ)) 616*01826a49SYabin Cui parser.add_argument( 617*01826a49SYabin Cui 'TARGET', 618*01826a49SYabin Cui type=str, 619*01826a49SYabin Cui help='Fuzz target(s) to build {{{}}}'.format(', '.join(TARGETS))) 620*01826a49SYabin Cui args, extra = parser.parse_known_args(args) 621*01826a49SYabin Cui args.extra = extra 622*01826a49SYabin Cui 623*01826a49SYabin Cui if args.TARGET and args.TARGET not in TARGETS: 624*01826a49SYabin Cui raise RuntimeError('{} is not a valid target'.format(args.TARGET)) 625*01826a49SYabin Cui 626*01826a49SYabin Cui if not args.corpora: 627*01826a49SYabin Cui args.corpora = abs_join(CORPORA_DIR, args.TARGET) 628*01826a49SYabin Cui if not args.output: 629*01826a49SYabin Cui args.output = abs_join(CORPORA_DIR, '{}-afl'.format(args.TARGET)) 630*01826a49SYabin Cui 631*01826a49SYabin Cui return args 632*01826a49SYabin Cui 633*01826a49SYabin Cui 634*01826a49SYabin Cuidef afl(args): 635*01826a49SYabin Cui try: 636*01826a49SYabin Cui args = afl_parser(args) 637*01826a49SYabin Cui except Exception as e: 638*01826a49SYabin Cui print(e) 639*01826a49SYabin Cui return 1 640*01826a49SYabin Cui target = abs_join(FUZZ_DIR, args.TARGET) 641*01826a49SYabin Cui 642*01826a49SYabin Cui corpora = create(args.corpora) 643*01826a49SYabin Cui output = create(args.output) 644*01826a49SYabin Cui 645*01826a49SYabin Cui cmd = [args.afl_fuzz, '-i', corpora, '-o', output] + args.extra 646*01826a49SYabin Cui cmd += [target, '@@'] 647*01826a49SYabin Cui print(' '.join(cmd)) 648*01826a49SYabin Cui subprocess.call(cmd) 649*01826a49SYabin Cui return 0 650*01826a49SYabin Cui 651*01826a49SYabin Cui 652*01826a49SYabin Cuidef regression(args): 653*01826a49SYabin Cui try: 654*01826a49SYabin Cui description = """ 655*01826a49SYabin Cui Runs one or more regression tests. 656*01826a49SYabin Cui The fuzzer should have been built with 657*01826a49SYabin Cui LIB_FUZZING_ENGINE='libregression.a'. 658*01826a49SYabin Cui Takes input from CORPORA. 659*01826a49SYabin Cui """ 660*01826a49SYabin Cui args = targets_parser(args, description) 661*01826a49SYabin Cui except Exception as e: 662*01826a49SYabin Cui print(e) 663*01826a49SYabin Cui return 1 664*01826a49SYabin Cui for target in args.TARGET: 665*01826a49SYabin Cui corpora = create(abs_join(CORPORA_DIR, target)) 666*01826a49SYabin Cui target = abs_join(FUZZ_DIR, target) 667*01826a49SYabin Cui cmd = [target, corpora] 668*01826a49SYabin Cui print(' '.join(cmd)) 669*01826a49SYabin Cui subprocess.check_call(cmd) 670*01826a49SYabin Cui return 0 671*01826a49SYabin Cui 672*01826a49SYabin Cui 673*01826a49SYabin Cuidef gen_parser(args): 674*01826a49SYabin Cui description = """ 675*01826a49SYabin Cui Generate a seed corpus appropriate for TARGET with data generated with 676*01826a49SYabin Cui decodecorpus. 677*01826a49SYabin Cui The fuzz inputs are prepended with a seed before the zstd data, so the 678*01826a49SYabin Cui output of decodecorpus shouldn't be used directly. 679*01826a49SYabin Cui Generates NUMBER samples prepended with FUZZ_RNG_SEED_SIZE random bytes and 680*01826a49SYabin Cui puts the output in SEED. 681*01826a49SYabin Cui DECODECORPUS is the decodecorpus binary, and must already be built. 682*01826a49SYabin Cui """ 683*01826a49SYabin Cui parser = argparse.ArgumentParser(prog=args.pop(0), description=description) 684*01826a49SYabin Cui parser.add_argument( 685*01826a49SYabin Cui '--number', 686*01826a49SYabin Cui '-n', 687*01826a49SYabin Cui type=int, 688*01826a49SYabin Cui default=100, 689*01826a49SYabin Cui help='Number of samples to generate') 690*01826a49SYabin Cui parser.add_argument( 691*01826a49SYabin Cui '--max-size-log', 692*01826a49SYabin Cui type=int, 693*01826a49SYabin Cui default=18, 694*01826a49SYabin Cui help='Maximum sample size to generate') 695*01826a49SYabin Cui parser.add_argument( 696*01826a49SYabin Cui '--seed', 697*01826a49SYabin Cui type=str, 698*01826a49SYabin Cui help='Override the default seed dir (default: {})'.format( 699*01826a49SYabin Cui abs_join(CORPORA_DIR, 'TARGET-seed'))) 700*01826a49SYabin Cui parser.add_argument( 701*01826a49SYabin Cui '--decodecorpus', 702*01826a49SYabin Cui type=str, 703*01826a49SYabin Cui default=DECODECORPUS, 704*01826a49SYabin Cui help="decodecorpus binary (default: $DECODECORPUS='{}')".format( 705*01826a49SYabin Cui DECODECORPUS)) 706*01826a49SYabin Cui parser.add_argument( 707*01826a49SYabin Cui '--zstd', 708*01826a49SYabin Cui type=str, 709*01826a49SYabin Cui default=ZSTD, 710*01826a49SYabin Cui help="zstd binary (default: $ZSTD='{}')".format(ZSTD)) 711*01826a49SYabin Cui parser.add_argument( 712*01826a49SYabin Cui '--fuzz-rng-seed-size', 713*01826a49SYabin Cui type=int, 714*01826a49SYabin Cui default=4, 715*01826a49SYabin Cui help="FUZZ_RNG_SEED_SIZE used for generate the samples (must match)" 716*01826a49SYabin Cui ) 717*01826a49SYabin Cui parser.add_argument( 718*01826a49SYabin Cui 'TARGET', 719*01826a49SYabin Cui type=str, 720*01826a49SYabin Cui help='Fuzz target(s) to build {{{}}}'.format(', '.join(TARGETS))) 721*01826a49SYabin Cui args, extra = parser.parse_known_args(args) 722*01826a49SYabin Cui args.extra = extra 723*01826a49SYabin Cui 724*01826a49SYabin Cui if args.TARGET and args.TARGET not in TARGETS: 725*01826a49SYabin Cui raise RuntimeError('{} is not a valid target'.format(args.TARGET)) 726*01826a49SYabin Cui 727*01826a49SYabin Cui if not args.seed: 728*01826a49SYabin Cui args.seed = abs_join(CORPORA_DIR, '{}-seed'.format(args.TARGET)) 729*01826a49SYabin Cui 730*01826a49SYabin Cui if not os.path.isfile(args.decodecorpus): 731*01826a49SYabin Cui raise RuntimeError("{} is not a file run 'make -C {} decodecorpus'". 732*01826a49SYabin Cui format(args.decodecorpus, abs_join(FUZZ_DIR, '..'))) 733*01826a49SYabin Cui 734*01826a49SYabin Cui return args 735*01826a49SYabin Cui 736*01826a49SYabin Cui 737*01826a49SYabin Cuidef gen(args): 738*01826a49SYabin Cui try: 739*01826a49SYabin Cui args = gen_parser(args) 740*01826a49SYabin Cui except Exception as e: 741*01826a49SYabin Cui print(e) 742*01826a49SYabin Cui return 1 743*01826a49SYabin Cui 744*01826a49SYabin Cui seed = create(args.seed) 745*01826a49SYabin Cui with tmpdir() as compressed, tmpdir() as decompressed, tmpdir() as dict: 746*01826a49SYabin Cui info = TARGET_INFO[args.TARGET] 747*01826a49SYabin Cui 748*01826a49SYabin Cui if info.input_type == InputType.DICTIONARY_DATA: 749*01826a49SYabin Cui number = max(args.number, 1000) 750*01826a49SYabin Cui else: 751*01826a49SYabin Cui number = args.number 752*01826a49SYabin Cui cmd = [ 753*01826a49SYabin Cui args.decodecorpus, 754*01826a49SYabin Cui '-n{}'.format(args.number), 755*01826a49SYabin Cui '-p{}/'.format(compressed), 756*01826a49SYabin Cui '-o{}'.format(decompressed), 757*01826a49SYabin Cui ] 758*01826a49SYabin Cui 759*01826a49SYabin Cui if info.frame_type == FrameType.BLOCK: 760*01826a49SYabin Cui cmd += [ 761*01826a49SYabin Cui '--gen-blocks', 762*01826a49SYabin Cui '--max-block-size-log={}'.format(min(args.max_size_log, 17)) 763*01826a49SYabin Cui ] 764*01826a49SYabin Cui else: 765*01826a49SYabin Cui cmd += ['--max-content-size-log={}'.format(args.max_size_log)] 766*01826a49SYabin Cui 767*01826a49SYabin Cui print(' '.join(cmd)) 768*01826a49SYabin Cui subprocess.check_call(cmd) 769*01826a49SYabin Cui 770*01826a49SYabin Cui if info.input_type == InputType.RAW_DATA: 771*01826a49SYabin Cui print('using decompressed data in {}'.format(decompressed)) 772*01826a49SYabin Cui samples = decompressed 773*01826a49SYabin Cui elif info.input_type == InputType.COMPRESSED_DATA: 774*01826a49SYabin Cui print('using compressed data in {}'.format(compressed)) 775*01826a49SYabin Cui samples = compressed 776*01826a49SYabin Cui else: 777*01826a49SYabin Cui assert info.input_type == InputType.DICTIONARY_DATA 778*01826a49SYabin Cui print('making dictionary data from {}'.format(decompressed)) 779*01826a49SYabin Cui samples = dict 780*01826a49SYabin Cui min_dict_size_log = 9 781*01826a49SYabin Cui max_dict_size_log = max(min_dict_size_log + 1, args.max_size_log) 782*01826a49SYabin Cui for dict_size_log in range(min_dict_size_log, max_dict_size_log): 783*01826a49SYabin Cui dict_size = 1 << dict_size_log 784*01826a49SYabin Cui cmd = [ 785*01826a49SYabin Cui args.zstd, 786*01826a49SYabin Cui '--train', 787*01826a49SYabin Cui '-r', decompressed, 788*01826a49SYabin Cui '--maxdict={}'.format(dict_size), 789*01826a49SYabin Cui '-o', abs_join(dict, '{}.zstd-dict'.format(dict_size)) 790*01826a49SYabin Cui ] 791*01826a49SYabin Cui print(' '.join(cmd)) 792*01826a49SYabin Cui subprocess.check_call(cmd) 793*01826a49SYabin Cui 794*01826a49SYabin Cui # Copy the samples over and prepend the RNG seeds 795*01826a49SYabin Cui for name in os.listdir(samples): 796*01826a49SYabin Cui samplename = abs_join(samples, name) 797*01826a49SYabin Cui outname = abs_join(seed, name) 798*01826a49SYabin Cui with open(samplename, 'rb') as sample: 799*01826a49SYabin Cui with open(outname, 'wb') as out: 800*01826a49SYabin Cui CHUNK_SIZE = 131072 801*01826a49SYabin Cui chunk = sample.read(CHUNK_SIZE) 802*01826a49SYabin Cui while len(chunk) > 0: 803*01826a49SYabin Cui out.write(chunk) 804*01826a49SYabin Cui chunk = sample.read(CHUNK_SIZE) 805*01826a49SYabin Cui return 0 806*01826a49SYabin Cui 807*01826a49SYabin Cui 808*01826a49SYabin Cuidef minimize(args): 809*01826a49SYabin Cui try: 810*01826a49SYabin Cui description = """ 811*01826a49SYabin Cui Runs a libfuzzer fuzzer with -merge=1 to build a minimal corpus in 812*01826a49SYabin Cui TARGET_seed_corpus. All extra args are passed to libfuzzer. 813*01826a49SYabin Cui """ 814*01826a49SYabin Cui args = targets_parser(args, description) 815*01826a49SYabin Cui except Exception as e: 816*01826a49SYabin Cui print(e) 817*01826a49SYabin Cui return 1 818*01826a49SYabin Cui 819*01826a49SYabin Cui for target in args.TARGET: 820*01826a49SYabin Cui # Merge the corpus + anything else into the seed_corpus 821*01826a49SYabin Cui corpus = abs_join(CORPORA_DIR, target) 822*01826a49SYabin Cui seed_corpus = abs_join(CORPORA_DIR, "{}_seed_corpus".format(target)) 823*01826a49SYabin Cui extra_args = [corpus, "-merge=1"] + args.extra 824*01826a49SYabin Cui libfuzzer(target, corpora=seed_corpus, extra_args=extra_args) 825*01826a49SYabin Cui seeds = set(os.listdir(seed_corpus)) 826*01826a49SYabin Cui # Copy all crashes directly into the seed_corpus if not already present 827*01826a49SYabin Cui crashes = abs_join(CORPORA_DIR, '{}-crash'.format(target)) 828*01826a49SYabin Cui for crash in os.listdir(crashes): 829*01826a49SYabin Cui if crash not in seeds: 830*01826a49SYabin Cui shutil.copy(abs_join(crashes, crash), seed_corpus) 831*01826a49SYabin Cui seeds.add(crash) 832*01826a49SYabin Cui 833*01826a49SYabin Cui 834*01826a49SYabin Cuidef zip_cmd(args): 835*01826a49SYabin Cui try: 836*01826a49SYabin Cui description = """ 837*01826a49SYabin Cui Zips up the seed corpus. 838*01826a49SYabin Cui """ 839*01826a49SYabin Cui args = targets_parser(args, description) 840*01826a49SYabin Cui except Exception as e: 841*01826a49SYabin Cui print(e) 842*01826a49SYabin Cui return 1 843*01826a49SYabin Cui 844*01826a49SYabin Cui for target in args.TARGET: 845*01826a49SYabin Cui # Zip the seed_corpus 846*01826a49SYabin Cui seed_corpus = abs_join(CORPORA_DIR, "{}_seed_corpus".format(target)) 847*01826a49SYabin Cui zip_file = "{}.zip".format(seed_corpus) 848*01826a49SYabin Cui cmd = ["zip", "-r", "-q", "-j", "-9", zip_file, "."] 849*01826a49SYabin Cui print(' '.join(cmd)) 850*01826a49SYabin Cui subprocess.check_call(cmd, cwd=seed_corpus) 851*01826a49SYabin Cui 852*01826a49SYabin Cui 853*01826a49SYabin Cuidef list_cmd(args): 854*01826a49SYabin Cui print("\n".join(TARGETS)) 855*01826a49SYabin Cui 856*01826a49SYabin Cui 857*01826a49SYabin Cuidef short_help(args): 858*01826a49SYabin Cui name = args[0] 859*01826a49SYabin Cui print("Usage: {} [OPTIONS] COMMAND [ARGS]...\n".format(name)) 860*01826a49SYabin Cui 861*01826a49SYabin Cui 862*01826a49SYabin Cuidef help(args): 863*01826a49SYabin Cui short_help(args) 864*01826a49SYabin Cui print("\tfuzzing helpers (select a command and pass -h for help)\n") 865*01826a49SYabin Cui print("Options:") 866*01826a49SYabin Cui print("\t-h, --help\tPrint this message") 867*01826a49SYabin Cui print("") 868*01826a49SYabin Cui print("Commands:") 869*01826a49SYabin Cui print("\tbuild\t\tBuild a fuzzer") 870*01826a49SYabin Cui print("\tlibfuzzer\tRun a libFuzzer fuzzer") 871*01826a49SYabin Cui print("\tafl\t\tRun an AFL fuzzer") 872*01826a49SYabin Cui print("\tregression\tRun a regression test") 873*01826a49SYabin Cui print("\tgen\t\tGenerate a seed corpus for a fuzzer") 874*01826a49SYabin Cui print("\tminimize\tMinimize the test corpora") 875*01826a49SYabin Cui print("\tzip\t\tZip the minimized corpora up") 876*01826a49SYabin Cui print("\tlist\t\tList the available targets") 877*01826a49SYabin Cui 878*01826a49SYabin Cui 879*01826a49SYabin Cuidef main(): 880*01826a49SYabin Cui args = sys.argv 881*01826a49SYabin Cui if len(args) < 2: 882*01826a49SYabin Cui help(args) 883*01826a49SYabin Cui return 1 884*01826a49SYabin Cui if args[1] == '-h' or args[1] == '--help' or args[1] == '-H': 885*01826a49SYabin Cui help(args) 886*01826a49SYabin Cui return 1 887*01826a49SYabin Cui command = args.pop(1) 888*01826a49SYabin Cui args[0] = "{} {}".format(args[0], command) 889*01826a49SYabin Cui if command == "build": 890*01826a49SYabin Cui return build(args) 891*01826a49SYabin Cui if command == "libfuzzer": 892*01826a49SYabin Cui return libfuzzer_cmd(args) 893*01826a49SYabin Cui if command == "regression": 894*01826a49SYabin Cui return regression(args) 895*01826a49SYabin Cui if command == "afl": 896*01826a49SYabin Cui return afl(args) 897*01826a49SYabin Cui if command == "gen": 898*01826a49SYabin Cui return gen(args) 899*01826a49SYabin Cui if command == "minimize": 900*01826a49SYabin Cui return minimize(args) 901*01826a49SYabin Cui if command == "zip": 902*01826a49SYabin Cui return zip_cmd(args) 903*01826a49SYabin Cui if command == "list": 904*01826a49SYabin Cui return list_cmd(args) 905*01826a49SYabin Cui short_help(args) 906*01826a49SYabin Cui print("Error: No such command {} (pass -h for help)".format(command)) 907*01826a49SYabin Cui return 1 908*01826a49SYabin Cui 909*01826a49SYabin Cui 910*01826a49SYabin Cuiif __name__ == "__main__": 911*01826a49SYabin Cui sys.exit(main()) 912