Building libFuzzer targets with MSVC
A few months ago while assessing the extensions API for a client, I got tasked with doing some light fuzzing against the application. The application allowed for third-party scripts to be executed in the parent application. I decided to use libFuzzer as a base, as it felt appropriate for the project. The client’s code was simple to compile and writing the harness turned out to be easy, but I hit a bit of a problem; I had originally written the harness and buildscript on a macOS laptop using llvm/clang, and my client usually built with MSVC and wanted to run the fuzzer on Windows hosts. Thankfully libFuzzer supports both platforms, so that should be easy, right?
For simple projects, the conversion is straightforward - newer versions of MSVC support /fsanitize=fuzzer
which both instruments the code and links libFuzzer libraries to the binary, and the syntax is similar to clang. Adding the relevant flag to the CMakeLists.txt
allows you to build cleanly, like it would on macOS.
if(MSVC) # Microsoft Visual C++
add_compile_options(/fsanitize=fuzzer) # Instrument and link libs which expect main harness
add_compile_options(/fsanitize=address) # Address sanitizer
else() # macOS, Unix
add_compile_options(-fsanitize=fuzzer) # Instrument and link libs which expect main harness
add_compile_options(-fsanitize=address) # Address sanitizer
endif()
no fuzzer-no-link
support in MSVC
For more involved projects that might have sub-projects and standalone libraries that are built as part of the project, this is a bit more tricky. The standard fuzzer libs linked as part of /fsanitize=fuzzer
contain the main()
symbol and expect the fuzzer target functions (i.e. LLVMFuzzerTestOneInput
or similar) to be present in the binary. This will not be true if individual libraries are compiled separately from the harness as part of the build and compilation might fail with errors noting unresolved external symbols. Errors might also occur if standalone executables (e.g. utilities) with their own main
are built as part of the process.
Clang has a solution in -fsanitize=fuzzer-no-link
, which links fuzzer libraries that do not contain the main function. This can be added for libraries that are built separately from the fuzzing harness. This neatly sidesteps the problem, and there shouldn’t be any unresolved symbol errors during compilation.
Sadly, current versions of MSVC do not support the /fsanitize=fuzzer-no-link
flag. There are some notes in Microsoft’s documentation which indicate that for such scenarios the idea is to use some combination of /NODEFAULTLIB
and linker flags to link the particular version of “no main” library needed for that build. I couldn’t really figure out the right incantation for the flags, and github dorking did not reveal projects that used those flags in a way that might be helpful to me. I did find plenty of projects on Github and other places that use libFuzzer and the fuzzer-no-link
flag, and those projects generally disabled building libFuzzer binaries with MSVC with a comment along the lines of “not supported yet by MSVC”. I only had a few days left on the project, so I decided to get creative.
I went back to the basic steps; I didn’t really care about linking the fuzzer libs while building the standalone libraries, but I did want coverage and sanitizers. A closer read of Microsoft’s documentation link above came handy here; /fsanitize=fuzzer
seems to do a few things, add libFuzzer to the library load list, and set some compile options. I simply had to mimic this behavior with flags that were supported by MSVC. We want the compile flags to enable coverage and sanitizers for the whole project (or subprojects we want to fuzz), but cared about the fuzzer libraries only for our test harness.
My premise was, functionally, replacing /fsanitize=fuzzer
with the set of /fsanitize-coverage=edge /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div
should mimic the behavior of fuzzer-no-link
, sans linking the fuzzer libraries. We can always link the fuzzer libs later, as long as we can compile it all. This is similar to the description for setting up LibFuzzer at https://llvm.org/docs/LibFuzzer.html.
Thankfully, my gambit paid off! Setting those options for the entire project allowed me to instrument all libraries with errors, with the harness linked with libFuzzer libs “with main” at the end.
workaround for fuzzer-no-link
The cmake file for the top-level project looks like the below:
if(MSVC)
add_compile_options(/fsanitize-coverage=edge) # Instrument
add_compile_options(/fsanitize-coverage=inline-8bit-counters) # Instrument
add_compile_options(/fsanitize-coverage=trace-cmp) # Instrument
add_compile_options(/fsanitize-coverage=trace-div) # Instrument
add_compile_options(/fsanitize=address) # Address Sanitizer
else()
add_compile_options(-fsanitize=fuzzer-no-link) # Instrument and link libs which do not expect main harness
add_compile_options(-fsanitize=address) # Address sanitizer
endif()
This configuration (or preferred way of passing flags) is suitable for the top-level directory of the project. When compiling with MSVC, these flags will cause instrumentation only and not link the fuzzer libs. Sanitizer libs are linked automatically when using /fsanitize=address
(though this behavior can be controlled).
The cmake file for the fuzzer harness itself can make use of convenience flags to enable libFuzzer support and address sanitizer, as listed in the first code listing.
if(MSVC) # Microsoft Visual C++
add_compile_options(/fsanitize=fuzzer) # Instrument and link libs which expect main harness
add_compile_options(/fsanitize=address) # Address sanitizer
else() # macOS, Unix
add_compile_options(-fsanitize=fuzzer) # Instrument and link libs which expect main harness
add_compile_options(-fsanitize=address) # Address sanitizer
target_link_options(... -fsanitize=fuzzer) # Link fuzzer libraries
endif()
afterthoughts
My solution above is a bit of a hack, made mostly for getting usable output in the short time frame I had for testing. The workaround lends itself well to situations where you want fast results or are dealing with a legacy codebase and are trying to POC if fuzzing is worth the effort with minimal modification to existing build files. During my Github dorking I found a few different examples of libFuzzer usage, some more sophisticated than others. Judicious use of cmake generators and tags makes it much easier to turn instrumentation on or off for certain code or directories and control the libraries linked during process.
While writing this post I came some new improvements for sanitizers in the MSVC 177 Preview build listed in https://devblogs.microsoft.com/cppblog/msvc-address-sanitizer-one-dll-for-all-runtime-configurations/. If I’m reading it correctly that should help in reducing the complexity of using libFuzzer with MSVC in projects similar to mine, and hopefully this leads to easier configuration and fuzzing of targets.