From 76efb95132844d0697d00bd65eadbc1bacf58669 Mon Sep 17 00:00:00 2001 From: Evian-Zhang Date: Thu, 12 Feb 2026 16:33:45 +0800 Subject: [PATCH 1/3] Add sections about fuzzing APIs with callbacks --- src/SUMMARY.md | 1 + src/cargo-fuzz/callback-fuzz.md | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/cargo-fuzz/callback-fuzz.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e2a3b65..e4d0bb6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,6 +7,7 @@ * [Tutorial](./cargo-fuzz/tutorial.md) * [Guide](./cargo-fuzz/guide.md) * [Structure-Aware Fuzzing](./cargo-fuzz/structure-aware-fuzzing.md) + * [Fuzzing APIs with Callbacks](./cargo-fuzz/callback-fuzz.md) * [Coverage](./cargo-fuzz/coverage.md) * [Targets](./cargo-fuzz/targets.md) * [Fuzzing on Windows](./cargo-fuzz/windows.md) diff --git a/src/cargo-fuzz/callback-fuzz.md b/src/cargo-fuzz/callback-fuzz.md new file mode 100644 index 0000000..4eeacad --- /dev/null +++ b/src/cargo-fuzz/callback-fuzz.md @@ -0,0 +1,34 @@ +# Fuzzing APIs with Callbacks + +It's very flexible to design APIs with callbacks in Rust, while it's not easy to write good fuzzing harnesses for those. + +```rust,ignore +pub fn api_with_callback(user_data: &[u8], callback: impl Fn(&[u32])) { + let dangling_data_ptr: *mut u32 = process_user_data(user_data); + let data_len: usize = HARDCODED_VALUE; + let data = unsafe { std::slice::from_raw_parts(dangling_data_ptr, data_len) }; + callback(data); +} +``` + +In the above example, creating slice from dangling pointer is definitely a UB. However, current fuzzing solutions are often equipped only with address sanitizer, which will detect violations only if an invalid memory is **accessed**. As a result, the creation of such a slice will not be catched by the address sanitizer, and the effectiveness depends on the quality of fuzzing harnesses. + +```rust,ignore +// Bad harness +fuzz_target!(|data: &[u8]| { + api_with_callback(data, |lib_data| {}); +}); + +// Good harness +fuzz_target!(|data: &[u8]| { + api_with_callback(data, |lib_data| { + lib_data.iter().for_each(|byte_ref| { + core::hint::black_box(*byte_ref); + }); + }); +}); +``` + +In the good harness above, each byte of `lib_data` is accessed (and the [`black_box`](https://doc.rust-lang.org/std/hint/fn.black_box.html) is used to avoid the access being optimized out), and any invalid memory accesses will be catched by address sanitizers, leading to effective bug detection. + +Generally, Rust crates mainly uses closures or traits to constrain the callbacks in APIs, and as long as there is a reference in the callback arguments, such a reference should be checked in the fuzzing harness to catch unsoundness. Beyond manuanlly writing checking patterns, crates like [touched](https://crates.io/crates/touched) provide convenient utilities for this purpose. From 5dea4caef294c4ee829cc71fe98e87446c7bf393 Mon Sep 17 00:00:00 2001 From: Evian-Zhang Date: Wed, 18 Feb 2026 15:45:21 +0800 Subject: [PATCH 2/3] Update docs to also mention data obtained from return value --- src/SUMMARY.md | 2 +- .../{callback-fuzz.md => oracles-accessing-data.md} | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) rename src/cargo-fuzz/{callback-fuzz.md => oracles-accessing-data.md} (58%) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e4d0bb6..6c9a091 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,7 +7,7 @@ * [Tutorial](./cargo-fuzz/tutorial.md) * [Guide](./cargo-fuzz/guide.md) * [Structure-Aware Fuzzing](./cargo-fuzz/structure-aware-fuzzing.md) - * [Fuzzing APIs with Callbacks](./cargo-fuzz/callback-fuzz.md) + * [Writing Oracles that Access Data](./cargo-fuzz/oracles-accessing-data.md) * [Coverage](./cargo-fuzz/coverage.md) * [Targets](./cargo-fuzz/targets.md) * [Fuzzing on Windows](./cargo-fuzz/windows.md) diff --git a/src/cargo-fuzz/callback-fuzz.md b/src/cargo-fuzz/oracles-accessing-data.md similarity index 58% rename from src/cargo-fuzz/callback-fuzz.md rename to src/cargo-fuzz/oracles-accessing-data.md index 4eeacad..00bdc01 100644 --- a/src/cargo-fuzz/callback-fuzz.md +++ b/src/cargo-fuzz/oracles-accessing-data.md @@ -1,4 +1,10 @@ -# Fuzzing APIs with Callbacks +# Writing Oracles that Access Data + +Rust requires that a reference should point to a valid value, as defined in [The Rust Reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.validity.reference-box): + +> A reference or `Box` must be aligned and non-null, it cannot be dangling, and it must point to a valid value. + +As a result, a high-quality harness should validate **every reference** obtained from the target library. There are two main categories to obtain data from the target library: either from the API's return value, or in the parameters of callbacks. It's very flexible to design APIs with callbacks in Rust, while it's not easy to write good fuzzing harnesses for those. @@ -31,4 +37,4 @@ fuzz_target!(|data: &[u8]| { In the good harness above, each byte of `lib_data` is accessed (and the [`black_box`](https://doc.rust-lang.org/std/hint/fn.black_box.html) is used to avoid the access being optimized out), and any invalid memory accesses will be catched by address sanitizers, leading to effective bug detection. -Generally, Rust crates mainly uses closures or traits to constrain the callbacks in APIs, and as long as there is a reference in the callback arguments, such a reference should be checked in the fuzzing harness to catch unsoundness. Beyond manuanlly writing checking patterns, crates like [touched](https://crates.io/crates/touched) provide convenient utilities for this purpose. +As described above, the reference data can be obtained either from the API's return value, or in the parameters of callbacks. As long as a reference is obtained from the target library, such a reference should be checked in the fuzzing harness to catch unsoundness. Beyond manuanlly writing checking patterns, crates like [touched](https://crates.io/crates/touched) provide convenient utilities for this purpose. From b76b01a483f1487402e861d44745864af55fd918 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 18 Feb 2026 08:20:57 -0800 Subject: [PATCH 3/3] Do not enumerate all the ways to get a value --- src/cargo-fuzz/oracles-accessing-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cargo-fuzz/oracles-accessing-data.md b/src/cargo-fuzz/oracles-accessing-data.md index 00bdc01..0930a0a 100644 --- a/src/cargo-fuzz/oracles-accessing-data.md +++ b/src/cargo-fuzz/oracles-accessing-data.md @@ -4,7 +4,7 @@ Rust requires that a reference should point to a valid value, as defined in [The > A reference or `Box` must be aligned and non-null, it cannot be dangling, and it must point to a valid value. -As a result, a high-quality harness should validate **every reference** obtained from the target library. There are two main categories to obtain data from the target library: either from the API's return value, or in the parameters of callbacks. +As a result, a high-quality harness should validate **every reference** obtained from the target library. It's very flexible to design APIs with callbacks in Rust, while it's not easy to write good fuzzing harnesses for those.