git-trace

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"