diff --git a/Cargo.toml b/Cargo.toml index 7a065f4..a5bc453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,11 @@ out_dir = "custom" members = [ "libpresifuzz_feedbacks", "libpresifuzz_observers", - "libpresifuzz_schedulers" + "libpresifuzz_schedulers", + "libpresifuzz_ec", + "libpresifuzz_mutators", + "libpresifuzz_riscv", + "libpresifuzz_stages", ] exclude = [ "fuzzers", diff --git a/fuzzers/cva6-vcs-fuzzer/Cargo.toml b/fuzzers/cva6-vcs-fuzzer/Cargo.toml new file mode 100644 index 0000000..76f87b4 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cva6_vcs_fuzzer" +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" + diff --git a/fuzzers/cva6-vcs-fuzzer/README.md b/fuzzers/cva6-vcs-fuzzer/README.md new file mode 100644 index 0000000..f40647c --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/README.md @@ -0,0 +1,54 @@ +# 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) + +## 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 +``` diff --git a/fuzzers/cva6-vcs-fuzzer/build.rs b/fuzzers/cva6-vcs-fuzzer/build.rs new file mode 100644 index 0000000..c7be299 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/build.rs @@ -0,0 +1,164 @@ +// 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: updating submodules"); + + assert!(Command::new("git") + .arg("submodule") + .arg("update") + .arg("--init") + .arg("--recursive") + .status() + .unwrap() + .success()); + + println!("INFO: cheking out good commit"); + + assert!(Command::new("git") + .arg("checkout") + .arg("b401ab3868d053a00779add51ea37cf3b8c98b21") + .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=../tests/custom/common/test.ld \ + --gcc_opts='-static -mcmodel=medany -fvisibility=hidden -nostdlib \ + -nostartfiles -g \ + ../tests/custom/common/crt.S -lgcc \ + -I../tests/custom/env -I../tests/custom/common'") + .stdout(Stdio::inherit()) + .env("CVA6_HOME_DIR", cur_dir) + .env("SPIKE_INSTALL_DIR", "/home/nasm/riscv_official") + .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-fuzzer/config.yml b/fuzzers/cva6-vcs-fuzzer/config.yml new file mode 100644 index 0000000..22dd73e --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/config.yml @@ -0,0 +1,12 @@ +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 /home/nasm/riscv/lib/libfesvr" + plus_args: + cov_enable: true + coverage_metrics: "line+fsm+cond+tgl+branch" + coverage_directory: "Coverage.vdb" + reset_coverage_before_use: true + system_timeout_s: 180 + vcs_timeout: "30us" + multi_seed: 0 diff --git a/fuzzers/cva6-vcs-fuzzer/cva6.patch b/fuzzers/cva6-vcs-fuzzer/cva6.patch new file mode 100644 index 0000000..9a88ea7 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/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-fuzzer/readme.md b/fuzzers/cva6-vcs-fuzzer/readme.md new file mode 100644 index 0000000..2e7f4fa --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/readme.md @@ -0,0 +1,65 @@ +# CVA6 Fuzzer + +Welcome to the CVA6 Fuzzer, a tool designed to fuzz the CVA6 CPU implementation, which can be found at [openhwgroup/cva6](https://github.com/openhwgroup/cva6). CVA6 is a CPU that implements a 64-bit RISC-V instruction set architecture. + +## Installation + +To install the CVA6 Fuzzer, follow these steps: + +1. Clone the repository: + + ```bash + git clone https://github.com/yourusername/cva6-fuzzer.git + + + +# CVA6 + + +To synthetize the CVA6 vcs testbench, it is require to install libfesvre that is included Spike the riscv isa simulator. +```bash +export RISCV=/home/$USER/riscv +bash ./ci/install-fesvr.sh +``` + +``` +cd verif/core-v-verif/vendor/riscv/riscv-isa-sim/ +mkdir build && cd build +../configure --prefix=$RISCV +make install-config-hdrs install-hdrs libfesvr.so +cp libfesvr.so $RISCV/lib +popd +``` + +* Note: The `setup-env.sh` script complains if Verilator is not installed, however Verilator is not used by the fuzzer. +If you do not have it already installed, feel free to comment the corresponding lines at the end of the script. +``` +cd ./verif/sim + +source ./setup-env.sh + +export DV_SIMULATORS=vcs-testharness +export CVA6_HOME_DIR=$(pwd)/../.. + +python3 cva6.py --target cv32a60x --iss=$DV_SIMULATORS --iss_yaml=cva6.yaml \ + --c_tests ../tests/custom/hello_world/hello_world.c \ + --linker=../tests/custom/common/test.ld \ + --gcc_opts="-static -mcmodel=medany -fvisibility=hidden -nostdlib \ + -nostartfiles -g \ + ../tests/custom/common/crt.S -lgcc \ + -I../tests/custom/env -I../tests/custom/common" +``` + +``` +cd fuzzers/cva6_vcs_fuzzer +cargo build +``` + +``` +./target/debug/cva6_vcs_fuzzer $CVA6_HOME_DIR/work-vcs/simv +permissive \ + +tohost_addr=80001000 \ + +elf_file=testcase.elf +permissive-off ++testcase.o +debug_disable=1 +ntb_random_seed=1 \ + -sv_lib /home/nasm/riscv/lib/libfesvr +``` + +The fuzzer runs with a custom RISCV ISA mutator diff --git a/fuzzers/cva6-vcs-fuzzer/run.sh b/fuzzers/cva6-vcs-fuzzer/run.sh new file mode 100644 index 0000000..d5f8fb5 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/run.sh @@ -0,0 +1 @@ +./simv +vcs+finish+20us -cm line+fsm+cond+tgl+branch -cm_dir Coverage.vdb +permissive +elf_file=./testcase.elf ++./testcase.elf +debug_disable=1 +ntb_random_seed=1 -sv_lib /home/sdp/riscv/lib/libfesvr +ntb_random_seed=28072 diff --git a/fuzzers/cva6-vcs-fuzzer/src/differential.rs b/fuzzers/cva6-vcs-fuzzer/src/differential.rs new file mode 100644 index 0000000..011c3ac --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/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-fuzzer/src/differential_feedback.rs b/fuzzers/cva6-vcs-fuzzer/src/differential_feedback.rs new file mode 100644 index 0000000..03462d7 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/src/differential_feedback.rs @@ -0,0 +1,146 @@ +// 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 libpresifuzz_observers::xtrace_observer::XTraceObserver; +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, + { + return Ok(false); + let mut interesting = false; + + #[cfg(feature = "input_injection")] + { + #[cfg(feature = "debug")] + cprintln!("[WARNING] Skipping trace comparison feedback because it is not supported when input_injection is enabled ..."); + return Ok(false); + } + + 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 dst_file = format!("simv_{}.log", self.counter); + // fs::copy("simv_0.log", dst_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-fuzzer/src/main.rs b/fuzzers/cva6-vcs-fuzzer/src/main.rs new file mode 100644 index 0000000..55a8a0e --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/src/main.rs @@ -0,0 +1,340 @@ +// 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::{ + corpus::{OnDiskCorpus, InMemoryCorpus}, + events::{EventConfig}, + fuzzer::{Fuzzer, StdFuzzer}, + schedulers::QueueScheduler, + events::{SimpleEventManager}, + monitors::SimpleMonitor, + state::StdState, + observers::{HitcountsMapObserver, StdMapObserver}, + feedbacks::MaxMapFeedback, + inputs::BytesInput, + monitors::multi::MultiMonitor, + HasFeedback, + feedback_not, feedback_and_fast, feedback_or, + Error, + stages::{ + StdMutationalStage + }, +}; +use libafl::executors::command::CommandConfigurator; +use libafl::state::HasMaxSize; + +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::io::Result; +use std::path::Path; +use std::env; +use std::rc::Rc; + +use clap::{App, Arg}; +use clap::AppSettings; + +use libpresifuzz_feedbacks::verdi_feedback::VerdiFeedback; +use libpresifuzz_feedbacks::sim_time_feedback::SimTimeFeedback; + +use libpresifuzz_observers::verdi_observer::VerdiShMapObserver; +use libpresifuzz_observers::verdi_observer::VerdiCoverageMetric; + +pub mod simv; +use crate::simv::SimvCommandConfigurator; + +use libpresifuzz_observers::xtrace_observer::XTraceObserver; +use libpresifuzz_observers::stats_observer::StatsObserver; + +use libpresifuzz_mutators::riscv_isa::riscv_mutations; +use libpresifuzz_mutators::scheduled::StdISAScheduledMutator; + +use libpresifuzz_ec::manager::*; +use libpresifuzz_ec::llmp::Launcher; + +use libpresifuzz_stages::mutational_objdump_sanitized::StdObjdumpSanitizedMutationalStage; +use libpresifuzz_stages::sync::SyncFromDiskStage; +use libpresifuzz_feedbacks::transferred::TransferredFeedback; + +mod differential_feedback; +mod differential; + +#[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(); +} + +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}"); + + // allocate a shared memory for coverage map + const MAP_SIZE: usize = 1024 * 232; + let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + + // create verdi observer and feedback + // monitor Toogle coverage + // apply filter if needed + let (verdi_feedback_tgl, verdi_observer_tgl) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_tgl", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Toggle, + &"".to_string() + ) + }; + + let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_tgl", MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_fsm, verdi_observer_fsm) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_fsm", + workdir, + shmem_ptr, + &VerdiCoverageMetric::FSM, + &"".to_string() + ) + }; + + let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_fsm", MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_condition, verdi_observer_condition) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_condition", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Condition, + &"".to_string() + ) + }; + + let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_condition", MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_line, verdi_observer_line) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_line", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Line, + &"".to_string() + ) + }; + + let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_line", MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + + let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_branch, verdi_observer_branch) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_branch", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Branch, + &"".to_string() + ) + }; + + let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_branch", MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + let mut feedback = feedback_or!(verdi_feedback_line, verdi_feedback_tgl, verdi_feedback_fsm, verdi_feedback_branch, verdi_feedback_condition); + + let mut objective = feedback_not!(TransferredFeedback); + + // Instantiate State with feedback, objective, in/out corpus + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + InMemoryCorpus::::new(), + 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::with_max_stack_pow(riscv_mutations(), 2); + + // Finally, instantiate the fuzzer + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut executor = simv.into_executor(tuple_list!(verdi_observer_line, verdi_observer_tgl, verdi_observer_fsm, verdi_observer_branch, verdi_observer_condition)); + + + 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)); + + 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-fuzzer/src/simv.rs b/fuzzers/cva6-vcs-fuzzer/src/simv.rs new file mode 100644 index 0000000..abcf9c8 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/src/simv.rs @@ -0,0 +1,441 @@ +// 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); + + if self.reset_coverage_before_use { + + // Clean existing vdb + assert!(Command::new("rm") + .arg("-rf") + .arg("./Coverage.vdb") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .unwrap() + .success()); + + // Copy virgin vdb + assert!(Command::new("cp") + .arg("-r") + .arg("./Virgin_coverage.vdb") + .arg("./Coverage.vdb") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .unwrap() + .success()); + } + + // 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") { + + let mut child = Command::new("bash") + .arg("./run.sh") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + let secs = Duration::from_secs(40); + let secs = Duration::from_secs(350); + + let _status_code = match child.wait_timeout(secs).unwrap() { + Some(status) => status.code(), + None => { + child.kill().unwrap(); + child.wait().unwrap().code() + } + }; + + let mut s = String::new(); + child.stdout.unwrap().read_to_string(&mut s).unwrap(); + + for (num, line) in s.split("\n").enumerate() { + println!("{}: {}", num, line); + } + + Ok(Command::new("ls") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to start process"))} + else { + Ok(Command::new("./simv") + .args(args_v) + .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-fuzzer/src/template.c b/fuzzers/cva6-vcs-fuzzer/src/template.c new file mode 100644 index 0000000..86db64c --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/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-fuzzer/src/testcase.S b/fuzzers/cva6-vcs-fuzzer/src/testcase.S new file mode 100644 index 0000000..0b745a8 --- /dev/null +++ b/fuzzers/cva6-vcs-fuzzer/src/testcase.S @@ -0,0 +1,92 @@ +# 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 +#----------------------------------------------------------------------------- +# + +.equ DRAM_START, 0x90000000 + + .globl main +main: + la t0, exception_entry + csrw mtvec,t0 + la t4, DRAM_START + sw x0, (t4) + 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 +payload: +.rept 1024 + .word 0xDEADBEEF +// .word 0xDEADBEAF +// .word 0xABABABAB +.endr +