bolero_libfuzzer/
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                        if let Some(shrunken) = shrunken {
64                            eprintln!("{:#}", shrunken);
65                        } else {
66                            let input = input::Bytes::new(slice, options);
67                            eprintln!(
68                                "{:#}",
69                                Failure {
70                                    seed: None,
71                                    error,
72                                    input
73                                }
74                            );
75                        }
76
77                        std::process::abort();
78                    }
79                }
80            })
81        }
82    }
83
84    impl ScopedEngine for LibFuzzerEngine {
85        type Output = Never;
86
87        fn run<F, R>(self, mut test: F, options: driver::Options) -> Self::Output
88        where
89            F: FnMut() -> R + core::panic::RefUnwindSafe,
90            R: bolero_engine::IntoResult,
91        {
92            panic::set_hook();
93            panic::forward_panic(false);
94
95            let options = &options;
96            // TODO implement caching
97            // let mut cache = driver::cache::Cache::default();
98            let mut report = GeneratorReport::default();
99            report.spawn_timer();
100
101            // extend the lifetime of the bytes so it can be stored in local storage
102            let driver = bolero_engine::driver::bytes::Driver::new(&[][..], options);
103            let driver = bolero_engine::driver::object::Object(driver);
104            let driver = Box::new(driver);
105            let mut driver = Some(driver);
106
107            start(&mut |slice: &[u8]| {
108                // extend the lifetime of the slice so it can be stored in TLS
109                let input: &'static [u8] = unsafe { core::mem::transmute::<&[u8], &[u8]>(slice) };
110                let mut drv = driver.take().unwrap();
111                drv.reset(input, options);
112                let (drv, result) = bolero_engine::any::run(drv, &mut test);
113                driver = Some(drv);
114
115                match result {
116                    Ok(is_valid) => {
117                        report.on_result(is_valid);
118                    }
119                    Err(error) => {
120                        eprintln!(
121                            "{:#}",
122                            Failure {
123                                seed: None,
124                                error,
125                                input: (),
126                            }
127                        );
128
129                        std::process::abort();
130                    }
131                }
132            });
133        }
134    }
135
136    #[derive(Default)]
137    struct GeneratorReport {
138        total_runs: u64,
139        window_runs: u64,
140        total_valid: u64,
141        window_valid: u64,
142        should_print: std::sync::Arc<atomic::AtomicBool>,
143    }
144
145    impl GeneratorReport {
146        pub fn spawn_timer(&self) {
147            let should_print = self.should_print.clone();
148            std::thread::spawn(move || {
149                while std::sync::Arc::strong_count(&should_print) > 1 {
150                    std::thread::sleep(Duration::from_secs(1));
151                    should_print.store(true, atomic::Ordering::Relaxed);
152                }
153            });
154        }
155
156        pub fn on_result(&mut self, is_valid: bool) {
157            self.window_runs += 1;
158            if is_valid {
159                self.window_valid += 1;
160            }
161
162            // nothing to report
163            if self.window_runs == self.window_valid {
164                return;
165            }
166
167            if !self.should_print.swap(false, atomic::Ordering::Relaxed) {
168                return;
169            }
170
171            self.total_runs += self.window_runs;
172            self.total_valid += self.window_valid;
173
174            let total_perc = self.total_valid as f32 / self.total_runs as f32 * 100.0;
175            let window_perc = self.window_valid as f32 / self.window_runs as f32 * 100.0;
176            println!(
177                "#{}\tGENERATE\tvalid: {} ({:.2}%) valid/s: {} ({:.2}%)",
178                self.total_runs, self.total_valid, total_perc, self.window_valid, window_perc,
179            );
180            self.window_runs = 0;
181            self.window_valid = 0;
182        }
183    }
184
185    fn start<F: FnMut(&[u8])>(run_one_test: &mut F) -> Never {
186        unsafe {
187            TESTFN = Some(std::mem::transmute::<TestFn, TestFn>(
188                run_one_test as &mut dyn FnMut(&[u8]),
189            ));
190        }
191
192        // Libfuzzer can generate multiple jobs that can make the binary recurse.
193        // Still, let’s limit recursion depth to some reasonable amount.
194        let recursion_level = std::env::var("__BOLERO_LIBFUZZER_RECURSE")
195            .as_deref()
196            .unwrap_or("0")
197            .parse()
198            .unwrap_or(usize::MAX);
199
200        if recursion_level > 10 {
201            eprintln!("LOOPING BINARY");
202            std::process::exit(1);
203        }
204
205        std::env::set_var(
206            "__BOLERO_LIBFUZZER_RECURSE",
207            (recursion_level + 1).to_string(),
208        );
209
210        // create a vector of NULL terminated strings
211        let args = std::env::args()
212            .next()
213            .as_deref()
214            .into_iter()
215            .chain(
216                std::env::var("BOLERO_LIBFUZZER_ARGS")
217                    .expect("missing libfuzzer args")
218                    .split(' '),
219            )
220            .map(|arg| CString::new(arg).unwrap())
221            .collect::<Vec<_>>();
222
223        // convert the strings to raw pointers
224        let c_args = args
225            .iter()
226            .map(|arg| arg.as_ptr())
227            .chain(Some(core::ptr::null())) // add a null pointer to the end
228            .collect::<Vec<_>>();
229
230        let res = unsafe { LLVMFuzzerStartTest(args.len() as c_int, c_args.as_ptr()) };
231
232        std::process::exit(res);
233    }
234
235    #[doc(hidden)]
236    #[no_mangle]
237    pub unsafe extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32 {
238        let data_slice = std::slice::from_raw_parts(data, size);
239        (TESTFN.as_mut().expect("uninitialized test function"))(data_slice);
240        0
241    }
242
243    #[doc(hidden)]
244    #[no_mangle]
245    pub unsafe extern "C" fn LLVMFuzzerInitialize(
246        _argc: *const isize,
247        _argv: *const *const *const u8,
248    ) -> isize {
249        0
250    }
251}
252
253#[doc(hidden)]
254#[cfg(all(feature = "lib", fuzzing_libfuzzer))]
255pub use fuzzer::*;