From 943fadbc362fb4e316761570552f0d0a85eb4f7d Mon Sep 17 00:00:00 2001 From: "Corteggiani, Nassim" Date: Mon, 1 Jul 2024 22:15:59 +0200 Subject: [PATCH] processorfuzz init commit --- fuzzers/cva6-vcs-processorfuzz/Cargo.toml | 39 ++ fuzzers/cva6-vcs-processorfuzz/README.md | 95 +++++ fuzzers/cva6-vcs-processorfuzz/build.rs | 160 +++++++ fuzzers/cva6-vcs-processorfuzz/config.yml | 11 + fuzzers/cva6-vcs-processorfuzz/cva6 | 1 + fuzzers/cva6-vcs-processorfuzz/cva6.patch | 294 +++++++++++++ fuzzers/cva6-vcs-processorfuzz/plot.py | 104 +++++ .../src/differential.rs | 290 +++++++++++++ .../src/differential_feedback.rs | 138 ++++++ fuzzers/cva6-vcs-processorfuzz/src/main.rs | 284 +++++++++++++ fuzzers/cva6-vcs-processorfuzz/src/simv.rs | 392 ++++++++++++++++++ fuzzers/cva6-vcs-processorfuzz/src/template.c | 29 ++ fuzzers/cva6-vcs-processorfuzz/src/testcase.S | 162 ++++++++ .../cva6-vcs-processorfuzz/src/testcase.ld | 66 +++ .../src/differential_feedback.rs | 32 +- libpresifuzz_observers/src/trace_observer.rs | 212 +++++++++- 16 files changed, 2289 insertions(+), 20 deletions(-) create mode 100644 fuzzers/cva6-vcs-processorfuzz/Cargo.toml create mode 100644 fuzzers/cva6-vcs-processorfuzz/README.md create mode 100644 fuzzers/cva6-vcs-processorfuzz/build.rs create mode 100644 fuzzers/cva6-vcs-processorfuzz/config.yml create mode 160000 fuzzers/cva6-vcs-processorfuzz/cva6 create mode 100644 fuzzers/cva6-vcs-processorfuzz/cva6.patch create mode 100644 fuzzers/cva6-vcs-processorfuzz/plot.py create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/differential.rs create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/differential_feedback.rs create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/main.rs create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/simv.rs create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/template.c create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/testcase.S create mode 100644 fuzzers/cva6-vcs-processorfuzz/src/testcase.ld diff --git a/fuzzers/cva6-vcs-processorfuzz/Cargo.toml b/fuzzers/cva6-vcs-processorfuzz/Cargo.toml new file mode 100644 index 0000000..ebdd95a --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cva6_vcs_processorfuzz" +version = "0.0.1" +edition = "2021" +authors = ["Nassim Corteggiani "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = false + +[profile.dev] +opt-level = 0 +strip = "debuginfo" +lto = false +debug = true +panic = "unwind" + +[dependencies] +libpresifuzz_riscv = { path = "../../libpresifuzz_riscv"} +libpresifuzz_ec = { path = "../../libpresifuzz_ec", features=["debug"]} +libpresifuzz_mutators = {path="../../libpresifuzz_mutators"} +libpresifuzz_observers = { path = "../../libpresifuzz_observers"} +libpresifuzz_feedbacks = { path = "../../libpresifuzz_feedbacks"} +libpresifuzz_stages = { path = "../../libpresifuzz_stages"} +libafl = { version = "0.11.2"} +libafl_bolts = { version = "0.11.2"} +yaml-rust = "0.4.5" +rand = "0.8.5" +serde_yaml = "0.9.27" +tempdir = "0.3.7" +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +clap = { version = "3.2", features = ["default"] } +fs_extra = "1.2.0" +wait-timeout = "0.1.5" diff --git a/fuzzers/cva6-vcs-processorfuzz/README.md b/fuzzers/cva6-vcs-processorfuzz/README.md new file mode 100644 index 0000000..ed5c62d --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/README.md @@ -0,0 +1,95 @@ +# Fuzzer Overview + +This documentation outlines the process of setting up and using an RTL fuzzer for CVA6 architecture using libAFL and PreSiFuzz. +This setup demonstrates feedback-guided fuzzing using hardware code coverage reported by commercial simulator. + +# Prerequisites + +The following tools need to be installed on your own. + +* Latest version of [Spike](https://github.com/riscv-software-src/riscv-isa-sim), the RISC-V ISA simulator, installed. +Follow the installation instructions from the Spike GitHub repository. + +* Synopsys VCS (Verilog Compiler Simulator) installed and properly initialized. VCS is a widely used Verilog simulator. + Ensure it is configured and ready for simulation tasks. + +* [RISC-V GNU Compiler Toolchain] (https://github.com/riscv-collab/riscv-gnu-toolchain) + +With everything properly installed, please make sure to set the right environment variables in your `bashrc` or equivalent file. The fuzzer expects the following configuration in bash: + +Please, find below an example of configuratio +```bash +export RISCV=$HOME/riscv +export VERDI_HOME=/usr/synopsys/verdi/U-2023.03-SP2 +export VCS_HOME=/usr/synopsys/vcs/U-2023.03-SP2 +export SNPSLMD_LICENSE_FILE=server@server.fr +export LD_LIBRARY_PATH=$VERDI_HOME/share/NPI/lib/linux64 +``` + +## Building + +The build.rs script performs the following tasks: + +* Initially, it downloads the `cva6` mainstream, initializes submodules, and applies the `cva6.patch` patch. +* Next, it downloads the source code for `libfesvr` and builds it. +* Finally, it compiles the `./src/testcase.S` file and builds the simv self-contained simulator. Generated files are then copied into the `build` folder. + +To complete the steps above, simply run: +```sh +$ cargo build +``` +## Troubleshooting + +It may happened that some environement variables are not properly define and the build script may fail. +Please, check `./cva6/verif/simv/setup-env.sh` and make sure that settings are valid. + +## Running the fuzzer + +When starting, the fuzzer creates a work directory where it saves intermediates files such as mutants, and symbolic links to the `simv` and its dependencies in `./build`. +Work directory are saved into `TMPDIR` with a unique directory per fuzzer instance. Naming follows `presifuzz_`. +Synchronization information are saved into the `sync` directory, it includes `testcase` and associated `coverage map`. + +``` +$ cp ../../target/debug/cva6_vcs_fuzzer . +$ mkdir sync +``` + +To run a single fuzzer instance: +``` +$ AFL_LAUNCHER_CLIENT=1 ./cva6_vcs_fuzzer +``` + +To run multiple fuzzer instances: +``` +for i in {1..10}; do AFL_LAUNCHER_CLIENT=$i ./cva6_vcs_fuzzer & done +``` + +# Customizing + +The fuzzer is bootstraped using the seed files into the `seeds` folder. Feel free to customize the content of this file with any interesting seed. +When starting the fuzzer loads the initial inputs (i.e., the seeds), and only keep interesting ones in the corpus (i.e., coverage novelty). +Coverage novelty consider any changes for all supported code coverage metrics on vcs, i.e., branch, conditional, line, toggle, and FSM. + +Then, starts the fuzzer loop that iteratively calls the different stages. +StdMutationalStage is responsible for generating new mutant by applying mutation to the existing testcase in the corpus. +The mutations work at the ISA level by first deserializing the binary testcase into stream of instruction, then different mutations might be applied (e.g., adding instruction, removing instruction, changing opcode, ..). +The mutation can easily be customized by changing `../../libpresifuzz_mutators/src/riscv_isa.rs`. + +The generated testcase is then inserted into a template ELF file by simplify injecting the code after the `payload` label. +This template contains epilogue and prologue code. +The current version is very simple. We first init registers to some known values, and we change the `mtvec` to points to our own trap handler. +The trap handler is there to stop earlier the testcase execution if we trop too often. Otherwise, it always try to return to the instruction after the failing one. +This version is a naive implementation, better performance could be achieved with some changes on the testharness (e.g., early simulation stop, irq support). + + +# Ploting data + +The fuzzer saves statistics into the `sync`directory. +It is possible to plot coverage over time using the `plot.py`: + +```python +python3 ./plot.py -m branch -d ./sync +``` + +The `-m` option is there to provide the coverage metric that is either tgl, cond, branch, line, fsm. +The `-d` points to the directory where stats are saved. diff --git a/fuzzers/cva6-vcs-processorfuzz/build.rs b/fuzzers/cva6-vcs-processorfuzz/build.rs new file mode 100644 index 0000000..3b53b77 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/build.rs @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +use std::path::{Path}; +use std::env; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::fs; + +fn main() { + println!("cargo:warning=MESSAGE"); + + // if Path::new("./build").is_dir() { + // assert!(fs::remove_dir_all("./build").is_ok()); + // } + // assert!(fs::create_dir("./build").is_ok()); + + let rtl_dir = PathBuf::from("cva6"); + if !rtl_dir.exists() { + + println!("INFO: Cloning cva6 repository.."); + + assert!(Command::new("git") + .arg("clone") + .arg("https://github.com/openhwgroup/cva6.git") + .arg(rtl_dir.as_os_str()) + .status() + .unwrap() + .success()); + + let popd = env::current_dir().unwrap(); + + let cva6_dir = PathBuf::from("./cva6".to_string()); + let cva6_dir = cva6_dir.as_os_str().to_str().unwrap().to_string(); + std::env::set_current_dir(&cva6_dir).expect("Unable to change into cva6 directory"); + + println!("INFO: cheking out good commit"); + + assert!(Command::new("git") + .arg("checkout") + .arg("b401ab3868d053a00779add51ea37cf3b8c98b21") + .status() + .unwrap() + .success()); + + println!("INFO: updating submodules"); + + assert!(Command::new("git") + .arg("submodule") + .arg("update") + .arg("--init") + .arg("--recursive") + .status() + .unwrap() + .success()); + + assert!(Command::new("git") + .arg("apply") + .arg("../cva6.patch") + .status() + .unwrap() + .success()); + + let popd = popd.as_os_str().to_str().unwrap().to_string(); + std::env::set_current_dir(&popd).expect("Unable to change into cva6-vcs-fuzzer directory"); + } + + + let binding = env::current_dir().unwrap(); + let cur_dir = binding.to_str().unwrap(); + let mut cur_dir = String::from(cur_dir); + cur_dir.push_str("/cva6"); + + env::set_var("CVA6_HOME_DIR", cur_dir.clone()); + + if !Path::new("./cva6/tools/spike/lib/libfesvr.so").exists() { + println!("INFO: building fesvr.."); + + assert!(Command::new("mkdir") + .arg("-p") + .arg("./cva6/tools/spike/lib/") + .status() + .unwrap() + .success()); + } + + if !Path::new("./cva6/tmp").is_dir() { + assert!(Command::new("mkdir") + .arg("./cva6/tmp") + .status() + .unwrap() + .success()); + + assert!(Command::new("bash") + .arg("-c") + .arg("cd ./cva6 \ + && RISCV=$CVA6_HOME_DIR/tools/spike source ./ci/install-fesvr.sh") + .env("CVA6_HOME_DIR", cur_dir.clone()) + .status() + .unwrap() + .success()); + } + + + if !Path::new("./build/simv").is_file() { + println!("INFO: building cva6.."); + + assert!(Command::new("bash") + .arg("-c") + .arg("echo $CVA6_HOME_DIR && cd ./cva6/verif/sim/ && source ./setup-env.sh && python3 ./cva6.py --target cv32a60x --iss=vcs-testharness --iss_yaml=cva6.yaml \ + --asm_tests $CVA6_HOME_DIR/../src/testcase.S \ + --linker=$CVA6_HOME_DIR/../src/testcase.ld \ + --gcc_opts='-static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -g -lgcc'") + .stdout(Stdio::inherit()) + .env("CVA6_HOME_DIR", cur_dir) + .status() + .unwrap() + .success()); + } + + println!("cargo:rerun-if-changed=cva6_project"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=build"); + + println!("INFO: creating build dir.."); + + assert!(Command::new("bash") + .arg("-c") + .arg("cp -r ./cva6/work-vcs/* ./build") + .status() + .unwrap() + .success()); + + assert!(Command::new("bash") + .arg("-c") + .arg("cp ./cva6/verif/sim/out_*/directed_asm_tests/testcase.o ./build/iram.elf") + .status() + .unwrap() + .success()); + + + let key = "VERDI_HOME"; + let mut verdi_lib = match env::var(key) { + Ok(val) => val, + Err(_e) => "".to_string(), + }; + + if verdi_lib.is_empty() { + println!("The env variable 'VERDI_HOME' is not set"); + return; + } + + verdi_lib.push_str("/share/NPI/lib/linux64"); + + println!("cargo:rustc-link-search=native=./build"); + println!("cargo:rustc-link-search=native={}", verdi_lib); + +} + diff --git a/fuzzers/cva6-vcs-processorfuzz/config.yml b/fuzzers/cva6-vcs-processorfuzz/config.yml new file mode 100644 index 0000000..99faa80 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/config.yml @@ -0,0 +1,11 @@ +fuzzer: + max_testcase_size: 128 +simv: + vcs_args: "+permissive +tohost_addr=80001000 +elf_file=./testcase.elf +permissive-off ++./testcase.elf +debug_disable=1 +ntb_random_seed=1 -sv_lib ~/riscv/lib/libfesvr" + plus_args: + cov_enable: true + coverage_metrics: "line+fsm+cond+tgl+branch" + coverage_directory: "Coverage.vdb" + system_timeout_s: 180 + vcs_timeout: "30us" + multi_seed: 0 diff --git a/fuzzers/cva6-vcs-processorfuzz/cva6 b/fuzzers/cva6-vcs-processorfuzz/cva6 new file mode 160000 index 0000000..b401ab3 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/cva6 @@ -0,0 +1 @@ +Subproject commit b401ab3868d053a00779add51ea37cf3b8c98b21 diff --git a/fuzzers/cva6-vcs-processorfuzz/cva6.patch b/fuzzers/cva6-vcs-processorfuzz/cva6.patch new file mode 100644 index 0000000..9a88ea7 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/cva6.patch @@ -0,0 +1,294 @@ +diff --git a/Makefile b/Makefile +index a8f7fdcb..0d13fc1a 100644 +--- a/Makefile ++++ b/Makefile +@@ -303,7 +303,7 @@ vcs_build: $(dpi-library)/ariane_dpi.so + vlogan $(if $(VERDI), -kdb,) -full64 -nc -sverilog -assert svaext +define+$(defines) +incdir+$(VCS_HOME)/etc/uvm/src $(VCS_HOME)/etc/uvm/src/uvm_pkg.sv $(filter %.sv,$(src)) $(list_incdir) &&\ + vlogan $(if $(VERDI), -kdb,) -full64 -nc -sverilog -ntb_opts uvm-1.2 &&\ + vlogan $(if $(VERDI), -kdb,) -full64 -nc -sverilog -ntb_opts uvm-1.2 $(tbs) +define+$(defines) $(list_incdir) &&\ +- vcs $(if $(VERDI), -kdb -debug_access+all -lca,) -full64 -timescale=1ns/1ns -ntb_opts uvm-1.2 work.ariane_tb -error="IWNF" ++ vcs $(if $(VERDI), -kdb -debug_access+all -lca,) -lca -cm line+cond+fsm+tgl+path+branch+assert -cm_dir Coverage.vdb -full64 -timescale=1ns/1ns -ntb_opts uvm-1.2 work.ariane_tb -error="IWNF" + + vcs: vcs_build + cd $(vcs-library) && \ +diff --git a/ci/install-fesvr.sh b/ci/install-fesvr.sh +index 7401457e..7bdaab70 100755 +--- a/ci/install-fesvr.sh ++++ b/ci/install-fesvr.sh +@@ -1,28 +1,29 @@ + #!/bin/bash + set -e + ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +-VERSION="35d50bc40e59ea1d5566fbd3d9226023821b1bb6" ++VERSION="34d39b91463228da423a1f27ba1ac417c26ef786" + +-cd $ROOT/tmp ++#cd $ROOT/tmp + + if [ -z ${NUM_JOBS} ]; then + NUM_JOBS=1 + fi + +-if [ ! -e "${RISCV}/bin/spike" ]; then ++#if [ ! -e "${RISCV}/bin/spike" ]; then + echo "Installing fesvr" +- git clone https://github.com/riscv/riscv-isa-sim.git +- cd riscv-isa-sim +- git checkout $VERSION ++# git clone https://github.com/riscv/riscv-isa-sim.git ++# cd riscv-isa-sim ++# git checkout $VERSION ++ cd $ROOT/verif/core-v-verif/vendor/riscv/riscv-isa-sim/ + mkdir -p build + cd build + ../configure --prefix="$RISCV/" +- make install-config-hdrs install-hdrs libfesvr.a ++ make install-config-hdrs install-hdrs libfesvr.so + mkdir -p $RISCV/lib +- cp libfesvr.a $RISCV/lib +-else +- echo "Using fesvr from cached directory." +-fi ++ cp libfesvr.so $RISCV/lib ++#else ++# echo "Using fesvr from cached directory." ++#fi + + + +diff --git a/ci/install-spike.sh b/ci/install-spike.sh +index 16a15581..95216878 100755 +--- a/ci/install-spike.sh ++++ b/ci/install-spike.sh +@@ -9,7 +9,7 @@ if [ -z ${NUM_JOBS} ]; then + NUM_JOBS=1 + fi + +-if [ ! -e "${RISCV}/bin/spike" ]; then ++#if [ ! -e "${RISCV}/bin/spike" ]; then + echo "Installing Spike" + git clone https://github.com/riscv/riscv-isa-sim.git + cd riscv-isa-sim +@@ -19,9 +19,9 @@ if [ ! -e "${RISCV}/bin/spike" ]; then + ../configure --prefix="$RISCV/" + make -j${NUM_JOBS} + make install +-else +- echo "Using Spike from cached directory." +-fi ++#else ++# echo "Using Spike from cached directory." ++#fi + + + +diff --git a/core/cache_subsystem/hpdcache b/core/cache_subsystem/hpdcache +index 645e4222..c9956896 160000 +--- a/core/cache_subsystem/hpdcache ++++ b/core/cache_subsystem/hpdcache +@@ -1 +1 @@ +-Subproject commit 645e4222c3d23fbedb5b0fec1922f72fd692a40a ++Subproject commit c9956896659489c23dd6d85ce692ab940c74a7e2 +diff --git a/verif/core-v-verif b/verif/core-v-verif +index 4f9dd2af..34d39b91 160000 +--- a/verif/core-v-verif ++++ b/verif/core-v-verif +@@ -1 +1 @@ +-Subproject commit 4f9dd2af648c4cb252d88972812ec16d86a7d717 ++Subproject commit 34d39b91463228da423a1f27ba1ac417c26ef786 +diff --git a/verif/sim/Makefile b/verif/sim/Makefile +index d10a84d8..dde958fd 100644 +--- a/verif/sim/Makefile ++++ b/verif/sim/Makefile +@@ -105,8 +105,8 @@ endif + cov-exclude-list ?= $(root-dir)/cov-exclude-mod.lst + + ifdef cov +- cov-comp-opt = -cm line+cond+tgl -cm_hier $(cov-exclude-list) +- cov-run-opt = -cm line+cond+tgl -cm_name $(TESTNAME) ++ cov-comp-opt = -cm line+cond+tgl+branch -cm_hier $(cov-exclude-list) ++ cov-run-opt = -cm line+cond+tgl+branch -cm_name $(TESTNAME) + else + cov-comp-opt = + cov-run-opt = +@@ -130,16 +130,16 @@ spike: + vcs-testharness: + make -C $(path_var) work-dpi/ariane_dpi.so + make -C $(path_var) vcs_build target=$(target) defines=$(subst +define+,,$(isscomp_opts))$(if $(spike-tandem),SPIKE_TANDEM=1) +- $(path_var)/work-vcs/simv $(if $(VERDI), -verdi -do $(path_var)/util/init_testharness.do,) +permissive \ +- +tohost_addr=$(shell $$RISCV/bin/${CV_SW_PREFIX}nm -B $(elf) | grep -w tohost | cut -d' ' -f1) \ +- +elf_file=$(elf) +permissive-off ++$(elf) $(issrun_opts) \ +- $(if $(spike-tandem),-sv_lib $(SPIKE_INSTALL_DIR)/lib/libdisasm) \ +- $(if $(spike-tandem),-sv_lib $(SPIKE_INSTALL_DIR)/lib/libriscv) \ +- -sv_lib $(SPIKE_INSTALL_DIR)/lib/libfesvr ++ #$(path_var)/work-vcs/simv $(if $(VERDI), -verdi -do $(path_var)/util/init_testharness.do,) +permissive \ ++ # +tohost_addr=$(shell $$RISCV/bin/${CV_SW_PREFIX}nm -B $(elf) | grep -w tohost | cut -d' ' -f1) \ ++ # +elf_file=$(elf) +permissive-off ++$(elf) $(issrun_opts) \ ++ # $(if $(spike-tandem),-sv_lib $(SPIKE_INSTALL_DIR)/lib/libdisasm) \ ++ # $(if $(spike-tandem),-sv_lib $(SPIKE_INSTALL_DIR)/lib/libriscv) \ ++ # -sv_lib $(SPIKE_INSTALL_DIR)/lib/libfesvr + # TODO: Add support for waveform collection. + # Generate disassembled log. +- $(tool_path)/spike-dasm --isa=$(variant) < ./trace_rvfi_hart_00.dasm > $(log) +- grep $(isspostrun_opts) ./trace_rvfi_hart_00.dasm ++ #$(tool_path)/spike-dasm --isa=$(variant) < ./trace_rvfi_hart_00.dasm > $(log) ++ #grep $(isspostrun_opts) ./trace_rvfi_hart_00.dasm + + veri-testharness: + make -C $(path_var) verilate verilator="verilator --no-timing" target=$(target) defines=$(subst +define+,,$(isscomp_opts)) +diff --git a/verif/sim/cva6.py b/verif/sim/cva6.py +index 3940600c..14b34583 100644 +--- a/verif/sim/cva6.py ++++ b/verif/sim/cva6.py +@@ -1127,7 +1127,7 @@ def main(): + setup_logging(args.verbose) + logg = logging.getLogger() + +- check_tools_version() ++ # check_tools_version() + + # create file handler which logs even debug messages13.1.1 + fh = logging.FileHandler('logfile.log') +diff --git a/verif/tests/custom/hello_world/custom_test_template.S b/verif/tests/custom/hello_world/custom_test_template.S +index 9636395a..264e0b7e 100644 +--- a/verif/tests/custom/hello_world/custom_test_template.S ++++ b/verif/tests/custom/hello_world/custom_test_template.S +@@ -12,22 +12,90 @@ + #----------------------------------------------------------------------------- + # + ++.equ DRAM_START, 0x80000000 ++ + .globl main + main: +-# core of the test +-# (example of) final self-check test +- li a0, 0xCAFE; +- li a1, 0xCAFE; +- xor a2, a0, a1; +- beqz a2, pass; +- +-fail: +- # Failure post-processing (messages, ecall setup etc.) +- li a0, 0x0; +- jal exit; +- +-pass: +- # Success post-processing (messages, ecall setup etc.) +- li a0, 0x0; +- jal exit; ++ la t0, exception_entry ++ csrw mtvec,t0 ++ la t4, DRAM_START ++ sw x0, (t4) ++ jal x0, payload ++ ++.p2align 4 ++exception_entry: ++ csrr t0, mepc ++ lb t1, 0(t0) ++ li a0, 0x3 ++ and t1, t1, a0 ++ /* Increment mepc by 2 or 4 depending on whether the instruction at mepc ++ is compressed or not. */ ++ bne t1, a0, end_handler_incr_mepc2 ++ addi t0, t0, 2 ++end_handler_incr_mepc2: ++ addi t0, t0, 2 ++ csrw mepc, t0 ++end_handler_ret: ++ lw ra, 0(sp) ++ lw a0, 4(sp) ++ lw a1, 8(sp) ++ lw a2, 12(sp) ++ lw a3, 16(sp) ++ lw a4, 20(sp) ++ lw a5, 24(sp) ++ lw a6, 28(sp) ++ lw a7, 32(sp) ++ lw t0, 36(sp) ++ lw t1, 40(sp) ++ lw t2, 44(sp) ++ lw t3, 48(sp) ++ lw t4, 52(sp) ++ lw t5, 56(sp) ++ lw t6, 60(sp) ++ addi sp,sp,64 ++ mret ++ ++ ++ ++// csrr t3, mepc ++// csrr t4, 0xfC2 #CSR_MSTATUS_REG_ADDR ++// andi t4, t4, 0x00000080 #MSTATUS_IL_MASK ++// srli t4, t4, 7 #MSTATUS_IL_SHIFT ++// slli t4, t4, 1 #*2 ++// add t3, t3, t4 ++// add t3, t3, 2 ++// csrw mepc, t3 ++// la t4, DRAM_START ++// lw t5, (t4) ++// add t5, t5, 1 ++// li t6, 100 ++// beq t5, t6, exit ++// sw t5, (t4) ++// mret ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++// lui t0,0x101 ++ ++.p2align 4 ++exit: ++ la t3, end ++ csrw sepc, t3 ++ mret ++.p2align 4 ++end: ++ jal x0, end ++ ++.p2align 4 ++payload: ++.rept 72948 ++ .word 0xDEADBEEF ++// .word 0xDEADBEAF ++// .word 0xABABABAB ++.endr + +diff --git a/verif/tests/custom/hello_world/hello_world.c b/verif/tests/custom/hello_world/hello_world.c +index d59802c2..ef3385e0 100644 +--- a/verif/tests/custom/hello_world/hello_world.c ++++ b/verif/tests/custom/hello_world/hello_world.c +@@ -17,16 +17,17 @@ + */ + + #include +-#include ++// #include + + int main(int argc, char* arg[]) { +- +- printf("%d: Hello World !", 0); +- +- int a = 0; +- for (int i = 0; i < 5; i++) +- { +- a += i; +- } +- return 0; ++ ++ int a = 0, b = 1, n=100, c, i; ++ for (i = 2; i <= n; i++) { ++ c = a + b; ++ a = b; ++ b = c; ++ } ++ ++ return 0; + } ++ diff --git a/fuzzers/cva6-vcs-processorfuzz/plot.py b/fuzzers/cva6-vcs-processorfuzz/plot.py new file mode 100644 index 0000000..a59d81f --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/plot.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# SPDX-FileCopyrightText: 2022 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import re +import time +import copy +from datetime import datetime +import matplotlib.pyplot as plt +import numpy as np +from collections import OrderedDict +import pandas +import seaborn as sns +import json +import argparse + +print(sns.__version__) + + +def is_valid_directory(arg): + if not os.path.isdir(arg): + raise argparse.ArgumentTypeError(f"'{arg}' is not a valid directory") + return arg + +def parse_args(): + parser = argparse.ArgumentParser(description="Parse command line arguments") + parser.add_argument("-d", "--directory", type=is_valid_directory, required=True, help="Input directory") + parser.add_argument("-m", "--metric", choices=["line", "branch", "cond", "tgl", "fsm"], required=True, help="Metric to be provided") + return parser.parse_args() + +args = parse_args() +print("Input directory:", args.directory) +print("Metric:", args.metric) + +stats_directories = [args.directory] +metric = args.metric + +# first, let's load all the data +data = [] +for stats_directory in stats_directories: + + i = 0 + delta = 0 + for stats_filename in os.scandir(stats_directory): + + f = stats_filename + + if "stats" not in f.name: + continue + + + print(f"Analyzing file {f.name}: ") + + if os.path.isfile(f): + f = open(f) + + last_runtime = 0 + last_coverage = 0 + + lines = f.readlines() + for l in lines: + + l = json.loads(l) + + if "coverage_verdi_"+metric in l["UpdateUserStats"]["name"]: + a = l["UpdateUserStats"]["value"]["value"]["Ratio"][0] + b = l["UpdateUserStats"]["value"]["value"]["Ratio"][1] + last_coverage = a/b + + if "time_verdi_"+metric in l["UpdateUserStats"]["name"]: + last_runtime = l["UpdateUserStats"]["value"]["value"]["Number"] + data += [{"runtime": last_runtime, "score": last_coverage}] + i += 1 + +# let's order the timepoint +dataset = [] +runtime = [] +coverage = [] +max_cov = 0.0 + +time_sorted = sorted(data, key=lambda x: x['runtime']) + +delta = time_sorted[0]["runtime"] + +for item in time_sorted: + + runtime += [item["runtime"]] + + if max_cov < item["score"]: + max_cov = item["score"] + + coverage += [max_cov] + +dataset = {"Execution Time": runtime, "Score": coverage} +print(dataset) +ax = sns.lineplot(x=dataset["Execution Time"], y=dataset["Score"], legend="full") + +plt.title(f"{metric} coverage score over time.") +plt.legend(loc='upper center') +plt.savefig(f'{metric}.png') diff --git a/fuzzers/cva6-vcs-processorfuzz/src/differential.rs b/fuzzers/cva6-vcs-processorfuzz/src/differential.rs new file mode 100644 index 0000000..011c3ac --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/differential.rs @@ -0,0 +1,290 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code)] +//! Executor for differential fuzzing. +//! It wraps two executors that will be run after each other with the same input. +//! In comparison to the [`crate::executors::CombinedExecutor`] it also runs the secondary executor in `run_target`. +//! +use std::{cell::UnsafeCell, fmt::Debug}; + +use libafl_bolts::{ownedref::OwnedMutPtr, tuples::MatchName}; +use serde::{Deserialize, Serialize}; + +use libafl::{ + executors::{Executor, ExitKind, HasObservers}, + inputs::UsesInput, + observers::{DifferentialObserversTuple, ObserversTuple, UsesObservers}, + state::UsesState, + Error, +}; +#[cfg(feature = "debug")] +use color_print::cprintln; + +/// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one +#[derive(Debug)] +pub struct DiffExecutor { + primary: A, + secondary: B, + observers: UnsafeCell>, +} + +impl DiffExecutor { + /// Create a new `DiffExecutor`, wrapping the given `executor`s. + pub fn new(primary: A, secondary: B, observers: DOT) -> Self + where + A: UsesState + HasObservers, + B: UsesState + HasObservers, + DOT: DifferentialObserversTuple, + OTA: ObserversTuple, + OTB: ObserversTuple, + { + Self { + primary, + secondary, + observers: UnsafeCell::new(ProxyObserversTuple { + primary: OwnedMutPtr::Ptr(core::ptr::null_mut()), + secondary: OwnedMutPtr::Ptr(core::ptr::null_mut()), + differential: observers, + }), + } + } + + /// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`. + pub fn primary(&mut self) -> &mut A { + &mut self.primary + } + + /// Retrieve the secondary `Executor` that is wrapped by this `DiffExecutor`. + pub fn secondary(&mut self) -> &mut B { + &mut self.secondary + } +} + +impl Executor for DiffExecutor +where + A: Executor + HasObservers, + B: Executor + HasObservers, + EM: UsesState, + DOT: DifferentialObserversTuple, + Z: UsesState, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut Self::State, + mgr: &mut EM, + input: &Self::Input, + ) -> Result { + self.observers(); // update in advance + let observers = self.observers.get_mut(); + observers + .differential + .pre_observe_first_all(observers.primary.as_mut())?; + observers.primary.as_mut().pre_exec_all(state, input)?; + let ret1 = self.primary.run_target(fuzzer, state, mgr, input)?; + + if ret1 != ExitKind::Ok && ret1 != ExitKind::Timeout { + #[cfg(feature = "debug")] + cprintln!("[WARNING] First executor did not return success {:?}, skipping testcase!", ret1); + + return Ok(ExitKind::Ok); + } + + self.primary.post_run_reset(); + observers + .primary + .as_mut() + .post_exec_all(state, input, &ret1)?; + observers + .differential + .post_observe_first_all(observers.primary.as_mut())?; + observers + .differential + .pre_observe_second_all(observers.secondary.as_mut())?; + observers.secondary.as_mut().pre_exec_all(state, input)?; + + let ret2 = self.secondary.run_target(fuzzer, state, mgr, input)?; + + self.secondary.post_run_reset(); + observers + .secondary + .as_mut() + .post_exec_all(state, input, &ret2)?; + observers + .differential + .post_observe_second_all(observers.secondary.as_mut())?; + + if ret1 == ret2 { + Ok(ret1) + } else { + // We found a diff in the exit codes! + Ok(ExitKind::Diff { + primary: ret1.into(), + secondary: ret2.into(), + }) + } + } +} + +/// Proxy the observers of the inner executors +#[derive(Serialize, Deserialize, Debug)] +#[serde( + bound = "A: serde::Serialize + serde::de::DeserializeOwned, B: serde::Serialize + serde::de::DeserializeOwned, DOT: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct ProxyObserversTuple { + primary: OwnedMutPtr, + secondary: OwnedMutPtr, + differential: DOT, +} + +impl ObserversTuple for ProxyObserversTuple +where + A: ObserversTuple, + B: ObserversTuple, + DOT: DifferentialObserversTuple, + S: UsesInput, +{ + fn pre_exec_all(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> { + self.differential.pre_exec_all(state, input) + } + + fn post_exec_all( + &mut self, + state: &mut S, + input: &S::Input, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.differential.post_exec_all(state, input, exit_kind) + } + + fn pre_exec_child_all(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> { + self.differential.pre_exec_child_all(state, input) + } + + fn post_exec_child_all( + &mut self, + state: &mut S, + input: &S::Input, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.differential + .post_exec_child_all(state, input, exit_kind) + } + + /// Returns true if a `stdout` observer was added to the list + #[inline] + fn observes_stdout(&self) -> bool { + self.primary.as_ref().observes_stdout() || self.secondary.as_ref().observes_stdout() + } + /// Returns true if a `stderr` observer was added to the list + #[inline] + fn observes_stderr(&self) -> bool { + self.primary.as_ref().observes_stderr() || self.secondary.as_ref().observes_stderr() + } + + /// Runs `observe_stdout` for all stdout observers in the list + fn observe_stdout(&mut self, stdout: &[u8]) { + self.primary.as_mut().observe_stderr(stdout); + self.secondary.as_mut().observe_stderr(stdout); + } + + /// Runs `observe_stderr` for all stderr observers in the list + fn observe_stderr(&mut self, stderr: &[u8]) { + self.primary.as_mut().observe_stderr(stderr); + self.secondary.as_mut().observe_stderr(stderr); + } +} + +impl MatchName for ProxyObserversTuple +where + A: MatchName, + B: MatchName, + DOT: MatchName, +{ + fn match_name(&self, name: &str) -> Option<&T> { + if let Some(t) = self.primary.as_ref().match_name::(name) { + Some(t) + } else if let Some(t) = self.secondary.as_ref().match_name::(name) { + Some(t) + } else { + self.differential.match_name::(name) + } + } + fn match_name_mut(&mut self, name: &str) -> Option<&mut T> { + if let Some(t) = self.primary.as_mut().match_name_mut::(name) { + Some(t) + } else if let Some(t) = self.secondary.as_mut().match_name_mut::(name) { + Some(t) + } else { + self.differential.match_name_mut::(name) + } + } +} + +impl ProxyObserversTuple { + fn set(&mut self, primary: &A, secondary: &B) { + self.primary = OwnedMutPtr::Ptr(primary as *const A as *mut A); + self.secondary = OwnedMutPtr::Ptr(secondary as *const B as *mut B); + } +} + +impl UsesObservers for DiffExecutor +where + A: HasObservers, + B: HasObservers, + OTA: ObserversTuple, + OTB: ObserversTuple, + DOT: DifferentialObserversTuple, +{ + type Observers = ProxyObserversTuple; +} + +impl UsesState for DiffExecutor +where + A: UsesState, + B: UsesState, +{ + type State = A::State; +} + +impl HasObservers for DiffExecutor +where + A: HasObservers, + B: HasObservers, + OTA: ObserversTuple, + OTB: ObserversTuple, + DOT: DifferentialObserversTuple, +{ + #[inline] + fn observers(&self) -> &ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + // .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_ref().unwrap() + } + } + + #[inline] + fn observers_mut(&mut self) -> &mut ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_mut().unwrap() + } + } +} diff --git a/fuzzers/cva6-vcs-processorfuzz/src/differential_feedback.rs b/fuzzers/cva6-vcs-processorfuzz/src/differential_feedback.rs new file mode 100644 index 0000000..6ca4d92 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/differential_feedback.rs @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code, unused_variables, unused_mut)] + +use core::fmt::Debug; +use libafl_bolts::Named; +use libafl::{ + corpus::Testcase, + events::EventFirer, + executors::ExitKind, + feedbacks::Feedback, + inputs::{UsesInput}, + observers::ObserversTuple, + state::State, + Error, +}; +use serde::{Deserialize, Serialize}; +use std::str; +extern crate fs_extra; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +use std::fmt::Write; +use std::{ + fs, +}; + +/// Nop feedback that annotates execution time in the new testcase, if any +/// for this Feedback, the testcase is never interesting (use with an OR). +/// It decides, if the given [`TimeObserver`] value of a run is interesting. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct DifferentialFeedback { + first_name: String, + name: String, + counter: u32, +} + +impl Feedback for DifferentialFeedback +where + S: UsesInput + State +{ + #[allow(clippy::wrong_self_convention)] + #[allow(dead_code)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + + #[cfg(feature = "debug")] + cprintln!("[WARNING] Skipping trace comparison feedback because it is not implemented for cva6 ..."); + return Ok(false); + + //TODO: implement differential testing for cva6 + Spike + //let observer = observers.match_name::(self.first_name.as_str()).unwrap(); + + //let mut output_arg = String::new(); + //write!(output_arg, "--ofile=backup_{}.log", self.counter).expect("Unable to backup compare.pl log"); + + //if observer.mismatch == true { + // + // interesting = true; + // + // let dst_file = format!("testcase_state_{}.log", self.counter); + // fs::copy(observer.logfile.as_str(), dst_file).expect("Unable to create copy of log file"); + + // self.counter += 1; + // + // let dst_file = format!("testcase.elf_spike_{}.log", self.counter); + // fs::copy("testcase.elf_spike.log", dst_file).expect("Unable to create copy of log file"); + // + // let dst_file = format!("testcase_{}.elf", self.counter); + // fs::copy("testcase.elf", dst_file).expect("Unable to create copy of log file"); + //} + + //let _ = std::fs::remove_file("testcase_state.log"); + //let _ = std::fs::remove_file("testcase.elf_spike.log"); + + //return Ok(interesting); + } + + #[inline] + fn append_metadata( + &mut self, + _state: &mut S, + _observers: &OT, + _testcase: &mut Testcase, + ) -> Result<(), Error> + where + OT: ObserversTuple + { + Ok(()) + } + + /// Discard the stored metadata in case that the testcase is not added to the corpus + #[inline] + fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { + Ok(()) + } +} + +impl Named for DifferentialFeedback { + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl DifferentialFeedback { + /// Creates a new [`DifferentialFeedback`], deciding if the given [`VerdiObserver`] value of a run is interesting. + #[must_use] + pub fn new_with_observer( + name: &'static str, + first_name: &'static str, + ) -> Self { + Self { + first_name: first_name.to_string(), + name: name.to_string(), + counter: 0, + } + } +} diff --git a/fuzzers/cva6-vcs-processorfuzz/src/main.rs b/fuzzers/cva6-vcs-processorfuzz/src/main.rs new file mode 100644 index 0000000..9c23af6 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/main.rs @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr(test, deny(dead_code, unreachable_code))] +#![allow(dead_code, unreachable_code)] + +use std::path::PathBuf; + +#[cfg(feature = "tui")] +use libafl::monitors::tui::TuiMonitor; + +// #[cfg(not(feature = "tui"))] +use libafl::executors::command::CommandConfigurator; +use libafl::state::HasMaxSize; +use libafl::{ + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::EventConfig, + events::SimpleEventManager, + feedback_and_fast, feedback_not, feedback_or, + feedbacks::MaxMapFeedback, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::BytesInput, + monitors::multi::MultiMonitor, + monitors::SimpleMonitor, + observers::{HitcountsMapObserver, StdMapObserver}, + schedulers::QueueScheduler, + stages::StdMutationalStage, + state::StdState, + Error, HasFeedback, +}; + +use tempdir::TempDir; + +#[cfg(not(target_vendor = "apple"))] +use libafl_bolts::shmem::StdShMemProvider; +#[cfg(target_vendor = "apple")] +use libafl_bolts::shmem::UnixShMemProvider; +use libafl_bolts::{ + core_affinity::Cores, + current_nanos, + rands::StdRand, + shmem::{ShMem, ShMemProvider}, + tuples::tuple_list, + AsMutSlice, +}; +#[cfg(feature = "std")] +use std::net::{SocketAddr, ToSocketAddrs}; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +use std::os::unix::fs; + +use std::env; +use std::io::Result; +use std::path::Path; +use std::rc::Rc; + +use clap::AppSettings; +use clap::{App, Arg}; + +//use libpresifuzz_feedbacks::verdi_feedback::VerdiFeedback; + +use libpresifuzz_feedbacks::verdi_xml_feedback::VerdiFeedback; +use libpresifuzz_observers::verdi_xml_observer::VerdiCoverageMetric; +use libpresifuzz_observers::verdi_xml_observer::VerdiCoverageMetric::*; +use libpresifuzz_observers::verdi_xml_observer::VerdiXMLMapObserver; + +pub mod simv; +use crate::simv::SimvCommandConfigurator; + +use libpresifuzz_mutators::riscv_isa::riscv_mutations; +use libpresifuzz_mutators::scheduled::StdISAScheduledMutator; + +use libpresifuzz_ec::llmp::Launcher; +use libpresifuzz_ec::manager::*; +use libpresifuzz_feedbacks::transferred::TransferredFeedback; +use libpresifuzz_stages::sync::SyncFromDiskStage; + +mod differential; +mod differential_feedback; +//mod verdi_feedback; +//use crate::verdi_feedback::VerdiFeedback; + +#[derive(Debug)] +pub struct WorkDir(Option); + +// Forward inherent methods to the tempdir crate. +impl WorkDir { + pub fn new(prefix: &str) -> Result { + TempDir::new(prefix).map(Some).map(WorkDir) + } + + pub fn path(&self) -> &Path { + self.0.as_ref().unwrap().path() + } +} + +/// Leaks the inner TempDir if we are unwinding. +impl Drop for WorkDir { + fn drop(&mut self) { + ::std::mem::forget(self.0.take()) + } +} + +pub fn symlink_files(from: Vec<&str>, to: Vec<&str>, workdir: &str) { + let current_dir = env::current_dir() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + .to_string(); + + for i in 0..from.len() { + #[cfg(feature = "debug")] + cprintln!( + "[INFO] symbolic link for {}/{} to {}/{}", + current_dir, + from[i], + workdir, + to[i] + ); + + let src = format!("{}/{}", current_dir, from[i]); + let dst = format!("{}/{}", workdir, to[i]); + + fs::symlink(&src, &dst).expect("Fail to create symlink for yaml file to workdir!"); + } +} + +/// The actual fuzzer +#[allow(clippy::too_many_lines, clippy::too_many_arguments)] +#[allow(clippy::similar_names)] +pub fn main() { + // color_backtrace::install(); + + fuzz(); +} + +macro_rules! create_verdi_observer_and_feedback { + ($fdb:ident, $obs:ident, $metric:ident, $minimized:expr, $workdir:ident) => { + let ($obs, $fdb) = { + let verdi_observer = VerdiXMLMapObserver::new( + concat!("verdi_", stringify!($metric)), + &"Coverage.vdb".to_string(), + &$workdir.to_string(), + match $metric { + Toggle => VerdiCoverageMetric::Toggle, + Branch => VerdiCoverageMetric::Branch, + Line => VerdiCoverageMetric::Line, + Condition => VerdiCoverageMetric::Condition, + FSM => VerdiCoverageMetric::FSM, + _ => VerdiCoverageMetric::Toggle, + }, + &"".to_string(), //&"ariane_tb.dut.i_ariane".to_string() + ); + + let feedback = VerdiFeedback::new_with_observer( + concat!("verdi_", stringify!($metric)), + &$workdir.to_string(), + $minimized, + ); + (feedback, verdi_observer) + }; + }; +} + +pub fn fuzz() { + let yaml_fd = std::fs::File::open("config.yml").unwrap(); + let config: serde_yaml::Value = serde_yaml::from_reader(yaml_fd).unwrap(); + + let max_testcase_size: usize = config["fuzzer"]["max_testcase_size"] + .as_u64() + .unwrap_or(1024) + .try_into() + .unwrap(); + + // allocate the shared memory provider for later use + #[cfg(target_vendoe = "apple")] + let mut shmem_provider = UnixShMemProvider::new().unwrap(); + #[cfg(not(target_vendor = "apple"))] + let shmem_provider = StdShMemProvider::new().unwrap(); + let mut shmem_provider_client = shmem_provider.clone(); + + let mon = MultiMonitor::new(|s| println!("{s}")); + + let sync_dir = format!("{}/sync/", std::env::current_dir().unwrap().display()); + println!("sync_dir: {}", sync_dir); + + let corpus_dir = format!("{}/seeds", env::current_dir().unwrap().display()); + + let mut run_client = |_state: Option<_>, mut mgr, _core_id| { + // get a unique temp-dir name + let tmp_dir = WorkDir::new("presifuzz_").expect("Unable to create temporary directory"); + let workdir = tmp_dir.path().as_os_str().to_str().unwrap().to_owned(); + + let workdir: &str = workdir.as_str(); + + symlink_files( + vec!["config.yml", "run.sh"], + vec!["config.yml", "run.sh"], + workdir, + ); + + let simv = + SimvCommandConfigurator::new_from_config_file("config.yml", workdir, &mut [], "", 1); + + std::env::set_current_dir(&workdir).expect("Unable to change into {dir}"); + let mut spike_trace_observer = ExecTraceObserver::::new("spike_exec_trace_observer", "./spike.log"); + let mut rocket_trace_observer = ExecTraceObserver::::new("cva6_exec_trace_observer", "./cva6.log"); + + let mut objective = differential_feedback::DifferentialFeedback::new_with_observer( + "spike_exec_trace_observer", + "cva6_exec_trace_observer", + "differential_trace_feedback", + ); + + let processorfuzz_feedback = ProcessorFuzzFeedback::new("spike_exec_trace_observer"); + let mut feedback = feedback_or!( + processorfuzz_feedback, + ); + + // Instantiate State with feedback, objective, in/out corpus + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + OnDiskCorpus::::new(&PathBuf::from("./corpus")).unwrap(), + InMemoryCorpus::new(), + &mut feedback, + &mut objective, + ) + .unwrap(); + state.set_max_size(max_testcase_size); + + // Simle FIFO scheduler + let scheduler = QueueScheduler::new(); + + // RISCV ISA mutator + let mutator = StdISAScheduledMutator::new(riscv_mutations()); + + // Finally, instantiate the fuzzer + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut executor = differential::DiffExecutor::new( + spike.into_executor(tuple_list!()), + simv.into_executor(tuple_list!()), + tuple_list!(spike_trace_observer, rocket_trace_observer), + ); + + let corpus_dir = PathBuf::from(corpus_dir.to_string()); + + // load initial inputs if any seeds provided + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[corpus_dir.clone()]) + .unwrap(); + + // Instantiate a mutational stage that will apply mutations to the selected testcase + let sync_dir = PathBuf::from(sync_dir.to_string()); + //let mut stages = tuple_list!(SyncFromDiskStage::new(sync_dir), StdMutationalStage::with_max_iterations(mutator, 1)); + let mut stages = tuple_list!( + SyncFromDiskStage::new(sync_dir), + StdMutationalStage::new(mutator) + ); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in fuzzing loop"); + + Ok(()) + }; + + match Launcher::<_, _, _, BytesInput>::builder() + .configuration(EventConfig::from_name("default")) + .monitor(mon) + .run_client(&mut run_client) + .stdout_file(Some("/dev/null")) + .sync_dir(sync_dir.clone()) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => (), + Err(err) => panic!("Fuzzingg failed {err:?}"), + }; +} diff --git a/fuzzers/cva6-vcs-processorfuzz/src/simv.rs b/fuzzers/cva6-vcs-processorfuzz/src/simv.rs new file mode 100644 index 0000000..f9587d8 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/simv.rs @@ -0,0 +1,392 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code)] + +use libafl::{ + executors::{command::CommandConfigurator}, + inputs::{HasTargetBytes, Input}, + Error, +}; +use libafl_bolts::{ + AsMutSlice, AsSlice, + ownedref::OwnedMutSlice +}; + +use std::assert; +use std::path::Path; +use std::process::{Child, Command}; +use std::os::unix::fs; +use std::time::Duration; +use std::env; +use std::process::Stdio; +use rand::Rng; +use std::io::ErrorKind; +use libpresifuzz_riscv::dasm::RiscvInstructions; +use libpresifuzz_riscv::elf::ELF; +use std::io::Read; +use wait_timeout::ChildExt; + +#[cfg(feature = "root_snapshot")] +use subprocess::Exec; +#[cfg(feature = "root_snapshot")] +use subprocess::Redirection; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +extern crate yaml_rust; + +fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + + +#[derive(Clone, Debug)] +pub struct SimvCommandConfigurator<'a> { + workdir : String, + vcs_args : String, + plus_args : String, + coverage_metrics : String, + coverage_directory : String, + reset_coverage_before_use : bool, + system_timeout_s : u64, + vcs_timeout : String, + testcase_buf : OwnedMutSlice<'a, u8>, + shm_id : String, + seed : u32, +} + +impl<'a> CommandConfigurator for SimvCommandConfigurator<'a> { + + fn spawn_child(&mut self, input: &I) -> Result { + + // clean old files if any + let old_log = "testcase_state.log"; + if let Ok(_metadata) = std::fs::metadata(&old_log) { + let _ = std::fs::remove_file(&old_log); + } + + if self.seed != 0 { + let mut rng = rand::thread_rng(); + + self.seed = rng.gen_range(0..100000); + } + + #[cfg(feature = "debug")] + cprintln!("[INFO] Running simv with seed {} ...", self.seed); + + // Simv Command Executor prepares simv inputs and start simv with proper arguments + // 1. Generate testcase in expected format + self.generate_testcase(input); + + // 2. args string into vec + let mut forged_cmd_args = format!("\ + +vcs+finish+{vcs_timeout} \ + -cm {coverage_metrics} \ + -cm_dir {coverage_directory} \ + {plus_args} \ + {vcs_args} \ + ", + plus_args = self.plus_args, + vcs_args = self.vcs_args, + vcs_timeout = self.vcs_timeout, + coverage_metrics = self.coverage_metrics, + coverage_directory = self.coverage_directory); + + if cfg!(feature = "root_snapshot") + { + forged_cmd_args.push_str(" +restore "); + forged_cmd_args.push_str(&format!(" +ntb_random_reseed={} ", self.seed)); + } else { + forged_cmd_args.push_str(&format!(" +ntb_random_seed={} ", self.seed)); + } + + let args_vec: Vec<&str> = forged_cmd_args.split(' ').collect(); + let args_v = &args_vec[0 .. args_vec.len()]; + + #[cfg(feature = "debug")] + println!("Executing command: {:?}", forged_cmd_args); + #[cfg(feature = "debug")] + println!("Executing command: {:?}", args_v); + + // 3. spawn simv + if !cfg!(feature = "debug") { + + Ok(Command::new("bash") + .arg("./run.sh") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to start process")) + } else { + //Ok(Command::new("./simv") + // .args(args_v) + Ok(Command::new("bash") + .arg("./run.sh") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to start process")) + } + } + + fn exec_timeout(&self) -> Duration { + Duration::from_secs(self.system_timeout_s.into()) + } +} + +impl<'a> SimvCommandConfigurator<'a> { + pub fn new_from_simv(simv: &SimvCommandConfigurator, testcase_buf: &'a mut [u8], shm_id: &'static str, seed: u32) -> SimvCommandConfigurator<'a> { + + #[cfg(feature = "debug")] + cprintln!("[INFO] New simv with shm_id {} ...", shm_id); + + return SimvCommandConfigurator{ + // testcase_file_name: testcase_file_name, + workdir : simv.workdir.to_string(), + vcs_args : simv.vcs_args.to_string(), + plus_args : simv.plus_args.to_string(), + coverage_metrics : simv.coverage_metrics.to_string(), + coverage_directory : simv.coverage_directory.to_string(), + reset_coverage_before_use : simv.reset_coverage_before_use, + system_timeout_s : simv.system_timeout_s, + vcs_timeout : simv.vcs_timeout.to_string(), + testcase_buf : OwnedMutSlice::from(testcase_buf), + shm_id : shm_id.to_string(), + seed : seed, + }; + } + + pub fn new_from_config_file(config_filename: &'static str, workdir: &str, testcase_buf: &'a mut [u8], shm_id: &'static str, seed: u32) -> SimvCommandConfigurator<'a> { + + #[cfg(feature = "debug")] + { + println!("Loading simv configuration from {}", config_filename); + println!("Simv workdir directory is {}", workdir); + } + + // parse yaml configuration file to extact: + // * simv arguments + // * simv executable name + // * simv version + // * simv ISA + + let yaml_fd = std::fs::File::open(config_filename).unwrap(); + let config: serde_yaml::Value = serde_yaml::from_reader(yaml_fd).unwrap(); + + let vcs_args = config["simv"]["vcs_args"] + .as_str() + .unwrap_or(""); + + let plus_args = config["simv"]["plus_args"] + .as_str() + .unwrap_or(""); + + let coverage_metrics = config["simv"]["coverage_metrics"] + .as_str() + .unwrap_or("tgl"); + + let coverage_directory = config["simv"]["coverage_directory"] + .as_str() + .unwrap_or("Coverage.vdb"); + + let reset_coverage_before_use = config["simv"]["reset_coverage_before_use"] + .as_bool() + .unwrap_or(false); + + let system_timeout_s = config["simv"]["system_timeout_s"] + .as_u64() + .unwrap_or(0); + + let vcs_timeout = config["simv"]["vcs_timeout"] + .as_str() + .unwrap_or("10us"); + + #[cfg(feature = "debug")] + { + println!("simv.vcs_args = {}", vcs_args); + println!("simv.plus_args = {}", plus_args); + println!("simv.vcs_timeout_s = {}", vcs_timeout); + println!("simv.system_timeout_s = {}", system_timeout_s); + println!("simv.coverage_directory = {}", coverage_directory); + println!("simv.coverage_metrics = {}", coverage_metrics); + println!("simv.reset_coverage_before_use = {}", reset_coverage_before_use); + } + + let mut vcs_home = env::current_dir().unwrap().as_os_str().to_str().unwrap().to_string(); + vcs_home.push_str("/build"); + + let mut src_s = vec![ + format!("{}/simv.daidir", vcs_home), + format!("{}/csrc", vcs_home), + format!("{}/simv", vcs_home), + format!("{}/vc_hdrs.h", vcs_home), + format!("{}/iram.elf", vcs_home)]; + + let mut dst_s = vec![ + format!("{}/simv.daidir", workdir), + format!("{}/csrc", workdir), + format!("{}/simv", workdir), + format!("{}/vc_hdrs.h", vcs_home), + format!("{}/iram.elf", workdir)]; + + for i in 0..src_s.len() { + #[cfg(feature = "debug")] + println!("Creating symlink from {src} to {dst}", src = &src_s[i], dst = &dst_s[i]); + + match fs::symlink(&src_s[i], &dst_s[i]) { + Err(e) if e.kind() == ErrorKind::AlreadyExists => { + println!("No need to copy {} because file already exists", &src_s[i]); + }, + Ok(_) => {}, + _ => { panic!("Fail to create symbolic link for simv workdir!");} + }; + } + + let src = format!("{}/{}", vcs_home, coverage_directory); + let dst = format!("{}/{}", workdir, coverage_directory); + copy_dir_all(&src, &dst).expect("Unable to copy vdb folder to workdir!"); + + if reset_coverage_before_use == true { + let dst = format!("{}/Virgin_coverage.vdb", workdir); + copy_dir_all(&src, &dst).expect("Unable to create Virgin copy of vdb folder in workdir!"); + } + + return SimvCommandConfigurator{ + // testcase_file_name: testcase_file_name, + workdir : workdir.to_string(), + vcs_args : vcs_args.to_string(), + plus_args : plus_args.to_string(), + coverage_metrics : coverage_metrics.to_string(), + coverage_directory : coverage_directory.to_string(), + reset_coverage_before_use : reset_coverage_before_use, + system_timeout_s : system_timeout_s, + vcs_timeout : vcs_timeout.to_string(), + testcase_buf : OwnedMutSlice::from(testcase_buf), + shm_id : shm_id.to_string(), + seed : seed, + }; + } + + fn generate_testcase(&mut self, input: &I) { + + let target = input.target_bytes(); + let buf = target.as_slice(); + let size = buf.len(); + + let run_from_iram = true; + + if cfg!(feature = "input_injection") { + + self.testcase_buf.as_mut_slice()[..size].copy_from_slice(&vec![0; size]); + self.testcase_buf.as_mut_slice()[0..4].copy_from_slice(&(0x00004297_i32).to_ne_bytes()); + self.testcase_buf.as_mut_slice()[4..8].copy_from_slice(&(0x03c28293_i32).to_ne_bytes()); + self.testcase_buf.as_mut_slice()[8..12].copy_from_slice(&(0x30529073_i32).to_ne_bytes()); + // self.testcase_buf.as_mut_slice()[12..size+12].copy_from_slice(&buf.as_slice()[..size]); + + // endianess swith here from big to little endian + // let &mut slice = &self.testcase_buf.as_mut_slice()[12..size+12]; + for k in (0..buf.as_slice().len()).step_by(4) { + let offset = k+12; + if k+3 < buf.as_slice().len() { + self.testcase_buf.as_mut_slice()[offset] = buf.as_slice()[k+3]; + self.testcase_buf.as_mut_slice()[offset+1] = buf.as_slice()[k+2]; + self.testcase_buf.as_mut_slice()[offset+2] = buf.as_slice()[k+1]; + self.testcase_buf.as_mut_slice()[offset+3] = buf.as_slice()[k]; + } + else if k+1 < buf.as_slice().len() { + self.testcase_buf.as_mut_slice()[offset] = buf.as_slice()[k+1]; + self.testcase_buf.as_mut_slice()[offset+1] = buf.as_slice()[k]; + } + else { + break; + } + } + + } else { + assert!(Path::new("iram.elf").exists()); + + let slice = input.target_bytes(); + let slice = slice.as_slice(); + let riscv_ins = RiscvInstructions::from_le(slice.to_vec()); + + let mut input_filename = String::from(&self.workdir); + input_filename.push_str("/"); + input_filename.push_str("testcase"); + input_filename.push_str(".elf"); + + let mut elf_template = ELF::new("iram.elf").unwrap(); + + elf_template.update(&riscv_ins); + elf_template.write_elf(&input_filename).unwrap(); + } + // std::fs::remove_file("simv_0.log").expect("simv_0.log could not be found! Please check Makefile"); + // std::fs::remove_file("simv_1.log").expect("simv_0.log could not be found! Please check Makefile"); + } + + pub fn generate_root_snapshot(&mut self) { + // Optionnal root snapshot + #[cfg(feature = "root_snapshot")] + { + let mut forged_cmd_args = format!("\ + +vcs+finish+{vcs_timeout} \ + -cm {coverage_metrics} \ + -cm_dir {coverage_directory} \ + {plus_args} \ + {vcs_args} \ + +SEED={seed}", + plus_args = self.plus_args, + vcs_args = self.vcs_args, + vcs_timeout = self.vcs_timeout, + coverage_metrics = self.coverage_metrics, + coverage_directory = self.coverage_directory, + seed = self.seed); + + forged_cmd_args.push_str(&format!(" +ntb_random_seed={} ", self.seed)); + + let args_vec: Vec<&str> = forged_cmd_args.split(' ').collect(); + let args_v = &args_vec[0 .. args_vec.len()]; + + #[cfg(feature = "debug")] + cprintln!("[INFO] generating initial snapshot..."); + + let _output = Exec::cmd("./simv") + .args(args_v) + .stdout(Redirection::Pipe) + .stderr(Redirection::Merge) + .capture().unwrap() + .stdout_str(); + } + } +} + + +#[cfg(feature = "std")] +#[cfg(test)] +mod tests { + + #[test] + fn test_simv_executor() { + } +} + diff --git a/fuzzers/cva6-vcs-processorfuzz/src/template.c b/fuzzers/cva6-vcs-processorfuzz/src/template.c new file mode 100644 index 0000000..86db64c --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/template.c @@ -0,0 +1,29 @@ +/* +** +** Copyright 2020 OpenHW Group +** +** Licensed under the Solderpad Hardware Licence, 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 +** +** https://solderpad.org/licenses/ +** +** 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. +** +*/ + +#include + +int main(int argc, char* arg[]) { + + int a = 0; + for (int i = 0; i < 5; i++) + { + a += i; + } + return 0; +} diff --git a/fuzzers/cva6-vcs-processorfuzz/src/testcase.S b/fuzzers/cva6-vcs-processorfuzz/src/testcase.S new file mode 100644 index 0000000..8a94ccb --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/testcase.S @@ -0,0 +1,162 @@ +# Copyright 2022 Thales DIS design services SAS +# +# Licensed under the Solderpad Hardware Licence, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.0 +# You may obtain a copy of the License at https://solderpad.org/licenses/ +# +# Original Author: Guillaume Chauvon (guillaume.chauvon@thalesgroup.fr) + +#***************************************************************************** +# custom_test_template.S +#----------------------------------------------------------------------------- +# +DRAM_START: + .word 0x90000000 + +.align 2 + .globl _start + .section ".text.init" +_start: + la t0, exception_entry + csrw mtvec,t0 + la t4, DRAM_START + sw x0, (t4) + + la x1, DRAM_START + la x2, DRAM_START + la x3, DRAM_START + la x4, DRAM_START + la x5, DRAM_START + la x6, DRAM_START + la x7, DRAM_START + la x8, DRAM_START + la x9, DRAM_START + la x10,DRAM_START + la x11,DRAM_START + la x12,DRAM_START + la x13,DRAM_START + la x14,DRAM_START + la x15,DRAM_START + la x16,DRAM_START + la x17,DRAM_START + la x18,DRAM_START + la x19,DRAM_START + la x20,DRAM_START + la x21,DRAM_START + la x22,DRAM_START + la x23,DRAM_START + la x24,DRAM_START + la x25,DRAM_START + la x26,DRAM_START + la x27,DRAM_START + la x28,DRAM_START + la x29,DRAM_START + la x30,DRAM_START + la x31,DRAM_START + + jal x0, payload + +.align 2 +exception_entry: + csrr t0, mepc + lb t1, 0(t0) + li a0, 0x3 + and t1, t1, a0 + + // stop the fuzzer if counter reaches threshold + la t4, DRAM_START + lw t5, (t4) + add t5, t5, 1 + li t6, 10 + beq t5, t6, exit + sw t5, (t4) + /* Increment mepc by 2 or 4 depending on whether the instruction at mepc + is compressed or not. */ + bne t1, a0, end_handler_incr_mepc2 + addi t0, t0, 2 +end_handler_incr_mepc2: + addi t0, t0, 2 + csrw mepc, t0 +end_handler_ret: + mret + +//csrr t3, mepc +//csrr t4, 0xfC2 #CSR_MSTATUS_REG_ADDR +//andi t4, t4, 0x00000080 #MSTATUS_IL_MASK +//srli t4, t4, 7 #MSTATUS_IL_SHIFT +//slli t4, t4, 1 #*2 +//add t3, t3, t4 +//add t3, t3, 2 +//csrw mepc, t3 +//la t4, DRAM_START +//lw t5, (t4) +//add t5, t5, 1 +//li t6, 100 +//beq t5, t6, exit +//sw t5, (t4) +//mret +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 + +.align 4 +exit: + la t3, end + csrw sepc, t3 + mret +.align 4 +end: + wfi + jal x0, end + +.globl payload +.align 4 +// .section ".text" +payload: +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.rept 17000 + .word 0xDEADBEEF +// .word 0xDEADBEAF +// .word 0xABABABAB +.endr +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 +.word 0x00000013 + +.globl code +.align 4 +.section ".text" +.rept 17000 + .word 0xDEADBEEF +.endr diff --git a/fuzzers/cva6-vcs-processorfuzz/src/testcase.ld b/fuzzers/cva6-vcs-processorfuzz/src/testcase.ld new file mode 100644 index 0000000..a50b017 --- /dev/null +++ b/fuzzers/cva6-vcs-processorfuzz/src/testcase.ld @@ -0,0 +1,66 @@ +/*======================================================================*/ +/* Proxy kernel linker script */ +/*======================================================================*/ +/* This is the linker script used when building the proxy kernel. */ + +/*----------------------------------------------------------------------*/ +/* Setup */ +/*----------------------------------------------------------------------*/ + +/* The OUTPUT_ARCH command specifies the machine architecture where the + argument is one of the names used in the BFD library. More + specifically one of the entires in bfd/cpu-mips.c */ + +OUTPUT_ARCH( "riscv" ) +ENTRY(_start) + +/*----------------------------------------------------------------------*/ +/* Sections */ +/*----------------------------------------------------------------------*/ + +SECTIONS +{ + + /* text: test code section */ + . = 0x80000000; + .text.init : { *(.text.init) } + + . = ALIGN(0x1000); + .tohost : { *(.tohost) } + + . = ALIGN(0x1000); + .text : { *(.text) } + + /* data segment */ + .data : { *(.data) } + + .sdata : { + __global_pointer$ = . + 0x800; + *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata*) + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + + /* bss segment */ + .sbss : { + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : { *(.bss) } + + /* thread-local data segment */ + .tdata : + { + _tdata_begin = .; + *(.tdata) + _tdata_end = .; + } + .tbss : + { + *(.tbss) + _tbss_end = .; + } + + /* End of uninitalized data segement */ + _end = .; +} + diff --git a/libpresifuzz_feedbacks/src/differential_feedback.rs b/libpresifuzz_feedbacks/src/differential_feedback.rs index a4ebe5c..5ebf91a 100644 --- a/libpresifuzz_feedbacks/src/differential_feedback.rs +++ b/libpresifuzz_feedbacks/src/differential_feedback.rs @@ -17,8 +17,10 @@ use libafl::{ use libafl_bolts::Named; -use libpresifuzz_observers::trace_observer::{ExecTrace, ExecTraceParser}; +use libpresifuzz_observers::trace_observer::{ExecTraceObserver, ExecTraceParser}; +use libpresifuzz_observers::trace_observer::OpLog::MemOp; +use libpresifuzz_observers::trace_observer::OpLog::RegOp; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct DifferentialFeedback @@ -49,12 +51,36 @@ where EM: EventFirer, OT: ObserversTuple { - let ref_observer = observers.match_name::>(&self.ref_observer_name).unwrap(); - let core_observer = observers.match_name::>(&self.core_observer_name).unwrap(); + let ref_observer = observers.match_name::>(&self.ref_observer_name).unwrap(); + let core_observer = observers.match_name::>(&self.core_observer_name).unwrap(); if core_observer.cnt() == ref_observer.cnt() { return Ok(false); } + + for k in 0..ref_observer.cnt() { + if ref_observer.trace()[k].pc != core_observer.trace()[k].pc{ + return Ok(false); + } + + if ref_observer.trace()[k].inst != core_observer.trace()[k].inst{ + return Ok(false); + } + + if ref_observer.trace()[k].ops.len() != core_observer.trace()[k].ops.len() { + return Ok(false); + } + + for j in 0..ref_observer.trace()[k].ops.len() { + let ref_op = &ref_observer.trace()[k].ops[j]; + let core_op = &core_observer.trace()[k].ops[j]; + + if *ref_op != *core_op { + return Ok(false); + } + } + } + Ok(true) } } diff --git a/libpresifuzz_observers/src/trace_observer.rs b/libpresifuzz_observers/src/trace_observer.rs index 80abb54..b19ddca 100644 --- a/libpresifuzz_observers/src/trace_observer.rs +++ b/libpresifuzz_observers/src/trace_observer.rs @@ -20,8 +20,76 @@ use std::{str}; use regex::Regex; use std::io::{self, BufRead}; +#[derive(Copy, Clone, Serialize, Deserialize, Debug)] +pub struct CSRLog { + mstatus_xIE: u32, + mstatus_xPIE: u32, + mstatus_xPP: u32, + mstatus_XS: u32, + mstatus_FS: u32, + mstatus_MPRV: u32, + mstatus_SUM: u32, + mstatus_MXR: u32, + mstatus_TVM: u32, + mstatus_TW: u32, + mstatus_TSR: u32, + mstatus_xXL: u32, + mstatus_SD: u32, + mscause: u32, + medeleg: u32, + mscounteren: u32, + frm: u32, + fflags: u32 +} +// Implement PartialEq for CSRLog +impl PartialEq for CSRLog { + fn eq(&self, other: &Self) -> bool { + self.mstatus_xIE == other.mstatus_xIE && + self.mstatus_xPIE == other.mstatus_xPIE && + self.mstatus_xPP == other.mstatus_xPP && + self.mstatus_XS == other.mstatus_XS && + self.mstatus_FS == other.mstatus_FS && + self.mstatus_MPRV == other.mstatus_MPRV && + self.mstatus_SUM == other.mstatus_SUM && + self.mstatus_MXR == other.mstatus_MXR && + self.mstatus_TVM == other.mstatus_TVM && + self.mstatus_TW == other.mstatus_TW && + self.mstatus_TSR == other.mstatus_TSR && + self.mstatus_xXL == other.mstatus_xXL && + self.mstatus_SD == other.mstatus_SD && + self.mscause == other.mscause && + self.medeleg == other.medeleg && + self.mscounteren == other.mscounteren && + self.frm == other.frm && + self.fflags == other.fflags + } +} +impl CSRLog { + pub fn from_array(values: [u32; 18]) -> Self { + CSRLog { + mstatus_xIE: values[0], + mstatus_xPIE: values[1], + mstatus_xPP: values[2], + mstatus_XS: values[3], + mstatus_FS: values[4], + mstatus_MPRV: values[5], + mstatus_SUM: values[6], + mstatus_MXR: values[7], + mstatus_TVM: values[8], + mstatus_TW: values[9], + mstatus_TSR: values[10], + mstatus_xXL: values[11], + mstatus_SD: values[12], + mscause: values[13], + medeleg: values[14], + mscounteren: values[15], + frm: values[16], + fflags: values[17], + } + } +} #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub enum OpType { @@ -29,6 +97,13 @@ pub enum OpType { Write, } +// Implement PartialEq for OpType +impl PartialEq for OpType { + fn eq(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } +} + #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub struct MemOp { pub op_type: OpType, @@ -37,12 +112,29 @@ pub struct MemOp { } +// Implement PartialEq for MemOp +impl PartialEq for MemOp { + fn eq(&self, other: &Self) -> bool { + self.op_type == other.op_type && + self.address == other.address && + self.value == other.value + } +} + #[derive(Clone, Serialize, Deserialize, Debug)] pub struct RegOp { pub op_type: OpType, pub name: String, pub value: u64, +} +// Implement PartialEq for RegOp +impl PartialEq for RegOp { + fn eq(&self, other: &Self) -> bool { + self.op_type == other.op_type && + self.name == other.name && + self.value == other.value + } } #[derive(Clone, Serialize, Deserialize, Debug)] @@ -51,37 +143,48 @@ pub enum OpLog { MemOp(MemOp), } +impl PartialEq for OpLog { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (OpLog::RegOp(a), OpLog::RegOp(b)) => a == b, + (OpLog::MemOp(a), OpLog::MemOp(b)) => a == b, + _ => false, + } + } +} + #[derive(Clone, Serialize, Deserialize, Debug)] pub struct TraceLog { pub pc: u64, pub inst: u64, pub ops: Vec, + pub csr: Option, } pub trait ExecTraceParser { fn new() -> Self; - fn parse(&self, workdir: &str) -> Result, Error> ; + fn parse(&self, trace_filename: &str) -> Result, Error> ; } #[derive(Clone, Serialize, Deserialize, Debug)] #[allow(clippy::unsafe_derive_deserialize)] -pub struct ExecTrace +pub struct ExecTraceObserver { name: String, - workdir: String, + trace_filename: String, trace: Vec, trace_parser: T, } -impl ExecTrace +impl ExecTraceObserver where T: ExecTraceParser, { - pub fn new(name: &str, workdir: &str) -> Self { + pub fn new(name: &str, trace_filename: &str) -> Self { Self { name: name.to_string(), trace: Vec::::new(), - workdir: workdir.to_string(), + trace_filename: trace_filename.to_string(), trace_parser: T::new(), } } @@ -89,10 +192,14 @@ where pub fn cnt(&self) -> usize { self.trace.len() } + + pub fn trace(&self) -> &Vec { + &self.trace + } } -impl Named for ExecTrace +impl Named for ExecTraceObserver where T: ExecTraceParser, { @@ -101,7 +208,7 @@ where } } -impl HasLen for ExecTrace +impl HasLen for ExecTraceObserver where T: ExecTraceParser, { @@ -110,7 +217,7 @@ where } } -impl Observer for ExecTrace +impl Observer for ExecTraceObserver where S: UsesInput, T: ExecTraceParser, @@ -127,24 +234,24 @@ where _exit_kind: &ExitKind, ) -> Result<(), Error> { - self.trace = self.trace_parser.parse(&self.workdir).unwrap(); + self.trace = self.trace_parser.parse(&self.trace_filename).unwrap(); Ok(()) } } #[derive(Copy, Clone, Serialize, Deserialize, Debug)] -pub struct SpikeExecTrace; +pub struct SpikeExecTraceObserver; -impl ExecTraceParser for SpikeExecTrace +impl ExecTraceParser for SpikeExecTraceObserver { fn new() -> Self { - SpikeExecTrace {} + SpikeExecTraceObserver {} } - fn parse(&self, workdir: &str) -> Result, Error> { + fn parse(&self, trace_filename: &str) -> Result, Error> { let mut trace = Vec::::new(); - let spike_file = format!("{}/spike.err", workdir); + let spike_file = trace_filename; let file = File::open(spike_file).expect("Unable to open spike trace file"); let reader = io::BufReader::new(file); @@ -161,6 +268,7 @@ impl ExecTraceParser for SpikeExecTrace pc: u64::from_str_radix(&caps[1], 16).unwrap(), inst: u64::from_str_radix(&caps[2], 16).unwrap(), ops, + csr: None }); } else if let Some(caps) = spike_rest_commit_re.captures(log_line) { @@ -178,6 +286,7 @@ impl ExecTraceParser for SpikeExecTrace pc: u64::from_str_radix(&caps[1], 16).unwrap(), inst: u64::from_str_radix(&caps[2], 16).unwrap(), ops, + csr: None }); } } @@ -186,6 +295,75 @@ impl ExecTraceParser for SpikeExecTrace } } +#[derive(Copy, Clone, Serialize, Deserialize, Debug)] +pub struct ProcessorFuzzExecTraceObserver; + +impl ExecTraceParser for ProcessorFuzzExecTraceObserver +{ + fn new() -> Self { + ProcessorFuzzExecTraceObserver {} + } + fn parse(&self, trace_filename: &str) -> Result, Error> { + let mut trace = Vec::::new(); + + let spike_file = trace_filename; + + let file = File::open(spike_file).expect("Unable to open spike trace file"); + let reader = io::BufReader::new(file); + + let spike_store_commit_re = Regex::new(r"core\s+\d+: \d+ 0x(\w+) \(0x(\w+)\)\s+mem\s+0x(\w+)\s+0x(\w+)").unwrap(); + let spike_rest_commit_re = Regex::new(r"core\s+\d+: \d+ 0x(\w+) \(0x(\w+)\)\s+(\w+)\s+0x(\w+)(\s+(\w+)\s+0x(\w+))?").unwrap(); + for line in reader.lines() { + + let mut csr_values = [0u32; 18]; + + if let Ok(log_line) = &line { + + if let Some(caps) = spike_store_commit_re.captures(log_line) { + + for (i, cap) in caps.iter().skip(3).enumerate() { + if let Some(cap) = cap { + csr_values[i] = cap.as_str().parse().unwrap(); + } + } + let ops = vec![ + OpLog::MemOp(MemOp{op_type: OpType::Write, address: u64::from_str_radix(&caps[3], 16).unwrap(), value: u64::from_str_radix(&caps[4], 16).unwrap()}) + ]; + trace.push(TraceLog { + pc: u64::from_str_radix(&caps[1], 16).unwrap(), + inst: u64::from_str_radix(&caps[2], 16).unwrap(), + ops, + csr: Some(CSRLog::from_array(csr_values)) + }); + } + else if let Some(caps) = spike_rest_commit_re.captures(log_line) { + for (i, cap) in caps.iter().skip(3).enumerate() { + if let Some(cap) = cap { + csr_values[i] = cap.as_str().parse().unwrap(); + } + } + let mut ops = vec![ + OpLog::RegOp(RegOp{op_type: OpType::Read, name: caps[3].to_string(), value: u64::from_str_radix(&caps[4], 16).unwrap()}) + ]; + + if caps.get(5) != None && &caps[6] == "mem" { + ops.push(OpLog::MemOp(MemOp{op_type: OpType::Read, address: u64::from_str_radix(&caps[7], 16).unwrap(), value: u64::from_str_radix(&caps[4], 16).unwrap()})); + } else if caps.get(5) != None { + ops.push(OpLog::RegOp(RegOp{op_type: OpType::Read, name: caps[6].to_string(), value: u64::from_str_radix(&caps[7], 16).unwrap()})); + } + + trace.push(TraceLog { + pc: u64::from_str_radix(&caps[1], 16).unwrap(), + inst: u64::from_str_radix(&caps[2], 16).unwrap(), + ops, + csr: Some(CSRLog::from_array(csr_values)) + }); + } + } + } + Ok(trace) + } +} // TODO: Re-enable this test using vdb from open source design @@ -200,7 +378,7 @@ mod tests { use libafl_bolts::current_time; use libafl::prelude::InMemoryCorpus; use libafl::prelude::ConstFeedback; - use crate::trace_observer::{ExecTrace, SpikeExecTrace}; + use crate::trace_observer::{ExecTraceObserver, SpikeExecTraceObserver}; use libafl::prelude::StdState; use libafl::state::HasMaxSize; use libafl::observers::Observer; @@ -216,7 +394,7 @@ mod tests { let mut feedback = ConstFeedback::new(true); let mut objective = ConstFeedback::new(false); - let mut spike_trace_observer = ExecTrace::::new("spike_trace", "./"); + let mut spike_trace_observer = ExecTraceObserver::::new("spike_trace_observer", "./spike.err"); let mut state = StdState::new( rand,