Skip to content

Release v4.1.0: version bump and ChangeLog #296

Release v4.1.0: version bump and ChangeLog

Release v4.1.0: version bump and ChangeLog #296

Workflow file for this run

name: Fuzz Testing
on:
schedule:
- cron: '0 4 * * *' # Nightly 4am UTC
workflow_dispatch: # Manual trigger
pull_request:
branches: [ '*' ]
types: [opened, reopened, ready_for_review]
push:
branches: [ master ]
jobs:
# The matrix context is not available in a job-level if:, so route by run
# type here instead: 600s full runs go to the nightly schedule / manual
# dispatch; 60s smoke runs go to non-draft PR open/reopen/ready and merges
# to master. Intermediate PR pushes (synchronize) are not a trigger.
select:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.pick.outputs.matrix }}
steps:
- name: Pick fuzz matrix for ${{ github.event_name }}
id: pick
run: |
# schedule / dispatch -> full (600s); PR / push -> smoke (60s).
case "${{ github.event_name }}" in
schedule|workflow_dispatch) WANT=false ;;
*) WANT=true ;;
esac
PQC="--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
MATRIX=$(jq -nc --argjson want "$WANT" --arg pqc "$PQC" '{
include: [
{name:"fuzz-full", fuzz_time:600, smoke_only:false, wolfssl_extra_flags:"", wolftpm_extra_flags:"", max_len:4096},
{name:"fuzz-smoke", fuzz_time:60, smoke_only:true, wolfssl_extra_flags:"", wolftpm_extra_flags:"", max_len:4096},
{name:"fuzz-full-pqc", fuzz_time:600, smoke_only:false, wolfssl_extra_flags:$pqc, wolftpm_extra_flags:"--enable-v185", max_len:8192},
{name:"fuzz-smoke-pqc", fuzz_time:60, smoke_only:true, wolfssl_extra_flags:$pqc, wolftpm_extra_flags:"--enable-v185", max_len:8192}
] | map(select(.smoke_only == $want))
}')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
fuzz:
needs: select
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.select.outputs.matrix) }}
steps:
- name: Checkout wolfTPM
uses: actions/checkout@v4
- name: ASLR workaround
run: sudo sysctl vm.mmap_rnd_bits=28
- name: Setup wolfSSL with fuzzer support
uses: ./.github/actions/setup-wolfssl
with:
cc: clang
configure-flags: --enable-wolftpm --enable-pkcallbacks --enable-keygen ${{ matrix.wolfssl_extra_flags }}
cflags: -fsanitize=fuzzer-no-link,address -fno-omit-frame-pointer -g -O1 -DWC_RSA_NO_PADDING
ldflags: -fsanitize=address
- name: Build fuzz target
run: |
./autogen.sh
CC=clang ./configure --enable-fwtpm --enable-fuzz \
${{ matrix.wolftpm_extra_flags }} \
CFLAGS="-fsanitize=fuzzer-no-link,address -fno-omit-frame-pointer -g -O1" \
LDFLAGS="-fsanitize=address"
make -j$(nproc)
- name: Generate seed corpus
run: python3 tests/fuzz/gen_corpus.py
- name: Verify v1.85 PQC opcode coverage in corpus
if: contains(matrix.name, 'pqc')
run: |
# Without this guard, fuzz-*-pqc could spend 10 minutes fuzzing
# classical paths with -DWOLFTPM_V185 set and never exercise an
# Encapsulate or SignSequenceComplete opcode. Fail fast if any of
# the 8 new v1.85 command codes is absent from the seed corpus.
# Seeds are binary; opcodes appear as raw 4-byte big-endian
# sequences. Hex-dump the concatenated corpus and grep the
# resulting hex stream for each opcode's bytes.
CORPUS_HEX=$(cat tests/fuzz/corpus/*.bin | xxd -p | tr -d '\n')
MISSING=()
for cc in 000001a3 000001a4 000001a5 000001a6 \
000001a7 000001a8 000001a9 000001aa; do
if ! echo "$CORPUS_HEX" | grep -q "$cc"; then
MISSING+=("0x${cc^^}")
fi
done
if [ ${#MISSING[@]} -gt 0 ]; then
echo "ERROR: PQC seed corpus missing the following command codes:"
printf ' %s\n' "${MISSING[@]}"
echo "Update tests/fuzz/gen_corpus.py to emit a seed for each."
exit 1
fi
echo "All 8 v1.85 PQC opcodes (0x1A3-0x1AA) present in seed corpus."
- name: Run fuzzer
env:
ASAN_OPTIONS: "detect_leaks=1:abort_on_error=1:symbolize=1"
run: |
echo "Fuzzing for ${{ matrix.fuzz_time }} seconds..."
timeout ${{ matrix.fuzz_time }} \
./tests/fuzz/fwtpm_fuzz \
tests/fuzz/corpus/ \
-dict=tests/fuzz/tpm2.dict \
-max_len=${{ matrix.max_len }} \
-timeout=30 \
-rss_limit_mb=2048 \
-print_final_stats=1 \
|| FUZZ_RC=$?
# timeout returns 124 on normal expiry, fuzzer returns 0 on no crash
if [ "${FUZZ_RC:-0}" -eq 124 ] || [ "${FUZZ_RC:-0}" -eq 0 ]; then
echo "Fuzzer completed without crashes"
else
echo "Fuzzer found crashes (exit code $FUZZ_RC)"
ls -la crash-* 2>/dev/null || true
exit 1
fi
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes-${{ matrix.name }}
path: |
crash-*
oom-*
timeout-*
retention-days: 30
if-no-files-found: ignore