From 34aeb70748ef8ee186fe53f0db2580a445452dc2 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Wed, 12 Feb 2025 17:17:33 -0500 Subject: [PATCH] cmake: Avoid fuzzer "multiple definition of `main'" errors This change builds libraries with -fsanitize=fuzzer-no-link instead of -fsanitize=fuzzer when the cmake -DSANITIZERS=fuzzer option is specified. This is necessary to make fuzzing and IPC cmake options compatible with each other and avoid CI failures in #30975 which enables IPC in the fuzzer CI build: https://cirrus-ci.com/task/5366255504326656?logs=ci#L2817 https://cirrus-ci.com/task/5233064575500288?logs=ci#L2384 The failures can also be reproduced by checking out #31741 and building with `cmake -B build -DBUILD_FOR_FUZZING=ON -DSANITIZERS=fuzzer -DENABLE_IPC=ON` with this fix reverted. The fix updates the cmake build so when -DSANITIZERS=fuzzer is specified, the fuzz test binary is built with -fsanitize=fuzzer (so it can use libFuzzer's main function), and libraries are built with -fsanitize=fuzzer-no-link (so they can be linked into other executables with their own main functions). Previously when -DSANITIZERS=fuzzer was specified, -fsanitize=fuzzer was applied to ALL libraries and executables. This was inappropriate because it made it impossible to build any executables other than the fuzz test executable without triggering link errors: - "multiple definition of `main'" - "undefined reference to `LLVMFuzzerTestOneInput'" if they depended on any libraries instrumented for fuzzing. This was especially a problem when the ENABLE_IPC option was set because it made building the mpgen code generator impossible so nothing else that depended on generated sources, including the fuzz test binary, could be built either. This commit was previously part of https://github.com/bitcoin/bitcoin/pull/31741 and had some discussion there starting in https://github.com/bitcoin/bitcoin/pull/31741#pullrequestreview-2619682385 --- CMakeLists.txt | 30 ++++++++++++++++++++++++++---- src/test/fuzz/CMakeLists.txt | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a51c3e4fcd..eb8aa5dd05e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -348,13 +348,35 @@ target_link_libraries(core_interface INTERFACE Threads::Threads ) +# Define "sanitize_interface" with flags intended to apply to all libraries and +# executables to support sanitizers and fuzzing, and a separate +# "fuzzer_interface" with flags specifically intended to apply to the fuzz test +# binary. Reason these are separate interfaces is that if -DSANITIZERS=fuzzer is +# specified, the fuzz test binary should be built with -fsanitize=fuzzer (so it +# can use libFuzzer's main function), but libraries should be built with +# -fsanitize=fuzzer-no-link (so they can be linked into other executables that +# have their own main functions). add_library(sanitize_interface INTERFACE) +add_library(fuzzer_interface INTERFACE) target_link_libraries(core_interface INTERFACE sanitize_interface) if(SANITIZERS) + # Transform list of sanitizers into -fsanitize flags, replacing "fuzzer" with + # "fuzzer-no-link" in sanitize_interface flags, and moving "fuzzer" to + # fuzzer_interface flags. + string(REGEX REPLACE "(^|,)fuzzer($|,)" "\\1fuzzer-no-link\\2" SANITIZE_FLAG "${SANITIZERS}") + set(FUZZ_FLAG "") + if (NOT "${SANITIZE_FLAG}" STREQUAL "${SANITIZERS}") + set(FUZZ_FLAG "-fsanitize=fuzzer") + if(NOT BUILD_FOR_FUZZING) + message(FATAL_ERROR "Error: Enabling -DSANITIZERS=fuzzer without -DBUILD_FOR_FUZZING=ON is not supported.") + endif() + endif() + set(SANITIZE_FLAG "-fsanitize=${SANITIZE_FLAG}") + # First check if the compiler accepts flags. If an incompatible pair like # -fsanitize=address,thread is used here, this check will fail. This will also # fail if a bad argument is passed, e.g. -fsanitize=undfeined - try_append_cxx_flags("-fsanitize=${SANITIZERS}" TARGET sanitize_interface + try_append_cxx_flags("${SANITIZE_FLAG}" TARGET sanitize_interface RESULT_VAR cxx_supports_sanitizers SKIP_LINK ) @@ -367,12 +389,11 @@ if(SANITIZERS) # flag. This is a separate check so we can give a better error message when # the sanitize flags are supported by the compiler but the actual sanitizer # libs are missing. - try_append_linker_flag("-fsanitize=${SANITIZERS}" VAR SANITIZER_LDFLAGS + try_append_linker_flag("${SANITIZE_FLAG}" VAR SANITIZER_LDFLAGS SOURCE " #include #include extern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; } - __attribute__((weak)) // allow for libFuzzer linking int main() { return 0; } " RESULT_VAR linker_supports_sanitizers @@ -382,6 +403,7 @@ if(SANITIZERS) endif() endif() target_link_options(sanitize_interface INTERFACE ${SANITIZER_LDFLAGS}) +target_link_options(fuzzer_interface INTERFACE ${FUZZ_FLAG}) if(BUILD_FUZZ_BINARY) target_link_libraries(core_interface INTERFACE ${FUZZ_LIBS}) @@ -392,7 +414,7 @@ if(BUILD_FUZZ_BINARY) extern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; } // No main() function. " FUZZ_BINARY_LINKS_WITHOUT_MAIN_FUNCTION - LDFLAGS ${SANITIZER_LDFLAGS} + LDFLAGS ${SANITIZER_LDFLAGS} ${FUZZ_FLAG} LINK_LIBRARIES ${FUZZ_LIBS} ) endif() diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index e99c6d91f47..88abd0e6cef 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -137,6 +137,7 @@ add_executable(fuzz ) target_link_libraries(fuzz core_interface + fuzzer_interface test_fuzz bitcoin_cli bitcoin_common