Hi cargo-fuzz devs! I am working on fuzzing a Windows DLL with cargo-fuzz and have been hitting a wall with a particular linker error. I've reproduced the problem in a small & simple Cargo project to demonstrate the issue.
Problem Description
The issue arises when the to-be-fuzzed Cargo project defines its crate-type as ["cdylib"], specifically on Windows (it will compile into a DLL). The Cargo.toml looks something like this:
[package]
name = "simplelib"
version = "0.1.0"
edition = "2021"
# ...
[lib]
crate-type = ["cdylib"]
# ...
[dependencies.windows]
version = "0.58.0"
features = [ "Win32" ]
# ...
A very basic DLL DllMain function in lib.rs looks like this:
use windows::Win32::Foundation::*;
#[export_name = "DllMain"]
extern "system" fn dll_main(_: HINSTANCE, _: u32, _: *mut ()) -> bool
{
true
}
After running cargo fuzz init to initialize a basic fuzzing sub-project, the command cargo fuzz build (or cargo fuzz run) will fail during building with the following linker error:
= note: Creating library C:\Users\USERNAME\dev\simplelib\fuzz\target\x86_64-pc-windows-msvc\release\deps\simplelib.dll.lib and object C:\Users\USERNAME\dev\simplelib\fuzz\target\x86_64-pc-windows-msvc\release\deps\simplelib.dll.exp
LINK : error LNK2001: unresolved external symbol main
C:\Users\USERNAME\dev\simplelib\fuzz\target\x86_64-pc-windows-msvc\release\deps\simplelib.dll : fatal error LNK1120: 1 unresolved externals
Steps to Reproduce
- On a Windows system (with Rust/Cargo installed), open PowerShell
cargo init --lib ./simplelib
- (edit Cargo.toml to have the above information)
cargo fuzz init
cargo fuzz build
Problem Analysis
I spent time digging through the various options passed to the Rust compiler by cargo-fuzz, and found this commit, which adds the /include:main linker argument for builds using the msvc triple. Based on the comment left with that line of code, and reading up on the /include option, I understand the intention is to force libraries compiled on Windows to include the main symbol, so it can be found within LibFuzzer.
I also found #281, and based on the discussion there it seems like crate-type = ["cdylib"] may not be supported by cargo-fuzz. If I'm thinking about this problem correctly, I understand why cdylib crates cause issues: DLLs/shared libraries, by nature, are loaded at runtime and don't have (don't need) prior knowledge of a main function. But, as it sits now, it appears that Cargo projects that can only build as cdylibs aren't able to be built by cargo-fuzz. (Please correct me if I am wrong)
Possible Solution
Would it be possible to modify the cargo-fuzz building routine to build & instrument DLLs without the main function? This would allow the DLL to be built and instrumented in the same way as the fuzzing targets, but it would then be up to the user as to where the DLL should be dropped, so it can be loaded during fuzzing. (Since the DLL would be built with Sanitizer Coverage instrumentation, LibFuzzer should still be able to detect code coverage changes when the fuzzing target loads it in at runtime and invokes the DLL's functions.)
Perhaps something like this:
if build.triple.contains("-msvc") && !build.crate_type.ne("cdylib") {
// The entrypoint is in the bundled libfuzzer rlib, this gets the linker to find it.
// (Do not do this if we're building a DLL; instead let it build & be instrumented without `main`)
rustflags.push_str(" -Clink-arg=/include:main");
}
I've been testing this theory locally (modifying my local copy of cargo-fuzz's code by commenting out the rustflags.push_str(" -Clink-arg=/include:main") when building the DLL), and it's building successfully with instrumentation. After it's built, I drop it into a location where my fuzzing target can pick it up for loading, and when I run cargo fuzz run my_fuzz_target, LibFuzzer appears to be quickly discovering a set of cascading if-statements and a bug I inserted inside the DLL.
Hi cargo-fuzz devs! I am working on fuzzing a Windows DLL with cargo-fuzz and have been hitting a wall with a particular linker error. I've reproduced the problem in a small & simple Cargo project to demonstrate the issue.
Problem Description
The issue arises when the to-be-fuzzed Cargo project defines its
crate-typeas["cdylib"], specifically on Windows (it will compile into a DLL). TheCargo.tomllooks something like this:A very basic DLL
DllMainfunction inlib.rslooks like this:After running
cargo fuzz initto initialize a basic fuzzing sub-project, the commandcargo fuzz build(orcargo fuzz run) will fail during building with the following linker error:Steps to Reproduce
cargo init --lib ./simplelibcargo fuzz initcargo fuzz buildProblem Analysis
I spent time digging through the various options passed to the Rust compiler by cargo-fuzz, and found this commit, which adds the
/include:mainlinker argument for builds using themsvctriple. Based on the comment left with that line of code, and reading up on the /include option, I understand the intention is to force libraries compiled on Windows to include themainsymbol, so it can be found within LibFuzzer.I also found #281, and based on the discussion there it seems like
crate-type = ["cdylib"]may not be supported by cargo-fuzz. If I'm thinking about this problem correctly, I understand whycdylibcrates cause issues: DLLs/shared libraries, by nature, are loaded at runtime and don't have (don't need) prior knowledge of amainfunction. But, as it sits now, it appears that Cargo projects that can only build ascdylibsaren't able to be built by cargo-fuzz. (Please correct me if I am wrong)Possible Solution
Would it be possible to modify the cargo-fuzz building routine to build & instrument DLLs without the
mainfunction? This would allow the DLL to be built and instrumented in the same way as the fuzzing targets, but it would then be up to the user as to where the DLL should be dropped, so it can be loaded during fuzzing. (Since the DLL would be built with Sanitizer Coverage instrumentation, LibFuzzer should still be able to detect code coverage changes when the fuzzing target loads it in at runtime and invokes the DLL's functions.)Perhaps something like this:
I've been testing this theory locally (modifying my local copy of cargo-fuzz's code by commenting out the
rustflags.push_str(" -Clink-arg=/include:main")when building the DLL), and it's building successfully with instrumentation. After it's built, I drop it into a location where my fuzzing target can pick it up for loading, and when I runcargo fuzz run my_fuzz_target, LibFuzzer appears to be quickly discovering a set of cascading if-statements and a bug I inserted inside the DLL.