romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 54m ago
tests/fuzz.sh
#!/bin/sh # git-trace fuzz harness — ASan/UBSan build, then three workloads: # unit mutated blobs -> form_get/url_decode/gt_glob/hl_emit/escapers # stdin mutated mbox / ref-lines -> mailrecv, hook-post # net mutated HTTP requests -> the daemon socket # Usage (host): bash tests/fuzz.sh [iterations] (default 300) # Crash reproducers are saved to tests/fuzz-crashes/. set -u ITERS=${1:-300} if [ "${FZ_INNER:-}" != "1" ]; then SRC=$(cd "$(dirname "$0")/.." && (pwd -W 2>/dev/null || pwd)) exec env MSYS_NO_PATHCONV=1 docker run --rm -e FZ_INNER=1 \ -v "$SRC:/src" -w /src git-trace-dev sh /src/tests/fuzz.sh "$ITERS" fi # ---------------- in-container ---------------- export ASAN_OPTIONS=detect_leaks=0:abort_on_error=0 export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ASAN_FLAGS="-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -Wall -Wextra -std=c11 -D_GNU_SOURCE" FZ=/tmp/fz mkdir -p $FZ/corpus $FZ/crashes echo "== asan build ==" make clean >/dev/null 2>&1 make CFLAGS="$ASAN_FLAGS" >/dev/null 2>&1 || { echo "asan build failed"; exit 1; } OBJS=$(ls src/*.o | grep -v 'main\.o') cc $ASAN_FLAGS -Isrc -o $FZ/fuzzdrv tests/fuzzdrv.c $OBJS -lsqlite3 -lpthread \ || { echo "fuzzdrv build failed"; exit 1; } echo " ok asan build" export GIT_TRACE_REPOS=$FZ/repos GIT_TRACE_DB=$FZ/fz.db mkdir -p $GIT_TRACE_REPOS echo pw | ./git-trace useradd alice >/dev/null ./git-trace mkrepo alice demo >/dev/null TOK=$(./git-trace mktoken alice mail | tail -1) echo "== corpus ==" # seeds: two requests, a real format-patch mbox, source code, garbage printf 'GET /alice/demo/log HTTP/1.1\r\nHost: x\r\nCookie: gt_s=abc\r\n\r\n' \ > $FZ/corpus/req_get printf 'POST /login HTTP/1.1\r\nHost: x\r\nContent-Length: 23\r\n\r\nname=alice&pass=pw' \ > $FZ/corpus/req_post cp src/util.c $FZ/corpus/code.c printf 'csrf=%%zz&a=%%41%%00&b=&c=%s' "$(head -c 2000 /dev/urandom | od -An -tx1 | tr -d ' \n')" \ > $FZ/corpus/form head -c 4096 /dev/urandom > $FZ/corpus/bin # hostile glob: '*' + 'a*' x14 + 'b' vs 48 'a' — exponential for a # recursive matcher, must stay instant { printf '*'; i=0; while [ $i -lt 14 ]; do printf 'a*'; i=$((i+1)); done; printf 'b'; head -c 48 /dev/zero | tr '\0' 'a'; } > $FZ/corpus/glob_evil # oversized request: >64K of header lines with no terminator { printf 'GET / HTTP/1.1\r\n'; i=0; while [ $i -lt 4000 ]; do printf 'X-Pad-%d: %s\r\n' "$i" "$(head -c 20 /dev/urandom | od -An -tx1 | tr -d ' \n')"; i=$((i+1)); done; } > $FZ/corpus/req_huge # multipart body: first line = boundary for the fuzzdrv target { printf 'X-Bound42\n'; printf -- '--X-Bound42\r\nContent-Disposition: form-data; name="csrf"\r\n\r\nabc123\r\n'; printf -- '--X-Bound42\r\nContent-Disposition: form-data; name="file"; filename="a.png"\r\nContent-Type: application/octet-stream\r\n\r\n'; head -c 64 /dev/urandom; printf '\r\n--X-Bound42--\r\n'; } > $FZ/corpus/mp { printf 'From: Alice <alice@x>\nX-Git-Trace-Token: %s\n' "$TOK" printf 'Subject: [PATCH] test\nMessage-ID: <m1@x>\n\n' } > $FZ/corpus/mbox.hdr git init -q $FZ/wt && cd $FZ/wt && git config user.email a@x && \ git config user.name a && echo hello > f.c && git add f.c && \ git commit -qm init && git format-patch -1 --stdout >> $FZ/corpus/mbox.hdr mv $FZ/corpus/mbox.hdr $FZ/corpus/mbox cd /src ran() { # ran <tag> <iter> <casefile> <stderrfile> — record sanitizer crashes if grep -qE "AddressSanitizer|runtime error|SUMMARY:" "$4" 2>/dev/null; then cp "$3" "$FZ/crashes/$1-$2" echo " CRASH $1 $2" fi } echo "== unit fuzz ($ITERS iters x $(ls $FZ/corpus/* | wc -l) seeds) ==" for seed in $FZ/corpus/*; do i=0 while [ $i -lt "$ITERS" ]; do perl tests/mutate.pl "$seed" "$i" > $FZ/case timeout 5 $FZ/fuzzdrv $FZ/case 2>$FZ/err rc=$? if [ $rc -eq 124 ]; then cp $FZ/case $FZ/crashes/unit-hang-$i; echo " HANG unit $i" elif [ $rc -ne 0 ]; then ran "unit" "$i" $FZ/case $FZ/err fi i=$((i + 1)) done done echo "== mailrecv fuzz ($ITERS iters) ==" i=0 while [ $i -lt "$ITERS" ]; do perl tests/mutate.pl $FZ/corpus/mbox "$i" > $FZ/case timeout 10 ./git-trace mailrecv alice demo < $FZ/case >$FZ/out 2>$FZ/err if [ $? -eq 124 ]; then cp $FZ/case $FZ/crashes/mail-hang-$i; echo " HANG mail $i" fi ran "mail" "$i" $FZ/case $FZ/err i=$((i + 1)) done echo "== hook-post fuzz ($ITERS iters) ==" printf '0000000000000000000000000000000000000000 %s refs/heads/master\n' \ "$(git -C $FZ/wt rev-parse HEAD)" > $FZ/corpus/refs i=0 while [ $i -lt "$ITERS" ]; do perl tests/mutate.pl $FZ/corpus/refs "$i" > $FZ/case ( cd $GIT_TRACE_REPOS/alice/demo.git && \ timeout 10 /src/git-trace hook-post < $FZ/case >$FZ/out 2>$FZ/err ) if [ $? -eq 124 ]; then cp $FZ/case $FZ/crashes/hook-hang-$i; echo " HANG hook $i" fi grep -qE "AddressSanitizer|runtime error|SUMMARY:" $FZ/err && { cp $FZ/case $FZ/crashes/hook-$i; echo " CRASH hook $i"; } i=$((i + 1)) done echo "== net fuzz ($ITERS iters) ==" ./git-trace serve -p 8085 >$FZ/srv.log 2>&1 & SRV=$! sleep 1 perl tests/fuzznet.pl 127.0.0.1 8085 "$ITERS" $FZ/corpus sleep 1 if kill -0 $SRV 2>/dev/null; then echo " ok server survived" kill $SRV 2>/dev/null else echo " CRASH server died" cp $FZ/srv.log $FZ/crashes/server-death.log fi grep -qE "AddressSanitizer|runtime error|SUMMARY:" $FZ/srv.log && { echo " sanitizer report in server log:"; tail -20 $FZ/srv.log; cp $FZ/srv.log $FZ/crashes/server-sanitizer.log; } echo "== web-commit race hammer ==" # N parallel web commits racing one branch through the ASan build: # the CAS update-ref must keep the ref graph consistent and the # sanitizer must stay silent. ./git-trace serve -p 8086 >$FZ/srv2.log 2>&1 & SRV2=$! sleep 1 if perl tests/fuzzrace.pl 127.0.0.1 8086 alice pw alice demo 4 15 \ > $FZ/race.log 2>&1; then cat $FZ/race.log git -C $GIT_TRACE_REPOS/alice/demo.git fsck >$FZ/fsck.out \ 2>$FZ/fsck.err if [ $? -eq 0 ] && kill -0 $SRV2 2>/dev/null; then echo " ok fsck clean, server alive" else echo " CRASH race: fsck or server" head -5 $FZ/fsck.err cp $FZ/srv2.log $FZ/crashes/race.log 2>/dev/null fi else echo " CRASH race hammer" head -10 $FZ/race.log cp $FZ/srv2.log $FZ/crashes/race.log 2>/dev/null fi kill $SRV2 2>/dev/null grep -qE "AddressSanitizer|runtime error|SUMMARY:" $FZ/srv2.log 2>/dev/null && { echo " sanitizer report in race server log:"; tail -20 $FZ/srv2.log; cp $FZ/srv2.log $FZ/crashes/race-sanitizer.log; } echo "== fuzz done ==" CRASHES=$(find $FZ/crashes -type f | wc -l) if [ "$CRASHES" -gt 0 ]; then mkdir -p /src/tests/fuzz-crashes cp $FZ/crashes/* /src/tests/fuzz-crashes/ 2>/dev/null echo "$CRASHES crash(es) — reproducers in tests/fuzz-crashes/" ls /src/tests/fuzz-crashes/ exit 1 fi make clean >/dev/null 2>&1 # drop ASan objects so a normal make rebuilds echo "clean: 0 crashes"