gen_fuzz_seeds.sh (4702B)
1 #!/bin/bash 2 # This file is in the public domain. 3 # 4 # Extra fuzzing seed inputs, for targets whose uncovered code is 5 # concentrated in format variants that src/plugins/testdata/ does not 6 # contain. See src/fuzz/CAMPAIGN.md section 8 for the measurement that 7 # justifies each one. 8 # 9 # ./contrib/gen_fuzz_seeds.sh OUTDIR 10 # 11 # writes OUTDIR/<target>/ ready to be merged into a campaign corpus, or 12 # zipped into <target>_seed_corpus.zip next to the built targets. 13 # Needs qemu-utils (qemu-img) and GNU tar. 14 # 15 # The two targets the 2026-08-07 measurement found to be corpus-starved 16 # rather than time-starved: 17 # 18 # fuzz_diskimage 277 uncovered regions, 73% coverage, because the 19 # plugin dispatches on four container formats and 20 # testdata/ holds exactly one small sample of each. 21 # fuzz_tar 99 uncovered regions, 72% coverage, one plain 22 # ustar archive in testdata/. 23 # 24 # These are campaign seeds only. They deliberately do NOT go into 25 # src/plugins/testdata/, because the plugin tests assert on the exact 26 # metadata of the files there. 27 # 28 # Every harness input is the file image behind LE_FUZZ_EC_PREFIX bytes 29 # of configuration; all-zero selects what production does (fuzz_ec.h). 30 # 31 set -u 32 OUT="${1:?usage: gen_extra_seeds.sh OUTDIR}" 33 PREFIX_LEN=4 34 35 work=$(mktemp -d) 36 trap 'rm -rf "${work}"' EXIT 37 38 emit () # emit <target> <file> 39 { 40 local t="$1" f="$2" d="${OUT}/$1" 41 [ -s "${f}" ] || return 0 42 mkdir -p "${d}" 43 { head -c "${PREFIX_LEN}" /dev/zero; cat "${f}"; } \ 44 > "${d}/extra-$(basename "${f}")" 45 } 46 47 # --- diskimage ------------------------------------------------------- 48 q () { qemu-img create "$@" >/dev/null 2>&1; } 49 50 # QCOW2: v2 and v3, several cluster sizes, backed and standalone, with 51 # the optional feature bits that the plugin has separate branches for. 52 for cs in 512 4096 65536; do 53 q -f qcow2 -o "cluster_size=${cs},compat=1.1" "${work}/q3-${cs}.qcow2" 4M 54 emit fuzz_diskimage "${work}/q3-${cs}.qcow2" 55 q -f qcow2 -o "cluster_size=${cs},compat=0.10" "${work}/q2-${cs}.qcow2" 4M 56 emit fuzz_diskimage "${work}/q2-${cs}.qcow2" 57 done 58 q -f qcow2 -o cluster_size=512,lazy_refcounts=on,compat=1.1 \ 59 "${work}/q-lazy.qcow2" 4M 60 emit fuzz_diskimage "${work}/q-lazy.qcow2" 61 q -f qcow2 -o cluster_size=512,compression_type=zstd,compat=1.1 \ 62 "${work}/q-zstd.qcow2" 4M 63 emit fuzz_diskimage "${work}/q-zstd.qcow2" 64 q -f qcow2 -o cluster_size=512,extended_l2=on,compat=1.1 \ 65 "${work}/q-xl2.qcow2" 4M 66 emit fuzz_diskimage "${work}/q-xl2.qcow2" 67 (cd "${work}" && q -f qcow2 -o cluster_size=512,backing_file=q3-512.qcow2,backing_fmt=qcow2 \ 68 "${work}/q-backed.qcow2" 4M) 69 emit fuzz_diskimage "${work}/q-backed.qcow2" 70 71 # VMDK: every subformat qemu-img will write. The plugin reads the 72 # 80-byte sparse header and the embedded text descriptor, and those 73 # differ per subformat. 74 for sf in monolithicSparse twoGbMaxExtentSparse streamOptimized \ 75 monolithicFlat twoGbMaxExtentFlat; do 76 q -f vmdk -o "subformat=${sf}" "${work}/v-${sf}.vmdk" 4M 77 emit fuzz_diskimage "${work}/v-${sf}.vmdk" 78 done 79 80 # VHDX: block size and log size are both header fields with branches. 81 for bs in 1M 8M; do 82 for ls in 1M 4M; do 83 q -f vhdx -o "block_size=${bs},log_size=${ls}" "${work}/x-${bs}-${ls}.vhdx" 4M 84 emit fuzz_diskimage "${work}/x-${bs}-${ls}.vhdx" 85 done 86 done 87 88 # VHD (vpc): dynamic and fixed footers are different code paths. 89 q -f vpc "${work}/h-dyn.vhd" 4M 90 emit fuzz_diskimage "${work}/h-dyn.vhd" 91 q -f vpc -o subformat=fixed "${work}/h-fix.vhd" 2M 92 emit fuzz_diskimage "${work}/h-fix.vhd" 93 94 # --- tar ------------------------------------------------------------- 95 td="${work}/t" 96 mkdir -p "${td}/sub" 97 echo hello > "${td}/a.txt" 98 printf 'x%.0s' $(seq 1 3000) > "${td}/sub/big.txt" 99 ln -s a.txt "${td}/link" 2>/dev/null || true 100 ln "${td}/a.txt" "${td}/hard" 2>/dev/null || true 101 # A name long enough to need the GNU/pax long-name extension. 102 long="${td}/$(printf 'n%.0s' $(seq 1 120))" 103 echo long > "${long}" 104 105 for fmt in gnu ustar pax v7 oldgnu posix; do 106 tar --format="${fmt}" -cf "${work}/t-${fmt}.tar" -C "${td}" . 2>/dev/null 107 emit fuzz_tar "${work}/t-${fmt}.tar" 108 done 109 # Sparse members and per-file compression flavours. 110 tar --format=gnu --sparse -cf "${work}/t-sparse.tar" -C "${td}" . 2>/dev/null 111 emit fuzz_tar "${work}/t-sparse.tar" 112 # An archive carrying pax extended headers with unusual keywords. 113 tar --format=pax --pax-option=exthdr.name=%d/PaxHeaders/%f,comment=libextractor \ 114 -cf "${work}/t-paxopt.tar" -C "${td}" . 2>/dev/null 115 emit fuzz_tar "${work}/t-paxopt.tar" 116 117 echo "diskimage extra seeds: $(ls -1 "${OUT}/fuzz_diskimage" 2>/dev/null | wc -l)" 118 echo "tar extra seeds: $(ls -1 "${OUT}/fuzz_tar" 2>/dev/null | wc -l)"