Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tooling/ssa_fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ acvm.workspace = true
thiserror.workspace = true
libfuzzer-sys = { workspace = true, features = ["arbitrary-derive"] }
log.workspace = true
serde.workspace = true
[dev-dependencies]
rand.workspace = true

Expand Down
3 changes: 3 additions & 0 deletions tooling/ssa_fuzzer/fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ acvm.workspace = true
noir_ssa_executor.workspace = true
log.workspace = true
env_logger.workspace = true
bincode = "1.3"
rand.workspace = true
serde.workspace = true

[dependencies.noir_ssa_fuzzer]
path = "../"
Expand Down
3 changes: 2 additions & 1 deletion tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/base_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use noir_ssa_fuzzer::{
};
use noirc_driver::CompiledProgram;
use noirc_evaluator::ssa::ir::basic_block::BasicBlockId;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet, VecDeque},
hash::Hash,
Expand All @@ -24,7 +25,7 @@ const NUMBER_OF_BLOCKS_INSERTING_IN_LOOP: usize = 4;
/// Represents set of commands for the fuzzer
///
/// After executing all commands, terminates all blocks from current_block_queue with return
#[derive(Arbitrary, Debug, Clone, Hash)]
#[derive(Arbitrary, Debug, Clone, Hash, Serialize, Deserialize)]
pub(crate) enum FuzzerCommand {
/// Adds instructions to current_block_context from stored instruction_blocks
InsertSimpleInstructionBlock { instruction_block_idx: usize },
Expand Down
46 changes: 32 additions & 14 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use super::NUMBER_OF_VARIABLES_INITIAL;
use super::base_context::FuzzerCommand;
use super::fuzzer::Fuzzer;
use super::instruction::InstructionBlock;
use super::options::FuzzerOptions;
use super::{NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL};
use acvm::FieldElement;
use acvm::acir::native_types::{Witness, WitnessMap};
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use noir_ssa_fuzzer::typed_value::ValueType;
use serde::{Deserialize, Serialize};

/// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigneds to represent a field element
/// field = low + high * 2^128
#[derive(Debug, Clone, Hash, Arbitrary)]
#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) struct FieldRepresentation {
high: u128,
low: u128,
pub(crate) high: u128,
pub(crate) low: u128,
}

impl From<&FieldRepresentation> for FieldElement {
Expand All @@ -25,7 +26,7 @@ impl From<&FieldRepresentation> for FieldElement {
}
}

#[derive(Debug, Clone, Hash, Arbitrary)]
#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)]
pub(crate) enum WitnessValue {
Field(FieldRepresentation),
U64(u64),
Expand All @@ -37,15 +38,26 @@ pub(crate) enum WitnessValue {
/// Represents the data for the fuzzer
/// `methods` - sequence of instructions to be added to the program
/// `initial_witness` - initial witness values for the program as `FieldRepresentation`
#[derive(Arbitrary, Debug)]
#[derive(Arbitrary, Debug, Serialize, Deserialize)]
pub(crate) struct FuzzerData {
blocks: Vec<InstructionBlock>,
commands: Vec<FuzzerCommand>,
pub(crate) blocks: Vec<InstructionBlock>,
pub(crate) commands: Vec<FuzzerCommand>,
/// initial witness values for the program as `WitnessValue`
/// last and last but one values are preserved for the boolean values (true, false)
/// ↓ we subtract 2, because [initialize_witness_map] func inserts two boolean variables itself
initial_witness: [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - 2) as usize],
return_instruction_block_idx: usize,
pub(crate) initial_witness:
[WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
pub(crate) return_instruction_block_idx: usize,
}

impl Default for FuzzerData {
fn default() -> Self {
Self {
blocks: vec![],
commands: vec![],
initial_witness: [const { WitnessValue::U64(0) };
(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
return_instruction_block_idx: 0,
}
}
}

fn initialize_witness_map(
Expand All @@ -67,10 +79,16 @@ fn initialize_witness_map(
types.push(type_);
}
// insert true and false boolean values
witness_map.insert(Witness(NUMBER_OF_VARIABLES_INITIAL - 2), FieldElement::from(1_u32));
witness_map.insert(
Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES),
FieldElement::from(1_u32),
);
values.push(FieldElement::from(1_u32));
types.push(ValueType::Boolean);
witness_map.insert(Witness(NUMBER_OF_VARIABLES_INITIAL - 1), FieldElement::from(0_u32));
witness_map.insert(
Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES + 1),
FieldElement::from(0_u32),
);
values.push(FieldElement::from(0_u32));
types.push(ValueType::Boolean);
(witness_map, values, types)
Expand Down
3 changes: 2 additions & 1 deletion tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ impl Fuzzer {
(Err(acir_error), Err(brillig_error)) => {
log::debug!("ACIR compilation error: {:?}", acir_error);
log::debug!("Brillig compilation error: {:?}", brillig_error);
panic!("ACIR and Brillig compilation failed");
log::debug!("ACIR and Brillig compilation failed");
return None;
}
(Ok(acir), Err(brillig_error)) => {
let acir_result = execute_single(&acir.program, initial_witness);
Expand Down
7 changes: 4 additions & 3 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use noir_ssa_fuzzer::typed_value::ValueType;
use serde::{Deserialize, Serialize};

#[derive(Arbitrary, Debug, Clone, Copy)]
#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)]
pub(crate) struct Argument {
/// Index of the argument in the context of stored variables of this type
/// e.g. if we have variables with ids [0, 1] in u64 vector and variables with ids [5, 8] in fields vector
Expand All @@ -17,7 +18,7 @@ pub(crate) struct Argument {
/// Represents set of instructions
///
/// For operations that take two arguments we ignore type of the second argument.
#[derive(Arbitrary, Debug, Clone, Copy)]
#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)]
pub(crate) enum Instruction {
/// Addition of two values
AddChecked { lhs: Argument, rhs: Argument },
Expand Down Expand Up @@ -71,7 +72,7 @@ pub(crate) enum Instruction {

/// Represents set of instructions
/// NOT EQUAL TO SSA BLOCK
#[derive(Arbitrary, Debug, Clone)]
#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct InstructionBlock {
pub(crate) instructions: Vec<Instruction>,
}
2 changes: 2 additions & 0 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ pub(crate) mod instruction;
pub(crate) mod options;

pub(crate) const NUMBER_OF_VARIABLES_INITIAL: u32 = 7;
/// Numbers of variables that are predefined in the fuzzer
pub(crate) const NUMBER_OF_PREDEFINED_VARIABLES: u32 = 2;
37 changes: 22 additions & 15 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#![no_main]

mod fuzz_lib;
mod mutations;

use bincode::{deserialize, serialize};
use fuzz_lib::fuzz_target_lib::{FuzzerData, fuzz_target};
use fuzz_lib::options::{FuzzerCommandOptions, FuzzerOptions, InstructionOptions};
use fuzz_lib::options::{FuzzerOptions, InstructionOptions};
use mutations::mutate;
use noirc_driver::CompileOptions;
use rand::{SeedableRng, rngs::StdRng};

libfuzzer_sys::fuzz_target!(|data: FuzzerData| {
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
let _ = env_logger::try_init();
let mut compile_options = CompileOptions::default();
if let Ok(triage_value) = std::env::var("TRIAGE") {
Expand All @@ -26,22 +31,24 @@ libfuzzer_sys::fuzz_target!(|data: FuzzerData| {

// Disable some instructions with bugs that are not fixed yet
let instruction_options = InstructionOptions {
cast_enabled: true,
lt_enabled: true,
shl_enabled: false,
shr_enabled: false,
mod_enabled: true,
..InstructionOptions::default()
};
let options = FuzzerOptions {
constrain_idempotent_morphing_enabled: false,
constant_execution_enabled: false,
compile_options,
max_ssa_blocks_num: 30, // it takes too long to run program with more blocks
max_instructions_num: 1500, // it takes too long to run program with more instructions
max_iterations_num: 10000,
instruction_options,
fuzzer_command_options: FuzzerCommandOptions::default(),
};
let options =
FuzzerOptions { compile_options, instruction_options, ..FuzzerOptions::default() };
let data = deserialize(data).unwrap_or_default();
fuzz_target(data, options);
});

libfuzzer_sys::fuzz_mutator!(|data: &mut [u8], _size: usize, max_size: usize, seed: u32| {
let mut rng = StdRng::seed_from_u64(seed as u64);
let mut new_fuzzer_data: FuzzerData = deserialize(data).unwrap_or_default();
new_fuzzer_data = mutate(new_fuzzer_data, &mut rng);
let new_bytes = serialize(&new_fuzzer_data).unwrap();
if new_bytes.len() > max_size {
return 0;
}
data[..new_bytes.len()].copy_from_slice(&new_bytes);
new_bytes.len()
});
80 changes: 80 additions & 0 deletions tooling/ssa_fuzzer/fuzzer/src/mutations/commands_mutator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! This file contains mechanisms for deterministically mutating a given vector of [FuzzerCommand](crate::fuzz_lib::base_context::FuzzerCommand) values
//! Types of mutations applied:
//! 1. Random (randomly select a new vector of fuzzer commands)
//! 2. Remove command
//! 3. Add command
//! 4. Replace command with random command

use crate::fuzz_lib::base_context::FuzzerCommand;
use crate::mutations::configuration::{
BASIC_FUZZER_COMMAND_MUTATION_CONFIGURATION, FuzzerCommandMutationOptions,
};
use libfuzzer_sys::arbitrary::Unstructured;
use rand::{Rng, rngs::StdRng};

trait MutateVecFuzzerCommand {
fn mutate(rng: &mut StdRng, commands: &mut Vec<FuzzerCommand>);
}

/// Return new random vector of fuzzer commands
struct RandomMutation;
impl MutateVecFuzzerCommand for RandomMutation {
fn mutate(rng: &mut StdRng, commands: &mut Vec<FuzzerCommand>) {
let mut bytes = [0u8; 128];
rng.fill(&mut bytes);
*commands = Unstructured::new(&bytes).arbitrary().unwrap();
}
}

/// Remove randomly chosen command from the vector
struct RemoveCommandMutation;
impl MutateVecFuzzerCommand for RemoveCommandMutation {
fn mutate(rng: &mut StdRng, commands: &mut Vec<FuzzerCommand>) {
if !commands.is_empty() {
commands.remove(rng.gen_range(0..commands.len()));
}
}
}

/// Add randomly generated command to the vector
struct AddCommandMutation;
impl MutateVecFuzzerCommand for AddCommandMutation {
fn mutate(rng: &mut StdRng, commands: &mut Vec<FuzzerCommand>) {
let mut bytes = [0u8; 25];
rng.fill(&mut bytes);
let command = Unstructured::new(&bytes).arbitrary().unwrap();
commands.push(command);
}
}

/// Replace randomly chosen command with randomly generated command
struct ReplaceCommandMutation;
impl MutateVecFuzzerCommand for ReplaceCommandMutation {
fn mutate(rng: &mut StdRng, commands: &mut Vec<FuzzerCommand>) {
let mut bytes = [0u8; 25];
rng.fill(&mut bytes);
let command = Unstructured::new(&bytes).arbitrary().unwrap();
if !commands.is_empty() {
let command_idx = rng.gen_range(0..commands.len());
commands[command_idx] = command;
}
}
}

pub(crate) fn mutate_vec_fuzzer_command(
vec_fuzzer_command: &mut Vec<FuzzerCommand>,
rng: &mut StdRng,
) {
match BASIC_FUZZER_COMMAND_MUTATION_CONFIGURATION.select(rng) {
FuzzerCommandMutationOptions::Random => RandomMutation::mutate(rng, vec_fuzzer_command),
FuzzerCommandMutationOptions::RemoveCommand => {
RemoveCommandMutation::mutate(rng, vec_fuzzer_command)
}
FuzzerCommandMutationOptions::AddCommand => {
AddCommandMutation::mutate(rng, vec_fuzzer_command)
}
FuzzerCommandMutationOptions::ReplaceCommand => {
ReplaceCommandMutation::mutate(rng, vec_fuzzer_command)
}
}
}
Loading
Loading