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