| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Rust/LibFuzzer integration for Fuchsia |
| |
| use proc_macro::TokenStream; |
| use quote::{quote, quote_spanned}; |
| use std::ops::Deref; |
| use syn::parse::Error; |
| use syn::parse_macro_input; |
| use syn::FnArg::{self, Receiver, Typed}; |
| use syn::Type::Reference; |
| |
| /// Defines a fuzz target function. |
| /// |
| /// This macro creates an exported function with a well-known symbol, `LLVMFuzzerTestOneInput`, as |
| /// defined in https://llvm.org/docs/LibFuzzer.html#fuzz-target. The macro can be used in two |
| /// ways: |
| /// |
| /// The "manual" invocation simply takes a slice of bytes, uses them to exercise the API being |
| /// tested, and does not return anything. For example: |
| /// ``` |
| /// use fuchsia_fuzzing::fuzz; |
| /// |
| /// #[fuzz] |
| /// fn my_fuzzer(input: &[u8]) { |
| /// if let Some(x) = transform_bytes_to_something_else(input) { |
| /// do_something_with_my_api(x); |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// The "automatic" invocation is more flexible: it can take one or more inputs of types with the |
| /// `Arbitrary` trait, use them to exercise the API being tested, and does not return anything. |
| /// For example: |
| /// ``` |
| /// use { |
| /// arbitrary::Arbitrary, |
| /// fuchsia_fuzzing::fuzz, |
| /// }; |
| /// |
| /// #[derive(Arbitrary)] |
| /// pub enum Temp { |
| /// Celsius(i32), |
| /// Kelvin(u32), |
| /// } |
| /// |
| /// #[derive(Arbitrary)] |
| /// pub struct Metrics { |
| /// name: Option<String>, |
| /// metrics: HashMap<String, Vec<Temp>>, |
| /// } |
| /// |
| /// #[fuzz] |
| /// fn my_fuzzer(metrics: Metrics, log: Vec<String>) { |
| /// add_metrics_to_logs(metrics, logs); |
| /// } |
| /// ``` |
| /// |
| /// The recommended way to link the `LLVMFuzzerTestOneInput` into a fuzzer binary is to use the |
| /// `rustc_fuzzer` GN template from //build/rust/rustc_fuzzer.gni. |
| /// |
| #[proc_macro_attribute] |
| pub fn fuzz(_attr: TokenStream, item: TokenStream) -> TokenStream { |
| let item = parse_macro_input!(item as syn::ItemFn); |
| let syn::ItemFn { attrs, sig, vis: _, block } = item; |
| let sig_ident = &sig.ident; |
| let span = sig_ident.span(); |
| let name = sig_ident.to_string(); |
| |
| // Validate function signature. |
| if sig.inputs.len() == 0 { |
| return Error::new(span, "expected at least 1 parameter").to_compile_error().into(); |
| } |
| |
| let first_input = sig.inputs.first().unwrap(); |
| if let Receiver(_r) = first_input { |
| return Error::new(span, "fuzz target function cannot be a method") |
| .to_compile_error() |
| .into(); |
| } |
| |
| // Strip off a couple layers, and separate patterns and types. |
| let num_inputs = sig.inputs.len(); |
| let mut input_pats = sig.inputs.iter().filter_map(|a| match a { |
| Typed(p) => Some(p.pat.deref()), |
| _ => None, |
| }); |
| let mut input_tys = sig |
| .inputs |
| .iter() |
| .filter_map(|a| match a { |
| Typed(p) => Some(p.ty.deref()), |
| _ => None, |
| }) |
| .peekable(); |
| |
| // Build an example of what an input to manual fuzz target function looks like. |
| let manual_input: FnArg = syn::parse_quote! { input: &[u8] }; |
| let manual_input = match manual_input { |
| Typed(p) => Some(p), |
| _ => None, |
| } |
| .unwrap(); |
| |
| // Detect which kind of fuzz target function we have, manual or automatic. |
| let is_manual = if let (Reference(manual_ref), Reference(first_ref)) = |
| (manual_input.ty.deref(), input_tys.peek().unwrap()) |
| { |
| num_inputs == 1 && manual_ref.elem == first_ref.elem |
| } else { |
| false |
| }; |
| |
| let fuzz_target = if is_manual { |
| // Manual fuzz target function: single byte slice input. |
| let first_input_pat = input_pats.next(); |
| quote! { |
| // Data must not be modified; make an immutable slice. |
| let #first_input_pat = unsafe { std::slice::from_raw_parts(data, size) }; |
| let _ = #sig_ident(#first_input_pat); |
| } |
| } else { |
| // Automatic fuzz target function: variable Arbitrary inputs. |
| let input_tys_clone = input_tys.clone(); |
| let min_size = quote! { #(<#input_tys_clone as arbitrary::Arbitrary>::size_hint(0).0)+* }; |
| |
| let input_pats_clone = input_pats.clone(); |
| let arbitrary_pats = quote! { #(Ok(#input_pats_clone)),* }; |
| |
| let input_pats_clone = input_pats.clone(); |
| let invocation = quote! { #(#input_pats_clone),* }; |
| |
| // It would be nice to use `quote`s interpolation repetition here, but we need to handle the |
| // last input slightly differently than the others. |
| let mut arbitrary_tys = quote! {}; |
| let input_tys_clone = input_tys.clone(); |
| for (i, input_ty) in input_tys_clone.enumerate() { |
| if i != num_inputs - 1 { |
| arbitrary_tys.extend( |
| quote! { <#input_ty as arbitrary::Arbitrary>::arbitrary(&mut unstructured), }, |
| ); |
| } else { |
| arbitrary_tys |
| .extend(quote! { <#input_ty as arbitrary::Arbitrary>::arbitrary_take_rest(unstructured) }); |
| } |
| } |
| quote! { |
| // Data must not be modified; make an immutable slice. |
| let data = unsafe { std::slice::from_raw_parts(data, size) }; |
| // Early exit if not enough input bytes. |
| if data.len() < #min_size { |
| return 0; |
| } |
| let mut unstructured = arbitrary::Unstructured::new(data); |
| if let ( #arbitrary_pats ) = ( #arbitrary_tys ) { |
| let _ = #sig_ident(#invocation); |
| } |
| } |
| }; |
| |
| let output = quote_spanned! {span=> |
| // This anonymous constant prevents Rust code from calling this symbol natively. It can |
| // only be called by linking against the produced object file, e.g. with libFuzzer. |
| const _: () = { |
| // Creates a separate function to allow fuzzer authors to use `return`, etc. |
| #[cfg(fuzz_target = #name)] |
| #sig |
| #block |
| |
| // This function wraps the fuzz target function above to create input parameters and |
| // ensure the correct return value is used. |
| #[cfg(fuzz_target = #name)] |
| #[no_mangle] |
| #(#attrs)* |
| pub extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32 { |
| #fuzz_target |
| 0 // Always return zero per https://llvm.org/docs/LibFuzzer.html#fuzz-target. |
| } |
| }; |
| }; |
| output.into() |
| } |