bolero_libfuzzer_hydro/
lib.rs1#[doc(hidden)]
6#[cfg(any(test, all(feature = "lib", fuzzing_libfuzzer)))]
7pub mod fuzzer {
8 use bolero_engine::{
9 driver, input, panic, Engine, Failure, Never, ScopedEngine, TargetLocation, Test,
10 };
11 use core::time::Duration;
12 use std::{
13 ffi::CString,
14 os::raw::{c_char, c_int},
15 sync::atomic,
16 };
17
18 extern "C" {
19 pub fn LLVMFuzzerStartTest(a: c_int, b: *const *const c_char) -> c_int;
21 }
22
23 type TestFn<'a> = &'a mut dyn FnMut(&[u8]);
24
25 static mut TESTFN: Option<TestFn> = None;
26
27 #[derive(Debug, Default)]
28 pub struct LibFuzzerEngine {}
29
30 impl LibFuzzerEngine {
31 pub fn new(_location: TargetLocation) -> Self {
32 Self::default()
33 }
34 }
35
36 impl<T: Test> Engine<T> for LibFuzzerEngine
37 where
38 T::Value: core::fmt::Debug,
39 {
40 type Output = Never;
41
42 fn run(self, mut test: T, options: driver::Options) -> Self::Output {
43 panic::set_hook();
44 panic::forward_panic(false);
45
46 let options = &options;
47 let mut cache = driver::cache::Cache::default();
48 let mut report = GeneratorReport::default();
49 report.spawn_timer();
50
51 start(&mut |slice: &[u8]| {
52 let mut input = input::cache::Bytes::new(slice, options, &mut cache);
53
54 match test.test(&mut input) {
55 Ok(is_valid) => {
56 report.on_result(is_valid);
57 }
58 Err(error) => {
59 eprintln!("test failed; shrinking input...");
60
61 let shrunken = test.shrink(slice.to_vec(), None, options);
62
63 let bytes = if let Some((shrunken_bytes, shrunken)) = shrunken {
64 eprintln!("{shrunken:#}");
65 shrunken_bytes
66 } else {
67 let input = input::Bytes::new(slice, options);
68 eprintln!(
69 "{:#}",
70 Failure {
71 seed: None,
72 error,
73 hide_error: false,
74 input
75 }
76 );
77 slice.to_vec()
78 };
79
80 if let Ok(path) = std::env::var("BOLERO_FAILURE_OUTPUT") {
81 if let Ok(mut file) = std::fs::File::create(path) {
82 use std::io::Write;
83 file.write_all(&bytes)
84 .expect("failed to write failure output");
85 }
86 }
87
88 std::process::abort();
89 }
90 }
91 })
92 }
93 }
94
95 impl ScopedEngine for LibFuzzerEngine {
96 type Output = Never;
97
98 fn run<F, R>(self, mut test: F, options: driver::Options) -> Self::Output
99 where
100 F: FnMut(bool) -> R + core::panic::RefUnwindSafe,
101 R: bolero_engine::IntoResult,
102 {
103 panic::set_hook();
104 panic::forward_panic(false);
105
106 let options = &options;
107 let mut report = GeneratorReport::default();
110 report.spawn_timer();
111
112 let driver = bolero_engine::driver::bytes::Driver::new(&[][..], options);
114 let driver = bolero_engine::driver::object::Object(driver);
115 let driver = Box::new(driver);
116 let mut driver = Some(driver);
117
118 start(&mut |slice: &[u8]| {
119 let input: &'static [u8] = unsafe { core::mem::transmute::<&[u8], &[u8]>(slice) };
121 let mut drv = driver.take().unwrap();
122 drv.reset(input, options);
123 let (drv, result) = bolero_engine::any::run(drv, || test(false));
124 driver = Some(drv);
125
126 match result {
127 Ok(is_valid) => {
128 report.on_result(is_valid);
129 }
130 Err(mut error) => {
131 eprintln!("test failed; shrinking input...");
132
133 let shrunken =
134 bolero_engine::BorrowedSliceTest::new(|shrink_slice: &[u8]| {
135 let mut drv = driver.take().unwrap();
136 drv.reset(
137 unsafe {
138 core::mem::transmute::<&[u8], &'static [u8]>(shrink_slice)
139 },
140 options,
141 );
142 let (drv_back, result) = bolero_engine::any::run(drv, || test(false));
143 driver = Some(drv_back);
144 result.map(|_| ())
145 })
146 .shrink(slice.to_vec(), None, options);
147
148 let bytes = if let Some((shrunken_bytes, mut shrunken)) = shrunken {
149 if options.replay_on_fail() {
150 shrunken.hide_error = true;
151 }
152
153 eprintln!("{shrunken:#}");
154 shrunken_bytes
155 } else {
156 let input = input::Bytes::new(slice, options);
157 eprintln!(
158 "{:#}",
159 Failure {
160 seed: None,
161 error,
162 hide_error: options.replay_on_fail(),
163 input
164 }
165 );
166 slice.to_vec()
167 };
168
169 if let Ok(path) = std::env::var("BOLERO_FAILURE_OUTPUT") {
170 if let Ok(mut file) = std::fs::File::create(path) {
171 use std::io::Write;
172 file.write_all(&bytes)
173 .expect("failed to write failure output");
174 }
175 }
176
177 if options.replay_on_fail() {
178 panic::forward_panic(true);
179 let mut drv = driver.take().unwrap();
180 drv.reset(
181 unsafe { core::mem::transmute::<&[u8], &'static [u8]>(&bytes) },
182 options,
183 );
184 let (drv_back, result) = bolero_engine::any::scope::with(drv, || test(true));
185 result.into_result().expect_err("Did not crash when replaying, is it deterministic?");
186 driver = Some(drv_back);
187 }
188
189 std::process::abort();
190 }
191 }
192 });
193 }
194 }
195
196 #[derive(Default)]
197 struct GeneratorReport {
198 total_runs: u64,
199 window_runs: u64,
200 total_valid: u64,
201 window_valid: u64,
202 should_print: std::sync::Arc<atomic::AtomicBool>,
203 }
204
205 impl GeneratorReport {
206 pub fn spawn_timer(&self) {
207 let should_print = self.should_print.clone();
208 std::thread::spawn(move || {
209 while std::sync::Arc::strong_count(&should_print) > 1 {
210 std::thread::sleep(Duration::from_secs(1));
211 should_print.store(true, atomic::Ordering::Relaxed);
212 }
213 });
214 }
215
216 pub fn on_result(&mut self, is_valid: bool) {
217 self.window_runs += 1;
218 if is_valid {
219 self.window_valid += 1;
220 }
221
222 if self.window_runs == self.window_valid {
224 return;
225 }
226
227 if !self.should_print.swap(false, atomic::Ordering::Relaxed) {
228 return;
229 }
230
231 self.total_runs += self.window_runs;
232 self.total_valid += self.window_valid;
233
234 let total_perc = self.total_valid as f32 / self.total_runs as f32 * 100.0;
235 let window_perc = self.window_valid as f32 / self.window_runs as f32 * 100.0;
236 println!(
237 "#{}\tGENERATE\tvalid: {} ({:.2}%) valid/s: {} ({:.2}%)",
238 self.total_runs, self.total_valid, total_perc, self.window_valid, window_perc,
239 );
240 self.window_runs = 0;
241 self.window_valid = 0;
242 }
243 }
244
245 fn start<F: FnMut(&[u8])>(run_one_test: &mut F) -> Never {
246 unsafe {
247 TESTFN = Some(std::mem::transmute::<TestFn, TestFn>(
248 run_one_test as &mut dyn FnMut(&[u8]),
249 ));
250 }
251
252 let recursion_level = std::env::var("__BOLERO_LIBFUZZER_RECURSE")
255 .as_deref()
256 .unwrap_or("0")
257 .parse()
258 .unwrap_or(usize::MAX);
259
260 if recursion_level > 10 {
261 eprintln!("LOOPING BINARY");
262 std::process::exit(1);
263 }
264
265 std::env::set_var(
266 "__BOLERO_LIBFUZZER_RECURSE",
267 (recursion_level + 1).to_string(),
268 );
269
270 let args = std::env::args()
272 .next()
273 .as_deref()
274 .into_iter()
275 .chain(
276 std::env::var("BOLERO_LIBFUZZER_ARGS")
277 .expect("missing libfuzzer args")
278 .split(' '),
279 )
280 .map(|arg| CString::new(arg).unwrap())
281 .collect::<Vec<_>>();
282
283 let c_args = args
285 .iter()
286 .map(|arg| arg.as_ptr())
287 .chain(Some(core::ptr::null())) .collect::<Vec<_>>();
289
290 let res = unsafe { LLVMFuzzerStartTest(args.len() as c_int, c_args.as_ptr()) };
291
292 std::process::exit(res);
293 }
294
295 #[doc(hidden)]
296 #[no_mangle]
297 pub unsafe extern "C-unwind" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32 {
298 let data_slice = std::slice::from_raw_parts(data, size);
299 (TESTFN.as_mut().expect("uninitialized test function"))(data_slice);
300 0
301 }
302
303 #[doc(hidden)]
304 #[no_mangle]
305 pub unsafe extern "C" fn LLVMFuzzerInitialize(
306 _argc: *const isize,
307 _argv: *const *const *const u8,
308 ) -> isize {
309 0
310 }
311}
312
313#[doc(hidden)]
314#[cfg(all(feature = "lib", fuzzing_libfuzzer))]
315pub use fuzzer::*;