Skip to content

Commit 316757d

Browse files
authored
Add a new CI builder that builds fuzz targets (#5352)
This adds a new CI builder that builds with the configuration necessary to build the fully-executable fuzz targets -- not just the fuzz-target _logic_ (which is already built and smoke-tested as part of the unit test suite) -- but the actual instrumented-for-greybox-fuzzing build, with unified-build mode, linked against a fuzz driver and ready to run. This is the configuration that OSS-fuzz runs, and it runs a build script that's lightly adapted from the build script OSS-fuzz runs. This is intended to "keep the OSS-fuzz build building".
2 parents e15b643 + f91706f commit 316757d

2 files changed

Lines changed: 135 additions & 81 deletions

File tree

.github/workflows/build.yml

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
complete:
1919
if: always()
20-
needs: [static-checks, build-linux, build-mac]
20+
needs: [static-checks, build-linux, build-linux-fuzz-targets, build-mac]
2121
runs-on:
2222
- namespace-profile-noble-24-04-stellar-core-x64-small
2323
steps:
@@ -194,6 +194,98 @@ jobs:
194194
run: |
195195
echo "${{ github.sha }}" > "build-${{matrix.toolchain}}-${{matrix.protocol}}/.last-tested-commit-sha"
196196
197+
198+
build-linux-fuzz-targets:
199+
if: github.event.repository.visibility != 'private'
200+
runs-on:
201+
- namespace-profile-noble-24-04-stellar-core-x64-large;overrides.cache-tag=config-clang-libfuzzer
202+
steps:
203+
- name: Checkout with Namespace Git mirrors cache
204+
uses: namespacelabs/nscloud-checkout-action@v7
205+
with:
206+
fetch-depth: 1
207+
submodules: recursive
208+
209+
- name: Configure Namespace cache volume
210+
uses: namespacelabs/nscloud-cache-action@v1
211+
with:
212+
path: |
213+
~/.cargo
214+
build-clang-libfuzzer
215+
216+
- name: Check if commit already tested
217+
id: check-last-tested-commit
218+
run: |
219+
LAST_TESTED_COMMIT_SHA_FILE="build-clang-libfuzzer/.last-tested-commit-sha"
220+
if [ -f "$LAST_TESTED_COMMIT_SHA_FILE" ] && [ "$(cat "$LAST_TESTED_COMMIT_SHA_FILE")" = "${{ github.sha }}" ]; then
221+
echo "Commit ${{ github.sha }} already tested successfully, skipping build"
222+
echo "skip=true" >> $GITHUB_OUTPUT
223+
else
224+
echo "skip=false" >> $GITHUB_OUTPUT
225+
fi
226+
227+
- name: Install rustup
228+
if: steps.check-last-tested-commit.outputs.skip != 'true'
229+
run: ./install-rust.sh
230+
231+
- name: Install rustup components
232+
if: steps.check-last-tested-commit.outputs.skip != 'true'
233+
run: rustup component add rustfmt
234+
235+
- uses: stellar/binaries@v50
236+
if: steps.check-last-tested-commit.outputs.skip != 'true'
237+
with:
238+
name: cargo-cache
239+
version: 0.8.3
240+
241+
- uses: stellar/binaries@v50
242+
if: steps.check-last-tested-commit.outputs.skip != 'true'
243+
with:
244+
name: cargo-sweep
245+
version: 0.7.0
246+
247+
# Restore original modification time of files based on the date of the most
248+
# recent commit that modified them as mtimes affect the Go test cache.
249+
- name: Restore modification time of checkout files
250+
if: steps.check-last-tested-commit.outputs.skip != 'true'
251+
shell: bash
252+
run: |
253+
# Set a base, fixed modification time of all directories.
254+
# git-restore-mtime doesn't set the mtime of all directories.
255+
# (see https://github.com/MestreLion/git-tools/issues/47 for details)
256+
touch -m -t '201509301646' $(find . -type d -not -path '.git/*')
257+
# Restore original modification time from git. git clone sets the
258+
# modification time to the current time, but Go tests that access fixtures
259+
# get invalidated if their modification times change.
260+
#
261+
# Note: git-restore-mtime needs to be baked into the image we're
262+
# running on.
263+
git restore-mtime
264+
265+
- name: Build fuzz targets
266+
if: steps.check-last-tested-commit.outputs.skip != 'true'
267+
run: |
268+
export CC=clang
269+
export CXX=clang++
270+
export CFLAGS="-g -fsanitize=fuzzer-no-link,address"
271+
export CXXFLAGS="-g -fsanitize=fuzzer-no-link,address"
272+
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
273+
export OUT=./fuzz-out
274+
rm -rf fuzz-out
275+
mkdir fuzz-out
276+
./build-fuzz.sh
277+
278+
- name: Check that some fuzz targets were built
279+
if: steps.check-last-tested-commit.outputs.skip != 'true' && hashFiles('fuzz-out/fuzz_*') == ''
280+
run: exit 1
281+
282+
- name: Record successful test commit
283+
if: ${{ success() && steps.check-last-tested-commit.outputs.skip != 'true' }}
284+
run: |
285+
echo "${{ github.sha }}" > "build-clang-libfuzzer/.last-tested-commit-sha"
286+
287+
288+
197289
build-mac:
198290
if: github.event.repository.visibility != 'private'
199291
runs-on:

build-fuzz.sh

Lines changed: 42 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,87 +5,49 @@
55

66
# Build script for oss-fuzz integration.
77
#
8-
# This script builds fuzz targets for use with libfuzzer, honggfuzz, or AFL++.
9-
# It follows the oss-fuzz build conventions:
10-
# - Uses environment variables: $CC, $CXX, $CFLAGS, $CXXFLAGS, $LIB_FUZZING_ENGINE, $OUT
11-
# - Produces binaries in $OUT
12-
# - Creates seed corpus archives as fuzz_<target>_seed_corpus.zip
13-
#
14-
# For local testing with libfuzzer:
15-
# export CC=clang
16-
# export CXX=clang++
17-
# export CFLAGS="-g -fsanitize=fuzzer-no-link,address"
18-
# export CXXFLAGS="-g -fsanitize=fuzzer-no-link,address"
19-
# export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
20-
# export OUT=./fuzz-out
21-
# ./build-fuzz.sh
22-
#
23-
# For honggfuzz (local):
24-
# export CC=hfuzz-clang
25-
# export CXX=hfuzz-clang++
26-
# export LIB_FUZZING_ENGINE="" # honggfuzz provides its own
27-
# export OUT=./fuzz-out
28-
# ./build-fuzz.sh
29-
#
30-
# Outputs:
31-
# $OUT/fuzz_tx - Transaction application fuzzer
32-
# $OUT/fuzz_overlay - Overlay message fuzzer
33-
# $OUT/fuzz_tx_seed_corpus.zip - Seed corpus for tx fuzzer
34-
# $OUT/fuzz_overlay_seed_corpus.zip - Seed corpus for overlay fuzzer
35-
36-
set -eux
37-
38-
# Get source directory (where this script lives)
39-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
40-
SRC="${SRC:-$SCRIPT_DIR}"
41-
42-
# Create output directory
43-
: "${OUT:=./fuzz-out}"
44-
mkdir -p "$OUT"
45-
46-
# Change to source directory
47-
cd "$SRC"
48-
49-
# Run autogen if configure doesn't exist
50-
if [ ! -f configure ]; then
51-
./autogen.sh
8+
# This script is copy of the "build.sh" integration script
9+
# that OSS-fuzz uses to build fuzz targets for running on their infrastructure
10+
# very slightly modified to work with our CI (using ccache + cached build dirs)
11+
12+
SRC_DIR="$(pwd)"
13+
OUT="${OUT:-$SRC_DIR/fuzz-out}"
14+
if [[ "$OUT" != /* ]]; then
15+
OUT="$SRC_DIR/$OUT"
5216
fi
17+
mkdir -p "$OUT"
5318

54-
# Configure with fuzzing support
55-
# The --enable-fuzz flag:
56-
# - Adds FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION define
57-
# - Sets FUZZING_LIBS from LIB_FUZZING_ENGINE
58-
# - Enables building of fuzz_* targets (via ENABLE_FUZZ in Makefile.am)
59-
./configure \
60-
--enable-fuzz \
61-
--without-postgres
62-
63-
# Build fuzz targets using automake rules
64-
# This builds fuzz_tx, fuzz_overlay, and Soroban targets with proper FUZZ_TARGET_NAME defines
65-
make -j"$(nproc)" fuzz-targets
66-
67-
# Install fuzz targets to $OUT
68-
# Install all fuzz targets (C++ and Soroban)
69-
for target in tx overlay soroban_expr soroban_wasmi; do
70-
if [ -f "src/fuzz_$target" ]; then
71-
cp "src/fuzz_$target" "$OUT/"
72-
fi
73-
done
74-
75-
# Generate and package seed corpus (if stellar-core supports it)
76-
# For now, create empty corpus directories
77-
mkdir -p corpus/tx corpus/overlay corpus/soroban_expr corpus/soroban_wasmi
78-
79-
# Create corpus archives (even if empty, oss-fuzz expects them)
80-
# Create corpus archives for all targets (even if empty, oss-fuzz expects them)
81-
for target in tx overlay soroban_expr soroban_wasmi; do
82-
if [ -d "corpus/$target" ] && [ "$(ls -A "corpus/$target" 2>/dev/null)" ]; then
83-
(cd "corpus/$target" && zip -q "$OUT/fuzz_${target}_seed_corpus.zip" *)
84-
else
85-
# Create empty zip if no corpus
86-
touch empty && zip -q "$OUT/fuzz_${target}_seed_corpus.zip" empty && rm empty
19+
mkdir -p "build-clang-libfuzzer"
20+
cd "build-clang-libfuzzer"
21+
22+
#### ccache config
23+
export CCACHE_DIR="$(pwd)/.ccache"
24+
export CCACHE_COMPRESS=true
25+
export CCACHE_COMPRESSLEVEL=9
26+
# cache size should be large enough for a full build
27+
export CCACHE_MAXSIZE=800M
28+
export CCACHE_CPP2=true
29+
30+
# periodically check to see if caches are old and purge them if so
31+
CACHE_MAX_DAYS=30
32+
if [ -d "${CCACHE_DIR}" ] ; then
33+
if [ -n "$(find ${CCACHE_DIR} -mtime +$CACHE_MAX_DAYS -print -quit)" ] ; then
34+
echo Purging old cache dirs "${CCACHE_DIR}" ./target "${HOME}/.cargo/registry" "${HOME}/.cargo/git"
35+
rm -rf "${CCACHE_DIR}" ./target "${HOME}/.cargo/registry" "${HOME}/.cargo/git"
8736
fi
88-
done
37+
fi
8938

90-
echo "Build complete. Fuzz targets in $OUT:"
91-
ls -la "$OUT"
39+
ccache -p
40+
ccache -s
41+
ccache -z
42+
43+
. "${HOME}/.cargo/env"
44+
(cd "${SRC_DIR}" && ./autogen.sh)
45+
46+
# NB: the oss-fuzz driver injects sanitizer flags to CFLAGS, CXXFLAGS
47+
# and RUSTFLAGS. This overlaps with our own support for sanitizers,
48+
# but not fatally. It does require us to enable the unified rust
49+
# build.
50+
"${SRC_DIR}/configure" --enable-fuzz --enable-unified-rust-unsafe-for-production --disable-postgres --enable-ccache
51+
make -j $(nproc)
52+
make -C src fuzz-targets
53+
cp src/fuzz_* "${OUT}"

0 commit comments

Comments
 (0)