diff --git a/README.md b/README.md index a4d7933b..1834a9db 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,10 @@ test-fuzz = { version = "*", features = ["self_ty_in_mod_name"] } The `test-fuzz` package currently supports the following features: +### `cast_checks` + +Use [`cast_checks`] to automatically check target functions for invalid casts. + ### `self_ty_in_mod_name` Incorporate an `impl`'s `Self` type into the names of modules generated for the `impl`. Expansion of the `test_fuzz` macro adds a module definition to the enclosing scope. By default, the module is named `target_fuzz`, where `target` is the name of the target. If the target appears in an `impl` block, then use of this feature causes the module to instead be named `path_target_fuzz`, where `path` is the path of the `impl`'s `Self` type converted to snake case and joined with `_`. (See also [`rename`] above.) @@ -521,6 +525,7 @@ These options are incompatible in the following sense. If a fuzz target's argume [`cargo test-fuzz` command]: #cargo-test-fuzz-command [`cargo test-fuzz`]: #cargo-test-fuzz-command [`cargo-clone`]: https://github.com/JanLikar/cargo-clone +[`cast_checks`]: https://github.com/trailofbits/cast_checks [`convert`]: #convert--x-y [`core::ops::Add`]: https://doc.rust-lang.org/beta/core/ops/trait.Add.html [`core::ops::Div`]: https://doc.rust-lang.org/beta/core/ops/trait.Div.html diff --git a/cargo-test-fuzz/tests/fuzz_cast.rs b/cargo-test-fuzz/tests/fuzz_cast.rs new file mode 100644 index 00000000..fec999df --- /dev/null +++ b/cargo-test-fuzz/tests/fuzz_cast.rs @@ -0,0 +1,35 @@ +use predicates::prelude::*; +use testing::{examples, retry, CommandExt}; + +const MAX_TOTAL_TIME: &str = "60"; + +#[test] +fn fuzz_cast() { + examples::test("cast", "test") + .unwrap() + .logged_assert() + .success(); + + for use_cast_checks in [false, true] { + let mut args = vec![ + "--exit-code", + "--run-until-crash", + "--max-total-time", + MAX_TOTAL_TIME, + ]; + let code = if use_cast_checks { + args.push("--features=test-fuzz/cast_checks"); + 1 + } else { + 0 + }; + retry(3, || { + examples::test_fuzz("cast", "target") + .unwrap() + .args(&args) + .logged_assert() + .try_code(predicate::eq(code)) + }) + .unwrap(); + } +} diff --git a/examples/tests/cast.rs b/examples/tests/cast.rs new file mode 100644 index 00000000..18d9a62c --- /dev/null +++ b/examples/tests/cast.rs @@ -0,0 +1,9 @@ +#[test_fuzz::test_fuzz] +fn target(x: u64) { + let _ = x as u32; +} + +#[test] +fn test() { + target(0); +} diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 312b87bb..ca453610 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -23,6 +23,7 @@ quote = "1.0" syn = { version = "2.0", features = ["full", "parsing", "visit", "visit-mut"] } [features] +__cast_checks = [] __persistent = [] __self_ty_in_mod_name = ["heck"] diff --git a/macro/src/lib.rs b/macro/src/lib.rs index af050a1e..5b98c7af 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -294,6 +294,18 @@ fn map_method_or_fn( } } + let mut attrs = attrs.clone(); + let maybe_use_cast_checks = if cfg!(feature = "__cast_checks") { + attrs.push(parse_quote! { + #[test_fuzz::cast_checks::enable] + }); + quote! { + use test_fuzz::cast_checks; + } + } else { + quote! {} + }; + let impl_ty_idents = type_idents(generics); let ty_idents = type_idents(&sig.generics); let combined_type_idents = [impl_ty_idents.clone(), ty_idents.clone()].concat(); @@ -724,6 +736,8 @@ fn map_method_or_fn( ( parse_quote! { #(#attrs)* #vis #defaultness #sig { + #maybe_use_cast_checks + #write_generic_args_and_args #in_production_write_generic_args_and_args diff --git a/test-fuzz/Cargo.toml b/test-fuzz/Cargo.toml index e7ffe507..f9c719f6 100644 --- a/test-fuzz/Cargo.toml +++ b/test-fuzz/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/trailofbits/test-fuzz" [dependencies] afl = { version = "0.15", optional = true } +cast_checks = { version = "0.1", optional = true } serde = "1.0" internal = { path = "../internal", package = "test-fuzz-internal", version = "=5.0.0" } @@ -34,6 +35,7 @@ testing = { path = "../testing", package = "test-fuzz-testing" } # https://github.com/djkoloski/rust_serialization_benchmark [features] +cast_checks = ["dep:cast_checks", "test-fuzz-macro/__cast_checks"] self_ty_in_mod_name = ["test-fuzz-macro/__self_ty_in_mod_name"] serde_bincode = ["internal/__serde_bincode"] serde_cbor = ["internal/__serde_cbor"] diff --git a/test-fuzz/src/lib.rs b/test-fuzz/src/lib.rs index 00f193ef..b80dd3bc 100644 --- a/test-fuzz/src/lib.rs +++ b/test-fuzz/src/lib.rs @@ -6,6 +6,10 @@ pub use test_fuzz_macro::{test_fuzz, test_fuzz_impl}; #[cfg(feature = "__persistent")] pub use afl; +// smoelius: Do the same for `cast_checks`. +#[cfg(feature = "cast_checks")] +pub use cast_checks; + // smoelius: Unfortunately, the same trick doesn't work for serde. // https://github.com/serde-rs/serde/issues/1465