From fb1201cb602ecde2d8a999e458736ada31df1acf Mon Sep 17 00:00:00 2001 From: Afonso Bordado Date: Mon, 5 Jul 2021 14:38:53 +0100 Subject: [PATCH 1/3] cranelift: CLIF Fuzzer limit instructions executed in interpreter --- fuzz/fuzz_targets/cranelift-fuzzgen.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs index 2a502ef198..234feb8ea2 100644 --- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs +++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs @@ -7,14 +7,17 @@ use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompi use cranelift_fuzzgen::*; use cranelift_interpreter::environment::FuncIndex; use cranelift_interpreter::environment::FunctionStore; -use cranelift_interpreter::interpreter::{Interpreter, InterpreterState}; +use cranelift_interpreter::interpreter::{Interpreter, InterpreterError, InterpreterState}; use cranelift_interpreter::step::ControlFlow; use cranelift_interpreter::step::CraneliftTrap; +const INTERPRETER_FUEL: u64 = 4096; + #[derive(Debug)] enum RunResult { Success(Vec), Trap(CraneliftTrap), + Timeout, Error(Box), } @@ -36,7 +39,8 @@ fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunR Ok(ControlFlow::Return(results)) => RunResult::Success(results.to_vec()), Ok(ControlFlow::Trap(trap)) => RunResult::Trap(trap), Ok(cf) => RunResult::Error(format!("Unrecognized exit ControlFlow: {:?}", cf).into()), - Err(e) => RunResult::Error(format!("InterpreterError: {:?}", e).into()), + Err(InterpreterError::FuelExhausted) => RunResult::Timeout, + Err(e) => RunResult::Error(e.into()), } } @@ -46,12 +50,12 @@ fn run_in_host(compiled_fn: &CompiledFunction, args: &[DataValue]) -> RunResult } fuzz_target!(|testcase: TestCase| { - let mut interpreter = { + let build_interpreter = || { let mut env = FunctionStore::default(); env.add(testcase.func.name.to_string(), &testcase.func); let state = InterpreterState::default().with_function_store(env); - let interpreter = Interpreter::new(state); + let interpreter = Interpreter::new(state).with_fuel(Some(INTERPRETER_FUEL)); interpreter }; @@ -60,6 +64,9 @@ fuzz_target!(|testcase: TestCase| { let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap(); for args in &testcase.inputs { + // We rebuild the interpreter every run so that we don't accidentally carry over any state + // between runs, such as fuel remaining. + let mut interpreter = build_interpreter(); let int_res = run_in_interpreter(&mut interpreter, args); match int_res { RunResult::Success(_) => {} @@ -71,6 +78,10 @@ fuzz_target!(|testcase: TestCase| { // not justify implementing it again here. return; } + RunResult::Timeout => { + // We probably generated an infinite loop, we can ignore this + return; + } RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res), } From f4bd7d17a3d778049d5fab0225f20f1d3ca86cd7 Mon Sep 17 00:00:00 2001 From: Afonso Bordado Date: Mon, 5 Jul 2021 21:23:54 +0100 Subject: [PATCH 2/3] cranelift: CLIF Fuzzer generate multiple blocks --- cranelift/fuzzgen/src/config.rs | 9 ++ cranelift/fuzzgen/src/function_generator.rs | 156 ++++++++++++++++---- 2 files changed, 137 insertions(+), 28 deletions(-) diff --git a/cranelift/fuzzgen/src/config.rs b/cranelift/fuzzgen/src/config.rs index cb10ae877e..b52918069d 100644 --- a/cranelift/fuzzgen/src/config.rs +++ b/cranelift/fuzzgen/src/config.rs @@ -9,6 +9,13 @@ pub struct Config { /// Number of variables that we allocate per function /// This value does not include the signature params pub vars_per_function: RangeInclusive, + /// Number of blocks that we generate per function. + /// This value does not include the entry block + pub blocks_per_function: RangeInclusive, + /// Number of params a block should take + /// This value does not apply to block0 which takes the function params + /// and is thus governed by `signature_params` + pub block_signature_params: RangeInclusive, } impl Default for Config { @@ -19,6 +26,8 @@ impl Default for Config { signature_rets: 0..=16, instructions_per_block: 0..=64, vars_per_function: 0..=16, + blocks_per_function: 0..=16, + block_signature_params: 0..=16, } } } diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index a92b71eae1..322b4676b3 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -2,11 +2,15 @@ use crate::config::Config; use anyhow::Result; use arbitrary::Unstructured; use cranelift::codegen::ir::types::*; -use cranelift::codegen::ir::{AbiParam, ExternalName, Function, Opcode, Signature, Type, Value}; +use cranelift::codegen::ir::{ + AbiParam, Block, ExternalName, Function, Opcode, Signature, Type, Value, +}; use cranelift::codegen::isa::CallConv; use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; use cranelift::prelude::{EntityRef, InstBuilder}; +type BlockSignature = Vec; + fn insert_opcode_arity_0( _fgen: &mut FunctionGenerator, builder: &mut FunctionBuilder, @@ -92,6 +96,7 @@ where u: &'r mut Unstructured<'data>, config: &'r Config, vars: Vec<(Type, Variable)>, + blocks: Vec<(Block, BlockSignature)>, } impl<'r, 'data> FunctionGenerator<'r, 'data> @@ -103,6 +108,7 @@ where u, config, vars: vec![], + blocks: vec![], } } @@ -203,12 +209,85 @@ where Ok(()) } + fn generate_jump(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + let (block, signature) = { + let target = self.u.choose(&self.blocks[..])?; + let target = target.clone(); + target + }; + + let vars = signature + .iter() + .map(|ty| self.get_variable_of_type(*ty)) + .collect::>>()?; + + let vals = vars + .into_iter() + .map(|v| builder.use_var(v)) + .collect::>(); + + builder.ins().jump(*block, &vals[..]); + Ok(()) + } + /// Inserts a random instruction into the block fn generate_instruction(&mut self, builder: &mut FunctionBuilder) -> Result<()> { let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?; inserter(self, builder, op, args, rets) } + /// Creates a random amount of blocks in this function + fn generate_blocks( + &mut self, + builder: &mut FunctionBuilder, + sig: &Signature, + ) -> Result> { + let extra_block_count = self + .u + .int_in_range(self.config.blocks_per_function.clone())?; + + // We must always have at least one block, so we generate the "extra" blocks and add 1 for + // the entry block. + let block_count = 1 + extra_block_count; + + let blocks = (0..block_count) + .map(|i| { + let block = builder.create_block(); + + // The first block has to have the function signature, but for the rest of them we generate + // a random signature; + if i == 0 { + builder.append_block_params_for_function_params(block); + Ok((block, sig.params.iter().map(|a| a.value_type).collect())) + } else { + Ok((block, self.generate_block_signature()?)) + } + }) + .collect::>>()?; + + Ok(blocks) + } + + fn generate_block_signature(&mut self) -> Result { + let param_count = self + .u + .int_in_range(self.config.block_signature_params.clone())?; + + let mut params = Vec::with_capacity(param_count); + for _ in 0..param_count { + params.push(self.generate_type()?); + } + Ok(params) + } + + /// We generate a function in multiple stages: by first creating a random number of empty + /// + /// * First we generate a random number of empty blocks + /// * Then we generate a random pool of variables to be used throughout the function + /// * We then visit each block and generate random instructions + /// + /// Because we generate all blocks and variables up front we already know everything that + /// we need when generating instructions (i.e. jump targets / variables) pub fn generate(mut self) -> Result { let sig = self.generate_signature()?; @@ -216,36 +295,57 @@ where let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig.clone()); let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx); - let block0 = builder.create_block(); - builder.append_block_params_for_function_params(block0); - builder.switch_to_block(block0); - builder.seal_block(block0); - // Define variables for the function signature - for (i, param) in sig.params.iter().enumerate() { - let var = self.create_var(&mut builder, param.value_type)?; - let block_param = builder.block_params(block0)[i]; - builder.def_var(var, block_param); + self.blocks = self.generate_blocks(&mut builder, &sig)?; + + // Main instruction generation loop + for (i, (block, signature)) in self.blocks.clone().iter().enumerate() { + builder.switch_to_block(*block); + + if i == 0 { + // Define variables for the function signature + for (i, param) in sig.params.iter().enumerate() { + let var = self.create_var(&mut builder, param.value_type)?; + let block_param = builder.block_params(*block)[i]; + builder.def_var(var, block_param); + } + + // Create a pool of vars that are going to be used in this function + for _ in 0..self.u.int_in_range(self.config.vars_per_function.clone())? { + let ty = self.generate_type()?; + let var = self.create_var(&mut builder, ty)?; + let value = self.generate_const(&mut builder, ty)?; + builder.def_var(var, value); + } + } else { + // Define variables for the block params + for (i, ty) in signature.iter().enumerate() { + let var = self.get_variable_of_type(*ty)?; + let block_param = builder.block_params(*block)[i]; + builder.def_var(var, block_param); + } + } + + // Generate block instructions + for _ in 0..self + .u + .int_in_range(self.config.instructions_per_block.clone())? + { + self.generate_instruction(&mut builder)?; + } + + // We always need to exit safely out of a block. + // For block 0 this means a return, but for other block it is a jump into any other + // random block. + if i == 0 { + // TODO: We should make this, part of the regular instruction selection + self.generate_return(&mut builder)?; + } else { + self.generate_jump(&mut builder)?; + } } - // Create a pool of vars that are going to be used in this function - for _ in 0..self.u.int_in_range(self.config.vars_per_function.clone())? { - let ty = self.generate_type()?; - let var = self.create_var(&mut builder, ty)?; - let value = self.generate_const(&mut builder, ty)?; - builder.def_var(var, value); - } - - for _ in 0..self - .u - .int_in_range(self.config.instructions_per_block.clone())? - { - self.generate_instruction(&mut builder)?; - } - - // TODO: We should make this part of the regular instruction selection - self.generate_return(&mut builder)?; - + builder.seal_all_blocks(); builder.finalize(); Ok(func) From f0f2efba26b71493e2f429ca14cf9c60c9122fed Mon Sep 17 00:00:00 2001 From: Afonso Bordado Date: Tue, 6 Jul 2021 10:09:19 +0100 Subject: [PATCH 3/3] cranelift: CLIF Fuzzer generate brz/brnz/bricmp instructions --- cranelift/fuzzgen/src/function_generator.rs | 259 ++++++++++++++------ cranelift/fuzzgen/src/lib.rs | 28 ++- 2 files changed, 199 insertions(+), 88 deletions(-) diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index 322b4676b3..e492b23279 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -1,13 +1,13 @@ use crate::config::Config; use anyhow::Result; -use arbitrary::Unstructured; +use arbitrary::{Arbitrary, Unstructured}; use cranelift::codegen::ir::types::*; use cranelift::codegen::ir::{ AbiParam, Block, ExternalName, Function, Opcode, Signature, Type, Value, }; use cranelift::codegen::isa::CallConv; use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; -use cranelift::prelude::{EntityRef, InstBuilder}; +use cranelift::prelude::{EntityRef, InstBuilder, IntCC}; type BlockSignature = Vec; @@ -117,11 +117,30 @@ where Ok(CallConv::SystemV) } + fn generate_intcc(&mut self) -> Result { + Ok(*self.u.choose( + &[ + IntCC::Equal, + IntCC::NotEqual, + IntCC::SignedLessThan, + IntCC::SignedGreaterThanOrEqual, + IntCC::SignedGreaterThan, + IntCC::SignedLessThanOrEqual, + IntCC::UnsignedLessThan, + IntCC::UnsignedGreaterThanOrEqual, + IntCC::UnsignedGreaterThan, + IntCC::UnsignedLessThanOrEqual, + IntCC::Overflow, + IntCC::NotOverflow, + ][..], + )?) + } + fn generate_type(&mut self) -> Result { // TODO: It would be nice if we could get these directly from cranelift let scalars = [ // IFLAGS, FFLAGS, - // B1, B8, B16, B32, B64, B128, + B1, // B8, B16, B32, B64, B128, I8, I16, I32, I64, // I128, // F32, F64, @@ -180,60 +199,140 @@ where /// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result { - let imm64 = match ty { - I8 => self.u.arbitrary::()? as i64, - I16 => self.u.arbitrary::()? as i64, - I32 => self.u.arbitrary::()? as i64, - I64 => self.u.arbitrary::()?, - _ => unreachable!(), - }; - let val = builder.ins().iconst(ty, imm64); + Ok(match ty { + ty if ty.is_int() => { + let imm64 = match ty { + I8 => self.u.arbitrary::()? as i64, + I16 => self.u.arbitrary::()? as i64, + I32 => self.u.arbitrary::()? as i64, + I64 => self.u.arbitrary::()?, + _ => unreachable!(), + }; + builder.ins().iconst(ty, imm64) + } + ty if ty.is_bool() => builder.ins().bconst(B1, bool::arbitrary(self.u)?), + _ => unimplemented!(), + }) + } - Ok(val) + /// Chooses a random block which can be targeted by a jump / branch. + /// This means any block that is not the first block. + /// + /// For convenience we also generate values that match the block's signature + fn generate_target_block( + &mut self, + builder: &mut FunctionBuilder, + ) -> Result<(Block, Vec)> { + let block_targets = &self.blocks[1..]; + let (block, signature) = self.u.choose(block_targets)?.clone(); + let args = self.generate_values_for_signature(builder, signature.into_iter())?; + Ok((block, args)) + } + + fn generate_values_for_signature>( + &mut self, + builder: &mut FunctionBuilder, + signature: I, + ) -> Result> { + signature + .map(|ty| { + let var = self.get_variable_of_type(ty)?; + let val = builder.use_var(var); + Ok(val) + }) + .collect() } fn generate_return(&mut self, builder: &mut FunctionBuilder) -> Result<()> { - let ret_params = builder.func.signature.returns.clone(); - - let vars = ret_params - .iter() - .map(|p| self.get_variable_of_type(p.value_type)) - .collect::>>()?; - - let vals = vars - .into_iter() - .map(|v| builder.use_var(v)) - .collect::>(); + let types: Vec = { + let rets = &builder.func.signature.returns; + rets.iter().map(|p| p.value_type).collect() + }; + let vals = self.generate_values_for_signature(builder, types.into_iter())?; builder.ins().return_(&vals[..]); Ok(()) } fn generate_jump(&mut self, builder: &mut FunctionBuilder) -> Result<()> { - let (block, signature) = { - let target = self.u.choose(&self.blocks[..])?; - let target = target.clone(); - target - }; - - let vars = signature - .iter() - .map(|ty| self.get_variable_of_type(*ty)) - .collect::>>()?; - - let vals = vars - .into_iter() - .map(|v| builder.use_var(v)) - .collect::>(); - - builder.ins().jump(*block, &vals[..]); + let (block, args) = self.generate_target_block(builder)?; + builder.ins().jump(block, &args[..]); Ok(()) } - /// Inserts a random instruction into the block - fn generate_instruction(&mut self, builder: &mut FunctionBuilder) -> Result<()> { - let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?; - inserter(self, builder, op, args, rets) + /// Generates a brz/brnz into a random block + fn generate_br(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + let (block, args) = self.generate_target_block(builder)?; + + let condbr_types = [ + I8, I16, I32, I64, // TODO: I128 + B1, + ]; + let _type = *self.u.choose(&condbr_types[..])?; + let var = self.get_variable_of_type(_type)?; + let val = builder.use_var(var); + + if bool::arbitrary(self.u)? { + builder.ins().brz(val, block, &args[..]); + } else { + builder.ins().brnz(val, block, &args[..]); + } + + // After brz/brnz we must generate a jump + self.generate_jump(builder)?; + Ok(()) + } + + fn generate_bricmp(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + let (block, args) = self.generate_target_block(builder)?; + let cond = self.generate_intcc()?; + + let bricmp_types = [ + I8, I16, I32, I64, // TODO: I128 + ]; + let _type = *self.u.choose(&bricmp_types[..])?; + + let lhs_var = self.get_variable_of_type(_type)?; + let lhs_val = builder.use_var(lhs_var); + + let rhs_var = self.get_variable_of_type(_type)?; + let rhs_val = builder.use_var(rhs_var); + + builder + .ins() + .br_icmp(cond, lhs_val, rhs_val, block, &args[..]); + + // After bricmp's we must generate a jump + self.generate_jump(builder)?; + Ok(()) + } + + /// We always need to exit safely out of a block. + /// This either means a jump into another block or a return. + fn finalize_block(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + let gen = self.u.choose( + &[ + Self::generate_bricmp, + Self::generate_br, + Self::generate_jump, + Self::generate_return, + ][..], + )?; + + gen(self, builder) + } + + /// Fills the current block with random instructions + fn generate_instructions(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + for _ in 0..self + .u + .int_in_range(self.config.instructions_per_block.clone())? + { + let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?; + inserter(self, builder, op, args, rets)?; + } + + Ok(()) } /// Creates a random amount of blocks in this function @@ -260,7 +359,11 @@ where builder.append_block_params_for_function_params(block); Ok((block, sig.params.iter().map(|a| a.value_type).collect())) } else { - Ok((block, self.generate_block_signature()?)) + let sig = self.generate_block_signature()?; + sig.iter().for_each(|ty| { + builder.append_block_param(block, *ty); + }); + Ok((block, sig)) } }) .collect::>>()?; @@ -280,7 +383,29 @@ where Ok(params) } - /// We generate a function in multiple stages: by first creating a random number of empty + fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> { + let block = builder.current_block().unwrap(); + let func_params = builder.func.signature.params.clone(); + + // Define variables for the function signature + for (i, param) in func_params.iter().enumerate() { + let var = self.create_var(builder, param.value_type)?; + let block_param = builder.block_params(block)[i]; + builder.def_var(var, block_param); + } + + // Create a pool of vars that are going to be used in this function + for _ in 0..self.u.int_in_range(self.config.vars_per_function.clone())? { + let ty = self.generate_type()?; + let var = self.create_var(builder, ty)?; + let value = self.generate_const(builder, ty)?; + builder.def_var(var, value); + } + + Ok(()) + } + + /// We generate a function in multiple stages: /// /// * First we generate a random number of empty blocks /// * Then we generate a random pool of variables to be used throughout the function @@ -299,27 +424,18 @@ where self.blocks = self.generate_blocks(&mut builder, &sig)?; // Main instruction generation loop - for (i, (block, signature)) in self.blocks.clone().iter().enumerate() { + for (i, (block, block_sig)) in self.blocks.clone().iter().enumerate() { + let is_block0 = i == 0; builder.switch_to_block(*block); - if i == 0 { - // Define variables for the function signature - for (i, param) in sig.params.iter().enumerate() { - let var = self.create_var(&mut builder, param.value_type)?; - let block_param = builder.block_params(*block)[i]; - builder.def_var(var, block_param); - } - - // Create a pool of vars that are going to be used in this function - for _ in 0..self.u.int_in_range(self.config.vars_per_function.clone())? { - let ty = self.generate_type()?; - let var = self.create_var(&mut builder, ty)?; - let value = self.generate_const(&mut builder, ty)?; - builder.def_var(var, value); - } + if is_block0 { + // The first block is special because we must create variables both for the + // 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)?; } else { // Define variables for the block params - for (i, ty) in signature.iter().enumerate() { + for (i, ty) in block_sig.iter().enumerate() { let var = self.get_variable_of_type(*ty)?; let block_param = builder.block_params(*block)[i]; builder.def_var(var, block_param); @@ -327,22 +443,9 @@ where } // Generate block instructions - for _ in 0..self - .u - .int_in_range(self.config.instructions_per_block.clone())? - { - self.generate_instruction(&mut builder)?; - } + self.generate_instructions(&mut builder)?; - // We always need to exit safely out of a block. - // For block 0 this means a return, but for other block it is a jump into any other - // random block. - if i == 0 { - // TODO: We should make this, part of the regular instruction selection - self.generate_return(&mut builder)?; - } else { - self.generate_jump(&mut builder)?; - } + self.finalize_block(&mut builder)?; } builder.seal_all_blocks(); diff --git a/cranelift/fuzzgen/src/lib.rs b/cranelift/fuzzgen/src/lib.rs index a18ce78960..8ea043f331 100644 --- a/cranelift/fuzzgen/src/lib.rs +++ b/cranelift/fuzzgen/src/lib.rs @@ -47,6 +47,23 @@ where } } + fn generate_datavalue(&mut self, ty: Type) -> Result { + Ok(match ty { + ty if ty.is_int() => { + let imm = match ty { + I8 => self.u.arbitrary::()? as i128, + I16 => self.u.arbitrary::()? as i128, + I32 => self.u.arbitrary::()? as i128, + I64 => self.u.arbitrary::()? as i128, + _ => unreachable!(), + }; + DataValue::from_integer(imm, ty)? + } + ty if ty.is_bool() => DataValue::B(bool::arbitrary(self.u)?), + _ => unimplemented!(), + }) + } + fn generate_test_inputs(&mut self, signature: &Signature) -> Result> { let num_tests = self.u.int_in_range(self.config.test_case_inputs.clone())?; let mut inputs = Vec::with_capacity(num_tests); @@ -55,16 +72,7 @@ where let test_args = signature .params .iter() - .map(|p| { - let imm = match p.value_type { - I8 => self.u.arbitrary::()? as i128, - I16 => self.u.arbitrary::()? as i128, - I32 => self.u.arbitrary::()? as i128, - I64 => self.u.arbitrary::()? as i128, - _ => unreachable!(), - }; - Ok(DataValue::from_integer(imm, p.value_type)?) - }) + .map(|p| self.generate_datavalue(p.value_type)) .collect::>()?; inputs.push(test_args);