From 83fa85d6ba2d57999149f5e5986c40aee5d1b0e6 Mon Sep 17 00:00:00 2001 From: Juan-M-V <102986292+Juan-M-V@users.noreply.github.com> Date: Fri, 30 Jun 2023 15:19:06 -0300 Subject: [PATCH] Cairo fuzzer (#1227) * Create fuzzer * Remove cli * Update fuzzer * Change fuzzer name * Update Cargo.toml * Update Cargo.toml * Add fuzzer workflow (#1240) * add workflow to run fuzzers * delete extra changes * add pulling inputs folder from outside repo * fix paths * change test workflow to use cache and store the report * actualice workflow * Update fuzzer.yml * delete extra files * Delete Cargo.toml * Delete main.rs --------- Co-authored-by: dafifynn * Update Cargo.toml --------- Co-authored-by: Juanma Co-authored-by: daphneherlambda <94461321+daphneherlambda@users.noreply.github.com> Co-authored-by: dafifynn --- .github/workflows/fuzzer.yml | 57 ++++++++++++ fuzzer/Cargo.toml | 30 +++++++ fuzzer/README.md | 3 + fuzzer/json.dict | 61 +++++++++++++ fuzzer/src/fuzz_json.rs | 165 +++++++++++++++++++++++++++++++++++ 5 files changed, 316 insertions(+) create mode 100644 .github/workflows/fuzzer.yml create mode 100644 fuzzer/Cargo.toml create mode 100644 fuzzer/README.md create mode 100644 fuzzer/json.dict create mode 100644 fuzzer/src/fuzz_json.rs diff --git a/.github/workflows/fuzzer.yml b/.github/workflows/fuzzer.yml new file mode 100644 index 0000000000..ed4a26d6dc --- /dev/null +++ b/.github/workflows/fuzzer.yml @@ -0,0 +1,57 @@ +name: "Fuzzer Workflow" +on: + schedule: + # At the end of every day + - cron: "0 0 * * *" +jobs: + changelog: + runs-on: ubuntu-latest + steps: + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + + + - name: Cache Inputs + id: cache-inputs + uses: actions/cache@v3 + with: + # Path where the inputs for the fuzzer are stored + path: fuzzer/hfuzz_workspace/fuzz_json/input + key: ${{ runner.os }}-inputs + + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential + sudo apt-get install binutils-dev + sudo apt-get install libunwind-dev + sudo curl https://sh.rustup.rs -sSf | bash -s -- -y --default-toolchain nightly + + - name: Set Environment Variable + run: echo "PATH="/root/.cargo/bin:${PATH}"" >> $GITHUB_ENV + + - name: Install Honggfuzz + run: cargo install honggfuzz + + - if: ${{ steps.cache-inputs.outputs.cache-hit != 'true' }} + # If didnĀ“t have any inputs starts from 0 + name: Initializing fuzzer from 0 + run: | + cd fuzzer + HFUZZ_RUN_ARGS="--dict=json.dict --run_time 10800 --timeout 60 -T" cargo hfuzz run fuzz_json + + # If has cached inputs starts with them and run with minimize + - if: ${{ steps.cache-inputs.outputs.cache-hit != 'false' }} + name: Initializing fuzzer with previous inputs + run: | + cd fuzzer + HFUZZ_RUN_ARGS="--dict=json.dict --run_time 10800 --minimize --timeout 60 -T" cargo hfuzz run fuzz_json + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: changing report + file_pattern: '*/hfuzz_workspace/fuzzer/fuzz_json/HONGGFUZZ* */hfuzz_workspace/fuzzer/fuzz_json/SIG*' diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml new file mode 100644 index 0000000000..40fe1e0806 --- /dev/null +++ b/fuzzer/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "fuzzer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +arbitrary = { version = "1.3.0", features = ["derive"] } +honggfuzz = "0.5.55" +bincode = { version = "2.0.0-rc.2", tag = "v2.0.0-rc.2", git = "https://github.com/bincode-org/bincode.git" } +cairo-vm = { path = "../vm" } +mimalloc = { version = "0.1.29", default-features = false, optional = true } +nom = "7" +thiserror = { version = "1.0.32" } + +[dev-dependencies] +assert_matches = "1.5.0" +rstest = "0.17.0" + +[workspace] +members = ["."] + +[features] +default = ["with_mimalloc"] +with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] + +[[bin]] +name = "fuzz_json" +path = "src/fuzz_json.rs" diff --git a/fuzzer/README.md b/fuzzer/README.md new file mode 100644 index 0000000000..a2b1be9a40 --- /dev/null +++ b/fuzzer/README.md @@ -0,0 +1,3 @@ +## fuzz_json +This fuzzer creates a json file directly from bytes. +`HFUZZ_RUN_ARGS="--dict=json.dict" cargo hfuzz run fuzz_json` diff --git a/fuzzer/json.dict b/fuzzer/json.dict new file mode 100644 index 0000000000..d2e5252576 --- /dev/null +++ b/fuzzer/json.dict @@ -0,0 +1,61 @@ +# +# AFL dictionary for JSON +# ----------------------- +# +# Just the very basics. +# +# Inspired by a dictionary by Jakub Wilk +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" + +"$ref" +"type" +"coordinates" +"@context" +"@id" + +"," +":" \ No newline at end of file diff --git a/fuzzer/src/fuzz_json.rs b/fuzzer/src/fuzz_json.rs new file mode 100644 index 0000000000..00366cd5d1 --- /dev/null +++ b/fuzzer/src/fuzz_json.rs @@ -0,0 +1,165 @@ +use arbitrary::Arbitrary; +use bincode::enc::write::Writer; +use cairo_vm::cairo_run::{self, EncodeTraceError}; +use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; +use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; +use cairo_vm::vm::errors::trace_errors::TraceError; +use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +use honggfuzz::fuzz; +use std::fmt; +use std::io::{self, Write}; +use std::path::PathBuf; +use thiserror::Error; + +#[cfg(feature = "with_mimalloc")] +use mimalloc::MiMalloc; + +#[cfg(feature = "with_mimalloc")] +#[global_allocator] +static ALLOC: MiMalloc = MiMalloc; + +#[derive(Debug, Arbitrary)] +struct Args { + program_content: Vec, + trace_file: Option, + print_output: bool, + entrypoint: String, + memory_file: Option, + layout: Layout, + proof_mode: bool, + secure_run: Option, +} + +#[derive(Debug, Arbitrary)] +enum Layout { + Plain, + Small, + Dex, + Starknet, + StarknetWithKeccak, + RecursiveLargeOutput, + AllCairo, + AllSolidity, + Dynamic, +} + +impl fmt::Display for Layout { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Layout::Plain => write!(f, "plain"), + Layout::Small => write!(f, "small"), + Layout::Dex => write!(f, "dex"), + Layout::Starknet => write!(f, "starknet"), + Layout::StarknetWithKeccak => write!(f, "starknet_with_keccak"), + Layout::RecursiveLargeOutput => write!(f, "recursive_large_output"), + Layout::AllCairo => write!(f, "all_cairo"), + Layout::AllSolidity => write!(f, "all_solidity"), + Layout::Dynamic => write!(f, "dynamic"), + } + } +} + +#[derive(Debug, Error)] +enum Error { + #[error("Failed to interact with the file system")] + IO(#[from] std::io::Error), + #[error("The cairo program execution failed")] + Runner(#[from] CairoRunError), + #[error(transparent)] + EncodeTrace(#[from] EncodeTraceError), + #[error(transparent)] + VirtualMachine(#[from] VirtualMachineError), + #[error(transparent)] + Trace(#[from] TraceError), +} + +struct FileWriter { + buf_writer: io::BufWriter, + bytes_written: usize, +} + +impl Writer for FileWriter { + fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> { + self.buf_writer + .write_all(bytes) + .map_err(|e| bincode::error::EncodeError::Io { + inner: e, + index: self.bytes_written, + })?; + + self.bytes_written += bytes.len(); + + Ok(()) + } +} + +impl FileWriter { + fn new(buf_writer: io::BufWriter) -> Self { + Self { + buf_writer, + bytes_written: 0, + } + } + + fn flush(&mut self) -> io::Result<()> { + self.buf_writer.flush() + } +} + +fn run(args: Args) -> Result<(), Error> { + let trace_enabled = args.trace_file.is_some(); + let mut hint_executor = BuiltinHintProcessor::new_empty(); + let cairo_run_config = cairo_run::CairoRunConfig { + entrypoint: &args.entrypoint, + trace_enabled, + relocate_mem: args.memory_file.is_some(), + layout: &args.layout.to_string(), + proof_mode: args.proof_mode, + secure_run: args.secure_run, + }; + + let (cairo_runner, mut vm) = + match cairo_run::cairo_run(&args.program_content, &cairo_run_config, &mut hint_executor) { + Ok(runner) => runner, + Err(error) => { + eprintln!("{error}"); + return Err(Error::Runner(error)); + } + }; + + if args.print_output { + let mut output_buffer = "Program Output:\n".to_string(); + vm.write_output(&mut output_buffer)?; + print!("{output_buffer}"); + } + + if let Some(trace_path) = args.trace_file { + let relocated_trace = vm.get_relocated_trace()?; + + let trace_file = std::fs::File::create(trace_path)?; + let mut trace_writer = + FileWriter::new(io::BufWriter::with_capacity(3 * 1024 * 1024, trace_file)); + + cairo_run::write_encoded_trace(relocated_trace, &mut trace_writer)?; + trace_writer.flush()?; + } + + if let Some(memory_path) = args.memory_file { + let memory_file = std::fs::File::create(memory_path)?; + let mut memory_writer = + FileWriter::new(io::BufWriter::with_capacity(5 * 1024 * 1024, memory_file)); + + cairo_run::write_encoded_memory(&cairo_runner.relocated_memory, &mut memory_writer)?; + memory_writer.flush()?; + } + + Ok(()) +} + +fn main() { + loop { + fuzz!(|args: Args| { + let _ = run(args); + }); + } +}