packages feed

crypton-2.1.3: cbits/tests/fuzz/run.sh

#!/bin/sh
# Generated bytes fed to the code that parses what comes off the wire.
#
# Each harness is an LLVMFuzzerTestOneInput.  With a clang that has the
# libFuzzer runtime, each gets a bounded campaign under ASan and UBSan; with
# one that does not -- Apple's, for instance -- standalone.c replays the
# corpus and a deterministic stream instead, which says the harnesses are
# wired up and nothing about coverage, and says so.
#
# fuzz_canary reads one byte past a buffer when the input opens with four
# particular bytes.  One chance in 2^32 puts that out of reach of random
# input and well within reach of a fuzzer that watches which comparisons it
# got past, so a campaign that does not report it is not fuzzing, and the
# silence of the others would mean nothing.
#
# Usage: cbits/tests/fuzz/run.sh [seconds-per-harness] [build-dir]
set -eu

root=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)
secs=${1:-30}
out=${2:-$(mktemp -d)}
cc=${CC:-cc}
mkdir -p "$out"
cd "$root"

D=cbits/decaf
san="-fsanitize=address,undefined -fno-sanitize-recover=all"

sources_for() {
	case $1 in
	canary)  echo "" ;;
	ed25519) echo "cbits/ed25519/ed25519.c cbits/crypton_sha512.c" ;;
	p256)    echo "cbits/p256/p256.c cbits/p256/p256_ec.c" ;;
	aead)    echo "cbits/crypton_aes.c cbits/aes/generic.c cbits/aes/gf.c" ;;
	decaf)   echo "$D/ed448goldilocks/decaf_all.c $D/ed448goldilocks/eddsa.c
	               $D/ed448goldilocks/scalar.c $D/p448/f_arithmetic.c
	               $D/p448/f_generic.c $D/utils.c
	               $D/p448/arch_ref64/f_impl.c cbits/crypton_sha3.c" ;;
	esac
}
includes_for() {
	case $1 in
	ed25519) echo "-Icbits/ed25519" ;;
	decaf)   echo "-DCRYPTON_DECAF_WORD_BITS=64 -I$D/include -I$D/p448
	               -I$D/include/arch_ref64 -I$D/p448/arch_ref64" ;;
	*)       echo "" ;;
	esac
}

# Is there a libFuzzer to drive these?
have_fuzzer=no
printf '#include <stdint.h>\n#include <stddef.h>\nint LLVMFuzzerTestOneInput(const uint8_t *d, size_t n){(void)d;(void)n;return 0;}\n' \
	> "$out/probe.c"
if $cc -fsanitize=fuzzer,address -o "$out/probe" "$out/probe.c" 2>/dev/null; then
	have_fuzzer=yes
fi

status=0
harnesses="canary ed25519 decaf p256 aead"

for h in $harnesses; do
	# shellcheck disable=SC2046,SC2086
	$cc -O1 -g $san $(test $have_fuzzer = yes && echo -fsanitize=fuzzer) \
		-Icbits -Icbits/include64 $(includes_for $h) \
		-o "$out/fuzz_$h" "cbits/tests/fuzz/fuzz_$h.c" \
		$(test $have_fuzzer = no && echo cbits/tests/fuzz/standalone.c) \
		$(sources_for $h) 2> "$out/$h.cc" || {
		echo "FAIL $h did not build"; sed -n '1,12p' "$out/$h.cc"; status=1; continue
	}

	if [ $have_fuzzer = no ]; then
		"$out/fuzz_$h" cbits/tests/fuzz/corpus 20000 > "$out/$h.log" 2>&1 || true
		if grep -qE "ERROR: |runtime error:" "$out/$h.log"; then
			echo "FOUND $h: the sanitizers reported on replayed input"
			grep -m1 -E "ERROR: |runtime error:" "$out/$h.log" | sed 's/^/    /'
			status=1
		else
			echo "ran  $h ($(tail -1 "$out/$h.log"))"
		fi
		continue
	fi

	mkdir -p "$out/corpus-$h"
	cp cbits/tests/fuzz/corpus/* "$out/corpus-$h/" 2>/dev/null || true
	# -use_value_profile records the operands of comparisons, which is how a
	# fuzzer gets past a check for particular bytes rather than waiting for
	# them to come up at random.  The canary is exactly that check, and the
	# parsers here are full of them.
	"$out/fuzz_$h" "$out/corpus-$h" -max_total_time="$secs" \
		-use_value_profile=1 -print_final_stats=1 \
		> "$out/$h.log" 2>&1 || true

	# What a find looks like.  AddressSanitizer writes "ERROR:", but
	# UndefinedBehaviorSanitizer writes "runtime error:" and nothing else,
	# so a detector that waits for "ERROR:" reads a campaign that found the
	# canary as one that found nothing -- which is what the first three
	# attempts at this file did.  libFuzzer's own line is the one that
	# covers every case: it writes the input out whatever reported.
	found=no
	grep -qE "Test unit written to|ERROR: |runtime error:|deadly signal" \
		"$out/$h.log" && found=yes

	if [ "$h" = canary ]; then
		if [ $found = no ]; then
			echo "FAIL canary: the harness that reads out of bounds on a"
			echo "     four-byte marker was not found in ${secs}s, so this"
			echo "     campaign is not exploring and nothing below counts."
			echo "     What it did do:"
			tail -12 "$out/$h.log" | sed 's/^/       /'
			status=1
		else
			echo "ok   canary: found, so the campaign explores"
		fi
		continue
	fi

	if [ $found = yes ]; then
		echo "FOUND $h: the sanitizers reported"
		grep -m1 -E "ERROR: |runtime error:|deadly signal" "$out/$h.log" |
			sed 's/^/    /'
		sed -n '/#0 /,/#8 /p' "$out/$h.log" | head -12 | sed 's/^/    /'
		status=1
	else
		echo "ok   $h: $(grep -oE 'stat::number_of_executed_units: *[0-9]+' \
			"$out/$h.log" | grep -oE '[0-9]+' | tail -1) inputs, nothing reported"
	fi
done

if [ $have_fuzzer = no ]; then
	echo "skip $cc has no libFuzzer, so the corpus was replayed and nothing explored"
fi
exit $status