Skip to content

Commit

Permalink
fuzzgen: Generate stack load/store instructions (#4438)
Browse files Browse the repository at this point in the history
* fuzzgen: Generate static stack slots

* fuzzgen: Generate stack manipulation instructions
  • Loading branch information
afonso360 authored Jul 13, 2022
1 parent 08a60a0 commit 03ece34
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 3 deletions.
8 changes: 8 additions & 0 deletions cranelift/fuzzgen/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub struct Config {
pub block_signature_params: RangeInclusive<usize>,
pub jump_tables_per_function: RangeInclusive<usize>,
pub jump_table_entries: RangeInclusive<usize>,

/// Stack slots.
/// The combination of these two determines stack usage per function
pub static_stack_slots_per_function: RangeInclusive<usize>,
/// Size in bytes
pub static_stack_slot_size: RangeInclusive<usize>,
}

impl Default for Config {
Expand All @@ -32,6 +38,8 @@ impl Default for Config {
block_signature_params: 0..=16,
jump_tables_per_function: 0..=4,
jump_table_entries: 0..=16,
static_stack_slots_per_function: 0..=8,
static_stack_slot_size: 0..=128,
}
}
}
114 changes: 111 additions & 3 deletions cranelift/fuzzgen/src/function_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use anyhow::Result;
use arbitrary::{Arbitrary, Unstructured};
use cranelift::codegen::ir::types::*;
use cranelift::codegen::ir::{
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, Type, Value,
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
};
use cranelift::codegen::isa::CallConv;
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift::prelude::{EntityRef, InstBuilder, IntCC, JumpTableData};
use cranelift::prelude::{
EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind,
};
use std::ops::RangeInclusive;

type BlockSignature = Vec<Type>;
Expand Down Expand Up @@ -47,6 +49,46 @@ fn insert_opcode_arity_2(
Ok(())
}

fn insert_stack_load(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
_opcode: Opcode,
_args: &'static [Type],
rets: &'static [Type],
) -> Result<()> {
let typevar = rets[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;

let val = builder.ins().stack_load(typevar, slot, offset);
let var = fgen.get_variable_of_type(typevar)?;
builder.def_var(var, val);

Ok(())
}

fn insert_stack_store(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
_opcode: Opcode,
args: &'static [Type],
_rets: &'static [Type],
) -> Result<()> {
let typevar = args[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;

let arg0 = fgen.get_variable_of_type(typevar)?;
let arg0 = builder.use_var(arg0);

builder.ins().stack_store(arg0, slot, offset);
Ok(())
}

type OpcodeInserter = fn(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
Expand Down Expand Up @@ -88,6 +130,15 @@ const OPCODE_SIGNATURES: &'static [(
(Opcode::Sdiv, &[I16, I16], &[I16], insert_opcode_arity_2),
(Opcode::Sdiv, &[I32, I32], &[I32], insert_opcode_arity_2),
(Opcode::Sdiv, &[I64, I64], &[I64], insert_opcode_arity_2),
// Stack Access
(Opcode::StackStore, &[I8], &[], insert_stack_store),
(Opcode::StackStore, &[I16], &[], insert_stack_store),
(Opcode::StackStore, &[I32], &[], insert_stack_store),
(Opcode::StackStore, &[I64], &[], insert_stack_store),
(Opcode::StackLoad, &[], &[I8], insert_stack_load),
(Opcode::StackLoad, &[], &[I16], insert_stack_load),
(Opcode::StackLoad, &[], &[I32], insert_stack_load),
(Opcode::StackLoad, &[], &[I64], insert_stack_load),
];

pub struct FunctionGenerator<'r, 'data>
Expand All @@ -99,6 +150,7 @@ where
vars: Vec<(Type, Variable)>,
blocks: Vec<(Block, BlockSignature)>,
jump_tables: Vec<JumpTable>,
static_stack_slots: Vec<StackSlot>,
}

impl<'r, 'data> FunctionGenerator<'r, 'data>
Expand All @@ -112,6 +164,7 @@ where
vars: vec![],
blocks: vec![],
jump_tables: vec![],
static_stack_slots: vec![],
}
}

Expand Down Expand Up @@ -181,6 +234,18 @@ where
Ok(sig)
}

/// Finds a stack slot with size of at least n bytes
fn stack_slot_with_size(&mut self, builder: &mut FunctionBuilder, n: u32) -> Result<StackSlot> {
let opts: Vec<_> = self
.static_stack_slots
.iter()
.filter(|ss| builder.func.sized_stack_slots[**ss].size >= n)
.map(|ss| *ss)
.collect();

Ok(*self.u.choose(&opts[..])?)
}

/// Creates a new var
fn create_var(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Variable> {
let id = self.vars.len();
Expand Down Expand Up @@ -218,7 +283,7 @@ where
};
builder.ins().iconst(ty, imm64)
}
ty if ty.is_bool() => builder.ins().bconst(B1, bool::arbitrary(self.u)?),
ty if ty.is_bool() => builder.ins().bconst(ty, bool::arbitrary(self.u)?),
_ => unimplemented!(),
})
}
Expand Down Expand Up @@ -381,6 +446,44 @@ where
Ok(())
}

fn generate_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
for _ in 0..self.param(&self.config.static_stack_slots_per_function)? {
let bytes = self.param(&self.config.static_stack_slot_size)? as u32;
let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes);
let slot = builder.create_sized_stack_slot(ss_data);

self.static_stack_slots.push(slot);
}
Ok(())
}

/// Zero initializes the stack slot by inserting `stack_store`'s.
fn initialize_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let i64_zero = builder.ins().iconst(I64, 0);
let i32_zero = builder.ins().iconst(I32, 0);
let i16_zero = builder.ins().iconst(I16, 0);
let i8_zero = builder.ins().iconst(I8, 0);

for &slot in self.static_stack_slots.iter() {
let init_size = builder.func.sized_stack_slots[slot].size;
let mut size = init_size;

// Insert the largest available store for the remaining size.
while size != 0 {
let offset = (init_size - size) as i32;
let (val, filled) = match size {
sz if sz / 8 > 0 => (i64_zero, 8),
sz if sz / 4 > 0 => (i32_zero, 4),
sz if sz / 2 > 0 => (i16_zero, 2),
_ => (i8_zero, 1),
};
builder.ins().stack_store(val, slot, offset);
size -= filled;
}
}
Ok(())
}

/// Creates a random amount of blocks in this function
fn generate_blocks(
&mut self,
Expand Down Expand Up @@ -467,6 +570,7 @@ where

// Function preamble
self.generate_jumptables(&mut builder)?;
self.generate_stack_slots(&mut builder)?;

// Main instruction generation loop
for (i, (block, block_sig)) in self.blocks.clone().iter().enumerate() {
Expand All @@ -478,6 +582,10 @@ where
// block signature and for the variable pool. Additionally, we must also define
// initial values for all variables that are not the function signature.
self.build_variable_pool(&mut builder)?;

// Stack slots have random bytes at the beginning of the function
// initialize them to a constant value so that execution stays predictable.
self.initialize_stack_slots(&mut builder)?;
} else {
// Define variables for the block params
for (i, ty) in block_sig.iter().enumerate() {
Expand Down

0 comments on commit 03ece34

Please sign in to comment.