Skip to content

Commit

Permalink
parent 486d4a5
Browse files Browse the repository at this point in the history
author Jacarte <[email protected]> 1638116643 +0100
committer Jacarte <[email protected]> 1638281813 +0100

Adding documentation

Typos

FIX: ignore no complete code in doc

FIX: missing documentation

Typos

FIX: ignore no complete code in doc

FIX: missing documentation

Update crates/wasm-mutate/README.md

Co-authored-by: Nick Fitzgerald <[email protected]>

Fixing fuzz_mutator_example

Fixing shrinking idea

Improving examples
  • Loading branch information
Jacarte committed Nov 30, 2021
1 parent 486d4a5 commit 40c2c71
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 115 deletions.
121 changes: 63 additions & 58 deletions crates/wasm-mutate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,9 @@ wasm-mutate = "0.1.0"
You can mutate a WebAssembly binary by using the cli tool:

```bash
./wasm-mutate original.wasm --seed 456828644046477751 -o out.wasm --preserve-semantics
./wasm-mutate original.wasm --seed 0 -o out.wasm --preserve-semantics
```


You can mutate a WebAssembly binary programmatically as well:

```rust
TODO
```


## Features

* **semantically equivalent transformations:** `wasm-mutate` has the ability to
Expand All @@ -54,66 +46,74 @@ TODO
reuses the fuzzer's raw input strings. `wasm-mutate` works with the
`LLVMFuzzerCustomMutator` hook and the
`libfuzzer_sys::fuzz_mutator!` macro.

### Example

```rust
#![no_main]

use libfuzzer_sys::fuzz_target;
use wasmparser::WasmFeatures;

fuzz_target!(|bytes: &[u8]| {
let _ = env_logger::try_init();

let mut seed = 0;
let (wasm, _config) = match wasm_tools_fuzz::generate_valid_module(bytes, |config, u| {
seed = u.arbitrary()?;
Ok(())
}) {
Ok(m) => m,
Err(_) => return,
};
log::debug!("seed = {}", seed);
let mutated_wasm = wasm_mutate::WasmMutate::default()
.seed(seed)
.fuel(1000)
.preserve_semantics(true)
.run(&wasm);
let mutated_wasm = match mutated_wasm {
Ok(w) => {
w
}
Err(e) => match e {
wasm_mutate::Error::NoMutationsApplicable => return,
e => panic!("Unexpected mutation failure: {}", e),
},
};

if log::log_enabled!(log::Level::Debug) {
std::fs::write("mutated.wasm", &mutated_wasm).expect("should write `mutated.wasm` okay");
if let Ok(mutated_wat) = wasmprinter::print_bytes(&mutated_wasm) {
std::fs::write("mutated.wat", &mutated_wat).expect("should write `mutated.wat` okay");
}
}
use libfuzzer_sys::{fuzz_mutator, fuzz_target};
use std::io::{BufRead, Read, Write};
use wasmparser::WasmFeatures;

fuzz_target!(|bytes: &[u8]| {
// Initialize the Wasm for example
});

fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
// Generate a random Wasm module with `wasm-smith` as well as a RNG seed for
let wasm = &data[..size];
let features = WasmFeatures::default();
let mut validator = wasmparser::Validator::new();
validator.wasm_features(features);
let validation_result = validator.validate_all(&wasm);

// Mutate the data if its a valid Wasm file, otherwise, create a random one
let wasm = if validation_result.is_ok() {
wasm.to_vec()
} else {
let (w, _) = match wasm_tools_fuzz::generate_valid_module_from_seed(seed, |config, u| {
config.module_linking_enabled = false;
config.exceptions_enabled = false;
config.simd_enabled = false;
config.reference_types_enabled = false;
config.memory64_enabled = false;
config.max_memories = 1;
Ok(())
}) {
Ok(m) => m,
Err(_) => {
return size;
}
};
w
};

let mutated_wasm = wasm_mutate::WasmMutate::default()
.seed(seed.into())
.fuel(1000)
.preserve_semantics(true)
.run(&wasm);

let mutated_wasm = match mutated_wasm {
Ok(w) => w,
Err(_) => wasm,
};

// The mutated Wasm should still be valid, since the input Wasm was valid.
let newsize = mutated_wasm.len();
data[..newsize].copy_from_slice(&mutated_wasm[..newsize]);
newsize
});

let features = WasmFeatures::default();
let mut validator = wasmparser::Validator::new();
validator.wasm_features(features);

let validation_result = validator.validate_all(&mutated_wasm);
log::debug!("validation result = {:?}", validation_result);
assert!(
validation_result.is_ok(),
"`wasm-mutate` should always produce a valid Wasm file"
);
});

```

* **test case reduction (WIP):** `wasm-mutate` can have the ability to restrict
mutations to only those that shrink the size of the Wasm module. If it is used
in this mode, `wasm-mutate` essentially becomes a Wasm test-case reducer. We
are currently working to provide a prototype of this feature as a separate
binary. The following pseudo-Rust provdes the general picture of it as an
binary. The following pseudo-Rust provides the general picture of it as an
standard hill-climbing algorithm.

```rust
Expand All @@ -124,7 +124,12 @@ TODO
.reduce(true);

while MAX_ITERATIONS > 0 {
wasm = wasmmutate.run(&wasm);
let new_wasm = wasmmutate.run(&wasm);
wasm = if check_equivalence(new_wasm, wasm) {
wasm
} else{
panic!("No valid transformations")
}
MAX_ITERATIONS -= 1;
}

Expand Down
1 change: 0 additions & 1 deletion crates/wasm-mutate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//! Wasm parser, validator, compiler, or any other Wasm-consuming
//! tool. `wasm-mutate` can serve as a custom mutator for mutation-based
//! fuzzing.
#![cfg_attr(not(feature = "structopt"), deny(missing_docs))]

mod error;
Expand Down
5 changes: 4 additions & 1 deletion crates/wasm-mutate/src/mutators/codemotion/ir/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Parsing and econding for code motion mutators.
use crate::{
module::map_block_type,
mutators::{codemotion::ir::parse_context::ParseContext, OperatorAndByteOffset},
Expand All @@ -6,8 +7,10 @@ use wasm_encoder::{Function, Instruction};
use wasmparser::{Operator, Range, TypeOrFuncType};

use self::parse_context::{Ast, Node, State};

/// Encodes an AST back to a Wasm function
pub struct AstBuilder;
pub mod parse_context;
pub(crate) mod parse_context;

/// Encodes the Wasm Ast
pub trait AstWriter {
Expand Down
27 changes: 25 additions & 2 deletions crates/wasm-mutate/src/mutators/codemotion/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! This mutator applies a random mutation over the control flow AST of an input
//! binary
//!
//! To extend `wasm-mutate` with another code motion mutator, the new mutator
//! struct should implement the [AstMutator] trait and we strongly recommend the
//! usage of the [ir::AstWriter] to define how the mutator writes the new AST back
//! to Wasm.
//!
//! For and example take a look at [IfComplementMutator][IfComplementMutator]
//!
//! Register the new mutator then in the meta [CodemotionMutator] logic.
//! ```ignore
//! let mutators: Vec<Box<dyn AstMutator>> = vec![
//! Box::new(IfComplementMutator),
//! ];
//! ```
use crate::{
module::map_type,
mutators::{
Expand All @@ -22,9 +39,11 @@ use log::debug;
#[cfg(test)]
use std::println as debug;

mod ir;
mod mutators;
pub mod ir;
pub mod mutators;

/// Code motion meta mutator, it groups all code motion mutators and select a
/// valid random one when an input Wasm binary is passed to it.
pub struct CodemotionMutator;

impl CodemotionMutator {
Expand Down Expand Up @@ -105,6 +124,8 @@ impl CodemotionMutator {
}
/// Trait to be implemented by all code motion mutators
pub trait AstMutator {
/// Transform the function AST in order to generate a new Wasm module
///
fn mutate<'a>(
&self,
config: &'a crate::WasmMutate,
Expand All @@ -116,6 +137,8 @@ pub trait AstMutator {
input_wasm: &'a [u8],
) -> Result<Function>;

/// Checks if this mutator can be applied to the passed `ast`
///
fn can_mutate<'a>(
&self,
config: &'a crate::WasmMutate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ use crate::{
},
};

/// This mutator selects a random `if` construction in a function and swap its branches.
/// Since this mutator preserves the original semantic of the input Wasm,
/// before the mutated if structure is encoded, a "negation" of the previous operand
/// in the stack is written. The "negation" is encoded with a `i32.eqz` operator.
///
pub struct IfComplementMutator;

#[derive(Default)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::{
},
};

/// This mutator selects a random `loop` construction in a function and tries to unroll it.
/// This mutator only works on empty-returning loops
pub struct LoopUnrollMutator;

#[derive(Default)]
Expand Down Expand Up @@ -167,6 +169,8 @@ impl AstWriter for LoopUnrollWriter {
}

impl LoopUnrollMutator {
/// Returns the indexes of empty return loop definitions inside the Wasm function
///
pub fn get_empty_returning_loops<'a>(&self, ast: &'a Ast) -> Vec<usize> {
let nodes = ast.get_nodes();
let mut loops = vec![];
Expand Down
1 change: 1 addition & 0 deletions crates/wasm-mutate/src/mutators/codemotion/mutators/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
//! Concrete code motion mutator implementations
pub mod if_complement;
pub mod loop_unrolling;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use wasmparser::CodeSectionReader;

use super::Mutator;

/// Sets the body of a function to unreachable
pub struct FunctionBodyUnreachable;

impl Mutator for FunctionBodyUnreachable {
Expand Down
38 changes: 30 additions & 8 deletions crates/wasm-mutate/src/mutators/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
//! Mutator trait
//!
//! `wasm-mutate` in build on top of three main group or mutators:
//! **top wasm module struct mutators**, [**code
//! motion mutators**][super::CodemotionMutator] and [**peephole
//! mutators**][super::PeepholeMutator].
//!
//! The former type is meant to change(mutate) the top structure of the input Wasm
//! binary, like for example, [by renaming the name of an exported function][super::RenameExportMutator].
//!
//! The later two types make changes deeper on the code of the input Wasm binary,
//! specifycally at the code section level of the binary. The code motion mutator
//! parses the code and provides an AST which is transformed in a semantically
//! equivalent way. We provide two concrete implementations using this type of
//! mutator: [LoopUnrollMutator][codemotion::mutators::loop_unrolling::LoopUnrollMutator] and [IfComplementMutator][codemotion::mutators::if_complement::IfComplementMutator].
//!
//! The last group of mutators are the [**peephole
//! mutators**][super::PeepholeMutator]. When it comes to the input Wasm binary code section, it
//! iterates through the defined functions, and then each instruction of the
//! functions is processed in order to construct an equivalent piece of code.
//!
use rand::prelude::SmallRng;
use wasm_encoder::Module;
use wasmparser::Operator;

use super::Result;
use crate::{ModuleInfo, WasmMutate};

/// This trait is implemented for all mutators
/// TODO extend and add example here
/// This trait needs to be implemented for all mutators
///
/// Take a look to the implementation of the
/// [RenameExportMutator][super::RenameExportMutator] implementation for a reference
pub trait Mutator {
/// Method where the mutation happpens
///
Expand All @@ -28,12 +50,12 @@ pub trait Mutator {
/// Type helper to wrap operator and the byte offset in the code section of a Wasm module
pub type OperatorAndByteOffset<'a> = (Operator<'a>, usize);

pub(crate) mod codemotion;
pub(crate) mod function_body_unreachable;
pub(crate) mod peephole;
pub(crate) mod remove_export;
pub(crate) mod rename_export;
pub(crate) mod snip_function;
pub mod codemotion;
pub mod function_body_unreachable;
pub mod peephole;
pub mod remove_export;
pub mod rename_export;
pub mod snip_function;

#[cfg(test)]
pub(crate) fn match_mutation(original: &str, mutator: &dyn Mutator, expected: &str) {
Expand Down
24 changes: 15 additions & 9 deletions crates/wasm-mutate/src/mutators/peephole/dfg/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! DFG extractor for Wasm functions. It converts Wasm operators to the [Lang]
//! intermediate representation.
use std::collections::HashMap;

use egg::{Id, RecExpr};
Expand All @@ -19,6 +21,8 @@ pub struct DFGBuilder {
parents: Vec<i32>,
}

/// Basic block of a Wasm's function defined as a range of operators in the
/// Wasm function
#[derive(Debug)]
pub struct BBlock {
pub(crate) range: Range,
Expand All @@ -41,19 +45,20 @@ pub struct StackEntry {
pub operator_idx: usize,
}

/// DFG structre for a piece of Wasm's function
#[derive(Clone, Default)]
pub struct MiniDFG {
// Some of the operators have no stack entry
// This will help to decide or not to mutate the operators, avoiding egrapphp creation, etc
// Each (key, value) entry corresponds to the index of the instruction in
// the Wasm BasicBlock and the index of the stack entry in the `entries` field
/// Some of the operators have no stack entry
/// This will help to decide or not to mutate the operators, avoiding egrapphp creation, etc
/// Each (key, value) entry corresponds to the index of the instruction in
/// the Wasm BasicBlock and the index of the stack entry in the `entries` field
pub map: HashMap<usize, usize>,
// Each stack entry represents a position in the operators stream
// containing its children
/// Each stack entry represents a position in the operators stream
/// containing its children
pub entries: Vec<StackEntry>,
// For each stack entry we keep the parental relation, the ith value is the index of
// the ith instruction's parent instruction
// We write each stack entry having no parent, i.e. a root in the dfg
/// For each stack entry we keep the parental relation, the ith value is the index of
/// the ith instruction's parent instruction.
/// We write each stack entry having no parent, i.e. a root in the dfg
pub parents: Vec<i32>,
}

Expand Down Expand Up @@ -196,6 +201,7 @@ impl MiniDFG {
}

impl<'a> DFGBuilder {
/// Returns a new DFG builder
pub fn new() -> Self {
DFGBuilder {
stack: Vec::new(),
Expand Down
Loading

0 comments on commit 40c2c71

Please sign in to comment.