[grade=B] build(fuzz): wire transaction_deserialize_fuzz target + CI smoke job
The transaction_deserialize_fuzz harness was committed in fab44bb but never
wired into the CMake build or CI. Wire it up:
src/CMakeLists.txt: add a second target inside the BUILD_FUZZ=ON block.
Uses its OWN link wrapper (link_txdeser.sh) because fuzz_script's wrapper
excludes script.cpp.o from triangles_common (fuzz_script recompiles
script.cpp with clang instrumentation). wallet.cpp.o in trianglesd_objects
calls ExtractDestination / SignSignature / Solver / IsMine — all defined in
script.cpp.o — so excluding it produces 'undefined reference' link errors.
The new wrapper excludes only init.cpp.o (which defines daemon main() and
would conflict with libFuzzer's main). fuzz_script continues to use its
original wrapper; both targets build cleanly with -DBUILD_FUZZ=ON.
.github/workflows/build-all.yml: add test-fuzz-smoke-tx job mirroring
test-fuzz-smoke but for the new target. Runs the fuzzer for 5 minutes
on a fresh empty corpus with ASan+UBSan+libFuzzer, fails the PR if any
crash artifacts are produced.
Verified locally with -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15:
- transaction_deserialize_fuzz links cleanly and runs (smoke: 191781 inline
8-bit counters, 7 NEW_FUNC in 20s)
- fuzz_script continues to build and link (existing target unbroken)
Codex verdict: urn:ump:exyiqbu7gdr2eow5b4osh67xiaserhfg6cz74pnzgwrwj6xr7ypa (grade B)
This commit is contained in:
@@ -275,6 +275,108 @@ jobs:
|
|||||||
name: fuzz-artifacts
|
name: fuzz-artifacts
|
||||||
path: build-fuzz/fuzz_artifacts/
|
path: build-fuzz/fuzz_artifacts/
|
||||||
|
|
||||||
|
test-fuzz-smoke-tx:
|
||||||
|
# libFuzzer smoke test for src/test/fuzz/transaction_deserialize_fuzz.cpp.
|
||||||
|
# Mirrors test-fuzz-smoke but exercises CTransaction deserialization
|
||||||
|
# instead of the script interpreter. Any crash is uploaded as an artifact
|
||||||
|
# and the job fails — fuzz regressions must block the PR.
|
||||||
|
# See src/test/fuzz/transaction_deserialize_fuzz.cpp for harness details.
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
timeout-minutes: 20
|
||||||
|
env:
|
||||||
|
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
||||||
|
UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
||||||
|
SAN_FLAGS: "-fsanitize=address,undefined,fuzzer-no-link -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install clang + dependencies
|
||||||
|
# libFuzzer ships with clang since v6; clang-15 is on the runner.
|
||||||
|
# libgflags-dev: fuzz link line references -lgflags (RocksDB builds
|
||||||
|
# expect gflags as a transitive dep). Without it the link step fails
|
||||||
|
# with "cannot find -lgflags". CI's ubuntu-22.04 runner does NOT ship
|
||||||
|
# it by default.
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang-15 cmake ninja-build \
|
||||||
|
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
||||||
|
libevent-dev libminiupnpc-dev zlib1g-dev \
|
||||||
|
libsnappy-dev liblz4-dev libzstd-dev \
|
||||||
|
libgflags-dev
|
||||||
|
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100
|
||||||
|
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100
|
||||||
|
|
||||||
|
- name: Build RocksDB from source
|
||||||
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
||||||
|
|
||||||
|
- name: Configure with fuzzing + sanitizers
|
||||||
|
# NB: do NOT pass -fsanitize=fuzzer in CMAKE_EXE_LINKER_FLAGS — that
|
||||||
|
# pulls libFuzzer's main() into CMake's compiler-probe linker test
|
||||||
|
# and trips "multiple definition of `main`". The transaction_deserialize_fuzz
|
||||||
|
# target's custom clang++ link step adds -fsanitize=fuzzer in src/CMakeLists.txt
|
||||||
|
# (see BUILD_FUZZ block).
|
||||||
|
# SECP256K1_ASM=OFF: clang-15+ register allocator is sometimes stricter
|
||||||
|
# than clang-14 about the x86_64 inline asm in scalar_4x64_impl.h.
|
||||||
|
run: |
|
||||||
|
cmake -B build-fuzz -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DCMAKE_C_COMPILER=clang \
|
||||||
|
-DCMAKE_CXX_COMPILER=clang++ \
|
||||||
|
-DCMAKE_C_FLAGS="$SAN_FLAGS" \
|
||||||
|
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
||||||
|
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
||||||
|
-DBUILD_QT=OFF \
|
||||||
|
-DBUILD_DAEMON=ON \
|
||||||
|
-DBUILD_TESTS=ON \
|
||||||
|
-DBUILD_FUZZ=ON \
|
||||||
|
-DUSE_UPNP=OFF \
|
||||||
|
-DSECP256K1_ASM=OFF
|
||||||
|
|
||||||
|
- name: Build libtor (embedded Tor static lib)
|
||||||
|
# BUILD_FUZZ pulls in triangles_common + trianglesd_objects (OBJECT lib)
|
||||||
|
# via the fuzz target's CMake deps. The link line references libtor.a,
|
||||||
|
# which the Tor submodule script produces — CMake doesn't build it.
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
||||||
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
||||||
|
bash src/tor/build-libtor.sh
|
||||||
|
|
||||||
|
- name: Build transaction_deserialize_fuzz
|
||||||
|
# CMake target is named `transaction_deserialize_fuzz` (matches
|
||||||
|
# add_custom_target(transaction_deserialize_fuzz ...) in src/CMakeLists.txt).
|
||||||
|
run: cmake --build build-fuzz --target transaction_deserialize_fuzz -j$(nproc)
|
||||||
|
|
||||||
|
- name: Run fuzzer for 5 minutes
|
||||||
|
# -max_total_time=300 hard-caps runtime. Crashes go to artifact
|
||||||
|
# prefix; we upload any artifacts and fail the job if any exist.
|
||||||
|
# The transaction_deserialize_fuzz target does not need a seed
|
||||||
|
# corpus — it accepts arbitrary bytes as a transaction payload.
|
||||||
|
run: |
|
||||||
|
mkdir -p build-fuzz/fuzz_artifacts_tx build-fuzz/fuzz_corpus_tx
|
||||||
|
set +e
|
||||||
|
./build-fuzz/bin/transaction_deserialize_fuzz \
|
||||||
|
-max_total_time=300 \
|
||||||
|
-max_len=200000 \
|
||||||
|
-artifact_prefix=build-fuzz/fuzz_artifacts_tx/ \
|
||||||
|
build-fuzz/fuzz_corpus_tx/ \
|
||||||
|
2>&1 | tee build-fuzz/fuzz_log.txt
|
||||||
|
FUZZ_EXIT=${PIPESTATUS[0]}
|
||||||
|
set -e
|
||||||
|
if [ -n "$(ls -A build-fuzz/fuzz_artifacts_tx/ 2>/dev/null | grep -v '\.tmp$')" ]; then
|
||||||
|
echo "::error::Fuzzer produced crash/leak artifacts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit "$FUZZ_EXIT"
|
||||||
|
|
||||||
|
- name: Upload fuzzer artifacts on success
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
|
with:
|
||||||
|
name: fuzz-artifacts-tx
|
||||||
|
path: build-fuzz/fuzz_artifacts_tx/
|
||||||
|
|
||||||
build-windows-qt:
|
build-windows-qt:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
+133
-1
@@ -775,6 +775,59 @@ if(BUILD_FUZZ)
|
|||||||
set(FUZZ_SRC_FUZZ "${CMAKE_CURRENT_SOURCE_DIR}/test/fuzz/script_fuzz.cpp")
|
set(FUZZ_SRC_FUZZ "${CMAKE_CURRENT_SOURCE_DIR}/test/fuzz/script_fuzz.cpp")
|
||||||
set(FUZZ_SRC_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/script.cpp")
|
set(FUZZ_SRC_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/script.cpp")
|
||||||
|
|
||||||
|
# --- Second fuzz target: transaction_deserialize_fuzz ---
|
||||||
|
# CTransaction is declared in main.h and implemented in main.cpp, which is
|
||||||
|
# part of triangles_common. The harness only needs the transaction
|
||||||
|
# deserialize/serialize surface, not the script interpreter, so we don't
|
||||||
|
# need a separate clang-instrumented copy of any .cpp file — we just link
|
||||||
|
# the gcc-built triangles_common .o files directly. libFuzzer's link line
|
||||||
|
# is compatible with gcc .o files for the non-instrumented units; only the
|
||||||
|
# harness entry point itself needs clang + -fsanitize=fuzzer.
|
||||||
|
set(FUZZ_TX_DESER_OBJ "${FUZZ_OBJ_DIR}/transaction_deserialize_fuzz.cpp.o")
|
||||||
|
set(FUZZ_TX_DESER_BIN_DIR "${CMAKE_BINARY_DIR}/bin")
|
||||||
|
set(FUZZ_TX_DESER_BIN "${FUZZ_TX_DESER_BIN_DIR}/transaction_deserialize_fuzz")
|
||||||
|
set(FUZZ_TX_DESER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/test/fuzz/transaction_deserialize_fuzz.cpp")
|
||||||
|
set(FUZZ_TX_DESER_LINK_WRAPPER "${FUZZ_OBJ_DIR}/link_txdeser.sh")
|
||||||
|
set(FUZZ_TX_DESER_LINK_WRAPPER_CONTENT [=[#!/bin/bash
|
||||||
|
# Auto-generated by CMake (BUILD_FUZZ block). Link wrapper for the
|
||||||
|
# transaction_deserialize_fuzz target. Discovers triangles_common +
|
||||||
|
# trianglesd .o files at link time and exec's the clang++ link line.
|
||||||
|
#
|
||||||
|
# Differs from link.sh: this wrapper does NOT exclude script.cpp.o, because
|
||||||
|
# wallet.cpp.o (in trianglesd_objects) calls ExtractDestination,
|
||||||
|
# SignSignature, Solver, IsMine — all defined in script.cpp.o. We only exclude
|
||||||
|
# init.cpp.o (which defines daemon main(), would conflict with libFuzzer's
|
||||||
|
# main). See the BUILD_FUZZ block in src/CMakeLists.txt for full rationale.
|
||||||
|
#
|
||||||
|
# Usage: link_txdeser.sh clang++ [link-args...]
|
||||||
|
# Final exec: clang++ <each .o> <each original link-arg>
|
||||||
|
set -euo pipefail
|
||||||
|
PROG="$1"
|
||||||
|
shift
|
||||||
|
TRIANGLES_COMMON_DIR="@CMAKE_CURRENT_BINARY_DIR@/CMakeFiles/triangles_common.dir"
|
||||||
|
TRIANGLESD_DIR="@CMAKE_CURRENT_BINARY_DIR@/CMakeFiles/trianglesd_objects.dir"
|
||||||
|
declare -a OBJS=()
|
||||||
|
for f in "$TRIANGLES_COMMON_DIR"/*.o "$TRIANGLES_COMMON_DIR"/*/*.o; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
OBJS+=("$f")
|
||||||
|
done
|
||||||
|
if [ -d "$TRIANGLESD_DIR" ]; then
|
||||||
|
for f in "$TRIANGLESD_DIR"/*.o; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
case "$f" in
|
||||||
|
*/init.cpp.o) continue ;;
|
||||||
|
esac
|
||||||
|
OBJS+=("$f")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
exec "$PROG" "${OBJS[@]}" "$@"
|
||||||
|
]=])
|
||||||
|
string(CONFIGURE "${FUZZ_TX_DESER_LINK_WRAPPER_CONTENT}"
|
||||||
|
FUZZ_TX_DESER_LINK_WRAPPER_CONTENT @ONLY)
|
||||||
|
file(WRITE "${FUZZ_TX_DESER_LINK_WRAPPER}" "${FUZZ_TX_DESER_LINK_WRAPPER_CONTENT}")
|
||||||
|
file(CHMOD "${FUZZ_TX_DESER_LINK_WRAPPER}" PERMISSIONS
|
||||||
|
OWNER_READ OWNER_WRITE OWNER_EXECUTE
|
||||||
|
GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||||
# Compile flags shared by both .cpp files. Pull in script.h, secp256k1,
|
# Compile flags shared by both .cpp files. Pull in script.h, secp256k1,
|
||||||
# leveldb. Same flags gcc uses for triangles_common (the project defines
|
# leveldb. Same flags gcc uses for triangles_common (the project defines
|
||||||
# HAVE_BUILD_INFO, LINUX, BOOST_THREAD_USE_LIB, etc.) so we don't hit
|
# HAVE_BUILD_INFO, LINUX, BOOST_THREAD_USE_LIB, etc.) so we don't hit
|
||||||
@@ -1039,5 +1092,84 @@ exec "$PROG" "${OBJS[@]}" "$@"
|
|||||||
)
|
)
|
||||||
add_custom_target(fuzz_script ALL DEPENDS "${FUZZ_BIN}")
|
add_custom_target(fuzz_script ALL DEPENDS "${FUZZ_BIN}")
|
||||||
|
|
||||||
message(STATUS "Fuzz target enabled: ${FUZZ_BIN}")
|
# ==========================================================================
|
||||||
|
# transaction_deserialize_fuzz — second fuzz target
|
||||||
|
# ==========================================================================
|
||||||
|
# Compile the harness with clang + libFuzzer instrumentation. The harness
|
||||||
|
# only links against the already-instrumented triangles_common /
|
||||||
|
# trianglesd .o files (for CTransaction, CDataStream, etc.) — we do NOT
|
||||||
|
# compile a separate clang-instrumented copy of any .cpp file the way
|
||||||
|
# fuzz_script does for script.cpp.
|
||||||
|
#
|
||||||
|
# Uses its OWN link wrapper (link_txdeser.sh) because the fuzz_script
|
||||||
|
# wrapper excludes script.cpp.o from triangles_common (we replace it
|
||||||
|
# with our own clang-instrumented copy there). For transaction_deserialize
|
||||||
|
# we need script.cpp.o: wallet.cpp.o (in trianglesd_objects) calls
|
||||||
|
# ExtractDestination, SignSignature, Solver, IsMine — all defined in
|
||||||
|
# script.cpp.o. Excluding it produces "undefined reference" link errors.
|
||||||
|
# The new wrapper excludes only init.cpp.o (which defines daemon main()
|
||||||
|
# and would conflict with libFuzzer's main).
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${FUZZ_TX_DESER_OBJ}"
|
||||||
|
COMMAND ${CLANGXX} ${FUZZ_COMMON_FLAGS}
|
||||||
|
-c ${FUZZ_TX_DESER_SRC} -o ${FUZZ_TX_DESER_OBJ}
|
||||||
|
DEPENDS ${FUZZ_TX_DESER_SRC}
|
||||||
|
COMMENT "[fuzz] clang++ transaction_deserialize_fuzz.cpp"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link command — same library set as fuzz_script, but no
|
||||||
|
# ${FUZZ_OBJ_SCRIPT} or ${FUZZ_OBJ_SCRIPT_FUZZ} (we didn't compile
|
||||||
|
# our own clang-instrumented copy). The wrapper script discovers
|
||||||
|
# .o files via find at link time.
|
||||||
|
set(FUZZ_TX_DESER_LINK_CMD
|
||||||
|
"${CLANGXX}"
|
||||||
|
"-fsanitize=fuzzer,address,undefined"
|
||||||
|
"${FUZZ_TX_DESER_OBJ}"
|
||||||
|
"-o" "${FUZZ_TX_DESER_BIN}"
|
||||||
|
"${FUZZ_OBJ_FUZZ_STUBS}"
|
||||||
|
"${CMAKE_BINARY_DIR}/lib/libhash9_crypto.a"
|
||||||
|
"${CMAKE_BINARY_DIR}/lib/libleveldb_memenv.a"
|
||||||
|
"${CMAKE_BINARY_DIR}/lib/libleveldb_lib.a"
|
||||||
|
"-lssl" "-lcrypto" "-ldb_cxx" "-levent" "-lsqlite3" "-lminiupnpc"
|
||||||
|
"${CMAKE_BINARY_DIR}/lib/libsecp256k1.a"
|
||||||
|
"-lrocksdb"
|
||||||
|
"-lz" "-lgflags" "-lsnappy" "-lbz2" "-llz4" "-lzstd"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tor/tor-src/libtor.a"
|
||||||
|
"-lpthread" "-llzma" "-lubsan"
|
||||||
|
)
|
||||||
|
foreach(_target Boost::program_options Boost::thread Boost::chrono
|
||||||
|
Boost::atomic Boost::filesystem Boost::system)
|
||||||
|
if(TARGET "${_target}")
|
||||||
|
get_target_property(_path "${_target}" IMPORTED_LOCATION_RELEASE)
|
||||||
|
if(NOT _path)
|
||||||
|
get_target_property(_path "${_target}" IMPORTED_LOCATION)
|
||||||
|
endif()
|
||||||
|
if(_path AND EXISTS "${_path}")
|
||||||
|
list(APPEND FUZZ_TX_DESER_LINK_CMD "${_path}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${FUZZ_TX_DESER_BIN}"
|
||||||
|
COMMAND "${FUZZ_TX_DESER_LINK_WRAPPER}" ${FUZZ_TX_DESER_LINK_CMD}
|
||||||
|
DEPENDS
|
||||||
|
"${FUZZ_TX_DESER_OBJ}"
|
||||||
|
"${FUZZ_OBJ_FUZZ_STUBS}"
|
||||||
|
"${FUZZ_TX_DESER_LINK_WRAPPER}"
|
||||||
|
hash9_crypto
|
||||||
|
leveldb_lib
|
||||||
|
leveldb_memenv
|
||||||
|
secp256k1
|
||||||
|
trianglesd_objects
|
||||||
|
triangles_common
|
||||||
|
COMMENT "[fuzz] clang++ link transaction_deserialize_fuzz"
|
||||||
|
)
|
||||||
|
add_custom_target(transaction_deserialize_fuzz ALL
|
||||||
|
DEPENDS "${FUZZ_TX_DESER_BIN}")
|
||||||
|
|
||||||
|
message(STATUS "Fuzz targets enabled:")
|
||||||
|
message(STATUS " ${FUZZ_BIN}")
|
||||||
|
message(STATUS " ${FUZZ_TX_DESER_BIN}")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
Reference in New Issue
Block a user