Skip to content

Commit 57d8b1f

Browse files
committed
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 bitcoin/bitcoin#31741 and had some discussion there starting in bitcoin/bitcoin#31741 (review)
1 parent 88debb3 commit 57d8b1f

File tree

2 files changed

+25
-5
lines changed

2 files changed

+25
-5
lines changed

CMakeLists.txt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,28 @@ target_link_libraries(core_interface INTERFACE
348348
Threads::Threads
349349
)
350350

351+
# Define sanitize_interface with -fsanitize flags intended to apply to all
352+
# libraries and executables.
351353
add_library(sanitize_interface INTERFACE)
352354
target_link_libraries(core_interface INTERFACE sanitize_interface)
353355
if(SANITIZERS)
356+
# Transform list of sanitizers into -fsanitize flags, replacing "fuzzer" with
357+
# "fuzzer-no-link" in sanitize_interface flags, and moving "fuzzer" to
358+
# fuzzer_interface flags. If -DSANITIZERS=fuzzer is specified, the fuzz test
359+
# binary should be built with -fsanitize=fuzzer (so it can use libFuzzer's
360+
# main function), but libraries should be built with -fsanitize=fuzzer-no-link
361+
# (so they can be linked into other executables that have their own main
362+
# functions).
363+
string(REGEX REPLACE "(^|,)fuzzer($|,)" "\\1fuzzer-no-link\\2" sanitize_opts "${SANITIZERS}")
364+
set(fuzz_flag "")
365+
if(NOT sanitize_opts STREQUAL SANITIZERS)
366+
set(fuzz_flag "-fsanitize=fuzzer")
367+
endif()
368+
354369
# First check if the compiler accepts flags. If an incompatible pair like
355370
# -fsanitize=address,thread is used here, this check will fail. This will also
356371
# fail if a bad argument is passed, e.g. -fsanitize=undfeined
357-
try_append_cxx_flags("-fsanitize=${SANITIZERS}" TARGET sanitize_interface
372+
try_append_cxx_flags("-fsanitize=${sanitize_opts}" TARGET sanitize_interface
358373
RESULT_VAR cxx_supports_sanitizers
359374
SKIP_LINK
360375
)
@@ -367,12 +382,11 @@ if(SANITIZERS)
367382
# flag. This is a separate check so we can give a better error message when
368383
# the sanitize flags are supported by the compiler but the actual sanitizer
369384
# libs are missing.
370-
try_append_linker_flag("-fsanitize=${SANITIZERS}" VAR SANITIZER_LDFLAGS
385+
try_append_linker_flag("-fsanitize=${sanitize_opts}" VAR SANITIZER_LDFLAGS
371386
SOURCE "
372387
#include <cstdint>
373388
#include <cstddef>
374389
extern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; }
375-
__attribute__((weak)) // allow for libFuzzer linking
376390
int main() { return 0; }
377391
"
378392
RESULT_VAR linker_supports_sanitizers
@@ -383,18 +397,23 @@ if(SANITIZERS)
383397
endif()
384398
target_link_options(sanitize_interface INTERFACE ${SANITIZER_LDFLAGS})
385399

400+
# Define fuzzer_interface with flags intended to apply to the fuzz test binary,
401+
# and perform a test compilation to determine correct value of
402+
# FUZZ_BINARY_LINKS_WITHOUT_MAIN_FUNCTION.
386403
if(BUILD_FUZZ_BINARY)
387-
target_link_libraries(core_interface INTERFACE ${FUZZ_LIBS})
388404
include(CheckSourceCompilesWithFlags)
389405
check_cxx_source_compiles_with_flags("
390406
#include <cstdint>
391407
#include <cstddef>
392408
extern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; }
393409
// No main() function.
394410
" FUZZ_BINARY_LINKS_WITHOUT_MAIN_FUNCTION
395-
LDFLAGS ${SANITIZER_LDFLAGS}
411+
LDFLAGS ${SANITIZER_LDFLAGS} ${fuzz_flag}
396412
LINK_LIBRARIES ${FUZZ_LIBS}
397413
)
414+
add_library(fuzzer_interface INTERFACE)
415+
target_link_options(fuzzer_interface INTERFACE ${fuzz_flag})
416+
target_link_libraries(fuzzer_interface INTERFACE ${FUZZ_LIBS})
398417
endif()
399418

400419
include(AddBoostIfNeeded)

src/test/fuzz/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ add_executable(fuzz
137137
)
138138
target_link_libraries(fuzz
139139
core_interface
140+
fuzzer_interface
140141
test_fuzz
141142
bitcoin_cli
142143
bitcoin_common

0 commit comments

Comments
 (0)