About
The most popular fuzzers — including libfuzzer and AFL — are
coverage-guided and mutation-based.
Coverage-guided means that the fuzzer observes which code is dynamically executed while running an input through the system under test. When creating new inputs, it will try to make inputs that execute new code paths, maximizing the amount of code that's been explored. If a new input triggers new code paths to be executed, then it is added to the corpus. If a new input only exercises code paths that have already been discovered, then it is thrown away.
Mutation-based means that, when creating a new input, the fuzzer modifies an existing input from its corpus. The idea is that, if the existing input triggered interesting behavior in the system under test, then a modification of that input probably will as well, but might additionally trigger some new behavior as well. Consider the scenario where we are fuzzing a compiler: if some input made it all the way through the parser, type checker, and into code generation — rather than bouncing off early due to an invalid token — then a new input derived from this one is also likely to go deep into the compiler's pipeline. At least it is more likely to do so than a completely new, random string.
But what happens when we aren't fuzzing a text or binary interface? What happens
when we have a custom input type that the fuzzer's built-in mutation strategies
aren't very good at targeting? Many fuzzers will expose a hook for customizing
the routine for mutating an existing input from its corpus to create a new
candidate input, for example libfuzzer has the fuzz_mutator!
hook.
mutatis exists to make writing these custom mutators easy and efficient.
Using Default Mutators
To randomly mutate a value with its default, off-the-shelf mutator:
- Create a
mutatis::Session. - Call
session.mutate, passing in the value you wish to mutate.
Here's a simple example of using mutatis and its default mutators to randomly
mutate a value:
#
# foo.unwrap
Combining and Customizing Mutators
You can use the mutator combinators in the
mutatis::mutators
module to build more complex mutators from simpler ones or to customize mutation
strategies to, for example, maintain a type's internal invariants or bound the
resulting values into a particular range. The mutatis::mutators module is
typically imported under the alias m.
To randomly mutate a value with a custom mutator:
- Create the custom mutator from
mutatis::mutatorscombinators andMutatetrait adapter methods. - Create a
mutatis::Session. - Call
session.mutate_with, passing in the value you wish to mutate and the mutator you wish to use to perform the mutation.
Here's an example of using mutatis to define a custom mutator for a custom
struct type that has multiple fields, and maintains a relationship between the
fields' values:
#
# foo.unwrap
Automatically Deriving Mutators with #[derive(Mutate)]
First, enable this crate's derive feature, then slap #[derive(Mutate)] onto
your type definitions:
#
# foo.unwrap
Writing Smoke Tests with mutatis::check
When you enable the check feature in Cargo.toml, the mutatis::check
module provides a tiny
property-based testing framework that is suitable for writing smoke tests that
you use for local development and CI. It is not intended to replace a
full-fledged, coverage-guided fuzzing engine that you'd use for in-depth,
continuous fuzzing.
#
See the check module's
documentation for more
details.
Documentation
API Reference Documentation
The API reference documentation is available on docs.rs.
Guide
Check out the guide for tutorials, discussions, and recipes; everything else that doesn't fall into the API-reference category.
License
Licensed under dual MIT or Apache-2.0 at your choice.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.