From c73b728bf5704892e2934fa1b00d1f5c0b66968c Mon Sep 17 00:00:00 2001 From: Juan-M-V <102986292+Juan-M-V@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:17:22 -0300 Subject: [PATCH] Fuzz cairo compiled programs (#1236) * Create fuzzer * Remove cli * Update fuzzer * Change fuzzer name * Move functions to utils; Create new fuzzer * Remove file and update readme * Change filename * Fix filenames * delete extra changes * Move to fuzzing dir * update and refactor cairo compiled programs fuzzer * fmt * add cairo_programs folder * fix warnings * fix formatting * add felt arbitrary * add felt arbitrary * add info to readme * add fuzzer makefile command * fix corrections --------- Co-authored-by: Juanma Co-authored-by: dafifynn Co-authored-by: Pedro Fontana --- Makefile | 7 + fuzzer/Cargo.toml | 11 + fuzzer/README.md | 9 + fuzzer/cairo_programs/.gitkeep | 0 fuzzer/src/cairo_compiled_programs_fuzzer.rs | 532 +++++++++++++++++++ 5 files changed, 559 insertions(+) create mode 100644 fuzzer/cairo_programs/.gitkeep create mode 100644 fuzzer/src/cairo_compiled_programs_fuzzer.rs diff --git a/Makefile b/Makefile index 8a0f770e86..fc1cef6e1d 100644 --- a/Makefile +++ b/Makefile @@ -303,3 +303,10 @@ clean: rm -rf cairo-vm-env rm -rf cairo-vm-pypy-env rm -rf cairo + +fuzzer-deps: + cargo +nightly install cargo-fuzz + +run-cairo-compiled-fuzzer: + cd fuzzer + cargo +nightly fuzz run --fuzz-dir . cairo_compiled_programs_fuzzer diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml index f1f1c0bfc4..7bf52c4251 100644 --- a/fuzzer/Cargo.toml +++ b/fuzzer/Cargo.toml @@ -5,22 +5,29 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[package.metadata] +cargo-fuzz = true + [workspace] members = ["."] [dependencies] arbitrary = { version = "1.3.0", features = ["derive"] } honggfuzz = "0.5.55" +libfuzzer-sys = "0.4" bincode = { version = "2.0.0-rc.3", tag = "v2.0.0-rc.3", git = "https://github.com/bincode-org/bincode.git" } cairo-vm = { path = "../vm", features = ["arbitrary"] } mimalloc = { version = "0.1.29", default-features = false, optional = true } nom = "7" thiserror = { version = "1.0.32" } +cairo-felt = { path = "../felt", features = ["arbitrary"] } +proptest = "1.2.0" [dev-dependencies] assert_matches = "1.5.0" rstest = "0.17.0" + [features] default = ["with_mimalloc"] with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] @@ -29,6 +36,10 @@ with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] name = "fuzz_json" path = "src/fuzz_json.rs" +[[bin]] +name = "cairo_compiled_programs_fuzzer" +path = "src/cairo_compiled_programs_fuzzer.rs" + [[bin]] name = "fuzz_program" path = "src/fuzz_program.rs" diff --git a/fuzzer/README.md b/fuzzer/README.md index a2b1be9a40..577c2abf5b 100644 --- a/fuzzer/README.md +++ b/fuzzer/README.md @@ -1,3 +1,12 @@ ## fuzz_json This fuzzer creates a json file directly from bytes. `HFUZZ_RUN_ARGS="--dict=json.dict" cargo hfuzz run fuzz_json` + +## cairo_compiled_programs_fuzzer +To run this fuzzer you need to be able to run cairo-compile command from the fuzzer folder beforehand. + +To run the fuzzer you need to have installed `cargo-fuzz`. If not, use the command `cargo +nightly install cargo-fuzz` + +To run simply use `cargo +nightly fuzz run --fuzz-dir . cairo_compiled_programs_fuzzer` + +We use nightly for this fuzzer because cargo fuzz runs with the -Z flag, which only works with +nightly. diff --git a/fuzzer/cairo_programs/.gitkeep b/fuzzer/cairo_programs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/fuzzer/src/cairo_compiled_programs_fuzzer.rs b/fuzzer/src/cairo_compiled_programs_fuzzer.rs new file mode 100644 index 0000000000..1b518cbfa9 --- /dev/null +++ b/fuzzer/src/cairo_compiled_programs_fuzzer.rs @@ -0,0 +1,532 @@ +#![no_main] +use cairo_felt::Felt252; +use cairo_vm::cairo_run::{self, CairoRunConfig}; +use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; +use libfuzzer_sys::{ + arbitrary::{Arbitrary, Unstructured}, + fuzz_target, +}; +use proptest::prelude::*; +use std::fs; +use std::process::Command; +use std::sync::atomic::{AtomicUsize, Ordering}; + +// Global counter for fuzz iteration +static FUZZ_ITERATION_COUNT: AtomicUsize = AtomicUsize::new(0); + +fuzz_target!(|data: (&[u8], Felt252, Felt252, Felt252, Felt252, u128)| { + // Define fuzzer iteration with id purposes + let _iteration_count = FUZZ_ITERATION_COUNT.fetch_add(1, Ordering::SeqCst); + + // Define default configuration + let cairo_run_config = CairoRunConfig::default(); + let mut hint_executor = BuiltinHintProcessor::new_empty(); + + let mut array = Vec::new(); + let mut unstructured = Unstructured::new(data.0); + + for _x in 0..(data.5 as u8) { + array.push(Felt252::arbitrary(&mut unstructured).unwrap()) + } + + // Create and run the programs + program_array_sum(&array, &cairo_run_config, &mut hint_executor); + program_unsafe_keccak(&array, &cairo_run_config, &mut hint_executor); + program_bitwise(&data.1, &data.2, &cairo_run_config, &mut hint_executor); + program_poseidon( + &data.1, + &data.2, + &data.3, + &cairo_run_config, + &mut hint_executor, + ); + program_range_check( + &data.1, + &data.2, + &data.3, + &cairo_run_config, + &mut hint_executor, + ); + program_ec_op(data.5, &cairo_run_config, &mut hint_executor); + program_pedersen(&data.1, &data.2, &cairo_run_config, &mut hint_executor); + program_ecdsa( + &data.1, + &data.2, + &data.3, + &data.4, + &cairo_run_config, + &mut hint_executor, + ); +}); + +fn program_array_sum( + array: &Vec, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let populated_array = array + .iter() + .enumerate() + .map(|(index, num)| format!("assert [ptr + {}] = {}; \n", index, num)) + .collect::>() + .join(" ") + .repeat(array.len()); + + let file_content = format!( + " + %builtins output + + from starkware.cairo.common.alloc import alloc + from starkware.cairo.common.serialize import serialize_word + + // Computes the sum of the memory elements at addresses: + // arr + 0, arr + 1, ..., arr + (size - 1). + func array_sum(arr: felt*, size) -> (sum: felt) {{ + if (size == 0) {{ + return (sum=0); + }} + + // size is not zero. + let (sum_of_rest) = array_sum(arr=arr + 1, size=size - 1); + return (sum=[arr] + sum_of_rest); + }} + + func main{{output_ptr: felt*}}() {{ + const ARRAY_SIZE = {}; + + // Allocate an array. + let (ptr) = alloc(); + + // Populate some values in the array. + {populated_array} + + // Call array_sum to compute the sum of the elements. + let (sum) = array_sum(arr=ptr, size=ARRAY_SIZE); + + // Write the sum to the program output. + serialize_word(sum); + + return (); + }} + ", + array.len() + ); + + // Create programs names and program + let cairo_path_array_sum = format!("cairo_programs/array_sum_{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_array_sum = format!("cairo_programs/array_sum_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_array_sum, file_content.as_bytes()); + + compile_program(&cairo_path_array_sum, &json_path_array_sum); + + let program_content_array_sum = std::fs::read(&json_path_array_sum).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_array_sum, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_array_sum, &json_path_array_sum); +} + +fn program_unsafe_keccak( + array: &Vec, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let populated_array = array + .iter() + .enumerate() + .map(|(index, num)| format!("assert data[{}] = {}; \n", index, num)) + .collect::>() + .join(" "); + + let file_content = format!( + " + %builtins output + + from starkware.cairo.common.alloc import alloc + from starkware.cairo.common.serialize import serialize_word + from starkware.cairo.common.keccak import unsafe_keccak + + func main{{output_ptr: felt*}}() {{ + alloc_locals; + + let (data: felt*) = alloc(); + + {populated_array} + + let (low: felt, high: felt) = unsafe_keccak(data, {}); + + serialize_word(low); + serialize_word(high); + + return (); + }} + ", + array.len() + ); + + // Create programs names and program + let cairo_path_unsafe_keccak = format!( + "cairo_programs/unsafe_keccak_{:?}.cairo", + FUZZ_ITERATION_COUNT + ); + let json_path_unsafe_keccak = format!( + "cairo_programs/unsafe_keccak_{:?}.json", + FUZZ_ITERATION_COUNT + ); + let _ = fs::write(&cairo_path_unsafe_keccak, file_content.as_bytes()); + + compile_program(&cairo_path_unsafe_keccak, &json_path_unsafe_keccak); + + let program_content_unsafe_keccak = std::fs::read(&json_path_unsafe_keccak).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run( + &program_content_unsafe_keccak, + cairo_run_config, + hint_executor, + ); + + // Remove files to save memory + delete_files(&cairo_path_unsafe_keccak, &json_path_unsafe_keccak); +} + +fn program_bitwise( + num1: &Felt252, + num2: &Felt252, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let and = num1 & num2; + let xor = num1 ^ num2; + let or = num1 | num2; + let file_content = format!(" + %builtins bitwise + from starkware.cairo.common.bitwise import bitwise_and, bitwise_xor, bitwise_or, bitwise_operations + from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + + func main{{bitwise_ptr: BitwiseBuiltin*}}() {{ + let (and_a) = bitwise_and({num1}, {num2}); + assert and_a = {and}; + let (xor_a) = bitwise_xor(num1, num2); + assert xor_a = {xor}; + let (or_a) = bitwise_or(num1, num2); + assert or_a = {or}; + + let (and_b, xor_b, or_b) = bitwise_operations({num1}, {num2}); + assert and_b = {and}; + assert xor_b = {xor}; + assert or_b = {or}; + return (); + }} + + "); + + // Create programs names and program + let cairo_path_bitwise = format!("cairo_programs/bitwise-{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_bitwise = format!("cairo_programs/bitwise-{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_bitwise, file_content.as_bytes()); + + compile_program(&cairo_path_bitwise, &json_path_bitwise); + + let program_content_bitwise = std::fs::read(&json_path_bitwise).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_bitwise, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_bitwise, &json_path_bitwise); +} + +fn program_poseidon( + num1: &Felt252, + num2: &Felt252, + num3: &Felt252, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let file_content = format!( + " + %builtins poseidon + from starkware.cairo.common.cairo_builtins import PoseidonBuiltin + from starkware.cairo.common.poseidon_state import PoseidonBuiltinState + from starkware.cairo.common.builtin_poseidon.poseidon import ( + poseidon_hash, + poseidon_hash_single, + poseidon_hash_many, + ) + from starkware.cairo.common.alloc import alloc + + func main{{poseidon_ptr: PoseidonBuiltin*}}() {{ + // Hash one + let (x) = poseidon_hash_single( + {num3} + ); + // Hash two + let (y) = poseidon_hash({num1}, {num2}); + // Hash three + let felts: felt* = alloc(); + assert felts[0] = {num1}; + assert felts[1] = {num2}; + assert felts[2] = {num3}; + let (z) = poseidon_hash_many(3, felts); + return (); + }} + + " + ); + + // Create programs names and program + let cairo_path_poseidon = format!("cairo_programs/poseidon_{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_poseidon = format!("cairo_programs/poseidon_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_poseidon, file_content.as_bytes()); + + compile_program(&cairo_path_poseidon, &json_path_poseidon); + + let program_content_poseidon = std::fs::read(&json_path_poseidon).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_poseidon, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_poseidon, &json_path_poseidon); +} + +fn program_range_check( + num1: &Felt252, + num2: &Felt252, + num3: &Felt252, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let file_content = format!( + " + %builtins range_check + + from starkware.cairo.common.math import assert_250_bit + from starkware.cairo.common.alloc import alloc + + func assert_250_bit_element_array{{range_check_ptr: felt}}( + array: felt*, array_length: felt, iterator: felt + ) {{ + if (iterator == array_length) {{ + return (); + }} + assert_250_bit(array[iterator]); + return assert_250_bit_element_array(array, array_length, iterator + 1); + }} + + func fill_array(array: felt*, base: felt, step: felt, array_length: felt, iterator: felt) {{ + if (iterator == array_length) {{ + return (); + }} + assert array[iterator] = base + step * iterator; + return fill_array(array, base, step, array_length, iterator + 1); + }} + + func main{{range_check_ptr: felt}}() {{ + alloc_locals; + tempvar array_length = {num1}; + let (array: felt*) = alloc(); + fill_array(array, {num2}, {num3}, array_length, 0); + assert_250_bit_element_array(array, array_length, 0); + return (); + }} + + " + ); + + // Create programs names and program + let cairo_path_range_check = format!( + "cairo_programs/range_check_{:?}.cairo", + FUZZ_ITERATION_COUNT + ); + let json_path_range_check = + format!("cairo_programs/range_check_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_range_check, file_content.as_bytes()); + + compile_program(&cairo_path_range_check, &json_path_range_check); + + let program_content_range_check = std::fs::read(&json_path_range_check).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run( + &program_content_range_check, + cairo_run_config, + hint_executor, + ); + + // Remove files to save memory + delete_files(&cairo_path_range_check, &json_path_range_check); +} + +fn program_ec_op( + num1: u128, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let file_content = format!( + " + %builtins ec_op + + from starkware.cairo.common.cairo_builtins import EcOpBuiltin + from starkware.cairo.common.ec_point import EcPoint + from starkware.cairo.common.ec import recover_y + + func main{{ec_op_ptr: EcOpBuiltin*}}() {{ + let x = {:#02x}; + let r: EcPoint = recover_y(x); + assert r.x = {:#02x}; + return (); + }} + + ", + num1, num1 + ); + + // Create programs names and program + let cairo_path_ec_op = format!("cairo_programs/ec_op_{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_ec_op = format!("cairo_programs/ec_op_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_ec_op, file_content.as_bytes()); + + compile_program(&cairo_path_ec_op, &json_path_ec_op); + + let program_content_ec_op = std::fs::read(&json_path_ec_op).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_ec_op, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_ec_op, &json_path_ec_op); +} + +fn program_pedersen( + num1: &Felt252, + num2: &Felt252, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let file_content = format!( + " + %builtins pedersen + + from starkware.cairo.common.cairo_builtins import HashBuiltin + from starkware.cairo.common.hash import hash2 + + func get_hash(hash_ptr: HashBuiltin*, num_a: felt, num_b: felt) -> ( + hash_ptr: HashBuiltin*, r: felt + ) {{ + with hash_ptr {{ + let (result) = hash2(num_a, num_b); + }} + return (hash_ptr=hash_ptr, r=result); + }} + + func builtins_wrapper{{ + pedersen_ptr: HashBuiltin*, + }}(num_a: felt, num_b: felt) {{ + let (pedersen_ptr, result: felt) = get_hash(pedersen_ptr, num_a, num_b); + + return (); + }} + + func builtins_wrapper_iter{{ + pedersen_ptr: HashBuiltin*, + }}(num_a: felt, num_b: felt, n_iterations: felt) {{ + builtins_wrapper(num_a, num_b); + if (n_iterations != 0) {{ + builtins_wrapper_iter(num_a, num_b, n_iterations - 1); + tempvar pedersen_ptr = pedersen_ptr; + }} else {{ + tempvar pedersen_ptr = pedersen_ptr; + }} + + return (); + }} + + func main{{ + pedersen_ptr: HashBuiltin*, + }}() {{ + let num_a = {num1}; + let num_b = {num2}; + builtins_wrapper_iter(num_a, num_b, 50000); + + return (); + }} + " + ); + + // Create programs names and program + let cairo_path_pedersen = format!("cairo_programs/pedersen_{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_pedersen = format!("cairo_programs/pedersen_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_pedersen, file_content.as_bytes()); + + compile_program(&cairo_path_pedersen, &json_path_pedersen); + + let program_content_pedersen = std::fs::read(&json_path_pedersen).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_pedersen, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_pedersen, &json_path_pedersen); +} + +fn program_ecdsa( + num1: &Felt252, + num2: &Felt252, + num3: &Felt252, + num4: &Felt252, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut BuiltinHintProcessor, +) { + let file_content = format!( + " + %builtins ecdsa + from starkware.cairo.common.serialize import serialize_word + from starkware.cairo.common.cairo_builtins import SignatureBuiltin + from starkware.cairo.common.signature import verify_ecdsa_signature + + func main{{ecdsa_ptr: SignatureBuiltin*}}() {{ + verify_ecdsa_signature( + {num4}, + {num1}, + {num2}, + {num3}, + ); + return (); + }} + + " + ); + + // Create programs names and program + let cairo_path_ecdsa = format!("cairo_programs/ecdsa_{:?}.cairo", FUZZ_ITERATION_COUNT); + let json_path_ecdsa = format!("cairo_programs/ecdsa_{:?}.json", FUZZ_ITERATION_COUNT); + let _ = fs::write(&cairo_path_ecdsa, file_content.as_bytes()); + + compile_program(&cairo_path_ecdsa, &json_path_ecdsa); + + let program_content_ecdsa = std::fs::read(&json_path_ecdsa).unwrap(); + + // Run the program with default configurations + let _ = cairo_run::cairo_run(&program_content_ecdsa, cairo_run_config, hint_executor); + + // Remove files to save memory + delete_files(&cairo_path_ecdsa, &json_path_ecdsa); +} + +fn compile_program(cairo_path: &str, json_path: &str) { + let _output = Command::new("cairo-compile") + .arg(cairo_path) + .arg("--output") + .arg(json_path) + .output() + .expect("failed to execute process"); +} + +fn delete_files(cairo_path: &str, json_path: &str) { + fs::remove_file(cairo_path).expect("failed to remove file"); + fs::remove_file(json_path).expect("failed to remove file"); +}