bolero_libfuzzer_hydro/
lib.rs

1//! libfuzzer plugin for bolero
2//!
3//! This crate should not be used directly. Instead, use `bolero`.
4
5#[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        // entrypoint for libfuzzer
20        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            // TODO implement caching
108            // let mut cache = driver::cache::Cache::default();
109            let mut report = GeneratorReport::default();
110            report.spawn_timer();
111
112            // extend the lifetime of the bytes so it can be stored in local storage
113            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                // extend the lifetime of the slice so it can be stored in TLS
120                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            // nothing to report
223            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        // Libfuzzer can generate multiple jobs that can make the binary recurse.
253        // Still, let’s limit recursion depth to some reasonable amount.
254        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        // create a vector of NULL terminated strings
271        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        // convert the strings to raw pointers
284        let c_args = args
285            .iter()
286            .map(|arg| arg.as_ptr())
287            .chain(Some(core::ptr::null())) // add a null pointer to the end
288            .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::*;