libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

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)"