Skip to content

Commit 0b25d72

Browse files
novafacingRowan Hart
andauthored
Windows Support for LibAFL-LibFuzzer (#3130)
* Add windows build script and additional changes to support windows for libafl-libfuzzer * Update build scripts and harness wrapping directives * Resolve issue with corpus edge count calculation * Add help message and make fork do nothing on Windows * Format harness_wrap.cpp * Clippy happiness pass * Clippy happiness pass * Clippy happiness pass * Correct logic * Correct logic * Update help output and make runs argument work * Add test for libafl_libfuzzer on windows * Add workflow for libafl_libfuzzer test * Fix copy without dependent task * Add libafl_libfuzzer_windows to preflight list * Format harness * Explicitly ignore windows fuzzer * Remove windows-specific copy from unix instructions * Ensure using nightly * Fix job name * Update build to use libFuzzer.lib on Windows to keep consistent with Linux * Remove nightly requirement --------- Co-authored-by: Rowan Hart <rowanhart@microsoft.com>
1 parent db1d38e commit 0b25d72

File tree

17 files changed

+351
-70
lines changed

17 files changed

+351
-70
lines changed

.github/workflows/build_and_test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ jobs:
329329
- ./fuzzers/inprocess/libfuzzer_stb_image
330330
# - ./fuzzers/structure_aware/libfuzzer_stb_image_concolic
331331
# - ./fuzzers/inprocess/sqlite_centralized_multi_machine
332+
# - ./fuzzers/inprocess/libafl_libfuzzer_windows
332333

333334
# Fuzz Anything
334335
- ./fuzzers/fuzz_anything/push_harness
@@ -569,6 +570,16 @@ jobs:
569570
- name: Build fuzzers/binary_only/frida_libpng
570571
run: cd fuzzers/binary_only/frida_libpng/ && just test
571572

573+
windows-libafl-libfuzzer:
574+
runs-on: windows-latest
575+
needs:
576+
- common
577+
steps:
578+
- uses: actions/checkout@v4
579+
- uses: ./.github/workflows/windows-tester-prepare
580+
- name: Build fuzzers/inprocess/libafl_libfuzzer_windows
581+
run: cd fuzzers/inprocess/libafl_libfuzzer_windows && just test
582+
572583
windows-libfuzzer-stb-image:
573584
runs-on: windows-latest
574585
needs:

fuzzers/binary_only/frida_libpng/Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ lib2: libpng
4747

4848
[windows]
4949
harness: lib lib2
50-
copy libpng-1.6.37\Release\libpng16.lib . && copy libpng-1.6.37\Release\libpng16.dll . && copy zlib\Release\zlib.lib . && copy zlib\Release\zlib.dll . && copy target\release\frida_fuzzer.exe .
50+
copy libpng-1.6.37\Release\libpng16.lib . && copy libpng-1.6.37\Release\libpng16.dll . && copy zlib\Release\zlib.lib . && copy zlib\Release\zlib.dll .
5151
cl /O2 /c /I .\libpng-1.6.37 harness.cc /Fo:harness.obj && link /DLL /OUT:libpng-harness.dll harness.obj libpng16.lib zlib.lib
5252

5353
[unix]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import "../../../just/libafl.just"
2+
3+
FUZZER_NAME := "libafl_libfuzzer_windows"
4+
FUZZER_NAME_WIN := "libafl_libfuzzer_windows.exe"
5+
6+
set windows-shell := ['cmd.exe', '/c']
7+
set unstable
8+
9+
[windows]
10+
libafl_libfuzzer:
11+
powershell -File ..\..\..\libafl_libfuzzer_runtime\build.ps1
12+
13+
[windows]
14+
harness: libafl_libfuzzer
15+
copy ..\..\..\libafl_libfuzzer_runtime\libFuzzer.lib .
16+
cl /c /O2 /EHsc /std:c++17 /MDd /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div /Fo:harness.obj harness.cc
17+
link harness.obj libFuzzer.lib sancov.lib /OUT:libafl_libfuzzer_windows.exe
18+
19+
[windows]
20+
run: harness
21+
if not exist corpus mkdir corpus
22+
{{FUZZER_NAME_WIN}} -use_value_profile=1 corpus
23+
24+
[windows]
25+
[script("cmd.exe", "/c")]
26+
test: harness
27+
if exist corpus rd /s /q corpus
28+
mkdir corpus
29+
{{FUZZER_NAME_WIN}} -use_value_profile=1 -runs=30000 corpus
30+
dir /a-d corpus && (echo Files exist) || (exit /b 1337)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# LibAFL-LibFuzzer Windows
2+
3+
A simple example demonstrating how to build LibFuzzer harnesses with LibAFL-LibFuzzer
4+
as an alternative runtime on Windows.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Simple decoder function with an off by one error that is triggered under
2+
// certain conditions.
3+
4+
#include <cstddef>
5+
#include <cstdint>
6+
7+
int DecodeInput(const uint8_t *data, size_t size) {
8+
if (size < 5) {
9+
return -1; // Error: not enough data
10+
}
11+
12+
if (data[0] != 'F' || data[1] != 'U' || data[2] != 'Z' || data[3] == 'Z') {
13+
return -1; // Error: invalid header
14+
}
15+
16+
if (data[4] <= 0) {
17+
return -1; // Error: invalid size
18+
}
19+
20+
int csum = 0;
21+
22+
for (size_t i = 5; i < size; ++i) {
23+
csum += data[i];
24+
}
25+
26+
return csum; // Error: checksum mismatch
27+
}
28+
29+
extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t *data,
30+
size_t size) {
31+
DecodeInput(data, size);
32+
return 0;
33+
}

libafl_libfuzzer/README.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,35 @@ To do so, [ensure a recent nightly version of Rust is installed](https://rustup.
8686
[`libafl_libfuzzer_runtime`](../libafl_libfuzzer_runtime) folder and build the runtime with the following command:
8787

8888
```bash
89-
./build.sh
89+
just build
9090
```
9191

92-
The static library will be available at `libFuzzer.a` in the [`libafl_libfuzzer_runtime`](../libafl_libfuzzer_runtime)
93-
directory.
94-
If you encounter build failures without clear error outputs that help you resolve the issue, please [submit an issue].
92+
Or you can call `build.sh` (Unix) or `build.ps1` (Windows).
9593

96-
This library may now be used in place of libFuzzer.
97-
To do so, change your CFLAGS/CXXFLAGS from `-fsanitize=fuzzer` to:
94+
The static library will be available at `libFuzzer.a` (`libFuzzer.lib` for Windows) in
95+
the [`libafl_libfuzzer_runtime`](../libafl_libfuzzer_runtime) directory. If you
96+
encounter build failures without clear error outputs that help you resolve the issue,
97+
please [submit an issue].
9898

99-
```
100-
-fsanitize=fuzzer-no-link -L/path/to/libafl_libfuzzer_runtime -lFuzzer
101-
```
99+
#### Unix
100+
101+
This library may now be used in place of libFuzzer. To do so, change your
102+
CFLAGS/CXXFLAGS from `-fsanitize=fuzzer` to:
103+
104+
``` -fsanitize=fuzzer-no-link -L/path/to/libafl_libfuzzer_runtime -lFuzzer ```
105+
106+
Alternatively, you may directly overwrite the system libFuzzer library and use
107+
`-fsanitize=fuzzer` as normal. This changes per system, but on my machine is located at
108+
`/usr/lib64/clang/16/lib/linux/libclang_rt.fuzzer-x86_64.a`.
109+
110+
#### Windows
111+
112+
For Windows, change your CFLAGS/CXXFLAGS from `-fsanitize=fuzzer` to:
113+
114+
```/fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div```
102115

103-
Alternatively, you may directly overwrite the system libFuzzer library and use `-fsanitize=fuzzer` as normal.
104-
This changes per system, but on my machine is located at `/usr/lib64/clang/16/lib/linux/libclang_rt.fuzzer-x86_64.a`.
116+
And then ensure you link with `sancov.lib` when producing your final executable. See
117+
`fuzzers\inprocess\libafl_libfuzzer_windows` for an example.
105118

106119
#### Caveats
107120

libafl_libfuzzer/runtime/Cargo.toml.template

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ version = "0.15.2"
44
edition = "2024"
55
publish = false
66

7-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8-
97
[features]
10-
default = ["fork"]
8+
default = []
119
## Enables forking mode for the LibAFL launcher (instead of starting new processes)
1210
fork = ["libafl/fork"]
1311
track_hit_feedbacks = [
1412
"libafl/track_hit_feedbacks",
1513
"libafl_targets/track_hit_feedbacks",
1614
]
15+
tui_monitor = ["libafl/tui_monitor"]
16+
17+
[target.'cfg(not(windows))'.features]
18+
## Enable the `fork` feature on non-windows platforms
19+
default = ["fork", "tui_monitor"]
1720

1821
[profile.release]
1922
lto = true
@@ -40,7 +43,6 @@ libafl = { path = "../libafl", default-features = false, features = [
4043
"regex",
4144
"errors_backtrace",
4245
"serdeany_autoreg",
43-
"tui_monitor",
4446
"unicode",
4547
] }
4648
libafl_bolts = { path = "../libafl_bolts", default-features = false, features = [

libafl_libfuzzer/runtime/src/corpus.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ where
154154
}
155155
#[inline]
156156
fn count_all(&self) -> usize {
157-
self.count_disabled().saturating_add(self.count_disabled())
157+
self.count().saturating_add(self.count_disabled())
158158
}
159159

160160
#[expect(clippy::used_underscore_items)]

libafl_libfuzzer/runtime/src/fuzz.rs

Lines changed: 73 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,59 @@
11
use core::ffi::c_int;
22
#[cfg(unix)]
3-
use std::io::{Write, stderr, stdout};
4-
use std::{fmt::Debug, fs::File, net::TcpListener, os::fd::AsRawFd, str::FromStr};
3+
use std::{
4+
fmt::Debug,
5+
fs::File,
6+
io::{Write, stderr, stdout},
7+
net::TcpListener,
8+
os::fd::AsRawFd,
9+
str::FromStr,
10+
};
511

12+
#[cfg(feature = "tui_monitor")]
13+
use libafl::monitors::tui::TuiMonitor;
614
use libafl::{
715
Error, Fuzzer, HasMetadata,
816
corpus::Corpus,
9-
events::{
10-
EventConfig, EventReceiver, ProgressReporter, SimpleEventManager,
11-
SimpleRestartingEventManager, launcher::Launcher,
12-
},
17+
events::{EventReceiver, ProgressReporter, SimpleEventManager},
1318
executors::ExitKind,
14-
monitors::{Monitor, MultiMonitor, tui::TuiMonitor},
19+
monitors::MultiMonitor,
1520
stages::StagesTuple,
1621
state::{HasCurrentStageId, HasExecutions, HasLastReportTime, HasSolutions, Stoppable},
1722
};
23+
#[cfg(unix)]
24+
use libafl::{
25+
events::{EventConfig, SimpleRestartingEventManager, launcher::Launcher},
26+
monitors::Monitor,
27+
};
28+
#[cfg(unix)]
1829
use libafl_bolts::{
1930
core_affinity::Cores,
2031
shmem::{ShMemProvider, StdShMemProvider},
2132
};
2233

2334
use crate::{feedbacks::LibfuzzerCrashCauseMetadata, fuzz_with, options::LibfuzzerOptions};
2435

36+
#[cfg(unix)]
2537
fn destroy_output_fds(options: &LibfuzzerOptions) {
26-
#[cfg(unix)]
27-
{
28-
use libafl_bolts::os::{dup2, null_fd};
38+
use libafl_bolts::os::{dup2, null_fd};
2939

30-
let null_fd = null_fd().unwrap();
31-
let stdout_fd = stdout().as_raw_fd();
32-
let stderr_fd = stderr().as_raw_fd();
40+
let null_fd = null_fd().unwrap();
41+
let stdout_fd = stdout().as_raw_fd();
42+
let stderr_fd = stderr().as_raw_fd();
3343

34-
if options.tui() {
44+
#[cfg(feature = "tui_monitor")]
45+
if options.tui() {
46+
dup2(null_fd, stdout_fd).unwrap();
47+
dup2(null_fd, stderr_fd).unwrap();
48+
return;
49+
}
50+
51+
if options.close_fd_mask() != 0 {
52+
if options.close_fd_mask() & u8::try_from(stderr_fd).unwrap() != 0 {
3553
dup2(null_fd, stdout_fd).unwrap();
54+
}
55+
if options.close_fd_mask() & u8::try_from(stderr_fd).unwrap() != 0 {
3656
dup2(null_fd, stderr_fd).unwrap();
37-
} else if options.close_fd_mask() != 0 {
38-
if options.close_fd_mask() & u8::try_from(stderr_fd).unwrap() != 0 {
39-
dup2(null_fd, stdout_fd).unwrap();
40-
}
41-
if options.close_fd_mask() & u8::try_from(stderr_fd).unwrap() != 0 {
42-
dup2(null_fd, stderr_fd).unwrap();
43-
}
4457
}
4558
}
4659
}
@@ -87,10 +100,17 @@ where
87100
return Err(Error::shutting_down());
88101
}
89102
}
90-
fuzzer.fuzz_loop(stages, executor, state, mgr)?;
103+
if options.runs() == 0 {
104+
fuzzer.fuzz_loop(stages, executor, state, mgr)?;
105+
} else {
106+
for _ in 0..options.runs() {
107+
fuzzer.fuzz_one(stages, executor, state, mgr)?;
108+
}
109+
}
91110
Ok(())
92111
}
93112

113+
#[cfg(unix)]
94114
fn fuzz_single_forking<M>(
95115
options: &LibfuzzerOptions,
96116
harness: &extern "C" fn(*const u8, usize) -> c_int,
@@ -121,9 +141,7 @@ where
121141
})
122142
}
123143

124-
/// Communicate the selected port to subprocesses
125-
const PORT_PROVIDER_VAR: &str = "_LIBAFL_LIBFUZZER_FORK_PORT";
126-
144+
#[cfg(unix)]
127145
fn fuzz_many_forking<M>(
128146
options: &LibfuzzerOptions,
129147
harness: &extern "C" fn(*const u8, usize) -> c_int,
@@ -134,6 +152,9 @@ fn fuzz_many_forking<M>(
134152
where
135153
M: Monitor + Clone + Debug + 'static,
136154
{
155+
// Communicate the selected port to subprocesses
156+
const PORT_PROVIDER_VAR: &str = "_LIBAFL_LIBFUZZER_FORK_PORT";
157+
137158
destroy_output_fds(options);
138159
let broker_port = std::env::var(PORT_PROVIDER_VAR)
139160
.map_err(Error::from)
@@ -194,35 +215,47 @@ pub fn fuzz(
194215
options: &LibfuzzerOptions,
195216
harness: &extern "C" fn(*const u8, usize) -> c_int,
196217
) -> Result<(), Error> {
218+
#[cfg(unix)]
197219
if let Some(forks) = options.forks() {
198220
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
221+
222+
#[cfg(feature = "tui_monitor")]
199223
if options.tui() {
200224
let monitor = TuiMonitor::builder()
201225
.title(options.fuzzer_name())
202226
.enhanced_graphics(true)
203227
.build();
204-
fuzz_many_forking(options, harness, shmem_provider, forks, monitor)
205-
} else if forks == 1 {
206-
let monitor = MultiMonitor::new(create_monitor_closure());
207-
fuzz_single_forking(options, harness, shmem_provider, monitor)
208-
} else {
209-
let monitor = MultiMonitor::new(create_monitor_closure());
210-
fuzz_many_forking(options, harness, shmem_provider, forks, monitor)
228+
return fuzz_many_forking(options, harness, shmem_provider, forks, monitor);
211229
}
212-
} else if options.tui() {
230+
231+
// Non-TUI path or when tui_monitor feature is disabled
232+
let monitor = MultiMonitor::new(create_monitor_closure());
233+
234+
if forks == 1 {
235+
return fuzz_single_forking(options, harness, shmem_provider, monitor);
236+
}
237+
238+
return fuzz_many_forking(options, harness, shmem_provider, forks, monitor);
239+
}
240+
241+
#[cfg(feature = "tui_monitor")]
242+
if options.tui() {
213243
// if the user specifies TUI, we assume they want to fork; it would not be possible to use
214244
// TUI safely otherwise
215245
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
216246
let monitor = TuiMonitor::builder()
217247
.title(options.fuzzer_name())
218248
.enhanced_graphics(true)
219249
.build();
220-
fuzz_many_forking(options, harness, shmem_provider, 1, monitor)
221-
} else {
222-
destroy_output_fds(options);
223-
fuzz_with!(options, harness, do_fuzz, |fuzz_single| {
224-
let mgr = SimpleEventManager::new(MultiMonitor::new(create_monitor_closure()));
225-
crate::start_fuzzing_single(fuzz_single, None, mgr)
226-
})
250+
return fuzz_many_forking(options, harness, shmem_provider, 1, monitor);
227251
}
252+
253+
// Default path when no forks or TUI are specified, or when tui_monitor feature is disabled
254+
#[cfg(unix)]
255+
destroy_output_fds(options);
256+
257+
fuzz_with!(options, harness, do_fuzz, |fuzz_single| {
258+
let mgr = SimpleEventManager::new(MultiMonitor::new(create_monitor_closure()));
259+
crate::start_fuzzing_single(fuzz_single, None, mgr)
260+
})
228261
}

0 commit comments

Comments
 (0)