-
-
Notifications
You must be signed in to change notification settings - Fork 457
Description
Note this issue was created with the help of an LLM, and I've extensively reviewed it for correctness and accuracy.
Is your feature request related to a problem? Please describe.
LibAFL's libFuzzer.a cannot be linked into shared objects with GNU ld because FuzzerInterceptors.cpp introduces a .preinit_array section. The ELF specification states that .preinit_array is only processed in executable files and must not appear in shared objects (ELF gABI, Dynamic Section). GNU ld enforces this at link time:
.preinit_array section is not allowed in DSO
This affects projects like Ruzzy (Ruby fuzzing) and Atheris (Python fuzzing) that merge libFuzzer.a with clang's sanitizer archives into shared objects (e.g. asan_with_fuzzer.so) which are LD_PRELOADed at runtime. This is a somewhat hacky solution, but it works.
LLVM/clang's libfuzzer avoids this by shipping the interceptors in a separate archive:
| Archive | main |
Interceptors |
|---|---|---|
libclang_rt.fuzzer.a |
yes | no |
libclang_rt.fuzzer_no_main.a |
no | no |
libclang_rt.fuzzer_interceptors.a |
no | yes |
The libafl_targets crate already has the internal feature flags to control both — libfuzzer_no_link_main (guarding main via #ifndef FUZZER_NO_LINK_MAIN in libfuzzer.c) and libfuzzer_interceptors (gating FuzzerInterceptors.cpp compilation in build.rs) — but libafl_libfuzzer_runtime hardcodes them and doesn't expose them to consumers.
Describe the solution you'd like
Add crate-level features to libafl_libfuzzer_runtime/Cargo.toml:
[features]
default = ["interceptors"]
interceptors = ["libafl_targets/libfuzzer_interceptors"]
no_link_main = ["libafl_targets/libfuzzer_no_link_main"]And remove "libfuzzer_interceptors" from the hardcoded libafl_targets dependency features list.
This is a backwards-compatible change — default behavior is preserved. Consumers can then configure the build via build.sh:
# Equivalent to libclang_rt.fuzzer_no_main.a (no main, no interceptors)
bash build.sh --cargo-args "--no-default-features --features no_link_main"
# No interceptors, with main (current weak symbol)
bash build.sh --cargo-args "--no-default-features"
# Default (current behavior, interceptors + main)
bash build.shDescribe alternatives you've considered
- Using
lldinstead of GNUld. lld permits.preinit_arrayin shared objects, but it adds a build dependency and the section is silently ignored at runtime anyway (the dynamic linker only processes.preinit_arrayfor the main executable). - Patching the cloned source with
sed -i '/"libfuzzer_interceptors",/d' Cargo.tomlbefore runningbuild.sh. This works but is fragile and requires modifying LibAFL source. - Stripping the
.preinit_arraysection fromlibFuzzer.awithobjcopy --remove-section. This fails because.debug_infohas relocations against the section.
Additional context
The main symbol in LibAFL's libFuzzer.a is already weak (W main), so it doesn't cause linker conflicts in practice. However, exposing no_link_main would provide parity with clang's libclang_rt.fuzzer_no_main.a and give consumers explicit control, matching the separation that LLVM's build system already provides.