make_seed_corpus.sh (4610B)
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 PATCHES="${SRCDIR}/patches" 44 45 FUZZERS="fuzz_request fuzz_options fuzz_eventloop fuzz_str fuzz_memorypool fuzz_auth_header fuzz_postprocessor" 46 47 if [ ! -d "${CORPUS}" ]; then 48 echo "ERROR: no corpus directory at ${CORPUS}" >&2 49 exit 1 50 fi 51 52 mkdir -p "${OUTDIR}" 53 54 STAGE="$(mktemp -d "${TMPDIR:-/tmp}/mhd-seed-corpus.XXXXXX")" 55 trap 'rm -rf "${STAGE}"' EXIT 56 57 for fuzzer in ${FUZZERS}; do 58 dir="${STAGE}/${fuzzer}" 59 mkdir -p "${dir}" 60 61 n=0 62 for f in "${CORPUS}/${fuzzer}"-*.bin; do 63 [ -f "${f}" ] || continue 64 cp "${f}" "${dir}/$(basename "${f}")" 65 n=$((n + 1)) 66 done 67 68 # Only the ones whose defect is already fixed are shipped. While a 69 # finding is open its proposed fix is kept as an unapplied diff in 70 # patches/$ID.diff, and its reproducer crashes the target by 71 # construction; shipping it would make every ClusterFuzz run start by 72 # rediscovering a bug that is already written down, and bury the 73 # findings that are actually new. Committing the fix deletes the diff, 74 # and that alone promotes the reproducer to a permanent regression seed 75 # here, with no further edit. 76 # 77 # patches/ therefore does not exist while nothing is open, which is the 78 # normal state; the test below simply never fires then. 79 # 80 # A reproducer belongs to the harness named in its file name, 81 # "K<n>-<harness>-<what>.bin"; the older ones predate that convention 82 # and are all fuzz_request inputs, so a name without a harness means 83 # fuzz_request. 84 if [ -d "${FINDINGS}" ]; then 85 for f in "${FINDINGS}"/*.bin; do 86 [ -f "${f}" ] || continue 87 owner="$(basename "${f}" | sed -n 's/^K[0-9]*-\(fuzz_[a-z_]*\)-.*/\1/p')" 88 [ -n "${owner}" ] || owner="fuzz_request" 89 [ "${owner}" = "${fuzzer}" ] || continue 90 id="$(basename "${f}" | sed -n 's/^\(K[0-9]*\).*/\1/p')" 91 if [ -n "${id}" ] && [ -f "${PATCHES}/${id}.diff" ]; then 92 echo " skipping ${fuzzer} seed $(basename "${f}"): ${id} is open" \ 93 "(${PATCHES}/${id}.diff)" 94 continue 95 fi 96 cp "${f}" "${dir}/known-finding-$(basename "${f}")" 97 n=$((n + 1)) 98 done 99 fi 100 101 if [ "${n}" -eq 0 ]; then 102 echo "ERROR: no seeds found for ${fuzzer} in ${CORPUS}" >&2 103 exit 1 104 fi 105 106 zip_path="${OUTDIR}/${fuzzer}_seed_corpus.zip" 107 rm -f "${zip_path}" 108 # -j: flat archive, which is what ClusterFuzz expects. 109 # -X: no extra file attributes, so the zip stays reproducible. 110 zip -q -j -X "${zip_path}" "${dir}"/* || { 111 echo "ERROR: failed to create ${zip_path}" >&2 112 exit 1 113 } 114 echo " ${zip_path}: ${n} seed(s)" 115 done