From 03ece34cbbf429dcc67184351a1c12648254f573 Mon Sep 17 00:00:00 2001 From: Afonso Bordado Date: Wed, 13 Jul 2022 19:47:54 +0100 Subject: [PATCH] fuzzgen: Generate stack load/store instructions (#4438) * fuzzgen: Generate static stack slots * fuzzgen: Generate stack manipulation instructions --- cranelift/fuzzgen/src/config.rs | 8 ++ cranelift/fuzzgen/src/function_generator.rs | 114 +++++++++++++++++++- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/cranelift/fuzzgen/src/config.rs b/cranelift/fuzzgen/src/config.rs index 458a6fd330..2217ced1a9 100644 --- a/cranelift/fuzzgen/src/config.rs +++ b/cranelift/fuzzgen/src/config.rs @@ -18,6 +18,12 @@ pub struct Config { pub block_signature_params: RangeInclusive, pub jump_tables_per_function: RangeInclusive, pub jump_table_entries: RangeInclusive, + + /// Stack slots. + /// The combination of these two determines stack usage per function + pub static_stack_slots_per_function: RangeInclusive, + /// Size in bytes + pub static_stack_slot_size: RangeInclusive, } impl Default for Config { @@ -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, } } } diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index 0814ac72ac..241feeef2a 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -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; @@ -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, @@ -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> @@ -99,6 +150,7 @@ where vars: Vec<(Type, Variable)>, blocks: Vec<(Block, BlockSignature)>, jump_tables: Vec, + static_stack_slots: Vec, } impl<'r, 'data> FunctionGenerator<'r, 'data> @@ -112,6 +164,7 @@ where vars: vec![], blocks: vec![], jump_tables: vec![], + static_stack_slots: vec![], } } @@ -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 { + 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 { let id = self.vars.len(); @@ -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!(), }) } @@ -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, @@ -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() { @@ -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() {