From 8b9a214280a87ac7e2e13372324cb2203db7a2ab Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Sun, 27 Oct 2024 15:10:15 +0100 Subject: [PATCH 01/17] Initialize the new runner using `cairo-native-runner` --- cairo-native-runner/.gitignore | 4 +++ cairo-native-runner/Cargo.toml | 8 +++++ cairo-native-runner/examples/hello.cairo | 6 ++++ cairo-native-runner/src/main.rs | 38 ++++++++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 cairo-native-runner/.gitignore create mode 100644 cairo-native-runner/Cargo.toml create mode 100644 cairo-native-runner/examples/hello.cairo create mode 100644 cairo-native-runner/src/main.rs diff --git a/cairo-native-runner/.gitignore b/cairo-native-runner/.gitignore new file mode 100644 index 0000000..aa223a5 --- /dev/null +++ b/cairo-native-runner/.gitignore @@ -0,0 +1,4 @@ +/target +/cairo2 +/corelib +cairo*.tar \ No newline at end of file diff --git a/cairo-native-runner/Cargo.toml b/cairo-native-runner/Cargo.toml new file mode 100644 index 0000000..7e38c81 --- /dev/null +++ b/cairo-native-runner/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cairo-native-runner" +version = "0.1.0" +edition = "2021" + +[dependencies] +cairo-native = "0.2.0-alpha.4" +starknet-types-core = "0.1.7" diff --git a/cairo-native-runner/examples/hello.cairo b/cairo-native-runner/examples/hello.cairo new file mode 100644 index 0000000..85256ca --- /dev/null +++ b/cairo-native-runner/examples/hello.cairo @@ -0,0 +1,6 @@ +use core::debug::PrintTrait; + +fn greet(name: felt252) { + 'Hello'.print(); + name.print(); +} \ No newline at end of file diff --git a/cairo-native-runner/src/main.rs b/cairo-native-runner/src/main.rs new file mode 100644 index 0000000..bb9c8ab --- /dev/null +++ b/cairo-native-runner/src/main.rs @@ -0,0 +1,38 @@ +use cairo_native::{ + context::NativeContext, executor::JitNativeExecutor, utils::cairo_to_sierra, Value, +}; +use starknet_types_core::felt::Felt; +use std::path::Path; + +fn main() { + let program_path = Path::new("examples/hello.cairo"); + + // Instantiate a Cairo Native MLIR context. This data structure is responsible for the MLIR + // initialization and compilation of sierra programs into a MLIR module. + let native_context = NativeContext::new(); + + // Compile the cairo program to sierra.s + let sierra_program = cairo_to_sierra(program_path); + + // Compile the sierra program into a MLIR module. + let native_program = native_context.compile(&sierra_program, false).unwrap(); + + // The parameters of the entry point. + let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; + + // Find the entry point id by its name. + let entry_point = "hello::hello::greet"; + let entry_point_id = cairo_native::utils::find_function_id(&sierra_program, entry_point) + .expect("entry point not found"); + + // Instantiate the executor. + let native_executor = JitNativeExecutor::from_native_module(native_program, Default::default()); + + // Execute the program. + let result = native_executor + .invoke_dynamic(entry_point_id, params, None) + .unwrap(); + + println!("Cairo program was compiled and executed successfully."); + println!("{:?}", result); +} From 727fb6ccfa496e285dc1d708b3fd85b545f18410 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Mon, 28 Oct 2024 01:20:43 +0100 Subject: [PATCH 02/17] Implement the `CairoNativeRunner` struct --- cairo-native-runner/Cargo.toml | 1 + cairo-native-runner/src/main.rs | 121 ++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/cairo-native-runner/Cargo.toml b/cairo-native-runner/Cargo.toml index 7e38c81..18c5ea1 100644 --- a/cairo-native-runner/Cargo.toml +++ b/cairo-native-runner/Cargo.toml @@ -4,5 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +cairo-lang-sierra = "2.8.4" cairo-native = "0.2.0-alpha.4" starknet-types-core = "0.1.7" diff --git a/cairo-native-runner/src/main.rs b/cairo-native-runner/src/main.rs index bb9c8ab..3034e3e 100644 --- a/cairo-native-runner/src/main.rs +++ b/cairo-native-runner/src/main.rs @@ -1,38 +1,113 @@ +use cairo_lang_sierra::ids::FunctionId; +use cairo_lang_sierra::program::Program; +use cairo_native::execution_result::ExecutionResult; +use cairo_native::module::NativeModule; use cairo_native::{ context::NativeContext, executor::JitNativeExecutor, utils::cairo_to_sierra, Value, }; use starknet_types_core::felt::Felt; use std::path::Path; +use std::sync::Arc; -fn main() { - let program_path = Path::new("examples/hello.cairo"); +pub struct CairoNativeRunner<'a> { + native_context: NativeContext, + sierra_program: Option>, + entry_point_id: Option, + native_module: Option>>, +} - // Instantiate a Cairo Native MLIR context. This data structure is responsible for the MLIR - // initialization and compilation of sierra programs into a MLIR module. - let native_context = NativeContext::new(); +impl<'a> CairoNativeRunner<'a> { + pub fn new() -> Self { + let native_context = NativeContext::new(); + Self { + native_context, + sierra_program: None, + entry_point_id: None, + native_module: None, + } + } - // Compile the cairo program to sierra.s - let sierra_program = cairo_to_sierra(program_path); + /// Initialize the runner + pub fn init(&mut self, program_path: &Path, entry_point: &str) -> Result<(), String> { + // Convert and store the Sierra program + self.convert_and_store_cairo_to_sierra(program_path)?; - // Compile the sierra program into a MLIR module. - let native_program = native_context.compile(&sierra_program, false).unwrap(); + // Find and store the entry point ID + self.entry_point_id = Some(self.find_entry_point_id(entry_point)?); - // The parameters of the entry point. - let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; + // Compile the Sierra program into a MLIR module + let native_module = self.compile_sierra_program()?; - // Find the entry point id by its name. - let entry_point = "hello::hello::greet"; - let entry_point_id = cairo_native::utils::find_function_id(&sierra_program, entry_point) - .expect("entry point not found"); + // Assign the compiled module to self.native_module + self.native_module = Some(native_module); + + Ok(()) + } + + fn convert_and_store_cairo_to_sierra(&mut self, program_path: &Path) -> Result<(), String> { + if self.sierra_program.is_none() { + self.sierra_program = Some(cairo_to_sierra(program_path)); + } + Ok(()) + } + + fn find_entry_point_id(&self, entry_point: &str) -> Result { + let sierra_program = self + .sierra_program + .as_ref() + .ok_or("Sierra program not available")?; + cairo_native::utils::find_function_id(sierra_program, entry_point) + .ok_or_else(|| format!("Entry point '{}' not found", entry_point)) + .cloned() + } + + fn compile_sierra_program(&self) -> Result>, String> { + let sierra_program = self + .sierra_program + .as_ref() + .ok_or("Sierra program not available")?; + let native_module = self + .native_context + .compile(sierra_program, false) + .map_err(|e| e.to_string())?; + Ok(Box::new(native_module)) + } - // Instantiate the executor. - let native_executor = JitNativeExecutor::from_native_module(native_program, Default::default()); + fn create_executor(native_program: Box>) -> JitNativeExecutor<'a> { + JitNativeExecutor::from_native_module(*native_program, Default::default()) + } + + /// Run the program + /// TODO : keep only the execution part in the method + pub fn run_program(&mut self, params: &[Value]) -> Result { + // Compile the Sierra program into a MLIR module + let native_program = self.compile_sierra_program()?; + + // Instantiate the executor + let native_executor = Self::create_executor(native_program); + + // Execute the program + native_executor + .invoke_dynamic(self.entry_point_id.as_ref().unwrap(), params, None) + .map_err(|e| e.to_string()) + } +} + +fn main() { + let program_path = Path::new("examples/hello.cairo"); + let entry_point = "hello::hello::greet"; + let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; - // Execute the program. - let result = native_executor - .invoke_dynamic(entry_point_id, params, None) - .unwrap(); + let mut runner = CairoNativeRunner::new(); - println!("Cairo program was compiled and executed successfully."); - println!("{:?}", result); + match runner.init(program_path, entry_point) { + Ok(()) => match runner.run_program(params) { + Ok(result) => { + println!("Cairo program was compiled and executed successfully."); + println!("{:?}", result); + } + Err(e) => eprintln!("Error during execution: {}", e), + }, + Err(e) => eprintln!("Error during initialization: {}", e), + } } From 644a9a5ff6b65fd091fa02e78477c72065479e89 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Tue, 29 Oct 2024 02:25:39 +0100 Subject: [PATCH 03/17] Create README & Refactor the project structure --- .../.gitignore | 0 .../Cargo.toml | 2 +- cairo-native-fuzzer/README.md | 3 ++ .../examples/hello.cairo | 0 cairo-native-fuzzer/src/main.rs | 25 +++++++++ cairo-native-fuzzer/src/runner/mod.rs | 1 + .../src/runner/runner.rs | 51 ++++--------------- 7 files changed, 40 insertions(+), 42 deletions(-) rename {cairo-native-runner => cairo-native-fuzzer}/.gitignore (100%) rename {cairo-native-runner => cairo-native-fuzzer}/Cargo.toml (83%) create mode 100644 cairo-native-fuzzer/README.md rename {cairo-native-runner => cairo-native-fuzzer}/examples/hello.cairo (100%) create mode 100644 cairo-native-fuzzer/src/main.rs create mode 100644 cairo-native-fuzzer/src/runner/mod.rs rename cairo-native-runner/src/main.rs => cairo-native-fuzzer/src/runner/runner.rs (57%) diff --git a/cairo-native-runner/.gitignore b/cairo-native-fuzzer/.gitignore similarity index 100% rename from cairo-native-runner/.gitignore rename to cairo-native-fuzzer/.gitignore diff --git a/cairo-native-runner/Cargo.toml b/cairo-native-fuzzer/Cargo.toml similarity index 83% rename from cairo-native-runner/Cargo.toml rename to cairo-native-fuzzer/Cargo.toml index 18c5ea1..365f7a7 100644 --- a/cairo-native-runner/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cairo-native-runner" +name = "cairo-native-fuzzer" version = "0.1.0" edition = "2021" diff --git a/cairo-native-fuzzer/README.md b/cairo-native-fuzzer/README.md new file mode 100644 index 0000000..868b4ae --- /dev/null +++ b/cairo-native-fuzzer/README.md @@ -0,0 +1,3 @@ +## Cairo Native Fuzzer + +Cairo Native Fuzzer is a rewrite of the Cairo Fuzzer based on [Cairo native from Lambdaclass](https://github.com/lambdaclass/cairo_native) developed to enhance fuzzer execution speed. diff --git a/cairo-native-runner/examples/hello.cairo b/cairo-native-fuzzer/examples/hello.cairo similarity index 100% rename from cairo-native-runner/examples/hello.cairo rename to cairo-native-fuzzer/examples/hello.cairo diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs new file mode 100644 index 0000000..2789871 --- /dev/null +++ b/cairo-native-fuzzer/src/main.rs @@ -0,0 +1,25 @@ +mod runner; +use crate::runner::runner::CairoNativeRunner; + +use cairo_native::Value; +use starknet_types_core::felt::Felt; +use std::path::Path; + +fn main() { + let program_path = Path::new("examples/hello.cairo"); + let entry_point = "hello::hello::greet"; + let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; + + let mut runner = CairoNativeRunner::new(); + + match runner.init(program_path, entry_point) { + Ok(()) => match runner.run_program(params) { + Ok(result) => { + println!("Cairo program was compiled and executed successfully."); + println!("{:?}", result); + } + Err(e) => eprintln!("Error during execution: {}", e), + }, + Err(e) => eprintln!("Error during initialization: {}", e), + } +} diff --git a/cairo-native-fuzzer/src/runner/mod.rs b/cairo-native-fuzzer/src/runner/mod.rs new file mode 100644 index 0000000..748377a --- /dev/null +++ b/cairo-native-fuzzer/src/runner/mod.rs @@ -0,0 +1 @@ +pub mod runner; diff --git a/cairo-native-runner/src/main.rs b/cairo-native-fuzzer/src/runner/runner.rs similarity index 57% rename from cairo-native-runner/src/main.rs rename to cairo-native-fuzzer/src/runner/runner.rs index 3034e3e..ba0d072 100644 --- a/cairo-native-runner/src/main.rs +++ b/cairo-native-fuzzer/src/runner/runner.rs @@ -5,29 +5,25 @@ use cairo_native::module::NativeModule; use cairo_native::{ context::NativeContext, executor::JitNativeExecutor, utils::cairo_to_sierra, Value, }; -use starknet_types_core::felt::Felt; use std::path::Path; use std::sync::Arc; -pub struct CairoNativeRunner<'a> { +pub struct CairoNativeRunner { native_context: NativeContext, sierra_program: Option>, entry_point_id: Option, - native_module: Option>>, } -impl<'a> CairoNativeRunner<'a> { +impl CairoNativeRunner { pub fn new() -> Self { let native_context = NativeContext::new(); Self { native_context, sierra_program: None, entry_point_id: None, - native_module: None, } } - /// Initialize the runner pub fn init(&mut self, program_path: &Path, entry_point: &str) -> Result<(), String> { // Convert and store the Sierra program self.convert_and_store_cairo_to_sierra(program_path)?; @@ -35,12 +31,6 @@ impl<'a> CairoNativeRunner<'a> { // Find and store the entry point ID self.entry_point_id = Some(self.find_entry_point_id(entry_point)?); - // Compile the Sierra program into a MLIR module - let native_module = self.compile_sierra_program()?; - - // Assign the compiled module to self.native_module - self.native_module = Some(native_module); - Ok(()) } @@ -61,30 +51,28 @@ impl<'a> CairoNativeRunner<'a> { .cloned() } - fn compile_sierra_program(&self) -> Result>, String> { + fn compile_sierra_program(&self) -> Result { let sierra_program = self .sierra_program .as_ref() .ok_or("Sierra program not available")?; - let native_module = self - .native_context + self.native_context .compile(sierra_program, false) - .map_err(|e| e.to_string())?; - Ok(Box::new(native_module)) + .map_err(|e| e.to_string()) } - fn create_executor(native_program: Box>) -> JitNativeExecutor<'a> { - JitNativeExecutor::from_native_module(*native_program, Default::default()) + fn create_executor<'a>(&self, native_program: NativeModule<'a>) -> JitNativeExecutor<'a> { + JitNativeExecutor::from_native_module(native_program, Default::default()) } - /// Run the program - /// TODO : keep only the execution part in the method + // Run the program + // TODO : Only keep the execution part in this method pub fn run_program(&mut self, params: &[Value]) -> Result { // Compile the Sierra program into a MLIR module let native_program = self.compile_sierra_program()?; // Instantiate the executor - let native_executor = Self::create_executor(native_program); + let native_executor = self.create_executor(native_program); // Execute the program native_executor @@ -92,22 +80,3 @@ impl<'a> CairoNativeRunner<'a> { .map_err(|e| e.to_string()) } } - -fn main() { - let program_path = Path::new("examples/hello.cairo"); - let entry_point = "hello::hello::greet"; - let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; - - let mut runner = CairoNativeRunner::new(); - - match runner.init(program_path, entry_point) { - Ok(()) => match runner.run_program(params) { - Ok(result) => { - println!("Cairo program was compiled and executed successfully."); - println!("{:?}", result); - } - Err(e) => eprintln!("Error during execution: {}", e), - }, - Err(e) => eprintln!("Error during initialization: {}", e), - } -} From 2b38585c77981f8bc555fe164b44d72b6363bb83 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Wed, 30 Oct 2024 22:11:30 +0100 Subject: [PATCH 04/17] Init the cairo-native-fuzzer architecture --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 0 cairo-native-fuzzer/src/fuzzer/mod.rs | 1 + cairo-native-fuzzer/src/runner/runner.rs | 48 +++++++++++++++--------- 3 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 cairo-native-fuzzer/src/fuzzer/fuzzer.rs create mode 100644 cairo-native-fuzzer/src/fuzzer/mod.rs diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs new file mode 100644 index 0000000..e69de29 diff --git a/cairo-native-fuzzer/src/fuzzer/mod.rs b/cairo-native-fuzzer/src/fuzzer/mod.rs new file mode 100644 index 0000000..20478c6 --- /dev/null +++ b/cairo-native-fuzzer/src/fuzzer/mod.rs @@ -0,0 +1 @@ +pub mod fuzzer; \ No newline at end of file diff --git a/cairo-native-fuzzer/src/runner/runner.rs b/cairo-native-fuzzer/src/runner/runner.rs index ba0d072..c053c2e 100644 --- a/cairo-native-fuzzer/src/runner/runner.rs +++ b/cairo-native-fuzzer/src/runner/runner.rs @@ -8,12 +8,28 @@ use cairo_native::{ use std::path::Path; use std::sync::Arc; +/// Cairo Runner that uses Cairo Native pub struct CairoNativeRunner { native_context: NativeContext, sierra_program: Option>, entry_point_id: Option, } +/// Compile the sierra program into a MLIR module +fn compile_sierra_program<'a>( + native_context: &'a NativeContext, + sierra_program: &'a Program, +) -> Result, String> { + native_context + .compile(sierra_program, false) + .map_err(|e| e.to_string()) +} + +// Function with memoization +fn create_executor<'a>(native_program: NativeModule<'a>) -> JitNativeExecutor<'a> { + JitNativeExecutor::from_native_module(native_program, Default::default()) +} + impl CairoNativeRunner { pub fn new() -> Self { let native_context = NativeContext::new(); @@ -24,8 +40,11 @@ impl CairoNativeRunner { } } + /// Initialize the runner + /// 1 - Start by compiling the Cairo program to Sierra + /// 2 - Store the entry point id in an instance variable pub fn init(&mut self, program_path: &Path, entry_point: &str) -> Result<(), String> { - // Convert and store the Sierra program + // Convert and store the Sierra programs self.convert_and_store_cairo_to_sierra(program_path)?; // Find and store the entry point ID @@ -34,6 +53,7 @@ impl CairoNativeRunner { Ok(()) } + /// Compile a Cairo program to Sierra fn convert_and_store_cairo_to_sierra(&mut self, program_path: &Path) -> Result<(), String> { if self.sierra_program.is_none() { self.sierra_program = Some(cairo_to_sierra(program_path)); @@ -41,6 +61,7 @@ impl CairoNativeRunner { Ok(()) } + /// Find the entry point id given it's name fn find_entry_point_id(&self, entry_point: &str) -> Result { let sierra_program = self .sierra_program @@ -51,28 +72,19 @@ impl CairoNativeRunner { .cloned() } - fn compile_sierra_program(&self) -> Result { - let sierra_program = self - .sierra_program - .as_ref() - .ok_or("Sierra program not available")?; - self.native_context - .compile(sierra_program, false) - .map_err(|e| e.to_string()) - } - - fn create_executor<'a>(&self, native_program: NativeModule<'a>) -> JitNativeExecutor<'a> { - JitNativeExecutor::from_native_module(native_program, Default::default()) - } - // Run the program - // TODO : Only keep the execution part in this method + #[inline] pub fn run_program(&mut self, params: &[Value]) -> Result { // Compile the Sierra program into a MLIR module - let native_program = self.compile_sierra_program()?; + let native_program = compile_sierra_program( + &self.native_context, + self.sierra_program + .as_ref() + .ok_or("Sierra program not available")?, + )?; // Instantiate the executor - let native_executor = self.create_executor(native_program); + let native_executor = create_executor(native_program); // Execute the program native_executor From 7e847c6c599174266b1dfbf442e84e88d1411e3f Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Wed, 30 Oct 2024 23:48:09 +0100 Subject: [PATCH 05/17] Create the `Fuzzer` struct --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 40 ++++++++++++++++++++++++ cairo-native-fuzzer/src/main.rs | 25 ++++++++------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index e69de29..8633917 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -0,0 +1,40 @@ +use cairo_native::Value; +use std::path::PathBuf; +use crate::runner::runner::CairoNativeRunner; + +pub struct Fuzzer { + program_path: PathBuf, + entry_point: String, + runner: CairoNativeRunner, + params: Vec, +} + +impl Fuzzer { + pub fn new(program_path: PathBuf, entry_point: String, params: Vec) -> Self { + Self { + program_path, + entry_point, + runner: CairoNativeRunner::new(), + params, + } + } + + /// init the fuzzer + pub fn init(&mut self) -> Result<(), String> { + self.runner.init(&self.program_path, &self.entry_point) + } + + /// Run the fuzzer + /// We just use an infinite loop for now + pub fn fuzz(&mut self) -> Result<(), String> { + loop { + match self.runner.run_program(&self.params) { + Ok(result) => { + println!("Cairo program was compiled and executed successfully."); + println!("{:?}", result); + } + Err(e) => eprintln!("Error during execution: {}", e), + } + } + } +} diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs index 2789871..0c59bca 100644 --- a/cairo-native-fuzzer/src/main.rs +++ b/cairo-native-fuzzer/src/main.rs @@ -1,25 +1,26 @@ mod runner; -use crate::runner::runner::CairoNativeRunner; +mod fuzzer; + +use crate::fuzzer::fuzzer::Fuzzer; use cairo_native::Value; use starknet_types_core::felt::Felt; use std::path::Path; fn main() { - let program_path = Path::new("examples/hello.cairo"); - let entry_point = "hello::hello::greet"; - let params = &[Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; + let program_path = Path::new("examples/hello.cairo").to_path_buf(); + let entry_point = "hello::hello::greet".to_string(); + let params = vec![Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; - let mut runner = CairoNativeRunner::new(); + let mut fuzzer = Fuzzer::new(program_path, entry_point, params); - match runner.init(program_path, entry_point) { - Ok(()) => match runner.run_program(params) { - Ok(result) => { - println!("Cairo program was compiled and executed successfully."); - println!("{:?}", result); + match fuzzer.init() { + Ok(()) => { + match fuzzer.fuzz() { + Ok(()) => println!("Fuzzing completed successfully."), + Err(e) => eprintln!("Error during fuzzing: {}", e), } - Err(e) => eprintln!("Error during execution: {}", e), - }, + } Err(e) => eprintln!("Error during initialization: {}", e), } } From cb483e3558ea1daf9178ac253b748c8107634c47 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Thu, 31 Oct 2024 19:55:39 +0100 Subject: [PATCH 06/17] Move `convert_and_store_cairo_to_sierra` method from the Runner to the Fuzzer --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 20 +++++++++++++++--- cairo-native-fuzzer/src/fuzzer/mod.rs | 2 +- cairo-native-fuzzer/src/main.rs | 12 +++++------ cairo-native-fuzzer/src/runner/runner.rs | 26 ++++++++---------------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 8633917..2952bbb 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -1,11 +1,15 @@ +use crate::runner::runner::CairoNativeRunner; +use cairo_lang_sierra::program::Program; +use cairo_native::utils::cairo_to_sierra; use cairo_native::Value; use std::path::PathBuf; -use crate::runner::runner::CairoNativeRunner; +use std::sync::Arc; pub struct Fuzzer { program_path: PathBuf, entry_point: String, runner: CairoNativeRunner, + sierra_program: Option>, params: Vec, } @@ -15,13 +19,23 @@ impl Fuzzer { program_path, entry_point, runner: CairoNativeRunner::new(), + sierra_program: None, params, } } - /// init the fuzzer + /// Init the fuzzer pub fn init(&mut self) -> Result<(), String> { - self.runner.init(&self.program_path, &self.entry_point) + self.convert_and_store_cairo_to_sierra()?; + self.runner.init(&self.entry_point, &self.sierra_program) + } + + /// Compile the Cairo program to Sierra + fn convert_and_store_cairo_to_sierra(&mut self) -> Result<(), String> { + if self.sierra_program.is_none() { + self.sierra_program = Some(cairo_to_sierra(&self.program_path)); + } + Ok(()) } /// Run the fuzzer diff --git a/cairo-native-fuzzer/src/fuzzer/mod.rs b/cairo-native-fuzzer/src/fuzzer/mod.rs index 20478c6..bdb5729 100644 --- a/cairo-native-fuzzer/src/fuzzer/mod.rs +++ b/cairo-native-fuzzer/src/fuzzer/mod.rs @@ -1 +1 @@ -pub mod fuzzer; \ No newline at end of file +pub mod fuzzer; diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs index 0c59bca..1fc8ec1 100644 --- a/cairo-native-fuzzer/src/main.rs +++ b/cairo-native-fuzzer/src/main.rs @@ -1,5 +1,5 @@ -mod runner; mod fuzzer; +mod runner; use crate::fuzzer::fuzzer::Fuzzer; @@ -15,12 +15,10 @@ fn main() { let mut fuzzer = Fuzzer::new(program_path, entry_point, params); match fuzzer.init() { - Ok(()) => { - match fuzzer.fuzz() { - Ok(()) => println!("Fuzzing completed successfully."), - Err(e) => eprintln!("Error during fuzzing: {}", e), - } - } + Ok(()) => match fuzzer.fuzz() { + Ok(()) => println!("Fuzzing completed successfully."), + Err(e) => eprintln!("Error during fuzzing: {}", e), + }, Err(e) => eprintln!("Error during initialization: {}", e), } } diff --git a/cairo-native-fuzzer/src/runner/runner.rs b/cairo-native-fuzzer/src/runner/runner.rs index c053c2e..b124758 100644 --- a/cairo-native-fuzzer/src/runner/runner.rs +++ b/cairo-native-fuzzer/src/runner/runner.rs @@ -2,10 +2,7 @@ use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; use cairo_native::execution_result::ExecutionResult; use cairo_native::module::NativeModule; -use cairo_native::{ - context::NativeContext, executor::JitNativeExecutor, utils::cairo_to_sierra, Value, -}; -use std::path::Path; +use cairo_native::{context::NativeContext, executor::JitNativeExecutor, Value}; use std::sync::Arc; /// Cairo Runner that uses Cairo Native @@ -25,7 +22,7 @@ fn compile_sierra_program<'a>( .map_err(|e| e.to_string()) } -// Function with memoization +// Create the Native Executor (with JIT) fn create_executor<'a>(native_program: NativeModule<'a>) -> JitNativeExecutor<'a> { JitNativeExecutor::from_native_module(native_program, Default::default()) } @@ -41,11 +38,14 @@ impl CairoNativeRunner { } /// Initialize the runner - /// 1 - Start by compiling the Cairo program to Sierra + /// 1 - Load the sierra_program instance variable /// 2 - Store the entry point id in an instance variable - pub fn init(&mut self, program_path: &Path, entry_point: &str) -> Result<(), String> { - // Convert and store the Sierra programs - self.convert_and_store_cairo_to_sierra(program_path)?; + pub fn init( + &mut self, + entry_point: &str, + sierra_program: &Option>, + ) -> Result<(), String> { + self.sierra_program = sierra_program.clone(); // Find and store the entry point ID self.entry_point_id = Some(self.find_entry_point_id(entry_point)?); @@ -53,14 +53,6 @@ impl CairoNativeRunner { Ok(()) } - /// Compile a Cairo program to Sierra - fn convert_and_store_cairo_to_sierra(&mut self, program_path: &Path) -> Result<(), String> { - if self.sierra_program.is_none() { - self.sierra_program = Some(cairo_to_sierra(program_path)); - } - Ok(()) - } - /// Find the entry point id given it's name fn find_entry_point_id(&self, entry_point: &str) -> Result { let sierra_program = self From 63ce42563eeb167fe399b3181fb61d6386492e7c Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Thu, 31 Oct 2024 22:46:46 +0100 Subject: [PATCH 07/17] Move `find_entry_point_id` method from the Runner to the Fuzzer --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 28 ++++++++++++++++++++---- cairo-native-fuzzer/src/runner/runner.rs | 24 +++++--------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 2952bbb..79b0066 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -1,9 +1,12 @@ -use crate::runner::runner::CairoNativeRunner; +use std::path::PathBuf; +use std::sync::Arc; + +use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; use cairo_native::utils::cairo_to_sierra; use cairo_native::Value; -use std::path::PathBuf; -use std::sync::Arc; + +use crate::runner::runner::CairoNativeRunner; pub struct Fuzzer { program_path: PathBuf, @@ -11,6 +14,7 @@ pub struct Fuzzer { runner: CairoNativeRunner, sierra_program: Option>, params: Vec, + entry_point_id: Option, } impl Fuzzer { @@ -21,13 +25,18 @@ impl Fuzzer { runner: CairoNativeRunner::new(), sierra_program: None, params, + entry_point_id: None, } } /// Init the fuzzer + /// - Compile Cairo code to Sierra + /// - Find the entry id + /// - Init the runner pub fn init(&mut self) -> Result<(), String> { self.convert_and_store_cairo_to_sierra()?; - self.runner.init(&self.entry_point, &self.sierra_program) + self.entry_point_id = Some(self.find_entry_point_id()); + self.runner.init(&self.entry_point_id, &self.sierra_program) } /// Compile the Cairo program to Sierra @@ -38,6 +47,17 @@ impl Fuzzer { Ok(()) } + /// Find the entry point id + fn find_entry_point_id(&self) -> FunctionId { + let sierra_program = self + .sierra_program + .as_ref() + .expect("Sierra program not available"); + cairo_native::utils::find_function_id(sierra_program, &self.entry_point) + .expect(&format!("Entry point '{}' not found", self.entry_point)) + .clone() + } + /// Run the fuzzer /// We just use an infinite loop for now pub fn fuzz(&mut self) -> Result<(), String> { diff --git a/cairo-native-fuzzer/src/runner/runner.rs b/cairo-native-fuzzer/src/runner/runner.rs index b124758..02a1581 100644 --- a/cairo-native-fuzzer/src/runner/runner.rs +++ b/cairo-native-fuzzer/src/runner/runner.rs @@ -1,9 +1,10 @@ +use std::sync::Arc; + use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; use cairo_native::execution_result::ExecutionResult; use cairo_native::module::NativeModule; use cairo_native::{context::NativeContext, executor::JitNativeExecutor, Value}; -use std::sync::Arc; /// Cairo Runner that uses Cairo Native pub struct CairoNativeRunner { @@ -39,32 +40,19 @@ impl CairoNativeRunner { /// Initialize the runner /// 1 - Load the sierra_program instance variable - /// 2 - Store the entry point id in an instance variable + /// 2 - Load the entry point id in an instance variable pub fn init( &mut self, - entry_point: &str, + entry_point_id: &Option, sierra_program: &Option>, ) -> Result<(), String> { self.sierra_program = sierra_program.clone(); - - // Find and store the entry point ID - self.entry_point_id = Some(self.find_entry_point_id(entry_point)?); + self.entry_point_id = entry_point_id.clone(); Ok(()) } - /// Find the entry point id given it's name - fn find_entry_point_id(&self, entry_point: &str) -> Result { - let sierra_program = self - .sierra_program - .as_ref() - .ok_or("Sierra program not available")?; - cairo_native::utils::find_function_id(sierra_program, entry_point) - .ok_or_else(|| format!("Entry point '{}' not found", entry_point)) - .cloned() - } - - // Run the program + // Run the program using Cairo Native #[inline] pub fn run_program(&mut self, params: &[Value]) -> Result { // Compile the Sierra program into a MLIR module From 0fc4c4f0c9c2bfcb6e6f523d1ceb93ddeadc59d8 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Fri, 1 Nov 2024 18:58:38 +0100 Subject: [PATCH 08/17] Implement entry point params types detection --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 39 +++++++++++++++++++ cairo-native-fuzzer/src/main.rs | 6 ++- .../src/mutator/argument_type.rs | 18 +++++++++ cairo-native-fuzzer/src/mutator/mod.rs | 1 + cairo-native-fuzzer/src/utils.rs | 12 ++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 cairo-native-fuzzer/src/mutator/argument_type.rs create mode 100644 cairo-native-fuzzer/src/mutator/mod.rs create mode 100644 cairo-native-fuzzer/src/utils.rs diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 79b0066..791e92a 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -6,7 +6,10 @@ use cairo_lang_sierra::program::Program; use cairo_native::utils::cairo_to_sierra; use cairo_native::Value; +use crate::mutator::argument_type::map_argument_type; +use crate::mutator::argument_type::ArgumentType; use crate::runner::runner::CairoNativeRunner; +use crate::utils::get_function_by_id; pub struct Fuzzer { program_path: PathBuf, @@ -58,6 +61,42 @@ impl Fuzzer { .clone() } + /// Returns a vector of the function parameter types + /// + /// For example, given a function with the prototype: + /// ``` + /// myfunction(a: felt252, b: felt252) -> felt252 + /// ``` + /// This function will return: + /// ``` + /// [Felt, Felt] + /// ``` + pub fn get_function_arguments_types(&self) -> Vec { + let func = match (&self.sierra_program, &self.entry_point_id) { + (Some(program), Some(entry_point_id)) => get_function_by_id(program, entry_point_id), + _ => None, + }; + + if let Some(func) = func { + let argument_types: Vec = func + .signature + .param_types + .iter() + .filter_map(|param_type| { + if let Some(debug_name) = ¶m_type.debug_name { + /// Map param_type to an `ArgumentType` + /// For now we only handle felt252 + return map_argument_type(debug_name); + } + None + }) + .collect(); + argument_types + } else { + Vec::new() + } + } + /// Run the fuzzer /// We just use an infinite loop for now pub fn fuzz(&mut self) -> Result<(), String> { diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs index 1fc8ec1..bb9d9da 100644 --- a/cairo-native-fuzzer/src/main.rs +++ b/cairo-native-fuzzer/src/main.rs @@ -1,12 +1,14 @@ mod fuzzer; +mod mutator; mod runner; - -use crate::fuzzer::fuzzer::Fuzzer; +mod utils; use cairo_native::Value; use starknet_types_core::felt::Felt; use std::path::Path; +use crate::fuzzer::fuzzer::Fuzzer; + fn main() { let program_path = Path::new("examples/hello.cairo").to_path_buf(); let entry_point = "hello::hello::greet".to_string(); diff --git a/cairo-native-fuzzer/src/mutator/argument_type.rs b/cairo-native-fuzzer/src/mutator/argument_type.rs new file mode 100644 index 0000000..c02be84 --- /dev/null +++ b/cairo-native-fuzzer/src/mutator/argument_type.rs @@ -0,0 +1,18 @@ +use starknet_types_core::felt::Felt; + +/// Enum representing the types of arguments that can be passed to a function +#[derive(Debug)] +pub enum ArgumentType { + Felt, + // TODO: Add support for other types +} + +/// Helper function to map argument types based on their debug names +/// This function takes a debug name string and returns the corresponding `ArgumentType` +pub fn map_argument_type(debug_name: &str) -> Option { + match debug_name { + "felt252" => Some(ArgumentType::Felt), + // TODO: Add support for other types + _ => None, + } +} diff --git a/cairo-native-fuzzer/src/mutator/mod.rs b/cairo-native-fuzzer/src/mutator/mod.rs new file mode 100644 index 0000000..50b0f09 --- /dev/null +++ b/cairo-native-fuzzer/src/mutator/mod.rs @@ -0,0 +1 @@ +pub mod argument_type; diff --git a/cairo-native-fuzzer/src/utils.rs b/cairo-native-fuzzer/src/utils.rs new file mode 100644 index 0000000..18e433f --- /dev/null +++ b/cairo-native-fuzzer/src/utils.rs @@ -0,0 +1,12 @@ +use cairo_lang_sierra::ids::FunctionId; +use cairo_lang_sierra::program::GenFunction; +use cairo_lang_sierra::program::Program; +use cairo_lang_sierra::program::StatementIdx; + +/// Find and return the function with the given `FunctionId` in the `Program` +pub fn get_function_by_id<'a>( + program: &'a Program, + function_id: &FunctionId, +) -> Option<&'a GenFunction> { + program.funcs.iter().find(|f| &f.id == function_id) +} From c6ac9fef16169918a7d036005322cfcdd09734fe Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Fri, 1 Nov 2024 19:24:39 +0100 Subject: [PATCH 09/17] Implement initial params inputs generator in `Fuzzer` --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 23 ++++++++++++++++++++--- cairo-native-fuzzer/src/main.rs | 3 +-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 791e92a..921928e 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -5,6 +5,7 @@ use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; use cairo_native::utils::cairo_to_sierra; use cairo_native::Value; +use starknet_types_core::felt::Felt; use crate::mutator::argument_type::map_argument_type; use crate::mutator::argument_type::ArgumentType; @@ -21,13 +22,13 @@ pub struct Fuzzer { } impl Fuzzer { - pub fn new(program_path: PathBuf, entry_point: String, params: Vec) -> Self { + pub fn new(program_path: PathBuf, entry_point: String) -> Self { Self { program_path, entry_point, runner: CairoNativeRunner::new(), sierra_program: None, - params, + params: Vec::new(), entry_point_id: None, } } @@ -39,7 +40,10 @@ impl Fuzzer { pub fn init(&mut self) -> Result<(), String> { self.convert_and_store_cairo_to_sierra()?; self.entry_point_id = Some(self.find_entry_point_id()); - self.runner.init(&self.entry_point_id, &self.sierra_program) + self.runner + .init(&self.entry_point_id, &self.sierra_program)?; + self.generate_params(); + Ok(()) } /// Compile the Cairo program to Sierra @@ -97,9 +101,22 @@ impl Fuzzer { } } + /// Generate params based on the function argument types + pub fn generate_params(&mut self) { + let argument_types = self.get_function_arguments_types(); + self.params = argument_types + .into_iter() + .map(|arg_type| match arg_type { + ArgumentType::Felt => Value::Felt252(Felt::from_bytes_be_slice(b"\x00")), + // TODO: Add support for other types + }) + .collect(); + } + /// Run the fuzzer /// We just use an infinite loop for now pub fn fuzz(&mut self) -> Result<(), String> { + self.generate_params(); loop { match self.runner.run_program(&self.params) { Ok(result) => { diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs index bb9d9da..7a002e2 100644 --- a/cairo-native-fuzzer/src/main.rs +++ b/cairo-native-fuzzer/src/main.rs @@ -12,9 +12,8 @@ use crate::fuzzer::fuzzer::Fuzzer; fn main() { let program_path = Path::new("examples/hello.cairo").to_path_buf(); let entry_point = "hello::hello::greet".to_string(); - let params = vec![Value::Felt252(Felt::from_bytes_be_slice(b"user"))]; - let mut fuzzer = Fuzzer::new(program_path, entry_point, params); + let mut fuzzer = Fuzzer::new(program_path, entry_point); match fuzzer.init() { Ok(()) => match fuzzer.fuzz() { From b224e320964cec7cc26117ef0f2c63cd23d23345 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Fri, 1 Nov 2024 19:59:44 +0100 Subject: [PATCH 10/17] Parse command-line arguments using `clap` --- cairo-native-fuzzer/Cargo.toml | 1 + cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 6 ++--- cairo-native-fuzzer/src/main.rs | 23 ++++++++++++++----- .../src/mutator/argument_type.rs | 2 -- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cairo-native-fuzzer/Cargo.toml b/cairo-native-fuzzer/Cargo.toml index 365f7a7..1502388 100644 --- a/cairo-native-fuzzer/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] cairo-lang-sierra = "2.8.4" cairo-native = "0.2.0-alpha.4" +clap = "4.5.20" starknet-types-core = "0.1.7" diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 921928e..5ed6c28 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -88,8 +88,8 @@ impl Fuzzer { .iter() .filter_map(|param_type| { if let Some(debug_name) = ¶m_type.debug_name { - /// Map param_type to an `ArgumentType` - /// For now we only handle felt252 + // Map param_type to an `ArgumentType` + // For now we only handle felt252 return map_argument_type(debug_name); } None @@ -107,7 +107,7 @@ impl Fuzzer { self.params = argument_types .into_iter() .map(|arg_type| match arg_type { - ArgumentType::Felt => Value::Felt252(Felt::from_bytes_be_slice(b"\x00")), + ArgumentType::Felt => Value::Felt252(Felt::from(0)), // TODO: Add support for other types }) .collect(); diff --git a/cairo-native-fuzzer/src/main.rs b/cairo-native-fuzzer/src/main.rs index 7a002e2..f27c271 100644 --- a/cairo-native-fuzzer/src/main.rs +++ b/cairo-native-fuzzer/src/main.rs @@ -3,17 +3,28 @@ mod mutator; mod runner; mod utils; -use cairo_native::Value; -use starknet_types_core::felt::Felt; -use std::path::Path; +use clap::Parser; +use std::path::PathBuf; use crate::fuzzer::fuzzer::Fuzzer; +/// Command-line arguments for the fuzzer +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Path to the Cairo program + #[arg(short, long)] + program_path: PathBuf, + + /// Entry point of the Sierra program + #[arg(short, long)] + entry_point: String, +} + fn main() { - let program_path = Path::new("examples/hello.cairo").to_path_buf(); - let entry_point = "hello::hello::greet".to_string(); + let args = Args::parse(); - let mut fuzzer = Fuzzer::new(program_path, entry_point); + let mut fuzzer = Fuzzer::new(args.program_path, args.entry_point); match fuzzer.init() { Ok(()) => match fuzzer.fuzz() { diff --git a/cairo-native-fuzzer/src/mutator/argument_type.rs b/cairo-native-fuzzer/src/mutator/argument_type.rs index c02be84..2af9c5a 100644 --- a/cairo-native-fuzzer/src/mutator/argument_type.rs +++ b/cairo-native-fuzzer/src/mutator/argument_type.rs @@ -1,5 +1,3 @@ -use starknet_types_core::felt::Felt; - /// Enum representing the types of arguments that can be passed to a function #[derive(Debug)] pub enum ArgumentType { From fb2c889a6af41ccda6c53cc39b5dde0751a3cffc Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Sat, 2 Nov 2024 00:26:48 +0100 Subject: [PATCH 11/17] Import felt252 mutator from `cairo-fuzzer` & Add Roadmap to README --- cairo-native-fuzzer/Cargo.toml | 5 + cairo-native-fuzzer/README.md | 15 + .../src/mutator/magic_values.rs | 248 ++++ cairo-native-fuzzer/src/mutator/mod.rs | 2 + cairo-native-fuzzer/src/mutator/mutator.rs | 1002 +++++++++++++++++ 5 files changed, 1272 insertions(+) create mode 100644 cairo-native-fuzzer/src/mutator/magic_values.rs create mode 100644 cairo-native-fuzzer/src/mutator/mutator.rs diff --git a/cairo-native-fuzzer/Cargo.toml b/cairo-native-fuzzer/Cargo.toml index 1502388..27874c8 100644 --- a/cairo-native-fuzzer/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -8,3 +8,8 @@ cairo-lang-sierra = "2.8.4" cairo-native = "0.2.0-alpha.4" clap = "4.5.20" starknet-types-core = "0.1.7" + +[dependencies.felt] +git = 'https://github.com/FuzzingLabs/cairo-rs' +rev = '48af153240392992f18a09e969bae6518eec9639' +package = 'cairo-felt' \ No newline at end of file diff --git a/cairo-native-fuzzer/README.md b/cairo-native-fuzzer/README.md index 868b4ae..4d385f9 100644 --- a/cairo-native-fuzzer/README.md +++ b/cairo-native-fuzzer/README.md @@ -1,3 +1,18 @@ ## Cairo Native Fuzzer Cairo Native Fuzzer is a rewrite of the Cairo Fuzzer based on [Cairo native from Lambdaclass](https://github.com/lambdaclass/cairo_native) developed to enhance fuzzer execution speed. + +### Roadmap + +### Step 1 : Create a basic fuzzer based on Cairo Native : +- [x] Implement the Cairo Native runner +- [x] Implement the fuzzer based on Cairo Native runner +- [ ] Import existing Felt252 mutator from the cairo-fuzzer + +### Step 2 : Integrate existing cairo-fuzzer features into Cairo Native fuzzer : +- [ ] Multithreading +- [ ] Support config files +- [ ] Property testing + +### Step 3 : Advanced features : +- [ ] Support `u8`, `u16`, `u32`, `u64`, `u128` and `u256` arguments \ No newline at end of file diff --git a/cairo-native-fuzzer/src/mutator/magic_values.rs b/cairo-native-fuzzer/src/mutator/magic_values.rs new file mode 100644 index 0000000..3acb062 --- /dev/null +++ b/cairo-native-fuzzer/src/mutator/magic_values.rs @@ -0,0 +1,248 @@ +//! A file containing a bunch of magic values, from honggfuzz +/* + * + * Authors: + * Robert Swiecki + * Brandon Falk + * + * Copyright 2010-2018 by Google Inc. All Rights Reserved. + * Copyright 2020 by Brandon Falk + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +/// Magic values of various sizes and endiannesses +pub const MAGIC_VALUES: &[&[u8]] = &[ + b"\x00", + b"\x01", + b"\x02", + b"\x03", + b"\x04", + b"\x05", + b"\x06", + b"\x07", + b"\x08", + b"\x09", + b"\x0a", + b"\x0b", + b"\x0c", + b"\x0d", + b"\x0e", + b"\x0f", + b"\x10", + b"\x20", + b"\x40", + b"\x7e", + b"\x7f", + b"\x80", + b"\x81", + b"\xc0", + b"\xfe", + b"\xff", + b"\x00\x00", + b"\x01\x01", + b"\x80\x80", + b"\xff\xff", + b"\x00\x01", + b"\x00\x02", + b"\x00\x03", + b"\x00\x04", + b"\x00\x05", + b"\x00\x06", + b"\x00\x07", + b"\x00\x08", + b"\x00\x09", + b"\x00\x0a", + b"\x00\x0b", + b"\x00\x0c", + b"\x00\x0d", + b"\x00\x0e", + b"\x00\x0f", + b"\x00\x10", + b"\x00\x20", + b"\x00\x40", + b"\x00\x7e", + b"\x00\x7f", + b"\x00\x80", + b"\x00\x81", + b"\x00\xc0", + b"\x00\xfe", + b"\x00\xff", + b"\x7e\xff", + b"\x7f\xff", + b"\x80\x00", + b"\x80\x01", + b"\xff\xfe", + b"\x00\x00", + b"\x01\x00", + b"\x02\x00", + b"\x03\x00", + b"\x04\x00", + b"\x05\x00", + b"\x06\x00", + b"\x07\x00", + b"\x08\x00", + b"\x09\x00", + b"\x0a\x00", + b"\x0b\x00", + b"\x0c\x00", + b"\x0d\x00", + b"\x0e\x00", + b"\x0f\x00", + b"\x10\x00", + b"\x20\x00", + b"\x40\x00", + b"\x7e\x00", + b"\x7f\x00", + b"\x80\x00", + b"\x81\x00", + b"\xc0\x00", + b"\xfe\x00", + b"\xff\x00", + b"\xff\x7e", + b"\xff\x7f", + b"\x00\x80", + b"\x01\x80", + b"\xfe\xff", + b"\x00\x00\x00\x00", + b"\x01\x01\x01\x01", + b"\x80\x80\x80\x80", + b"\xff\xff\xff\xff", + b"\x00\x00\x00\x01", + b"\x00\x00\x00\x02", + b"\x00\x00\x00\x03", + b"\x00\x00\x00\x04", + b"\x00\x00\x00\x05", + b"\x00\x00\x00\x06", + b"\x00\x00\x00\x07", + b"\x00\x00\x00\x08", + b"\x00\x00\x00\x09", + b"\x00\x00\x00\x0a", + b"\x00\x00\x00\x0b", + b"\x00\x00\x00\x0c", + b"\x00\x00\x00\x0d", + b"\x00\x00\x00\x0e", + b"\x00\x00\x00\x0f", + b"\x00\x00\x00\x10", + b"\x00\x00\x00\x20", + b"\x00\x00\x00\x40", + b"\x00\x00\x00\x7e", + b"\x00\x00\x00\x7f", + b"\x00\x00\x00\x80", + b"\x00\x00\x00\x81", + b"\x00\x00\x00\xc0", + b"\x00\x00\x00\xfe", + b"\x00\x00\x00\xff", + b"\x7e\xff\xff\xff", + b"\x7f\xff\xff\xff", + b"\x80\x00\x00\x00", + b"\x80\x00\x00\x01", + b"\xff\xff\xff\xfe", + b"\x00\x00\x00\x00", + b"\x01\x00\x00\x00", + b"\x02\x00\x00\x00", + b"\x03\x00\x00\x00", + b"\x04\x00\x00\x00", + b"\x05\x00\x00\x00", + b"\x06\x00\x00\x00", + b"\x07\x00\x00\x00", + b"\x08\x00\x00\x00", + b"\x09\x00\x00\x00", + b"\x0a\x00\x00\x00", + b"\x0b\x00\x00\x00", + b"\x0c\x00\x00\x00", + b"\x0d\x00\x00\x00", + b"\x0e\x00\x00\x00", + b"\x0f\x00\x00\x00", + b"\x10\x00\x00\x00", + b"\x20\x00\x00\x00", + b"\x40\x00\x00\x00", + b"\x7e\x00\x00\x00", + b"\x7f\x00\x00\x00", + b"\x80\x00\x00\x00", + b"\x81\x00\x00\x00", + b"\xc0\x00\x00\x00", + b"\xfe\x00\x00\x00", + b"\xff\x00\x00\x00", + b"\xff\xff\xff\x7e", + b"\xff\xff\xff\x7f", + b"\x00\x00\x00\x80", + b"\x01\x00\x00\x80", + b"\xfe\xff\xff\xff", + b"\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x01\x01\x01\x01\x01\x01\x01\x01", + b"\x80\x80\x80\x80\x80\x80\x80\x80", + b"\xff\xff\xff\xff\xff\xff\xff\xff", + b"\x00\x00\x00\x00\x00\x00\x00\x01", + b"\x00\x00\x00\x00\x00\x00\x00\x02", + b"\x00\x00\x00\x00\x00\x00\x00\x03", + b"\x00\x00\x00\x00\x00\x00\x00\x04", + b"\x00\x00\x00\x00\x00\x00\x00\x05", + b"\x00\x00\x00\x00\x00\x00\x00\x06", + b"\x00\x00\x00\x00\x00\x00\x00\x07", + b"\x00\x00\x00\x00\x00\x00\x00\x08", + b"\x00\x00\x00\x00\x00\x00\x00\x09", + b"\x00\x00\x00\x00\x00\x00\x00\x0a", + b"\x00\x00\x00\x00\x00\x00\x00\x0b", + b"\x00\x00\x00\x00\x00\x00\x00\x0c", + b"\x00\x00\x00\x00\x00\x00\x00\x0d", + b"\x00\x00\x00\x00\x00\x00\x00\x0e", + b"\x00\x00\x00\x00\x00\x00\x00\x0f", + b"\x00\x00\x00\x00\x00\x00\x00\x10", + b"\x00\x00\x00\x00\x00\x00\x00\x20", + b"\x00\x00\x00\x00\x00\x00\x00\x40", + b"\x00\x00\x00\x00\x00\x00\x00\x7e", + b"\x00\x00\x00\x00\x00\x00\x00\x7f", + b"\x00\x00\x00\x00\x00\x00\x00\x80", + b"\x00\x00\x00\x00\x00\x00\x00\x81", + b"\x00\x00\x00\x00\x00\x00\x00\xc0", + b"\x00\x00\x00\x00\x00\x00\x00\xfe", + b"\x00\x00\x00\x00\x00\x00\x00\xff", + b"\x7e\xff\xff\xff\xff\xff\xff\xff", + b"\x7f\xff\xff\xff\xff\xff\xff\xff", + b"\x80\x00\x00\x00\x00\x00\x00\x00", + b"\x80\x00\x00\x00\x00\x00\x00\x01", + b"\xff\xff\xff\xff\xff\xff\xff\xfe", + b"\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x01\x00\x00\x00\x00\x00\x00\x00", + b"\x02\x00\x00\x00\x00\x00\x00\x00", + b"\x03\x00\x00\x00\x00\x00\x00\x00", + b"\x04\x00\x00\x00\x00\x00\x00\x00", + b"\x05\x00\x00\x00\x00\x00\x00\x00", + b"\x06\x00\x00\x00\x00\x00\x00\x00", + b"\x07\x00\x00\x00\x00\x00\x00\x00", + b"\x08\x00\x00\x00\x00\x00\x00\x00", + b"\x09\x00\x00\x00\x00\x00\x00\x00", + b"\x0a\x00\x00\x00\x00\x00\x00\x00", + b"\x0b\x00\x00\x00\x00\x00\x00\x00", + b"\x0c\x00\x00\x00\x00\x00\x00\x00", + b"\x0d\x00\x00\x00\x00\x00\x00\x00", + b"\x0e\x00\x00\x00\x00\x00\x00\x00", + b"\x0f\x00\x00\x00\x00\x00\x00\x00", + b"\x10\x00\x00\x00\x00\x00\x00\x00", + b"\x20\x00\x00\x00\x00\x00\x00\x00", + b"\x40\x00\x00\x00\x00\x00\x00\x00", + b"\x7e\x00\x00\x00\x00\x00\x00\x00", + b"\x7f\x00\x00\x00\x00\x00\x00\x00", + b"\x80\x00\x00\x00\x00\x00\x00\x00", + b"\x81\x00\x00\x00\x00\x00\x00\x00", + b"\xc0\x00\x00\x00\x00\x00\x00\x00", + b"\xfe\x00\x00\x00\x00\x00\x00\x00", + b"\xff\x00\x00\x00\x00\x00\x00\x00", + b"\xff\xff\xff\xff\xff\xff\xff\x7e", + b"\xff\xff\xff\xff\xff\xff\xff\x7f", + b"\x00\x00\x00\x00\x00\x00\x00\x80", + b"\x01\x00\x00\x00\x00\x00\x00\x80", + b"\xfe\xff\xff\xff\xff\xff\xff\xff", +]; diff --git a/cairo-native-fuzzer/src/mutator/mod.rs b/cairo-native-fuzzer/src/mutator/mod.rs index 50b0f09..531846d 100644 --- a/cairo-native-fuzzer/src/mutator/mod.rs +++ b/cairo-native-fuzzer/src/mutator/mod.rs @@ -1 +1,3 @@ pub mod argument_type; +pub mod magic_values; +pub mod mutator; diff --git a/cairo-native-fuzzer/src/mutator/mutator.rs b/cairo-native-fuzzer/src/mutator/mutator.rs new file mode 100644 index 0000000..6b2ebed --- /dev/null +++ b/cairo-native-fuzzer/src/mutator/mutator.rs @@ -0,0 +1,1002 @@ +//! Basic fuzzer mutation strategies, largely ported from honggfuzz +/* + * + * Authors: + * Robert Swiecki + * Brandon Falk + * + * Copyright 2010-2018 by Google Inc. All Rights Reserved. + * Copyright 2020 by Brandon Falk + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ +#![allow(dead_code)] +extern crate alloc; + +use super::magic_values; +use alloc::vec::Vec; +use felt::Felt252; +use magic_values::MAGIC_VALUES; +/// An empty database that never returns an input, useful for fuzzers without +/// corpuses or input databases. +pub struct EmptyDatabase; + +impl InputDatabase for EmptyDatabase { + fn num_inputs(&self) -> usize { + 0 + } + fn input(&self, _idx: usize) -> Option { + None + } +} + +/// Routines to generically access a corpus/input database for a fuzzer. It's +/// up to the database to implement this trait, allowing generic access to +/// the number of inputs in the database, and accessors for a specific input. +/// +/// The inputs should have zero-indexed IDs such that any input in the range of +/// [0, self.num_inputs()) should be a valid input. +/// +/// If the `idx` does not lead to the same input each run, the determinism of +/// the mutator is unstable and can produce different results across different +/// runs. +pub trait InputDatabase { + /// Get the number of inputs in the database + fn num_inputs(&self) -> usize; + + /// Get an input with a specific zero-index identifier + /// If the `idx` is invalid or otherwise not available, this returns `None` + fn input(&self, idx: usize) -> Option; +} + +/// A basic random number generator based on xorshift64 with 64-bits of state +struct Rng { + /// The RNG's seed and state + seed: u64, + + /// If set, `rand_exp` behaves the same as `rand` + exp_disabled: bool, +} + +impl Rng { + /// Generate a random number + #[inline] + fn next(&mut self) -> u64 { + let val = self.seed; + self.seed ^= self.seed << 13; + self.seed ^= self.seed >> 17; + self.seed ^= self.seed << 43; + val + } + + /// Generates a random number with uniform distribution in the range of + /// [min, max] + #[inline] + fn rand(&mut self, min: usize, max: usize) -> usize { + // Make sure the range is sane + assert!(max >= min, "Bad range specified for rand()"); + + // If there is no range, just return `min` + if min == max { + return min; + } + + // If the range is unbounded, just return a random number + if min == 0 && max == core::usize::MAX { + return self.next() as usize; + } + + // Pick a random number in the range + min + (self.next() as usize % (max - min + 1)) + } + + /// Generates a random number with exponential distribution in the range of + /// [min, max] with a worst case deviation from uniform of 0.5x. Meaning + /// this will always return uniform at least half the time. + #[inline] + fn rand_exp(&mut self, min: usize, max: usize) -> usize { + // If exponential random is disabled, fall back to uniform + if self.exp_disabled { + return self.rand(min, max); + } + + if self.rand(0, 1) == 0 { + // Half the time, provide uniform + self.rand(min, max) + } else { + // Pick an exponentially difficult random number + let x = self.rand(min, max); + self.rand(min, x) + } + } +} + +/// A mutator, a playground for corrupting the public `input` vector when +/// `mutate` is invoked +pub struct Mutator { + /// Input vector to mutate, this is just an entire input files bytes + /// + /// It is strongly recommended that you do `input.clear()` and + /// `input.extend_from_slice()` to update this buffer, to prevent the + /// backing from being deallocated and reallocated. + pub input: Vec, + pub types: Vec, + /// If non-zero length, this contains a list of valid indicies into + /// `input`, indicating which bytes of the input should mutated. This often + /// comes from instrumentation like access tracking or taint tracking to + /// indicate which parts of the input are used. This will prevent us from + /// corrupting parts of the input which have zero effect on the program. + /// + /// It's possible you can have this take any meaning you want, all it does + /// is limit the corruption/splicing locations to the indicies in this + /// vector. Feel free to change this to have different meanings, like + /// indicate indicies which are used in comparison instructions! + /// + /// Since we use `rand_exp` to pick from this, this list should remain + /// sorted for best behavior. If you cannot sort this, you should probably + /// change the behaviors of `rand_offset` to be uniform + /// + /// It is strongly recommended that you do `accessed.clear()` and + /// `accessed.extend_from_slice()` to update this buffer, to prevent the + /// backing from being deallocated and reallocated. + pub accessed: Vec, + + /// Maximum size to allow inputs to expand to + max_input_size: usize, + + /// The random number generator used for mutations + rng: Rng, + + /// The mutations should prefer creating ASCII-printable characters + printable: bool, +} + +/// A byte corruption skeleton which has user-supplied corruption logic which +/// will be used to mutate a byte which is passed to it +/// +/// $corrupt takes a &mut self, `u8` as arguments, and returns a `u8` as the +/// corrupted value. +macro_rules! byte_corruptor { + ($func:ident, $corrupt:expr) => { + /// Corrupt a byte in the input + fn $func(&mut self) { + // Only corrupt a byte if there are bytes present + if !self.input.is_empty() { + // Pick a random byte offset + let offset = self.rand_offset(); + + // Perform the corruption + self.input[offset] = ($corrupt)(self, self.input[offset].clone()).into(); + } + } + }; +} + +impl Mutator { + /// Create a new mutator + pub fn new() -> Self { + Mutator { + input: Vec::new(), + types: Vec::new(), + accessed: Vec::new(), + max_input_size: 1024, + printable: false, + rng: Rng { + seed: 0x12640367f4b7ea35, + exp_disabled: false, + }, + } + } + + pub fn fix_inputs_types(&mut self) { + let mut idx = 0; + self.types.clone().into_iter().for_each(|input_type| { + let bits_to_save; + match input_type.as_str() { + "core::integer::u8" => { + bits_to_save = 8; + } + "core::integer::u16" => { + bits_to_save = 16; + } + "core::integer::u32" => { + bits_to_save = 32; + } + "core::integer::u64" => { + bits_to_save = 64; + } + "core::integer::u128" => { + bits_to_save = 128; + } + "core::integer::u256" => { + bits_to_save = 256; // still need to fix for this + } + "core::felt252" => { + bits_to_save = 252; + } + + _ => { + bits_to_save = 252; // Should be a Todo! but since the runner takes a felt252 vector, it's a good idea to send random data + } + } + if bits_to_save != 256 && bits_to_save != 252 { + let mut new_value = self.input[idx].to_be_bytes(); + for i in 0..new_value.len() - (bits_to_save / 8) { + new_value[i] = 0; + } + self.input[idx] = Felt252::from_bytes_be(&new_value); + } + idx += 1; + }); + } + /// Set whether or not this mutator should produce only ASCII-printable + /// characters. + /// + /// If non-printable characters are used in part of the corpus or existing + /// input, they may be inherited and still exist in the output of the + /// fuzzer. + pub fn printable(mut self, printable: bool) -> Self { + self.printable = printable; + self + } + + /// Allows enabling and disabling of exponential random in the fuzzer. If + /// disabled, all random selections will be uniform. + pub fn rand_exp(mut self, exponential_random: bool) -> Self { + self.rng.exp_disabled = !exponential_random; + self + } + + /// Sets the seed for the internal RNG + pub fn seed(mut self, seed: u64) -> Self { + self.rng.seed = seed ^ 0x12640367f4b7ea35; + self + } + + pub fn types(mut self, types: Vec) -> Self { + self.types.clone_from(&types); + self + } + /// Sets the maximum input size + pub fn max_input_size(mut self, size: usize) -> Self { + self.max_input_size = size; + self + } + + /// Performs standard mutation of an the input + pub fn mutate(&mut self, mutations: usize, inputs: &T) { + /// List of mutation strategies which do not require an input database + const STRATEGIES: &[fn(&mut Mutator)] = &[ + //Mutator::shrink, + //Mutator::expand, + Mutator::inc_byte, + Mutator::dec_byte, + Mutator::neg_byte, + Mutator::add_sub, + //Mutator::set, + Mutator::swap, + Mutator::copy, + Mutator::inter_splice, + //Mutator::insert_rand, + Mutator::overwrite_rand, + //Mutator::byte_repeat_overwrite, + //Mutator::byte_repeat_insert, + Mutator::magic_overwrite, + //Mutator::magic_insert, + Mutator::random_overwrite, + //Mutator::random_insert, + Mutator::splice_overwrite, + //Mutator::splice_insert, + ]; + + // Save the old state of the exponential random and randomly disable + // the exponential random + let old_exp_state = self.rng.exp_disabled; + if self.rng.rand(0, 1) == 0 { + self.rng.exp_disabled = true; + } + + for _ in 0..mutations { + // Pick a random mutation strategy + let sel = self.rng.rand(0, STRATEGIES.len() - 1); + + // Get the strategy + let strat = STRATEGIES[sel]; + + // Determine if we're doing an overwrite or insert splice strategy, + // as we have to handle these a bit specially due to the use of + // a generic input database. + let splice_overwrite = + core::ptr::eq(strat as *const (), Mutator::splice_overwrite as *const ()); + let splice_insert = + core::ptr::eq(strat as *const (), Mutator::splice_insert as *const ()); + + // Handle special-case mutations which need input database access + if splice_overwrite || splice_insert { + // Get the number of inputs in the database + let dblen = inputs.num_inputs(); + if dblen == 0 { + continue; + } + + // Select a random input + if let Some(inp) = inputs.input(self.rng.rand(0, dblen - 1)) { + // Nothing to splice for an empty input + /* if inp.is_empty() { + continue; + } */ + println!("Inputs selected => {:?}", inp); + // Pick a random offset and length from the input which + // we want to use for splicing + let donor_offset = self.rng.rand_exp(0, inp.to_string().len() - 1); + let donor_length = self.rng.rand_exp(1, inp.to_string().len() - donor_offset); + + if splice_overwrite { + // Cannot overwrite an empty input + if self.input.is_empty() { + continue; + } + + // Find an offset to overwrite in our input + let offset = self.rand_offset(); + let length = core::cmp::min(donor_length, self.input.len() - offset); + + println!("inputs to bytes => {:?}", &inp.to_be_bytes()); + println!( + "inputs slices => {:?}", + &inp.to_be_bytes()[donor_offset..donor_offset + length] + ); + // Overwrite it! + self.overwrite( + offset, + &inp.to_be_bytes()[donor_offset..donor_offset + length], + ); + } else { + // Find an offset to insert at in our input + let offset = self.rand_offset_int(true); + // Insert! + self.insert(offset, inp); + } + } + } else { + // Run the mutation strategy + strat(self); + } + } + + // Restore exponential random state to the old state + self.rng.exp_disabled = old_exp_state; + self.fix_inputs_types(); + } + + /// Pick a random offset in the input to corrupt. Any mutation + /// strategy which needs to pick a random byte should us this, such that + /// a bias can be applied to the offsets and automatically affect all + /// aspects of the fuzzer + /// + /// If `insert` is set to `true`, then the offset being returned is + /// expected to be for insertion. This means that we'll have a chance of + /// returning an offset before or after sections of the input. For example, + /// we may return an index which is `self.input.len()`, which would allow + /// for the user to insert data at the end of the input. + /// + /// If the input is zero length, then this will return a zero. Thus, it + /// is up to the caller to know whether or not the input size being zero + /// matters. For example, flipping a byte at offset 0 on a 0 size input + /// would cause a panic, but inserting at offset 0 may be desired. + fn rand_offset_int(&mut self, plus_one: bool) -> usize { + if !self.accessed.is_empty() { + // If we have an accessed list, use an index from the list of known + // accessed bytes from the input. This prevents us from corrupting + // a byte which cannot possibly influence the binary, as it is + // never read during the fuzz case. + self.accessed[self.rng.rand_exp(0, self.accessed.len() - 1)] + } else if !self.input.is_empty() { + // We have no accessed list, just return a random index + self.rng + .rand_exp(0, self.input.len() - (!plus_one) as usize) + } else { + // Input is entirely empty, just return index 0 such that + // things that insert into the input know that they should + // just insert at 0. + 0 + } + } + + /// Generate a random offset, see `rand_offset_int` for more info + fn rand_offset(&mut self) -> usize { + self.rand_offset_int(false) + } + + /// Dummy function, just used for RNG selection, logic is done in `mutate` + fn splice_overwrite(&mut self) {} + + /// Dummy function, just used for RNG selection, logic is done in `mutate` + fn splice_insert(&mut self) {} + + /// Randomly delete a chunk of the input + fn shrink(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a random offset to remove data at + let offset = self.rand_offset(); + + // Compute the number of bytes we could remove from this offset + let can_remove = self.input.len() - offset; + + // Compute a maximum number of bytes to remove + let max_remove = if self.rng.rand(0, 15) != 0 { + // 15 in 16 chance of removing at most 16 bytes, this limits the + // amount we remove in the most common case + core::cmp::min(16, can_remove) + } else { + // 1 in 16 chance of removing a random amount of bytes to the end + // of the input + can_remove + }; + + // Pick the amount of bytes to remove + let to_remove = self.rng.rand_exp(1, max_remove); + + // Remove the bytes from the input + let _ = self.input.drain(offset..offset + to_remove); + } + + /// Make space in the input, filling with zeros if `printable` is not + /// set, and if it is set, then fill with spaces. + fn expand(&mut self) { + // Nothing to do if the input size is >= the cap + if self.input.len() >= self.max_input_size { + return; + } + + // Pick a random offset to expand at + let offset = self.rand_offset_int(true); + + // Calculate a maximum expansion size based on our maximum allowed + // input size + let max_expand = self.max_input_size - self.input.len(); + + // Compute a maximum number of expansion bytes + let max_expand = if self.rng.rand(0, 15) != 0 { + // 15 in 16 chance of capping expansion to 16 bytes + core::cmp::min(16, max_expand) + } else { + // 1 in 16 chance of uncapped expansion + max_expand + }; + + // Create what to expand with + let iter = if self.printable { + core::iter::repeat(Felt252::from(b' ')).take(self.rng.rand_exp(1, max_expand)) + } else { + core::iter::repeat(Felt252::from(b'\0')).take(self.rng.rand_exp(1, max_expand)) + }; + + // Expand at `offset` with `iter` + self.input.splice(offset..offset, iter); + } + + /// Add or subtract a random amount with a random endianness from a random + /// size `u8` through `u64` + fn add_sub(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick an offset to corrupt at + let offset = self.rand_offset(); + + // Get the remaining number of bytes in the input + let remain = self.input.len() - offset; + + // Pick a random size of the add or subtract as a 1, 2, 4, or 8 byte + // signed integer + let intsize = match remain { + 1..=1 => 1, + 2..=3 => 1 << self.rng.rand(0, 1), + 4..=7 => 1 << self.rng.rand(0, 2), + 8..=core::usize::MAX => 1 << self.rng.rand(0, 3), + _ => unreachable!(), + }; + + // Determine the maximum number to add or subtract + let range = match intsize { + 1 => 16, + 2 => 4096, + 4 => 1024 * 1024, + 8 => 256 * 1024 * 1024, + _ => unreachable!(), + }; + + // Convert the range to a random number from [-range, range] + let delta = self.rng.rand(0, range * 2) as i32 - range as i32; + + /// Macro to mutate bytes in the input as a `$ty` + macro_rules! mutate { + ($ty:ty) => {{ + // Interpret the `offset` as a `$ty` + let tmp = self.input[offset].clone(); + + // Apply the delta, interpreting the bytes as a random + // endianness + let tmp = if self.rng.rand(0, 1) == 0 { + (Felt252::from(delta) + Felt252::from(tmp)) + } else { + //tmp.swap_bytes().wrapping_add(delta as $ty).swap_bytes() + Felt252::from(delta) + }; + + // Write the new value out to the input + self.input[offset] += Felt252::from(tmp); + }}; + } + + // Apply the delta to the offset + match intsize { + 1 => mutate!(u8), + 2 => mutate!(u16), + 4 => mutate!(u32), + 8 => mutate!(u64), + 16 => mutate!(Felt252), + _ => unreachable!(), + }; + } + + /// Randomly replace a sequence of bytes with the same random character + /// repeated a random amount of times + fn set(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick offset to memset at + let offset = self.rand_offset(); + + // Pick random length to remainder of input + let len = self.rng.rand_exp(1, self.input.len() - offset); + + // Pick the value to memset + let chr = if self.printable { + (self.rng.rand(0, 94) + 32) as u8 + } else { + self.rng.rand(0, 255) as u8 + }; + + // Replace the selected bytes at the offset with `chr` + self.input[offset..offset + len] + .iter_mut() + .for_each(|x| *x = Felt252::from(chr)); + } + + /// Swap two ranges in an input buffer + fn swap_ranges(vec: &mut [Felt252], mut offset1: usize, mut offset2: usize, mut len: usize) { + if offset1 < offset2 && offset1 + len >= offset2 { + // The ranges have the following layout here: + // [o1--------] + // [o2--------] + let tail = offset2 - offset1; + // Copy the tail from offset1 into offset2 + // [o1-][tail1] + // [o2-][tail2] + // This needs to happen in the reverse order so that the later + // values at offset1 are not mangled in the process of copying. + // Same as memmove. + for ii in (tail..len).rev() { + vec[offset2 + ii] = vec[offset1 + ii].clone(); + } + + // After this, the layout is the following: + // [o1-][xxxxx] + // [o2-][tail1] + len = tail; + } else if offset2 < offset1 && offset2 + len >= offset1 { + // The ranges have the following layout here: + // [o1--------] + // [o2--------] + let head = len - (offset1 - offset2); + // Copy the head from offset1 into offset2 + // [head1][o1-] + // [head2][o2-] + for ii in 0..head { + vec[offset2 + ii] = vec[offset1 + ii].clone(); + } + + // After this, the layout is the following: + // [xxxxx][o1-] + // [head1][o2-] + offset1 += head; + offset2 += head; + len -= head; + } + + // At this point, the ranges are non-overlapping + // and the swap can be done in a naive way. + for ii in 0..len { + vec.swap(offset1 + ii, offset2 + ii); + } + } + + /// Swap two difference sequence of bytes in the input to different places + fn swap(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick two random ranges in the input and calculate the remaining + // bytes for them + let src = self.rand_offset(); + let srcrem = self.input.len() - src; + let dst = self.rand_offset(); + let dstrem = self.input.len() - dst; + + // Pick a random length up to the max for both offsets + let len = self.rng.rand_exp(1, core::cmp::min(srcrem, dstrem)); + + // Swap the ranges of bytes + Self::swap_ranges(&mut self.input, src, dst, len); + } + + /// Insert `buf` at `offset` in the input. `buf` will be truncated to + /// ensure the input stays within the maximum input size + fn insert(&mut self, offset: usize, buf: Felt252) { + self.input.insert(offset, buf.clone()); + // Make sure we don't expand past the maximum input size + //let len = core::cmp::min(buf.len(), self.max_input_size - self.input.len()); + + // Splice in the `buf` + //self.input + // .splice(offset..offset, buf[..len].iter().copied()); + } + + /// Overwrite the bytes in the input with `buf` at `offset`. If `buf` + /// goes out of bounds of the input the `buf` will be truncated and the + /// copy will stop. + fn overwrite(&mut self, offset: usize, buf: &[u8]) { + // Get the slice that we may overwrite + let target = &mut self.input[offset..].to_vec(); + // Get the length to overwrite + let len = core::cmp::min(buf.len(), target.len()); + let mut i = 0; + while i < len { + target[i] = Felt252::from(buf[i]); + i += 1; + } + // Overwrite the bytes + //target.to[..len].copy_from_slice(&buf[..len]); + } + + /// Take the bytes from `source` for `len` bytes in the input, and insert + /// a copy of them at `dest` + fn insert_inplace(&mut self, source: usize, len: usize, dest: usize) { + // Nothing to do + if len == 0 || source == dest { + return; + } + + // Cap the insertion to the max input size + let len = core::cmp::min(len, self.max_input_size - self.input.len()); + + // Create an interator to splice into the input + let rep = core::iter::repeat(Felt252::from(b'\0')).take(len); + + // Expand at `dest` with `rep`, making room for the copy + self.input.splice(dest..dest, rep); + + // Determine where the splice occurred + let split_point = dest.saturating_sub(source).min(len); + + for ii in 0..split_point { + self.input[dest + ii] = self.input[source + ii].clone(); + } + + for ii in split_point..len { + self.input[dest + ii] = self.input[source + ii + len].clone(); + } + } + + /// Take the bytes from `source` for `len` bytes in the input, and copy + /// them to `dest` + fn overwrite_inplace(&mut self, source: usize, len: usize, dest: usize) { + // Nothing to do + if len == 0 || source == dest { + return; + } + + if source < dest { + // Copy forwards + for ii in 0..len { + self.input[dest + ii] = self.input[source + ii].clone(); + } + } else { + // Copy backwards + for ii in (0..len).rev() { + self.input[dest + ii] = self.input[source + ii].clone(); + } + } + } + + /// Copy bytes from one location in the input and overwrite them at another + /* fn convert<'a>(arr: &'a [&'a [u8; 32]]) -> &'a [u8] { + let size = arr.iter().map(|a| a.len()).sum(); + let ptr = arr.as_ptr() as *const u8; + + // SAFETY: We have ensured that the pointer and length are valid + unsafe { std::slice::from_raw_parts(ptr, size) } + } */ + + /// location in the input + fn copy(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a source and destination for a copy + let src = self.rand_offset(); + let srcrem = self.input.len() - src; + let dst = self.rand_offset(); + let dstrem = self.input.len() - dst; + + // Pick a random length up to the max for both offsets + let len = self.rng.rand_exp(1, core::cmp::min(srcrem, dstrem)); + + // Perform a copy inplace in the input + self.overwrite_inplace(src, len, dst); + } + + /// Take one location of the input and splice it into another + fn inter_splice(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a source and destination for an insertion + let src = self.rand_offset(); + let srcrem = self.input.len() - src; + let dst = self.rand_offset_int(true); + + // Pick a random length + let len = self.rng.rand_exp(1, srcrem); + + // Perform an insertion inplace in the input + self.insert_inplace(src, len, dst); + } + + /// Create 1 or 2 random bytes and insert them into the input + fn insert_rand(&mut self) { + // Pick some random values + let bytes = if self.printable { + [ + Felt252::from(self.rng.rand(0, 94) + 32), + Felt252::from(self.rng.rand(0, 94) + 32), + ] + } else { + [ + Felt252::from(self.rng.rand(0, 255)), + Felt252::from(self.rng.rand(0, 255)), + ] + }; + + // Pick a random offset + let offset = self.rand_offset_int(true); + self.insert(offset, bytes[0].clone()); + // Pick a random offset + let offset = self.rand_offset_int(true); + self.insert(offset, bytes[1].clone()); + } + + /// Create 1 or 2 random bytes and overwrite them at a location in the + /// input + fn overwrite_rand(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick some random values + let bytes = if self.printable { + [ + Felt252::from(self.rng.rand(0, 94) + 32).to_be_bytes(), + Felt252::from(self.rng.rand(0, 94) + 32).to_be_bytes(), + ] + } else { + [ + Felt252::from(self.rng.rand(0, 255)).to_be_bytes(), + Felt252::from(self.rng.rand(0, 255)).to_be_bytes(), + ] + }; + + // Pick a random offset and length + let offset = self.rand_offset(); + let len = core::cmp::min(self.input.len() - offset, 2); + let len = self.rng.rand(1, len); + + let test = &bytes[..len]; + + let size = test.iter().map(|a| a.len()).sum(); + let ptr = test.as_ptr() as *const u8; + + // SAFETY: We have ensured that the pointer and length are valid + unsafe { + let new_arr = std::slice::from_raw_parts(ptr, size); + // Overwrite the bytes + self.overwrite(offset, new_arr); + }; + } + + /// Find a byte and repeat it multiple times by overwriting the data after + /// it + fn byte_repeat_overwrite(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a random offset + let offset = self.rand_offset(); + + // Pick an amount to repeat + let amount = self.rng.rand_exp(1, self.input.len() - offset); + + // Get the old value and repeat it + let val = self.input[offset].clone(); + self.input[offset + 1..offset + amount] + .iter_mut() + .for_each(|x| *x = val.clone()); + } + + /// Find a byte and repeat it multiple times by splicing a random amount + /// of the byte in + fn byte_repeat_insert(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a random offset + let offset = self.rand_offset(); + + // Pick an amount to repeat, subtracting one to account for the byte + // we're copying itself + let amount = self.rng.rand_exp(1, self.input.len() - offset) - 1; + + // Make sure we don't expand past the maximum input size + let amount = core::cmp::min(self.max_input_size - self.input.len(), amount); + + // Get the value we want to repeat + let val = self.input[offset].clone(); + + // Create an interator we want to expand with + let iter = core::iter::repeat(val).take(amount); + + // Expand at `offset` with `iter` + self.input.splice(offset..offset, iter); + } + + /// Write over the input with a random magic value + fn magic_overwrite(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a random offset + let offset = self.rand_offset(); + + // Pick a random magic value + let magic_value = &MAGIC_VALUES[self.rng.rand(0, MAGIC_VALUES.len() - 1)]; + let mut magic_felt = Vec::new(); + for i in magic_value.iter() { + magic_felt.push(*i); + } + // Overwrite it + + let test = &magic_felt[..]; + + self.overwrite(offset, &test); + } + + /// Inject a magic value into the input + fn magic_insert(&mut self) { + // Pick a random offset + let offset = self.rand_offset_int(true); + + // Pick a random magic value + let magic_value = &MAGIC_VALUES[self.rng.rand(0, MAGIC_VALUES.len() - 1)]; + for i in magic_value.iter() { + self.insert(offset, Felt252::from(*i)); + } + // Insert it + } + + /// Overwrite a random offset of the input with random bytes + fn random_overwrite(&mut self) { + // Nothing to do on an empty input + if self.input.is_empty() { + return; + } + + // Pick a random offset + let offset = self.rand_offset(); + + // Pick an amount to overwrite + let amount = self.rng.rand_exp(1, self.input.len() - offset); + + // Overwrite with random data + let rng = &mut self.rng; + if self.printable { + self.input[offset..offset + amount] + .iter_mut() + .for_each(|x| { + *x = Felt252::from(rng.rand(0, 94) + 32); + }); + } else { + self.input[offset..offset + amount] + .iter_mut() + .for_each(|x| *x = Felt252::from(rng.rand(0, 255))); + } + } + + /// Insert random bytes into a random offset in the input + fn random_insert(&mut self) { + // Pick a random offset + let offset = self.rand_offset_int(true); + + // Pick an amount to insert + let amount = self.rng.rand_exp(0, self.input.len() - offset); + + // Make sure the amount doesn't expand us past the maximum input size + let amount = core::cmp::min(amount, self.max_input_size - self.input.len()); + + // Insert `amount` random bytes + let rng = &mut self.rng; + if self.printable { + self.input.splice( + offset..offset, + (0..amount).map(|_| Felt252::from(rng.rand(0, 94) + 32)), + ); + } else { + self.input.splice( + offset..offset, + (0..amount).map(|_| Felt252::from(rng.rand(0, 255))), + ); + } + } + + // Corrupt a random bit in the input + /* byte_corruptor!(bit, |obj: &mut Self, x: Felt252| -> Felt252 { + x ^ Felt252::from(1u8 << obj.rng.rand(0, 7)) + }); */ + + // Increment a byte in the input + byte_corruptor!(inc_byte, |_: &mut Self, x: Felt252| -> Felt252 { + x + Felt252::from(1) + }); + + // Decrement a byte in the input + byte_corruptor!(dec_byte, |_: &mut Self, x: Felt252| -> Felt252 { + x - Felt252::from(1) + }); + + // Negate a byte in the input + byte_corruptor!(neg_byte, |_: &mut Self, x: Felt252| -> Felt252 { -x }); +} From 17d20bf7f28a6398c50e538370616d71cb08afaa Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Sun, 3 Nov 2024 17:35:48 +0100 Subject: [PATCH 12/17] Implement `to_be_bytes` method for `Felt` --- cairo-native-fuzzer/src/mutator/mutator.rs | 92 +++++++++++++--------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/cairo-native-fuzzer/src/mutator/mutator.rs b/cairo-native-fuzzer/src/mutator/mutator.rs index 6b2ebed..b1b730d 100644 --- a/cairo-native-fuzzer/src/mutator/mutator.rs +++ b/cairo-native-fuzzer/src/mutator/mutator.rs @@ -26,17 +26,33 @@ extern crate alloc; use super::magic_values; use alloc::vec::Vec; -use felt::Felt252; use magic_values::MAGIC_VALUES; /// An empty database that never returns an input, useful for fuzzers without /// corpuses or input databases. pub struct EmptyDatabase; +use starknet_types_core::felt::Felt; + +// Define a trait for converting to a byte array +trait ToBeBytes { + fn to_be_bytes(&self) -> [u8; 32]; +} + +// Implement the ToBeBytes trait for the Felt type +impl ToBeBytes for Felt { + fn to_be_bytes(&self) -> [u8; 32] { + let (_, bytes) = self.to_bigint().to_bytes_be(); + let mut array = [0u8; 32]; + array[32 - bytes.len()..].copy_from_slice(&bytes); + array + } +} + impl InputDatabase for EmptyDatabase { fn num_inputs(&self) -> usize { 0 } - fn input(&self, _idx: usize) -> Option { + fn input(&self, _idx: usize) -> Option { None } } @@ -57,7 +73,7 @@ pub trait InputDatabase { /// Get an input with a specific zero-index identifier /// If the `idx` is invalid or otherwise not available, this returns `None` - fn input(&self, idx: usize) -> Option; + fn input(&self, idx: usize) -> Option; } /// A basic random number generator based on xorshift64 with 64-bits of state @@ -130,7 +146,7 @@ pub struct Mutator { /// It is strongly recommended that you do `input.clear()` and /// `input.extend_from_slice()` to update this buffer, to prevent the /// backing from being deallocated and reallocated. - pub input: Vec, + pub input: Vec, pub types: Vec, /// If non-zero length, this contains a list of valid indicies into /// `input`, indicating which bytes of the input should mutated. This often @@ -222,12 +238,12 @@ impl Mutator { "core::integer::u256" => { bits_to_save = 256; // still need to fix for this } - "core::felt252" => { + "core::Felt" => { bits_to_save = 252; } _ => { - bits_to_save = 252; // Should be a Todo! but since the runner takes a felt252 vector, it's a good idea to send random data + bits_to_save = 252; // Should be a Todo! but since the runner takes a Felt vector, it's a good idea to send random data } } if bits_to_save != 256 && bits_to_save != 252 { @@ -235,7 +251,7 @@ impl Mutator { for i in 0..new_value.len() - (bits_to_save / 8) { new_value[i] = 0; } - self.input[idx] = Felt252::from_bytes_be(&new_value); + self.input[idx] = Felt::from_bytes_be(&new_value); } idx += 1; }); @@ -482,9 +498,9 @@ impl Mutator { // Create what to expand with let iter = if self.printable { - core::iter::repeat(Felt252::from(b' ')).take(self.rng.rand_exp(1, max_expand)) + core::iter::repeat(Felt::from(b' ')).take(self.rng.rand_exp(1, max_expand)) } else { - core::iter::repeat(Felt252::from(b'\0')).take(self.rng.rand_exp(1, max_expand)) + core::iter::repeat(Felt::from(b'\0')).take(self.rng.rand_exp(1, max_expand)) }; // Expand at `offset` with `iter` @@ -536,14 +552,14 @@ impl Mutator { // Apply the delta, interpreting the bytes as a random // endianness let tmp = if self.rng.rand(0, 1) == 0 { - (Felt252::from(delta) + Felt252::from(tmp)) + (Felt::from(delta) + Felt::from(tmp)) } else { //tmp.swap_bytes().wrapping_add(delta as $ty).swap_bytes() - Felt252::from(delta) + Felt::from(delta) }; // Write the new value out to the input - self.input[offset] += Felt252::from(tmp); + self.input[offset] += Felt::from(tmp); }}; } @@ -553,7 +569,7 @@ impl Mutator { 2 => mutate!(u16), 4 => mutate!(u32), 8 => mutate!(u64), - 16 => mutate!(Felt252), + 16 => mutate!(Felt), _ => unreachable!(), }; } @@ -582,11 +598,11 @@ impl Mutator { // Replace the selected bytes at the offset with `chr` self.input[offset..offset + len] .iter_mut() - .for_each(|x| *x = Felt252::from(chr)); + .for_each(|x| *x = Felt::from(chr)); } /// Swap two ranges in an input buffer - fn swap_ranges(vec: &mut [Felt252], mut offset1: usize, mut offset2: usize, mut len: usize) { + fn swap_ranges(vec: &mut [Felt], mut offset1: usize, mut offset2: usize, mut len: usize) { if offset1 < offset2 && offset1 + len >= offset2 { // The ranges have the following layout here: // [o1--------] @@ -656,7 +672,7 @@ impl Mutator { /// Insert `buf` at `offset` in the input. `buf` will be truncated to /// ensure the input stays within the maximum input size - fn insert(&mut self, offset: usize, buf: Felt252) { + fn insert(&mut self, offset: usize, buf: Felt) { self.input.insert(offset, buf.clone()); // Make sure we don't expand past the maximum input size //let len = core::cmp::min(buf.len(), self.max_input_size - self.input.len()); @@ -676,7 +692,7 @@ impl Mutator { let len = core::cmp::min(buf.len(), target.len()); let mut i = 0; while i < len { - target[i] = Felt252::from(buf[i]); + target[i] = Felt::from(buf[i]); i += 1; } // Overwrite the bytes @@ -695,7 +711,7 @@ impl Mutator { let len = core::cmp::min(len, self.max_input_size - self.input.len()); // Create an interator to splice into the input - let rep = core::iter::repeat(Felt252::from(b'\0')).take(len); + let rep = core::iter::repeat(Felt::from(b'\0')).take(len); // Expand at `dest` with `rep`, making room for the copy self.input.splice(dest..dest, rep); @@ -786,13 +802,13 @@ impl Mutator { // Pick some random values let bytes = if self.printable { [ - Felt252::from(self.rng.rand(0, 94) + 32), - Felt252::from(self.rng.rand(0, 94) + 32), + Felt::from(self.rng.rand(0, 94) + 32), + Felt::from(self.rng.rand(0, 94) + 32), ] } else { [ - Felt252::from(self.rng.rand(0, 255)), - Felt252::from(self.rng.rand(0, 255)), + Felt::from(self.rng.rand(0, 255)), + Felt::from(self.rng.rand(0, 255)), ] }; @@ -815,13 +831,13 @@ impl Mutator { // Pick some random values let bytes = if self.printable { [ - Felt252::from(self.rng.rand(0, 94) + 32).to_be_bytes(), - Felt252::from(self.rng.rand(0, 94) + 32).to_be_bytes(), + Felt::from(self.rng.rand(0, 94) + 32).to_be_bytes(), + Felt::from(self.rng.rand(0, 94) + 32).to_be_bytes(), ] } else { [ - Felt252::from(self.rng.rand(0, 255)).to_be_bytes(), - Felt252::from(self.rng.rand(0, 255)).to_be_bytes(), + Felt::from(self.rng.rand(0, 255)).to_be_bytes(), + Felt::from(self.rng.rand(0, 255)).to_be_bytes(), ] }; @@ -923,7 +939,7 @@ impl Mutator { // Pick a random magic value let magic_value = &MAGIC_VALUES[self.rng.rand(0, MAGIC_VALUES.len() - 1)]; for i in magic_value.iter() { - self.insert(offset, Felt252::from(*i)); + self.insert(offset, Felt::from(*i)); } // Insert it } @@ -947,12 +963,12 @@ impl Mutator { self.input[offset..offset + amount] .iter_mut() .for_each(|x| { - *x = Felt252::from(rng.rand(0, 94) + 32); + *x = Felt::from(rng.rand(0, 94) + 32); }); } else { self.input[offset..offset + amount] .iter_mut() - .for_each(|x| *x = Felt252::from(rng.rand(0, 255))); + .for_each(|x| *x = Felt::from(rng.rand(0, 255))); } } @@ -972,31 +988,31 @@ impl Mutator { if self.printable { self.input.splice( offset..offset, - (0..amount).map(|_| Felt252::from(rng.rand(0, 94) + 32)), + (0..amount).map(|_| Felt::from(rng.rand(0, 94) + 32)), ); } else { self.input.splice( offset..offset, - (0..amount).map(|_| Felt252::from(rng.rand(0, 255))), + (0..amount).map(|_| Felt::from(rng.rand(0, 255))), ); } } // Corrupt a random bit in the input - /* byte_corruptor!(bit, |obj: &mut Self, x: Felt252| -> Felt252 { - x ^ Felt252::from(1u8 << obj.rng.rand(0, 7)) + /* byte_corruptor!(bit, |obj: &mut Self, x: Felt| -> Felt { + x ^ Felt::from(1u8 << obj.rng.rand(0, 7)) }); */ // Increment a byte in the input - byte_corruptor!(inc_byte, |_: &mut Self, x: Felt252| -> Felt252 { - x + Felt252::from(1) + byte_corruptor!(inc_byte, |_: &mut Self, x: Felt| -> Felt { + x + Felt::from(1) }); // Decrement a byte in the input - byte_corruptor!(dec_byte, |_: &mut Self, x: Felt252| -> Felt252 { - x - Felt252::from(1) + byte_corruptor!(dec_byte, |_: &mut Self, x: Felt| -> Felt { + x - Felt::from(1) }); // Negate a byte in the input - byte_corruptor!(neg_byte, |_: &mut Self, x: Felt252| -> Felt252 { -x }); + byte_corruptor!(neg_byte, |_: &mut Self, x: Felt| -> Felt { -x }); } From cd506c7699e3b402bcc06ac6a91271f816e6b9d7 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Mon, 4 Nov 2024 14:44:20 +0100 Subject: [PATCH 13/17] Create `mutate_param` placeholder method --- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 5ed6c28..2498d70 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -9,9 +9,11 @@ use starknet_types_core::felt::Felt; use crate::mutator::argument_type::map_argument_type; use crate::mutator::argument_type::ArgumentType; +use crate::mutator::mutator::Mutator; use crate::runner::runner::CairoNativeRunner; use crate::utils::get_function_by_id; +#[allow(dead_code)] pub struct Fuzzer { program_path: PathBuf, entry_point: String, @@ -19,6 +21,7 @@ pub struct Fuzzer { sierra_program: Option>, params: Vec, entry_point_id: Option, + mutator: Mutator, } impl Fuzzer { @@ -30,6 +33,7 @@ impl Fuzzer { sierra_program: None, params: Vec::new(), entry_point_id: None, + mutator: Mutator::new(), } } @@ -113,10 +117,34 @@ impl Fuzzer { .collect(); } + /// Mutate a single function parameter + pub fn mutate_param(&mut self, value: Value) -> Value { + match value { + Value::Felt252(felt) => { + // Perform some mutation on the felt value + // For now it's just a placeholder function + let mutated_felt = felt; + Value::Felt252(mutated_felt) + } + // TODO: Add support for other types + _ => value, + } + } + + /// Mutate the parameters using the Mutator + fn mutate_params(&mut self) { + // Iterate through the current params and mutate each one + for i in 0..self.params.len() { + let mutated_value = self.mutate_param(self.params[i].clone()); + self.params[i] = mutated_value; + } + } + /// Run the fuzzer /// We just use an infinite loop for now pub fn fuzz(&mut self) -> Result<(), String> { self.generate_params(); + self.mutate_params(); loop { match self.runner.run_program(&self.params) { Ok(result) => { From 11309c9bb34c207ec1ece323b1a9e302d0702034 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Mon, 4 Nov 2024 19:44:28 +0100 Subject: [PATCH 14/17] Create a basic mutator for testing purposes --- cairo-native-fuzzer/Cargo.toml | 3 +- cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 18 ++++---- .../src/mutator/basic_mutator.rs | 46 +++++++++++++++++++ cairo-native-fuzzer/src/mutator/mod.rs | 2 +- 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 cairo-native-fuzzer/src/mutator/basic_mutator.rs diff --git a/cairo-native-fuzzer/Cargo.toml b/cairo-native-fuzzer/Cargo.toml index 27874c8..3f99a09 100644 --- a/cairo-native-fuzzer/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -7,9 +7,10 @@ edition = "2021" cairo-lang-sierra = "2.8.4" cairo-native = "0.2.0-alpha.4" clap = "4.5.20" +rand = "0.8.5" starknet-types-core = "0.1.7" [dependencies.felt] git = 'https://github.com/FuzzingLabs/cairo-rs' rev = '48af153240392992f18a09e969bae6518eec9639' -package = 'cairo-felt' \ No newline at end of file +package = 'cairo-felt' diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index 2498d70..b967c50 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -9,7 +9,7 @@ use starknet_types_core::felt::Felt; use crate::mutator::argument_type::map_argument_type; use crate::mutator::argument_type::ArgumentType; -use crate::mutator::mutator::Mutator; +use crate::mutator::basic_mutator::Mutator; use crate::runner::runner::CairoNativeRunner; use crate::utils::get_function_by_id; @@ -22,6 +22,7 @@ pub struct Fuzzer { params: Vec, entry_point_id: Option, mutator: Mutator, + argument_types: Vec, } impl Fuzzer { @@ -34,6 +35,7 @@ impl Fuzzer { params: Vec::new(), entry_point_id: None, mutator: Mutator::new(), + argument_types: Vec::new(), } } @@ -46,6 +48,7 @@ impl Fuzzer { self.entry_point_id = Some(self.find_entry_point_id()); self.runner .init(&self.entry_point_id, &self.sierra_program)?; + self.argument_types = self.get_function_arguments_types(); self.generate_params(); Ok(()) } @@ -107,9 +110,9 @@ impl Fuzzer { /// Generate params based on the function argument types pub fn generate_params(&mut self) { - let argument_types = self.get_function_arguments_types(); - self.params = argument_types - .into_iter() + self.params = self + .argument_types + .iter() .map(|arg_type| match arg_type { ArgumentType::Felt => Value::Felt252(Felt::from(0)), // TODO: Add support for other types @@ -121,9 +124,8 @@ impl Fuzzer { pub fn mutate_param(&mut self, value: Value) -> Value { match value { Value::Felt252(felt) => { - // Perform some mutation on the felt value - // For now it's just a placeholder function - let mutated_felt = felt; + // Use the Mutator to mutate the felt value + let mutated_felt = self.mutator.mutate(felt); Value::Felt252(mutated_felt) } // TODO: Add support for other types @@ -144,7 +146,6 @@ impl Fuzzer { /// We just use an infinite loop for now pub fn fuzz(&mut self) -> Result<(), String> { self.generate_params(); - self.mutate_params(); loop { match self.runner.run_program(&self.params) { Ok(result) => { @@ -153,6 +154,7 @@ impl Fuzzer { } Err(e) => eprintln!("Error during execution: {}", e), } + self.mutate_params(); } } } diff --git a/cairo-native-fuzzer/src/mutator/basic_mutator.rs b/cairo-native-fuzzer/src/mutator/basic_mutator.rs new file mode 100644 index 0000000..8362439 --- /dev/null +++ b/cairo-native-fuzzer/src/mutator/basic_mutator.rs @@ -0,0 +1,46 @@ +use rand::Rng; +use starknet_types_core::felt::Felt; + +pub struct Mutator { + rng: rand::rngs::ThreadRng, +} + +impl Mutator { + pub fn new() -> Self { + Self { + rng: rand::thread_rng(), + } + } + + pub fn mutate(&mut self, felt: Felt) -> Felt { + // Perform a random mutation + let mutation_type = self.rng.gen_range(0..3); + match mutation_type { + 0 => self.add_small_random_value(felt), + 1 => self.subtract_small_random_value(felt), + 2 => self.flip_random_bit(felt), + // Fallback to the original value if something goes wrong + _ => felt, + } + } + + fn add_small_random_value(&mut self, felt: Felt) -> Felt { + // Random value between 1 and 9 + let small_value = self.rng.gen_range(1..10); + felt + Felt::from(small_value) + } + + fn subtract_small_random_value(&mut self, felt: Felt) -> Felt { + // Random value between 1 and 9 + let small_value = self.rng.gen_range(1..10); + felt - Felt::from(small_value) + } + + fn flip_random_bit(&mut self, felt: Felt) -> Felt { + // Random bit index between 0 and 251 + let bit_index = self.rng.gen_range(0..252); + let mut felt_bytes = felt.to_bytes_be(); + felt_bytes[bit_index / 8] ^= 1 << (bit_index % 8); + Felt::from_bytes_be(&felt_bytes) + } +} diff --git a/cairo-native-fuzzer/src/mutator/mod.rs b/cairo-native-fuzzer/src/mutator/mod.rs index 531846d..fb993d0 100644 --- a/cairo-native-fuzzer/src/mutator/mod.rs +++ b/cairo-native-fuzzer/src/mutator/mod.rs @@ -1,3 +1,3 @@ pub mod argument_type; +pub mod basic_mutator; pub mod magic_values; -pub mod mutator; From a3c8fbbcb9d64810e37147c9c13e9eab110f04b9 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Tue, 5 Nov 2024 23:27:19 +0100 Subject: [PATCH 15/17] Add mutations to the basic mutator --- .../src/mutator/basic_mutator.rs | 212 +++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) diff --git a/cairo-native-fuzzer/src/mutator/basic_mutator.rs b/cairo-native-fuzzer/src/mutator/basic_mutator.rs index 8362439..04e56d5 100644 --- a/cairo-native-fuzzer/src/mutator/basic_mutator.rs +++ b/cairo-native-fuzzer/src/mutator/basic_mutator.rs @@ -1,24 +1,43 @@ use rand::Rng; use starknet_types_core::felt::Felt; +use crate::mutator::magic_values::MAGIC_VALUES; + +/// This mutator only handle felt252 +/// TODO : Handle more types pub struct Mutator { rng: rand::rngs::ThreadRng, + max_input_size: usize, } impl Mutator { pub fn new() -> Self { Self { rng: rand::thread_rng(), + max_input_size: 252, } } pub fn mutate(&mut self, felt: Felt) -> Felt { // Perform a random mutation - let mutation_type = self.rng.gen_range(0..3); + let mutation_type = self.rng.gen_range(0..16); // Increase range to accommodate more strategies match mutation_type { 0 => self.add_small_random_value(felt), 1 => self.subtract_small_random_value(felt), 2 => self.flip_random_bit(felt), + 3 => self.inc_byte(felt), + 4 => self.dec_byte(felt), + // 5 => self.neg_byte(felt), + 6 => self.add_sub(felt), + 7 => self.swap(felt), + 8 => self.copy(felt), + 9 => self.inter_splice(felt), + 10 => self.magic_overwrite(felt), + 11 => self.magic_insert(felt), + 12 => self.random_overwrite(felt), + 13 => self.random_insert(felt), + 14 => self.byte_repeat_overwrite(felt), + 15 => self.byte_repeat_insert(felt), // Fallback to the original value if something goes wrong _ => felt, } @@ -43,4 +62,195 @@ impl Mutator { felt_bytes[bit_index / 8] ^= 1 << (bit_index % 8); Felt::from_bytes_be(&felt_bytes) } + + fn inc_byte(&mut self, felt: Felt) -> Felt { + felt + Felt::from(1) + } + + fn dec_byte(&mut self, felt: Felt) -> Felt { + felt - Felt::from(1) + } + + // fn neg_byte(&mut self, felt: Felt) -> Felt { + // -felt + // } + + fn add_sub(&mut self, felt: Felt) -> Felt { + // Add or subtract a random amount with a random endianness from a random size `u8` through `u64` + let delta = self.rng.gen_range(-100..100); // Example range + felt + Felt::from(delta) + } + + fn swap(&mut self, felt: Felt) -> Felt { + // Swap two ranges in an input buffer + let mut felt_bytes = felt.to_bytes_be(); + let len = felt_bytes.len(); + let src = self.rng.gen_range(0..len); + let dst = self.rng.gen_range(0..len); + let swap_len = self.rng.gen_range(1..=len.min(len - src.max(dst))); + + for i in 0..swap_len { + felt_bytes.swap(src + i, dst + i); + } + + Felt::from_bytes_be(&felt_bytes) + } + + fn copy(&mut self, felt: Felt) -> Felt { + // Copy bytes from one location in the input and overwrite them at another + let mut felt_bytes = felt.to_bytes_be(); + let len = felt_bytes.len(); + let src = self.rng.gen_range(0..len); + let dst = self.rng.gen_range(0..len); + let copy_len = self.rng.gen_range(1..=len.min(len - src.max(dst))); + + for i in 0..copy_len { + felt_bytes[dst + i] = felt_bytes[src + i]; + } + + Felt::from_bytes_be(&felt_bytes) + } + + fn inter_splice(&mut self, felt: Felt) -> Felt { + // Take one location of the input and splice it into another + let felt_bytes = felt.to_bytes_be(); + let len = felt_bytes.len(); + let src = self.rng.gen_range(0..len); + let dst = self.rng.gen_range(0..len); + let splice_len = self.rng.gen_range(1..=len.min(len - src.max(dst))); + + let mut new_bytes = Vec::new(); + new_bytes.extend_from_slice(&felt_bytes[..dst]); + new_bytes.extend_from_slice(&felt_bytes[src..src + splice_len]); + new_bytes.extend_from_slice(&felt_bytes[dst..]); + + // Ensure the length is exactly 32 bytes + if new_bytes.len() > 32 { + new_bytes.truncate(32); + } else if new_bytes.len() < 32 { + new_bytes.resize(32, 0); + } + + let mut array = [0u8; 32]; + array.copy_from_slice(&new_bytes); + + Felt::from_bytes_be(&array) + } + + fn magic_overwrite(&mut self, felt: Felt) -> Felt { + // Pick a random magic value + let magic_value = &MAGIC_VALUES[self.rng.gen_range(0..MAGIC_VALUES.len())]; + let mut felt_bytes = felt.to_bytes_be(); + + // Overwrite the bytes in the input with the magic value + let len = magic_value.len().min(felt_bytes.len()); + felt_bytes[..len].copy_from_slice(&magic_value[..len]); + + Felt::from_bytes_be(&felt_bytes) + } + + fn magic_insert(&mut self, felt: Felt) -> Felt { + // Pick a random magic value + let magic_value = &MAGIC_VALUES[self.rng.gen_range(0..MAGIC_VALUES.len())]; + let felt_bytes = felt.to_bytes_be(); + + // Insert the magic value at a random offset + let offset = self.rng.gen_range(0..=felt_bytes.len()); + let mut new_bytes = Vec::new(); + new_bytes.extend_from_slice(&felt_bytes[..offset]); + new_bytes.extend_from_slice(magic_value); + new_bytes.extend_from_slice(&felt_bytes[offset..]); + + // Ensure the length is exactly 32 bytes + if new_bytes.len() > 32 { + new_bytes.truncate(32); + } else if new_bytes.len() < 32 { + new_bytes.resize(32, 0); + } + + let mut array = [0u8; 32]; + array.copy_from_slice(&new_bytes); + + Felt::from_bytes_be(&array) + } + + fn random_overwrite(&mut self, felt: Felt) -> Felt { + // Overwrite a random offset of the input with random bytes + let mut felt_bytes = felt.to_bytes_be(); + let offset = self.rng.gen_range(0..felt_bytes.len()); + let amount = self.rng.gen_range(1..=felt_bytes.len() - offset); + + for i in offset..offset + amount { + felt_bytes[i] = self.rng.gen(); + } + + Felt::from_bytes_be(&felt_bytes) + } + + fn random_insert(&mut self, felt: Felt) -> Felt { + // Insert random bytes into a random offset in the input + let felt_bytes = felt.to_bytes_be(); + let offset = self.rng.gen_range(0..=felt_bytes.len()); + let amount = self + .rng + .gen_range(0..=self.max_input_size - felt_bytes.len()); + + let mut new_bytes = Vec::new(); + new_bytes.extend_from_slice(&felt_bytes[..offset]); + new_bytes.extend(std::iter::repeat(self.rng.gen::()).take(amount)); + new_bytes.extend_from_slice(&felt_bytes[offset..]); + + // Ensure the length is exactly 32 bytes + if new_bytes.len() > 32 { + new_bytes.truncate(32); + } else if new_bytes.len() < 32 { + new_bytes.resize(32, 0); + } + + let mut array = [0u8; 32]; + array.copy_from_slice(&new_bytes); + + Felt::from_bytes_be(&array) + } + + fn byte_repeat_overwrite(&mut self, felt: Felt) -> Felt { + // Find a byte and repeat it multiple times by overwriting the data after it + let mut felt_bytes = felt.to_bytes_be(); + let offset = self.rng.gen_range(0..felt_bytes.len()); + let amount = self.rng.gen_range(1..=felt_bytes.len() - offset); + + let val = felt_bytes[offset]; + for i in offset + 1..offset + amount { + felt_bytes[i] = val; + } + + Felt::from_bytes_be(&felt_bytes) + } + + fn byte_repeat_insert(&mut self, felt: Felt) -> Felt { + // Find a byte and repeat it multiple times by splicing a random amount of the byte in + let felt_bytes = felt.to_bytes_be(); + let offset = self.rng.gen_range(0..felt_bytes.len()); + let amount = self + .rng + .gen_range(1..=self.max_input_size - felt_bytes.len()); + + let val = felt_bytes[offset]; + let mut new_bytes = Vec::new(); + new_bytes.extend_from_slice(&felt_bytes[..offset]); + new_bytes.extend(std::iter::repeat(val).take(amount)); + new_bytes.extend_from_slice(&felt_bytes[offset..]); + + // Ensure the length is exactly 32 bytes + if new_bytes.len() > 32 { + new_bytes.truncate(32); + } else if new_bytes.len() < 32 { + new_bytes.resize(32, 0); + } + + let mut array = [0u8; 32]; + array.copy_from_slice(&new_bytes); + + Felt::from_bytes_be(&array) + } } From c199ae0828c192f1ecb1727bee8e742c1a4b9563 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Wed, 6 Nov 2024 23:55:11 +0100 Subject: [PATCH 16/17] Replace `invoke_dynamic` with `invoke_contract_dynamics` to allow starknet contracts execution & refactoring --- cairo-native-fuzzer/Cargo.toml | 2 + .../examples/fuzzinglabs.cairo | 56 ++++ cairo-native-fuzzer/examples/hello.cairo | 6 - cairo-native-fuzzer/src/fuzzer/fuzzer.rs | 40 +-- cairo-native-fuzzer/src/runner/mod.rs | 1 + cairo-native-fuzzer/src/runner/runner.rs | 28 +- .../src/runner/syscall_handler.rs | 277 ++++++++++++++++++ 7 files changed, 376 insertions(+), 34 deletions(-) create mode 100644 cairo-native-fuzzer/examples/fuzzinglabs.cairo delete mode 100644 cairo-native-fuzzer/examples/hello.cairo create mode 100644 cairo-native-fuzzer/src/runner/syscall_handler.rs diff --git a/cairo-native-fuzzer/Cargo.toml b/cairo-native-fuzzer/Cargo.toml index 3f99a09..bf54e3f 100644 --- a/cairo-native-fuzzer/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +cairo-lang-compiler = "2.8.4" cairo-lang-sierra = "2.8.4" +cairo-lang-starknet = "2.8.4" cairo-native = "0.2.0-alpha.4" clap = "4.5.20" rand = "0.8.5" diff --git a/cairo-native-fuzzer/examples/fuzzinglabs.cairo b/cairo-native-fuzzer/examples/fuzzinglabs.cairo new file mode 100644 index 0000000..1b496d5 --- /dev/null +++ b/cairo-native-fuzzer/examples/fuzzinglabs.cairo @@ -0,0 +1,56 @@ +use starknet::{ + Store, SyscallResult, StorageBaseAddress, storage_read_syscall, storage_write_syscall, + storage_address_from_base_and_offset +}; +use integer::{ + U128IntoFelt252, Felt252IntoU256, Felt252TryIntoU64, U256TryIntoFelt252, u256_from_felt252 +}; + + +#[starknet::contract] +mod test_contract { + #[storage] + struct Storage { + bal:u8 + } + #[external(v0)] + fn greet( +ref self: ContractState, + f: felt252, + u: felt252, + z: u16, + z2: u32, + i: u64, + n: u128, + g: u128, + l: u128, + a: felt252, + b: felt252, + s: u8, + ) { + if (f == 'f') { + if (u == 'u') { + if (z == 'z') { + if (z2 == 'z') { + if (i == 'i') { + if (n == 'n') { + if (g == 'g') { + if (l == 'l') { + if (a == 'a') { + if (b == 'b') { + if (s == 's') { + assert(1==0 , '!(f & t)'); + } + } + } + } + } + } + } + } + } + } + } + return (); + } +} \ No newline at end of file diff --git a/cairo-native-fuzzer/examples/hello.cairo b/cairo-native-fuzzer/examples/hello.cairo deleted file mode 100644 index 85256ca..0000000 --- a/cairo-native-fuzzer/examples/hello.cairo +++ /dev/null @@ -1,6 +0,0 @@ -use core::debug::PrintTrait; - -fn greet(name: felt252) { - 'Hello'.print(); - name.print(); -} \ No newline at end of file diff --git a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs index b967c50..1843e58 100644 --- a/cairo-native-fuzzer/src/fuzzer/fuzzer.rs +++ b/cairo-native-fuzzer/src/fuzzer/fuzzer.rs @@ -1,10 +1,10 @@ use std::path::PathBuf; use std::sync::Arc; +use cairo_lang_compiler::CompilerConfig; use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; -use cairo_native::utils::cairo_to_sierra; -use cairo_native::Value; +use cairo_lang_starknet::compile::compile_path; use starknet_types_core::felt::Felt; use crate::mutator::argument_type::map_argument_type; @@ -19,7 +19,7 @@ pub struct Fuzzer { entry_point: String, runner: CairoNativeRunner, sierra_program: Option>, - params: Vec, + params: Vec, entry_point_id: Option, mutator: Mutator, argument_types: Vec, @@ -55,9 +55,20 @@ impl Fuzzer { /// Compile the Cairo program to Sierra fn convert_and_store_cairo_to_sierra(&mut self) -> Result<(), String> { - if self.sierra_program.is_none() { - self.sierra_program = Some(cairo_to_sierra(&self.program_path)); - } + let contract = compile_path( + &self.program_path, + None, + CompilerConfig { + replace_ids: true, + ..Default::default() + }, + ) + .map_err(|e| format!("Failed to compile Cairo program: {}", e))?; + + let sierra_program = contract + .extract_sierra_program() + .map_err(|e| format!("Failed to extract Sierra program: {}", e))?; + self.sierra_program = Some(Arc::new(sierra_program)); Ok(()) } @@ -114,30 +125,23 @@ impl Fuzzer { .argument_types .iter() .map(|arg_type| match arg_type { - ArgumentType::Felt => Value::Felt252(Felt::from(0)), + ArgumentType::Felt => Felt::from(0), // TODO: Add support for other types }) .collect(); } /// Mutate a single function parameter - pub fn mutate_param(&mut self, value: Value) -> Value { - match value { - Value::Felt252(felt) => { - // Use the Mutator to mutate the felt value - let mutated_felt = self.mutator.mutate(felt); - Value::Felt252(mutated_felt) - } - // TODO: Add support for other types - _ => value, - } + pub fn mutate_param(&mut self, value: Felt) -> Felt { + // Use the Mutator to mutate the felt value + self.mutator.mutate(value) } /// Mutate the parameters using the Mutator fn mutate_params(&mut self) { // Iterate through the current params and mutate each one for i in 0..self.params.len() { - let mutated_value = self.mutate_param(self.params[i].clone()); + let mutated_value = self.mutate_param(self.params[i]); self.params[i] = mutated_value; } } diff --git a/cairo-native-fuzzer/src/runner/mod.rs b/cairo-native-fuzzer/src/runner/mod.rs index 748377a..919f9fb 100644 --- a/cairo-native-fuzzer/src/runner/mod.rs +++ b/cairo-native-fuzzer/src/runner/mod.rs @@ -1 +1,2 @@ pub mod runner; +pub mod syscall_handler; diff --git a/cairo-native-fuzzer/src/runner/runner.rs b/cairo-native-fuzzer/src/runner/runner.rs index 02a1581..94f9501 100644 --- a/cairo-native-fuzzer/src/runner/runner.rs +++ b/cairo-native-fuzzer/src/runner/runner.rs @@ -2,16 +2,12 @@ use std::sync::Arc; use cairo_lang_sierra::ids::FunctionId; use cairo_lang_sierra::program::Program; -use cairo_native::execution_result::ExecutionResult; +use cairo_native::execution_result::ContractExecutionResult; use cairo_native::module::NativeModule; -use cairo_native::{context::NativeContext, executor::JitNativeExecutor, Value}; +use cairo_native::{context::NativeContext, executor::JitNativeExecutor}; +use starknet_types_core::felt::Felt; -/// Cairo Runner that uses Cairo Native -pub struct CairoNativeRunner { - native_context: NativeContext, - sierra_program: Option>, - entry_point_id: Option, -} +use crate::runner::syscall_handler::SyscallHandler; /// Compile the sierra program into a MLIR module fn compile_sierra_program<'a>( @@ -28,6 +24,13 @@ fn create_executor<'a>(native_program: NativeModule<'a>) -> JitNativeExecutor<'a JitNativeExecutor::from_native_module(native_program, Default::default()) } +/// Cairo Runner that uses Cairo Native +pub struct CairoNativeRunner { + native_context: NativeContext, + sierra_program: Option>, + entry_point_id: Option, +} + impl CairoNativeRunner { pub fn new() -> Self { let native_context = NativeContext::new(); @@ -54,7 +57,7 @@ impl CairoNativeRunner { // Run the program using Cairo Native #[inline] - pub fn run_program(&mut self, params: &[Value]) -> Result { + pub fn run_program(&mut self, params: &[Felt]) -> Result { // Compile the Sierra program into a MLIR module let native_program = compile_sierra_program( &self.native_context, @@ -68,7 +71,12 @@ impl CairoNativeRunner { // Execute the program native_executor - .invoke_dynamic(self.entry_point_id.as_ref().unwrap(), params, None) + .invoke_contract_dynamic( + self.entry_point_id.as_ref().unwrap(), + params, + Some(u128::MAX), + SyscallHandler, + ) .map_err(|e| e.to_string()) } } diff --git a/cairo-native-fuzzer/src/runner/syscall_handler.rs b/cairo-native-fuzzer/src/runner/syscall_handler.rs new file mode 100644 index 0000000..2731130 --- /dev/null +++ b/cairo-native-fuzzer/src/runner/syscall_handler.rs @@ -0,0 +1,277 @@ +// Source : https://github.com/lambdaclass/cairo_native/blob/9791075918f17b1787084d85063180f6aa67a6ec/examples/erc20.rs + +use cairo_native::starknet::BlockInfo; +use cairo_native::starknet::ExecutionInfo; +use cairo_native::starknet::ExecutionInfoV2; +use cairo_native::starknet::ResourceBounds; +use cairo_native::starknet::Secp256k1Point; +use cairo_native::starknet::Secp256r1Point; +use cairo_native::starknet::StarknetSyscallHandler; +use cairo_native::starknet::SyscallResult; +use cairo_native::starknet::TxInfo; +use cairo_native::starknet::TxV2Info; +use cairo_native::starknet::U256; +use starknet_types_core::felt::Felt; + +#[derive(Debug)] +pub struct SyscallHandler; + +impl StarknetSyscallHandler for SyscallHandler { + fn get_block_hash(&mut self, block_number: u64, _gas: &mut u128) -> SyscallResult { + println!("Called `get_block_hash({block_number})` from MLIR."); + Ok(Felt::from_bytes_be_slice(b"get_block_hash ok")) + } + + fn get_execution_info( + &mut self, + _gas: &mut u128, + ) -> SyscallResult { + println!("Called `get_execution_info()` from MLIR."); + Ok(ExecutionInfo { + block_info: BlockInfo { + block_number: 1234, + block_timestamp: 2345, + sequencer_address: 3456.into(), + }, + tx_info: TxInfo { + version: 4567.into(), + account_contract_address: 5678.into(), + max_fee: 6789, + signature: vec![1248.into(), 2486.into()], + transaction_hash: 9876.into(), + chain_id: 8765.into(), + nonce: 7654.into(), + }, + caller_address: 6543.into(), + contract_address: 5432.into(), + entry_point_selector: 4321.into(), + }) + } + + fn get_execution_info_v2( + &mut self, + _remaining_gas: &mut u128, + ) -> SyscallResult { + println!("Called `get_execution_info_v2()` from MLIR."); + Ok(ExecutionInfoV2 { + block_info: BlockInfo { + block_number: 1234, + block_timestamp: 2345, + sequencer_address: 3456.into(), + }, + tx_info: TxV2Info { + version: 1.into(), + account_contract_address: 1.into(), + max_fee: 0, + signature: vec![1.into()], + transaction_hash: 1.into(), + chain_id: 1.into(), + nonce: 1.into(), + tip: 1, + paymaster_data: vec![1.into()], + nonce_data_availability_mode: 0, + fee_data_availability_mode: 0, + account_deployment_data: vec![1.into()], + resource_bounds: vec![ResourceBounds { + resource: 2.into(), + max_amount: 10, + max_price_per_unit: 20, + }], + }, + caller_address: 6543.into(), + contract_address: 5432.into(), + entry_point_selector: 4321.into(), + }) + } + + fn deploy( + &mut self, + class_hash: Felt, + contract_address_salt: Felt, + calldata: &[Felt], + deploy_from_zero: bool, + _gas: &mut u128, + ) -> SyscallResult<(Felt, Vec)> { + println!("Called `deploy({class_hash}, {contract_address_salt}, {calldata:?}, {deploy_from_zero})` from MLIR."); + Ok(( + class_hash + contract_address_salt, + calldata.iter().map(|x| x + Felt::ONE).collect(), + )) + } + + fn replace_class(&mut self, class_hash: Felt, _gas: &mut u128) -> SyscallResult<()> { + println!("Called `replace_class({class_hash})` from MLIR."); + Ok(()) + } + + fn library_call( + &mut self, + class_hash: Felt, + function_selector: Felt, + calldata: &[Felt], + _gas: &mut u128, + ) -> SyscallResult> { + println!( + "Called `library_call({class_hash}, {function_selector}, {calldata:?})` from MLIR." + ); + Ok(calldata.iter().map(|x| x * Felt::from(3)).collect()) + } + + fn call_contract( + &mut self, + address: Felt, + entry_point_selector: Felt, + calldata: &[Felt], + _gas: &mut u128, + ) -> SyscallResult> { + println!( + "Called `call_contract({address}, {entry_point_selector}, {calldata:?})` from MLIR." + ); + Ok(calldata.iter().map(|x| x * Felt::from(3)).collect()) + } + + fn storage_read( + &mut self, + address_domain: u32, + address: Felt, + _gas: &mut u128, + ) -> SyscallResult { + println!("Called `storage_read({address_domain}, {address})` from MLIR."); + Ok(address * Felt::from(3)) + } + + fn storage_write( + &mut self, + address_domain: u32, + address: Felt, + value: Felt, + _gas: &mut u128, + ) -> SyscallResult<()> { + println!("Called `storage_write({address_domain}, {address}, {value})` from MLIR."); + Ok(()) + } + + fn emit_event(&mut self, keys: &[Felt], data: &[Felt], _gas: &mut u128) -> SyscallResult<()> { + println!("Called `emit_event({keys:?}, {data:?})` from MLIR."); + Ok(()) + } + + fn send_message_to_l1( + &mut self, + to_address: Felt, + payload: &[Felt], + _gas: &mut u128, + ) -> SyscallResult<()> { + println!("Called `send_message_to_l1({to_address}, {payload:?})` from MLIR."); + Ok(()) + } + + fn keccak( + &mut self, + input: &[u64], + _gas: &mut u128, + ) -> SyscallResult { + println!("Called `keccak({input:?})` from MLIR."); + Ok(U256 { + hi: 0, + lo: 1234567890, + }) + } + + fn secp256k1_new( + &mut self, + _x: U256, + _y: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + unimplemented!() + } + + fn secp256k1_add( + &mut self, + _p0: Secp256k1Point, + _p1: Secp256k1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } + + fn secp256k1_mul( + &mut self, + _p: Secp256k1Point, + _m: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } + + fn secp256k1_get_point_from_x( + &mut self, + _x: U256, + _y_parity: bool, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + unimplemented!() + } + + fn secp256k1_get_xy( + &mut self, + _p: Secp256k1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + unimplemented!() + } + + fn secp256r1_new( + &mut self, + _x: U256, + _y: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + unimplemented!() + } + + fn secp256r1_add( + &mut self, + _p0: Secp256r1Point, + _p1: Secp256r1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } + + fn secp256r1_mul( + &mut self, + _p: Secp256r1Point, + _m: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } + + fn secp256r1_get_point_from_x( + &mut self, + _x: U256, + _y_parity: bool, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + unimplemented!() + } + + fn secp256r1_get_xy( + &mut self, + _p: Secp256r1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + unimplemented!() + } + + fn sha256_process_block( + &mut self, + _state: &mut [u32; 8], + _block: &[u32; 16], + _remaining_gas: &mut u128, + ) -> SyscallResult<()> { + unimplemented!() + } +} From aaa72c49ca11a40426fc9aa62de9e1ac0cf68657 Mon Sep 17 00:00:00 2001 From: Rog3rSm1th Date: Thu, 7 Nov 2024 15:37:13 +0100 Subject: [PATCH 17/17] Add `cairo-runtime-native` to dependencies --- cairo-native-fuzzer/Cargo.toml | 7 ++++++- cairo-native-fuzzer/README.md | 5 +---- cairo-native-fuzzer/examples/fuzzinglabs.cairo | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cairo-native-fuzzer/Cargo.toml b/cairo-native-fuzzer/Cargo.toml index bf54e3f..dce9317 100644 --- a/cairo-native-fuzzer/Cargo.toml +++ b/cairo-native-fuzzer/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" cairo-lang-compiler = "2.8.4" cairo-lang-sierra = "2.8.4" cairo-lang-starknet = "2.8.4" -cairo-native = "0.2.0-alpha.4" +cairo-native = { version = "0.2.0-alpha.4", features = ["with-runtime"] } +cairo-native-runtime = { version = "0.2.0-alpha.4", optional = true } clap = "4.5.20" rand = "0.8.5" starknet-types-core = "0.1.7" @@ -16,3 +17,7 @@ starknet-types-core = "0.1.7" git = 'https://github.com/FuzzingLabs/cairo-rs' rev = '48af153240392992f18a09e969bae6518eec9639' package = 'cairo-felt' + +[features] +default = ["with-runtime"] +with-runtime = ["dep:cairo-native-runtime"] \ No newline at end of file diff --git a/cairo-native-fuzzer/README.md b/cairo-native-fuzzer/README.md index 4d385f9..5f826bc 100644 --- a/cairo-native-fuzzer/README.md +++ b/cairo-native-fuzzer/README.md @@ -7,12 +7,9 @@ Cairo Native Fuzzer is a rewrite of the Cairo Fuzzer based on [Cairo native from ### Step 1 : Create a basic fuzzer based on Cairo Native : - [x] Implement the Cairo Native runner - [x] Implement the fuzzer based on Cairo Native runner -- [ ] Import existing Felt252 mutator from the cairo-fuzzer +- [x] Import existing mutator from the cairo-fuzzer ### Step 2 : Integrate existing cairo-fuzzer features into Cairo Native fuzzer : - [ ] Multithreading - [ ] Support config files - [ ] Property testing - -### Step 3 : Advanced features : -- [ ] Support `u8`, `u16`, `u32`, `u64`, `u128` and `u256` arguments \ No newline at end of file diff --git a/cairo-native-fuzzer/examples/fuzzinglabs.cairo b/cairo-native-fuzzer/examples/fuzzinglabs.cairo index 1b496d5..de8d46e 100644 --- a/cairo-native-fuzzer/examples/fuzzinglabs.cairo +++ b/cairo-native-fuzzer/examples/fuzzinglabs.cairo @@ -14,7 +14,7 @@ mod test_contract { bal:u8 } #[external(v0)] - fn greet( + fn Fuzz_symbolic_execution( ref self: ContractState, f: felt252, u: felt252,