blob: 193cb89330eaaa9f833a2ef08326feeee7b7875c [file] [log] [blame]
// 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,
quote::{quote, quote_spanned},
std::ops::Deref,
syn::{
parse::Error,
parse_macro_input,
FnArg::{self, Receiver, Typed},
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()
}