2
0

fuzz.py 28 KB

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