From 6148a187e2c41f92ecae6befdf4e2ac39cdd84ff Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 10 Sep 2026 14:08:32 -0700 Subject: [PATCH 1/3] fuzzing: enable the sanitizers in the fuzzing build hfuzz-clang decides whether to add -fsanitize=address and -fsanitize=undefined by looking HFUZZ_CC_ASAN and HFUZZ_CC_UBSAN up with getenv(2) as it compiles. They were passed to configure as arguments, which puts them in the environment of configure and nowhere else, so the compiler invocations make runs never saw them. Export them instead: the fuzzing binary goes from no __asan_report* symbols at all to 48 of them, and the number of coverage guards honggfuzz reports from 13821 to 36707. Signed-off-by: Kir Kolyshkin --- tests/fuzzing/run-tests.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/fuzzing/run-tests.sh b/tests/fuzzing/run-tests.sh index fce3ca08..81d97a30 100755 --- a/tests/fuzzing/run-tests.sh +++ b/tests/fuzzing/run-tests.sh @@ -13,7 +13,10 @@ SINGLE_RUN_TIME=$(( RUN_TIME / N_TESTS )) git config --global --add safe.directory /libocispec git clean -fdx ./autogen.sh -./configure HFUZZ_CC_UBSAN=1 HFUZZ_CC_ASAN=1 CC=hfuzz-clang CPPFLAGS="-D FUZZER" CFLAGS="-ggdb3 -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls" +# hfuzz-clang looks these up with getenv(2) as it compiles, so they have to be +# in the environment of make, not just of configure. +export HFUZZ_CC_UBSAN=1 HFUZZ_CC_ASAN=1 +./configure CC=hfuzz-clang CPPFLAGS="-D FUZZER" CFLAGS="-ggdb3 -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls" make -j "$(nproc)" function run_test { From b5b8465bff697223cd8b459a1d8081b2df1098ab Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 10 Sep 2026 14:13:30 -0700 Subject: [PATCH 2/3] fuzzing: do not build in the caller's tree The script ran git clean -fdx and a full build in /libocispec, which is the directory the caller bind mounts. In CI that is a fresh checkout, but anyone following the same recipe locally loses every untracked file in their working tree and gets a hfuzz-clang build left in it afterwards. Copy the tree to /tmp first and work there. Signed-off-by: Kir Kolyshkin --- tests/fuzzing/run-tests.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/fuzzing/run-tests.sh b/tests/fuzzing/run-tests.sh index 81d97a30..50698675 100755 --- a/tests/fuzzing/run-tests.sh +++ b/tests/fuzzing/run-tests.sh @@ -6,11 +6,20 @@ TIMEOUT=${TIMEOUT:=10} RUN_TIME=${RUN_TIME:=600} VERBOSITY=${VERBOSITY:=} +SRCDIR=/libocispec +# The build happens on a private copy: the fuzzing build is not one to leave +# behind in the tree the caller mounted, and the tree may well be a checkout +# that is being worked on. +WORKDIR=/tmp/libocispec + N_TESTS=7 SINGLE_RUN_TIME=$(( RUN_TIME / N_TESTS )) -git config --global --add safe.directory /libocispec +rm -rf "$WORKDIR" +cp -a "$SRCDIR" "$WORKDIR" +cd "$WORKDIR" +git config --global --add safe.directory "$WORKDIR" git clean -fdx ./autogen.sh # hfuzz-clang looks these up with getenv(2) as it compiles, so they have to be From fe963ee212469542660d68ae9b0ea10977c60182 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 10 Sep 2026 14:13:30 -0700 Subject: [PATCH 3/3] fuzzing: seed the corpus from a real one Every run started from the seven JSON files in tests/data, and honggfuzz stores the units it discovers in its input directory, which is inside the tree the run begins by wiping. Seed the corpus from giuseppe/containers-fuzzing-corpus instead, whose config-json directory is 923 config.json units the three runtime-spec modes can use, and keep it out of the source tree. Signed-off-by: Kir Kolyshkin --- tests/fuzzing/run-tests.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/fuzzing/run-tests.sh b/tests/fuzzing/run-tests.sh index 50698675..d62cab4d 100755 --- a/tests/fuzzing/run-tests.sh +++ b/tests/fuzzing/run-tests.sh @@ -5,6 +5,10 @@ set -xeu TIMEOUT=${TIMEOUT:=10} RUN_TIME=${RUN_TIME:=600} VERBOSITY=${VERBOSITY:=} +# Seed corpus, and where honggfuzz stores the units it discovers. +CORPUS=${CORPUS:=/tmp/corpus} +# Extra corpus to seed from, cloned unless empty. +CORPUS_REPO=${CORPUS_REPO:=https://github.com/giuseppe/containers-fuzzing-corpus} SRCDIR=/libocispec # The build happens on a private copy: the fuzzing build is not one to leave @@ -21,6 +25,14 @@ cp -a "$SRCDIR" "$WORKDIR" cd "$WORKDIR" git config --global --add safe.directory "$WORKDIR" git clean -fdx + +mkdir -p "$CORPUS" +cp -n tests/data/*.json "$CORPUS" || true +if test -n "$CORPUS_REPO"; then + git clone --depth 1 "$CORPUS_REPO" /tmp/extra-corpus + cp -n /tmp/extra-corpus/config-json/* "$CORPUS" || true +fi + ./autogen.sh # hfuzz-clang looks these up with getenv(2) as it compiles, so they have to be # in the environment of make, not just of configure. @@ -32,7 +44,7 @@ function run_test { export FUZZING_MODE=$1 # shellcheck disable=SC2086 - result=$(honggfuzz --exit_upon_crash $VERBOSITY --run_time "$SINGLE_RUN_TIME" --timeout "$TIMEOUT" -T -i tests/data -- src/ocispec/validate 2>&1 | tail -n 2) + result=$(honggfuzz --exit_upon_crash $VERBOSITY --run_time "$SINGLE_RUN_TIME" --timeout "$TIMEOUT" -T -i "$CORPUS" -- src/ocispec/validate 2>&1 | tail -n 2) echo "$result" echo "$result" | grep -q crashes_count:0 }