make_seed_corpus.sh (5177B)
1 #!/bin/bash -eu 2 # 3 # Package src/fuzz/corpus/ into the $OUT/<fuzzer>_seed_corpus.zip files that 4 # OSS-Fuzz/ClusterFuzz picks up automatically. 5 # 6 # This file is in the public domain. 7 # 8 # Usage: make_seed_corpus.sh [SRCDIR] [OUTDIR] 9 # 10 # SRCDIR top of the libmicrohttpd source tree (default: derived from $0) 11 # OUTDIR where the zips are written (default: $OUT, else ./out) 12 # 13 # Corpus layout in src/fuzz/corpus/: 14 # 15 # fuzz_<harness>-NN.bin per-harness seeds; the file name prefix is the 16 # harness the seed belongs to. Inputs are NOT 17 # interchangeable between harnesses: byte 0 of 18 # every harness input selects a different thing. 19 # known-findings/K*.bin byte-exact reproducers for the findings in 20 # src/fuzz/README section 6. Each goes into the 21 # seed corpus of the harness that found it, where 22 # OSS-Fuzz keeps re-running it forever - i.e. it 23 # becomes a permanent regression test. The owning 24 # harness is named in the file, "K<n>-<harness>- 25 # <what>.bin"; a name without one means 26 # fuzz_request, which is what the reproducers 27 # predating the convention are. Reproducers of 28 # findings that are still open are skipped; see 29 # the loop below. 30 # README documentation, not an input; excluded. 31 # 32 # The corpus itself is regenerated from the harnesses' built-in seeds with 33 # make -C src/fuzz refresh-corpus 34 # (which runs "./fuzz_<name> --write-corpus=corpus" for every harness). 35 # known-findings/ is hand-maintained and is never touched by that. 36 37 SELF_DIR="$(cd "$(dirname "$0")" && pwd)" 38 SRCDIR="${1:-$(cd "${SELF_DIR}/../.." && pwd)}" 39 OUTDIR="${2:-${OUT:-$(pwd)/out}}" 40 41 CORPUS="${SRCDIR}/src/fuzz/corpus" 42 FINDINGS="${CORPUS}/known-findings" 43 DISTILLED="${CORPUS}/distilled" 44 PATCHES="${SRCDIR}/patches" 45 46 FUZZERS="fuzz_request fuzz_options fuzz_eventloop fuzz_str fuzz_memorypool fuzz_auth_header fuzz_postprocessor" 47 48 if [ ! -d "${CORPUS}" ]; then 49 echo "ERROR: no corpus directory at ${CORPUS}" >&2 50 exit 1 51 fi 52 53 mkdir -p "${OUTDIR}" 54 55 STAGE="$(mktemp -d "${TMPDIR:-/tmp}/mhd-seed-corpus.XXXXXX")" 56 trap 'rm -rf "${STAGE}"' EXIT 57 58 for fuzzer in ${FUZZERS}; do 59 dir="${STAGE}/${fuzzer}" 60 mkdir -p "${dir}" 61 62 n=0 63 for f in "${CORPUS}/${fuzzer}"-*.bin; do 64 [ -f "${f}" ] || continue 65 cp "${f}" "${dir}/$(basename "${f}")" 66 n=$((n + 1)) 67 done 68 69 # Only the ones whose defect is already fixed are shipped. While a 70 # finding is open its proposed fix is kept as an unapplied diff in 71 # patches/$ID.diff, and its reproducer crashes the target by 72 # construction; shipping it would make every ClusterFuzz run start by 73 # rediscovering a bug that is already written down, and bury the 74 # findings that are actually new. Committing the fix deletes the diff, 75 # and that alone promotes the reproducer to a permanent regression seed 76 # here, with no further edit. 77 # 78 # patches/ therefore does not exist while nothing is open, which is the 79 # normal state; the test below simply never fires then. 80 # 81 # A reproducer belongs to the harness named in its file name, 82 # "K<n>-<harness>-<what>.bin"; the older ones predate that convention 83 # and are all fuzz_request inputs, so a name without a harness means 84 # fuzz_request. 85 if [ -d "${FINDINGS}" ]; then 86 for f in "${FINDINGS}"/*.bin; do 87 [ -f "${f}" ] || continue 88 owner="$(basename "${f}" | sed -n 's/^K[0-9]*-\(fuzz_[a-z_]*\)-.*/\1/p')" 89 [ -n "${owner}" ] || owner="fuzz_request" 90 [ "${owner}" = "${fuzzer}" ] || continue 91 id="$(basename "${f}" | sed -n 's/^\(K[0-9]*\).*/\1/p')" 92 if [ -n "${id}" ] && [ -f "${PATCHES}/${id}.diff" ]; then 93 echo " skipping ${fuzzer} seed $(basename "${f}"): ${id} is open" \ 94 "(${PATCHES}/${id}.diff)" 95 continue 96 fi 97 cp "${f}" "${dir}/known-finding-$(basename "${f}")" 98 n=$((n + 1)) 99 done 100 fi 101 102 # distilled/ is the edge-minimal residue of a fuzzing campaign, named 103 # "<harness>-dNNN.bin" and therefore routed by prefix like the seeds 104 # above. It is by far the largest part of the seed corpus and the 105 # reason a fresh ClusterFuzz run starts near the coverage the last 106 # campaign reached instead of climbing back to it. 107 if [ -d "${DISTILLED}" ]; then 108 for f in "${DISTILLED}/${fuzzer}"-*.bin; do 109 [ -f "${f}" ] || continue 110 cp "${f}" "${dir}/distilled-$(basename "${f}")" 111 n=$((n + 1)) 112 done 113 fi 114 115 if [ "${n}" -eq 0 ]; then 116 echo "ERROR: no seeds found for ${fuzzer} in ${CORPUS}" >&2 117 exit 1 118 fi 119 120 zip_path="${OUTDIR}/${fuzzer}_seed_corpus.zip" 121 rm -f "${zip_path}" 122 # -j: flat archive, which is what ClusterFuzz expects. 123 # -X: no extra file attributes, so the zip stays reproducible. 124 zip -q -j -X "${zip_path}" "${dir}"/* || { 125 echo "ERROR: failed to create ${zip_path}" >&2 126 exit 1 127 } 128 echo " ${zip_path}: ${n} seed(s)" 129 done