Skip to content
Open
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
8 changes: 8 additions & 0 deletions fuzzamoto-ir/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub enum CompiledAction {
/// Set mock time for all nodes in the test
SetTime(u64),
Probe,
/// Take an incremental snapshot
IncrementalSnapshot,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -396,6 +398,12 @@ impl Compiler {
self.handle_addr_operations(instruction)?;
}

Operation::IncrementalSnapshot => {
self.output
.actions
.push(CompiledAction::IncrementalSnapshot);
}

Operation::BeginWitnessStack
| Operation::AddWitness
| Operation::EndWitnessStack => {
Expand Down
1 change: 1 addition & 0 deletions fuzzamoto-ir/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ impl Instruction {
| Operation::TakeTxo => true,

Operation::Nop { .. }
| Operation::IncrementalSnapshot
| Operation::BeginBuildTx
| Operation::EndBuildTx
| Operation::BeginBuildTxInputs
Expand Down
14 changes: 12 additions & 2 deletions fuzzamoto-ir/src/mutators/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@ impl<R: RngCore> Splicer<R> for CombineMutator {
program: &mut Program,
splice_with: &Program,
rng: &mut R,
) -> MutatorResult {
self.splice_from(program, splice_with, rng, 0)
}

fn splice_from(
&mut self,
program: &mut Program,
splice_with: &Program,
rng: &mut R,
min_index: usize,
) -> MutatorResult {
let combine_index = program
.get_random_instruction_index(rng, &InstructionContext::Global)
.expect("Global instruction index should always exist");
.get_random_instruction_index_from(rng, &InstructionContext::Global, min_index)
.ok_or(MutatorError::NoMutationsAvailable)?;

let mut builder = ProgramBuilder::new(program.context.clone());

Expand Down
12 changes: 11 additions & 1 deletion fuzzamoto-ir/src/mutators/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,26 @@ pub struct InputMutator;

impl<R: RngCore> Mutator<R> for InputMutator {
fn mutate(
&mut self,
program: &mut Program,
rng: &mut R,
meta: Option<&PerTestcaseMetadata>,
) -> MutatorResult {
self.mutate_from(program, rng, meta, 0)
}

fn mutate_from(
&mut self,
program: &mut Program,
rng: &mut R,
_meta: Option<&PerTestcaseMetadata>,
min_index: usize,
) -> MutatorResult {
let Some(candidate_instruction) = program
.instructions
.iter()
.enumerate()
.filter(|(_, instruction)| instruction.is_input_mutable())
.filter(|(i, instruction)| *i >= min_index && instruction.is_input_mutable())
.choose(rng)
else {
return Err(MutatorError::NoMutationsAvailable);
Expand Down
23 changes: 23 additions & 0 deletions fuzzamoto-ir/src/mutators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ pub trait Mutator<R: RngCore> {
rng: &mut R,
meta: Option<&PerTestcaseMetadata>,
) -> MutatorResult;

/// Mutate the program, only considering instructions with index >= `min_index`.
fn mutate_from(
&mut self,
program: &mut Program,
rng: &mut R,
meta: Option<&PerTestcaseMetadata>,
_min_index: usize,
) -> MutatorResult {
self.mutate(program, rng, meta)
}

fn name(&self) -> &'static str;
}

Expand All @@ -36,4 +48,15 @@ pub trait Splicer<R: RngCore>: Mutator<R> {
splice_with: &Program,
rng: &mut R,
) -> MutatorResult;

/// Splice two programs together, only considering instructions with index >= `min_index`.
fn splice_from(
&mut self,
program: &mut Program,
splice_with: &Program,
rng: &mut R,
_min_index: usize,
) -> MutatorResult {
self.splice(program, splice_with, rng)
}
}
14 changes: 12 additions & 2 deletions fuzzamoto-ir/src/mutators/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,30 @@ pub struct OperationMutator<M> {
}

impl<R: RngCore, M: OperationByteMutator> Mutator<R> for OperationMutator<M> {
fn mutate(
&mut self,
program: &mut Program,
rng: &mut R,
meta: Option<&PerTestcaseMetadata>,
) -> MutatorResult {
self.mutate_from(program, rng, meta, 0)
}

#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_precision_loss)]
fn mutate(
fn mutate_from(
&mut self,
program: &mut Program,
rng: &mut R,
_meta: Option<&PerTestcaseMetadata>,
min_index: usize,
) -> MutatorResult {
let Some(candidate_instruction) = program
.instructions
.iter_mut()
.enumerate()
.filter(|(_, instr)| instr.is_operation_mutable())
.filter(|(i, instr)| *i >= min_index && instr.is_operation_mutable())
.choose(rng)
else {
return Err(super::MutatorError::NoMutationsAvailable);
Expand Down
19 changes: 15 additions & 4 deletions fuzzamoto-ir/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ pub enum Operation {
/// None = key-path only spend; Some = script-path with one spendable leaf
script_leaf: Option<TaprootLeafSpec>,
},

/// Snapshot creation
IncrementalSnapshot,
// TODO: SendGetBlockTxn
// TODO: SendGetBlocks
// TODO: SendGetHeaders
Expand Down Expand Up @@ -437,6 +440,8 @@ impl fmt::Display for Operation {
}
write!(f, ")")
}

Operation::IncrementalSnapshot => write!(f, "IncrementalSnapshot"),
}
}
}
Expand Down Expand Up @@ -584,7 +589,8 @@ impl Operation {
| Operation::Probe
| Operation::TaprootScriptsUseAnnex
| Operation::TaprootTxoUseAnnex
| Operation::BuildTaprootTree { .. } => false,
| Operation::BuildTaprootTree { .. }
| Operation::IncrementalSnapshot => false,
}
}

Expand Down Expand Up @@ -743,7 +749,8 @@ impl Operation {
| Operation::BuildCoinbaseTxInput
| Operation::AddCoinbaseTxOutput
| Operation::SendBlockTxn
| Operation::Probe => false,
| Operation::Probe
| Operation::IncrementalSnapshot => false,
}
}

Expand Down Expand Up @@ -916,6 +923,8 @@ impl Operation {
Operation::SendCompactBlock => vec![],
Operation::SendBlockTxn => vec![],
Operation::Probe => vec![],

Operation::IncrementalSnapshot => vec![],
}
}

Expand Down Expand Up @@ -1095,7 +1104,8 @@ impl Operation {
| Operation::BeginBlockTransactions
| Operation::BeginWitnessStack
| Operation::BuildPayToAnchor
| Operation::Probe => vec![],
| Operation::Probe
| Operation::IncrementalSnapshot => vec![],
}
}

Expand Down Expand Up @@ -1216,7 +1226,8 @@ impl Operation {
| Operation::EndBuildBlockTxn
| Operation::AddTxToBlockTxn
| Operation::SendBlockTxn
| Operation::Probe => vec![],
| Operation::Probe
| Operation::IncrementalSnapshot => vec![],
}
}
}
5 changes: 4 additions & 1 deletion fuzzamoto-libafl/src/feedbacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ where
if *self.enabled.borrow() && matches!(exit_kind, ExitKind::Timeout) {
let timeouts = state.metadata_or_insert_with(TimeoutsToVerify::new);
log::info!("Timeout detected, adding to verification queue!");
timeouts.push(input.clone());
// If we're using incremental snapshots, clear frozen_prefix_len.
let mut timeout_input = input.clone();
timeout_input.frozen_prefix_len = None;
timeouts.push(timeout_input);
return Ok(false);
}

Expand Down
41 changes: 36 additions & 5 deletions fuzzamoto-libafl/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
use std::{fs::File, hash::Hash, io::Read, path::PathBuf};

use fuzzamoto_ir::Program;
use fuzzamoto_ir::{Instruction, Operation, Program};

use libafl::inputs::{HasTargetBytes, Input};
use libafl_bolts::{HasLen, ownedref::OwnedSlice};

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash)]
pub struct IrInput {
ir: Program,
/// Program index where the incremental snapshot is taken. The snapshot operation
/// is injected here during execution.
#[serde(skip)]
pub frozen_prefix_len: Option<usize>,
}

impl Input for IrInput {}

impl IrInput {
pub fn new(ir: Program) -> Self {
Self { ir }
Self {
ir,
frozen_prefix_len: None,
}
}

pub fn ir(&self) -> &Program {
Expand All @@ -31,7 +38,29 @@ impl IrInput {
file.read_to_end(&mut bytes).unwrap();
let program = postcard::from_bytes(&bytes).unwrap();

Self { ir: program }
Self {
ir: program,
frozen_prefix_len: None,
}
}

fn insert_snapshot(&self) -> Program {
if let Some(prefix_len) = self.frozen_prefix_len {
let mut instructions = self.ir.instructions.clone();

// Insert snapshot opcode at the frozen prefix position
let snapshot_instr = Instruction {
inputs: vec![],
operation: Operation::IncrementalSnapshot,
};

let insert_pos = prefix_len.min(instructions.len());
instructions.insert(insert_pos, snapshot_instr);

Program::unchecked_new(self.ir.context.clone(), instructions)
} else {
self.ir.clone()
}
}
}

Expand All @@ -43,12 +72,14 @@ impl HasLen for IrInput {

impl HasTargetBytes for IrInput {
fn target_bytes(&self) -> OwnedSlice<'_, u8> {
let program = self.insert_snapshot();

#[cfg(not(feature = "compile_in_vm"))]
{
let mut compiler = fuzzamoto_ir::compiler::Compiler::new();

let compiled_input = compiler
.compile(self.ir())
.compile(&program)
.expect("Compilation should never fail");

let mut bytes =
Expand All @@ -64,7 +95,7 @@ impl HasTargetBytes for IrInput {
#[cfg(feature = "compile_in_vm")]
{
let mut bytes =
postcard::to_allocvec(self.ir()).expect("serialization should never fail");
postcard::to_allocvec(&program).expect("serialization should never fail");
log::trace!("Input size: {}", bytes.len());
if bytes.len() > 1 * 1024 * 1024 {
bytes = Vec::new();
Expand Down
17 changes: 15 additions & 2 deletions fuzzamoto-libafl/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ use crate::{
mutators::{IrGenerator, IrMutator, IrSpliceMutator, LibAflByteMutator},
options::FuzzerOptions,
schedulers::SupportedSchedulers,
stages::{IrMinimizerStage, ProbingStage, StabilityCheckStage, VerifyTimeoutsStage},
stages::{
IncrementalSnapshotStage, IrMinimizerStage, ProbingStage, SnapshotPlacementPolicy,
StabilityCheckStage, VerifyTimeoutsStage,
},
};

#[cfg(feature = "bench")]
Expand Down Expand Up @@ -442,6 +445,16 @@ where

let probing = ProbingStage::new(&stdout_observer_handle);
let stability = StabilityCheckStage::new(&map_observer_handle, &map_feedback_name, 8);

let mutation_stage = TuneableMutationalStage::new(&mut state, mutator);

let incremental_snapshot_stage = IncrementalSnapshotStage::new(
self.options.incremental_snapshots,
mutation_stage,
SnapshotPlacementPolicy::Balanced,
50,
);

let mut stages = tuple_list!(
ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| {
// Always try minimizing at least for one pass
Expand Down Expand Up @@ -482,7 +495,7 @@ where
tuple_list!(
stability,
probing,
TuneableMutationalStage::new(&mut state, mutator),
incremental_snapshot_stage,
timeout_verify_stage,
bench_stats_stage,
)
Expand Down
22 changes: 17 additions & 5 deletions fuzzamoto-libafl/src/mutators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ where
None
};

let min_index = input.frozen_prefix_len.unwrap_or(0);

Ok(
match self
.mutator
.mutate(input.ir_mut(), &mut self.rng, tc_data.as_deref())
{
match self.mutator.mutate_from(
input.ir_mut(),
&mut self.rng,
tc_data.as_deref(),
min_index,
) {
Ok(()) => MutationResult::Mutated,
_ => MutationResult::Skipped,
},
Expand Down Expand Up @@ -148,10 +152,12 @@ where

let other = other_testcase.load_input(state.corpus())?;

let min_index = input.frozen_prefix_len.unwrap_or(0);

let mut input_clone = input.clone();
if self
.mutator
.splice(input_clone.ir_mut(), other.ir(), &mut self.rng)
.splice_from(input_clone.ir_mut(), other.ir(), &mut self.rng, min_index)
.is_err()
{
return Ok(MutationResult::Skipped);
Expand Down Expand Up @@ -231,6 +237,12 @@ where
return Ok(MutationResult::Skipped);
};

let min_index = input.frozen_prefix_len.unwrap_or(0);

if index < min_index {
return Ok(MutationResult::Skipped);
}

let mut builder = fuzzamoto_ir::ProgramBuilder::new(input.ir().context.clone());

builder
Expand Down
Loading