xref: /aosp_15_r20/external/zstd/tests/fuzz/fuzz.py (revision 01826a4963a0d8a59bc3812d29bdf0fb76416722)
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