Skip to content

Commit 63b9226

Browse files
committed
Force at least one unique stack frame per fuzzer
This change is an attempt to address the behavior found at google/oss-fuzz#8389 where two distinct bugs were accidentally deduplicated into the same bug report. One of the reasons for this is that the stack traces between the two bugs were almost the same with only very minor differences. My hope is that by forcing a unique stack frame per fuzzer this will be less likely since there is guaranteed to be at least one stack frame per fuzz target which is unique with this change. While I was here I wrapped up the generated function by the `fuzz_target!` macro in a `const _: () = { ... }` to avoid adding this new `run` function in to the normal module's namespace and accidentally causing name collisions (e.g. if fuzz targets already have functions named `run`)
1 parent 74ac60c commit 63b9226

File tree

1 file changed

+79
-52
lines changed

1 file changed

+79
-52
lines changed

src/lib.rs

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -136,74 +136,101 @@ pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize
136136
#[macro_export]
137137
macro_rules! fuzz_target {
138138
(|$bytes:ident| $body:block) => {
139-
/// Auto-generated function
140-
#[no_mangle]
141-
pub extern "C" fn rust_fuzzer_test_input($bytes: &[u8]) {
142-
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
143-
// formatting of the input to that file. This is only intended for
144-
// `cargo fuzz`'s use!
139+
const _: () = {
140+
/// Auto-generated function
141+
#[no_mangle]
142+
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
143+
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
144+
// formatting of the input to that file. This is only intended for
145+
// `cargo fuzz`'s use!
145146

146-
// `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization.
147-
if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() {
148-
use std::io::Write;
149-
let mut file = std::fs::File::create(path)
150-
.expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file");
151-
writeln!(&mut file, "{:?}", $bytes)
152-
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
153-
return;
147+
// `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization.
148+
if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() {
149+
use std::io::Write;
150+
let mut file = std::fs::File::create(path)
151+
.expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file");
152+
writeln!(&mut file, "{:?}", bytes)
153+
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
154+
return;
155+
}
156+
157+
run(bytes)
154158
}
155159

156-
$body
157-
}
160+
// Split out the actual fuzzer into a separate function which is
161+
// tagged as never being inlined. This ensures that if the fuzzer
162+
// panics there's at least one stack frame which is named uniquely
163+
// according to this specific fuzzer that this is embedded within.
164+
//
165+
// Systems like oss-fuzz try to deduplicate crashes and without this
166+
// panics in separate fuzzers can accidentally appear the same
167+
// because each fuzzer will have a function called
168+
// `rust_fuzzer_test_input`. By using a normal Rust function here
169+
// it's named something like `the_fuzzer_name::_::run` which should
170+
// ideally help prevent oss-fuzz from deduplicate fuzz bugs across
171+
// distinct targets accidentally.
172+
#[inline(never)]
173+
fn run($bytes: &[u8]) {
174+
$body
175+
}
176+
};
158177
};
159178

160179
(|$data:ident: &[u8]| $body:block) => {
161180
$crate::fuzz_target!(|$data| $body);
162181
};
163182

164183
(|$data:ident: $dty: ty| $body:block) => {
165-
/// Auto-generated function
166-
#[no_mangle]
167-
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
168-
use $crate::arbitrary::{Arbitrary, Unstructured};
184+
const _: () = {
185+
/// Auto-generated function
186+
#[no_mangle]
187+
pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) {
188+
use $crate::arbitrary::{Arbitrary, Unstructured};
169189

170-
// Early exit if we don't have enough bytes for the `Arbitrary`
171-
// implementation. This helps the fuzzer avoid exploring all the
172-
// different not-enough-input-bytes paths inside the `Arbitrary`
173-
// implementation. Additionally, it exits faster, letting the fuzzer
174-
// get to longer inputs that actually lead to interesting executions
175-
// quicker.
176-
if bytes.len() < <$dty as Arbitrary>::size_hint(0).0 {
177-
return;
178-
}
190+
// Early exit if we don't have enough bytes for the `Arbitrary`
191+
// implementation. This helps the fuzzer avoid exploring all the
192+
// different not-enough-input-bytes paths inside the `Arbitrary`
193+
// implementation. Additionally, it exits faster, letting the fuzzer
194+
// get to longer inputs that actually lead to interesting executions
195+
// quicker.
196+
if bytes.len() < <$dty as Arbitrary>::size_hint(0).0 {
197+
return;
198+
}
179199

180-
let mut u = Unstructured::new(bytes);
181-
let data = <$dty as Arbitrary>::arbitrary_take_rest(u);
200+
let mut u = Unstructured::new(bytes);
201+
let data = <$dty as Arbitrary>::arbitrary_take_rest(u);
182202

183-
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
184-
// formatting of the input to that file. This is only intended for
185-
// `cargo fuzz`'s use!
203+
// When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug
204+
// formatting of the input to that file. This is only intended for
205+
// `cargo fuzz`'s use!
186206

187-
// `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization.
188-
if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() {
189-
use std::io::Write;
190-
let mut file = std::fs::File::create(path)
191-
.expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file");
192-
(match data {
193-
Ok(data) => writeln!(&mut file, "{:#?}", data),
194-
Err(err) => writeln!(&mut file, "Arbitrary Error: {}", err),
195-
})
196-
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
197-
return;
198-
}
207+
// `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization.
208+
if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() {
209+
use std::io::Write;
210+
let mut file = std::fs::File::create(path)
211+
.expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file");
212+
(match data {
213+
Ok(data) => writeln!(&mut file, "{:#?}", data),
214+
Err(err) => writeln!(&mut file, "Arbitrary Error: {}", err),
215+
})
216+
.expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file");
217+
return;
218+
}
199219

200-
let $data = match data {
201-
Ok(d) => d,
202-
Err(_) => return,
203-
};
220+
let data = match data {
221+
Ok(d) => d,
222+
Err(_) => return,
223+
};
204224

205-
$body
206-
}
225+
run(data)
226+
}
227+
228+
// See above for why this is split to a separate function.
229+
#[inline(never)]
230+
fn run($data: $dty) {
231+
$body
232+
}
233+
};
207234
};
208235
}
209236

0 commit comments

Comments
 (0)