Skip to content

Commit dfe756f

Browse files
committed
Enable keep/reject inputs from the corpus
This allows the fuzz target to indiciate whether an input was useful for the fuzz testing by returning Corpus::Keep or Corpus::Reject. Backwards compatibility is preserved by coercing the unit type () to Corpus::Keep. This maps to 0 (Keep) and -1 (Reject) in the libFuzzer API: https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs
1 parent 64ad66a commit dfe756f

File tree

1 file changed

+75
-15
lines changed

1 file changed

+75
-15
lines changed

src/lib.rs

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,37 @@
1414
pub use arbitrary;
1515
use once_cell::sync::OnceCell;
1616

17+
/// Indicates whether the input should be kept in the corpus or rejected. This
18+
/// should be returned by your fuzz target. If your fuzz target does not return
19+
/// a value (i.e., returns `()`), then the input will be kept in the corpus.
20+
#[derive(Debug)]
21+
pub enum Corpus {
22+
/// Keep the input in the corpus.
23+
Keep,
24+
25+
/// Reject the input and do not keep it in the corpus.
26+
Reject,
27+
}
28+
29+
impl From<()> for Corpus {
30+
fn from(_: ()) -> Self {
31+
Self::Keep
32+
}
33+
}
34+
35+
impl From<Corpus> for i32 {
36+
fn from(value: Corpus) -> i32 {
37+
match value {
38+
Corpus::Keep => 0,
39+
Corpus::Reject => -1,
40+
}
41+
}
42+
}
43+
1744
extern "C" {
1845
// We do not actually cross the FFI bound here.
1946
#[allow(improper_ctypes)]
20-
fn rust_fuzzer_test_input(input: &[u8]);
47+
fn rust_fuzzer_test_input(input: &[u8]) -> i32;
2148

2249
fn LLVMFuzzerMutate(data: *mut u8, size: usize, max_size: usize) -> usize;
2350
}
@@ -27,14 +54,17 @@ extern "C" {
2754
pub fn test_input_wrap(data: *const u8, size: usize) -> i32 {
2855
let test_input = ::std::panic::catch_unwind(|| unsafe {
2956
let data_slice = ::std::slice::from_raw_parts(data, size);
30-
rust_fuzzer_test_input(data_slice);
57+
rust_fuzzer_test_input(data_slice)
3158
});
32-
if test_input.err().is_some() {
33-
// hopefully the custom panic hook will be called before and abort the
34-
// process before the stack frames are unwinded.
35-
::std::process::abort();
59+
60+
match test_input {
61+
Ok(i) => i,
62+
Err(_) => {
63+
// hopefully the custom panic hook will be called before and abort the
64+
// process before the stack frames are unwinded.
65+
::std::process::abort();
66+
}
3667
}
37-
0
3868
}
3969

4070
#[doc(hidden)]
@@ -86,6 +116,30 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize
86116
/// # mod my_crate { pub fn parse(_: &[u8]) -> Result<(), ()> { unimplemented!() } }
87117
/// ```
88118
///
119+
/// ## Rejecting Inputs
120+
///
121+
/// To indicate whether an input should be kept in or rejected from the corpus,
122+
/// return a [Corpus] value from your fuzz target. For example:
123+
///
124+
/// ```no_run
125+
/// #![no_main]
126+
///
127+
/// use libfuzzer_sys::{Corpus, fuzz_target};
128+
///
129+
/// fuzz_target!(|input: String| -> Corpus {
130+
/// let parts: Vec<&str> = input.splitn(2, '=').collect();
131+
/// if parts.len() != 2 {
132+
/// return Corpus::Reject;
133+
/// }
134+
///
135+
/// let key = parts[0];
136+
/// let value = parts[1];
137+
/// my_crate::parse(key, value);
138+
/// Corpus::Keep
139+
/// );
140+
/// # mod my_crate { pub fn parse(_key: &str, _value: &str) -> Result<(), ()> { unimplemented!() } }
141+
/// ```
142+
///
89143
/// ## Arbitrary Input Types
90144
///
91145
/// The input is a `&[u8]` slice by default, but you can take arbitrary input
@@ -139,7 +193,7 @@ macro_rules! fuzz_target {
139193
const _: () = {
140194
/// Auto-generated function
141195
#[no_mangle]
142-
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
196+
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 {
143197
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
144198
// formatting of the input to that file. This is only intended for
145199
// `cargo fuzz`'s use!
@@ -154,7 +208,8 @@ macro_rules! fuzz_target {
154208
return;
155209
}
156210

157-
run(bytes)
211+
run(bytes);
212+
0
158213
}
159214

160215
// Split out the actual fuzzer into a separate function which is
@@ -181,10 +236,14 @@ macro_rules! fuzz_target {
181236
};
182237

183238
(|$data:ident: $dty: ty| $body:block) => {
239+
$crate::fuzz_target!(|$data: $dty| -> () $body);
240+
};
241+
242+
(|$data:ident: $dty: ty| -> $rty: ty $body:block) => {
184243
const _: () = {
185244
/// Auto-generated function
186245
#[no_mangle]
187-
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
246+
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) -> i32 {
188247
use $crate::arbitrary::{Arbitrary, Unstructured};
189248

190249
// Early exit if we don't have enough bytes for the `Arbitrary`
@@ -194,7 +253,7 @@ macro_rules! fuzz_target {
194253
// get to longer inputs that actually lead to interesting executions
195254
// quicker.
196255
if bytes.len() < <$dty as Arbitrary>::size_hint(0).0 {
197-
return;
256+
return -1;
198257
}
199258

200259
let mut u = Unstructured::new(bytes);
@@ -214,20 +273,21 @@ macro_rules! fuzz_target {
214273
Err(err) => writeln!(&mut file, "Arbitrary Error: {}", err),
215274
})
216275
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
217-
return;
276+
return -1;
218277
}
219278

220279
let data = match data {
221280
Ok(d) => d,
222-
Err(_) => return,
281+
Err(_) => return -1,
223282
};
224283

225-
run(data)
284+
let result: i32 = ::libfuzzer_sys::Corpus::from(run(data)).into();
285+
result
226286
}
227287

228288
// See above for why this is split to a separate function.
229289
#[inline(never)]
230-
fn run($data: $dty) {
290+
fn run($data: $dty) -> $rty {
231291
$body
232292
}
233293
};

0 commit comments

Comments
 (0)