diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 8c5d38320e..75100463c7 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -59,6 +59,8 @@ mips = [ ] # build qemu for mips (el, use with the 'be' feature of mips be) ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon +riscv32 = ["libafl_qemu_sys/riscv32"] # build qemu for riscv 32bit +riscv64 = ["libafl_qemu_sys/riscv64"] # build qemu for riscv 64bit ## Big Endian mode be = ["libafl_qemu_sys/be"] diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index 20e107e313..239ec2ede2 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -17,7 +17,7 @@ void __libafl_qemu_testfile() {} pub fn build() { // Note: Unique features are checked in libafl_qemu_sys println!( - r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "x86_64"))"# + r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "riscv32", "riscv64", "x86_64"))"# ); let emulation_mode = if cfg!(feature = "usermode") { @@ -92,6 +92,10 @@ pub fn build() { "mips".to_string() } else if cfg!(feature = "ppc") { "ppc".to_string() + } else if cfg!(feature = "riscv32") { + "riscv32".to_string() + } else if cfg!(feature = "riscv64") { + "riscv64".to_string() } else if cfg!(feature = "hexagon") { "hexagon".to_string() } else { @@ -99,7 +103,7 @@ pub fn build() { }; println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); - println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))"); + println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))"); let cross_cc = if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) { // TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc) diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index 555931505d..f386fec00b 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -194,6 +194,10 @@ pub fn generate( bindings .allowlist_type("ARMCPU") .allowlist_type("ARMv7MState") + } else if cpu_target == "riscv32" || cpu_target == "riscv64" { + bindings + .allowlist_type("RISCVCPU") + .allowlist_type("CPURISCVState") } else { bindings }; diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 7056b3cca9..c879da04fd 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -pub const QEMU_REVISION: &str = "805b14ffc44999952562e8f219d81c21a4fa50b9"; +pub const QEMU_REVISION: &str = "c3c9c2128566ff325aa1a2bdcedde717f7d86e2c"; #[allow(clippy::module_name_repetitions)] pub struct BuildResult { diff --git a/libafl_qemu/libafl_qemu_build/src/lib.rs b/libafl_qemu/libafl_qemu_build/src/lib.rs index 403a209ac0..3ec004bf7f 100644 --- a/libafl_qemu/libafl_qemu_build/src/lib.rs +++ b/libafl_qemu/libafl_qemu_build/src/lib.rs @@ -223,6 +223,7 @@ fn qemu_bindgen_clang_args( let target_arch_dir = match cpu_target { "x86_64" => format!("-I{}/target/i386", qemu_dir.display()), "aarch64" => format!("-I{}/target/arm", qemu_dir.display()), + "riscv32" | "riscv64" => format!("-I{}/target/riscv", qemu_dir.display()), _ => format!("-I{}/target/{cpu_target}", qemu_dir.display()), }; diff --git a/libafl_qemu/libafl_qemu_sys/Cargo.toml b/libafl_qemu/libafl_qemu_sys/Cargo.toml index 502ff3d110..5d89ebf1e5 100644 --- a/libafl_qemu/libafl_qemu_sys/Cargo.toml +++ b/libafl_qemu/libafl_qemu_sys/Cargo.toml @@ -33,6 +33,8 @@ aarch64 = [] # build qemu for aarch64 mips = [] # build qemu for mips (el, use with the 'be' feature of mips be) ppc = [] # build qemu for powerpc hexagon = [] # build qemu for hexagon +riscv32 = [] # build qemu for riscv 32bit +riscv64 = [] # build qemu for riscv 64bit be = [] diff --git a/libafl_qemu/libafl_qemu_sys/build_linux.rs b/libafl_qemu/libafl_qemu_sys/build_linux.rs index 1b5745dd73..b3a5b6edf9 100644 --- a/libafl_qemu/libafl_qemu_sys/build_linux.rs +++ b/libafl_qemu/libafl_qemu_sys/build_linux.rs @@ -41,12 +41,14 @@ pub fn build() { // Make sure we have at most one architecutre feature set // Else, we default to `x86_64` - having a default makes CI easier :) - assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon"); + assert_unique_feature!( + "arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon", "riscv32", "riscv64" + ); // Make sure that we don't have BE set for any architecture other than arm and mips // Sure aarch64 may support BE, but its not in common usage and we don't // need it yet and so haven't tested it - assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon"); + assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon", "riscv32", "riscv64"); let cpu_target = if cfg!(feature = "x86_64") { "x86_64".to_string() @@ -60,12 +62,16 @@ pub fn build() { "mips".to_string() } else if cfg!(feature = "ppc") { "ppc".to_string() + } else if cfg!(feature = "riscv32") { + "riscv32".to_string() + } else if cfg!(feature = "riscv64") { + "riscv64".to_string() } else if cfg!(feature = "hexagon") { "hexagon".to_string() } else { env::var("CPU_TARGET").unwrap_or_else(|_| { println!( - "cargo:warning=No architecture feature enabled or CPU_TARGET env specified for libafl_qemu, supported: arm, aarch64, hexagon, i386, mips, ppc, x86_64 - defaulting to x86_64" + "cargo:warning=No architecture feature enabled or CPU_TARGET env specified for libafl_qemu, supported: arm, aarch64, hexagon, i386, mips, ppc, riscv32, riscv64, x86_64 - defaulting to x86_64" ); "x86_64".to_string() }) @@ -73,7 +79,7 @@ pub fn build() { println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_GEN_STUBS"); println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); - println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))"); + println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))"); let jobs = env::var("NUM_JOBS") .ok() diff --git a/libafl_qemu/src/arch/mod.rs b/libafl_qemu/src/arch/mod.rs index ff95a150be..f4a03b63b8 100644 --- a/libafl_qemu/src/arch/mod.rs +++ b/libafl_qemu/src/arch/mod.rs @@ -32,3 +32,8 @@ pub use ppc::*; pub mod hexagon; #[cfg(cpu_target = "hexagon")] pub use hexagon::*; + +#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))] +pub mod riscv; +#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))] +pub use riscv::*; diff --git a/libafl_qemu/src/arch/riscv.rs b/libafl_qemu/src/arch/riscv.rs new file mode 100644 index 0000000000..daa19954d8 --- /dev/null +++ b/libafl_qemu/src/arch/riscv.rs @@ -0,0 +1,159 @@ +use core::ffi::c_long; +use std::sync::OnceLock; + +use capstone::arch::BuildsCapstone; +use enum_map::{enum_map, EnumMap}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +#[cfg(feature = "python")] +use pyo3::prelude::*; +pub use strum_macros::EnumIter; +#[cfg(feature = "riscv32")] +pub use syscall_numbers::riscv32::*; +#[cfg(feature = "riscv64")] +pub use syscall_numbers::riscv64::*; + +// QEMU specific +#[allow(non_upper_case_globals)] +pub const SYS_syscalls: c_long = 447; +#[allow(non_upper_case_globals)] +pub const SYS_riscv_flush_icache: c_long = SYS_arch_specific_syscall + 15; +#[allow(non_upper_case_globals)] +pub const SYS_riscv_hwprobe: c_long = SYS_arch_specific_syscall + 14; + +use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind}; + +#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] +#[repr(i32)] +pub enum Regs { + Zero = 0, // x0: Hardwired zero + Ra = 1, // x1: Return address + Sp = 2, // x2: Stack pointer + Gp = 3, // x3: Global pointer + Tp = 4, // x4: Thread pointer + T0 = 5, // x5: Temporary register + T1 = 6, // x6: Temporary register + T2 = 7, // x7: Temporary register + FP = 8, // x8: Saved register / frame pointer + S1 = 9, // x9: Saved register + A0 = 10, // x10: Function argument / return value + A1 = 11, // x11: Function argument / return value + A2 = 12, // x12: Function argument + A3 = 13, // x13: Function argument + A4 = 14, // x14: Function argument + A5 = 15, // x15: Function argument + A6 = 16, // x16: Function argument + A7 = 17, // x17: Function argument + S2 = 18, // x18: Saved register + S3 = 19, // x19: Saved register + S4 = 20, // x20: Saved register + S5 = 21, // x21: Saved register + S6 = 22, // x22: Saved register + S7 = 23, // x23: Saved register + S8 = 24, // x24: Saved register + S9 = 25, // x25: Saved register + S10 = 26, // x26: Saved register + S11 = 27, // x27: Saved register + T3 = 28, // x28: Temporary register + T4 = 29, // x29: Temporary register + T5 = 30, // x30: Temporary register + T6 = 31, // x31: Temporary register + Pc = 32, // Program Counter (code pointer not actual register) +} + +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); + +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { + enum_map! { + ExitArgs::Ret => Regs::A0, + ExitArgs::Cmd => Regs::A0, + ExitArgs::Arg1 => Regs::A1, + ExitArgs::Arg2 => Regs::A2, + ExitArgs::Arg3 => Regs::A3, + ExitArgs::Arg4 => Regs::A4, + ExitArgs::Arg5 => Regs::A5, + ExitArgs::Arg6 => Regs::A6, + } + }) +} + +#[cfg(not(feature = "riscv64"))] +pub type GuestReg = u32; +#[cfg(feature = "riscv64")] +pub type GuestReg = u64; + +/// Return a RISCV ArchCapstoneBuilder +pub fn capstone() -> capstone::arch::riscv::ArchCapstoneBuilder { + #[cfg(not(feature = "riscv64"))] + return capstone::Capstone::new() + .riscv() + .mode(capstone::arch::riscv::ArchMode::RiscV32); + #[cfg(feature = "riscv64")] + return capstone::Capstone::new() + .riscv() + .mode(capstone::arch::riscv::ArchMode::RiscV64); +} + +impl crate::ArchExtras for crate::CPU { + fn read_return_address(&self) -> Result + where + T: From, + { + self.read_reg(Regs::Ra) + } + + fn write_return_address(&self, val: T) -> Result<(), QemuRWError> + where + T: Into, + { + self.write_reg(Regs::Ra, val) + } + + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + where + T: From, + { + QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?; + + // Note that 64 bit values may be passed in two registers (and are even-odd eg. A0, A2 and A3 where A1 is empty), then this mapping is off. + // Note: This does not consider the floating point registers. + // See https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf + let reg_id = match idx { + 0 => Regs::A0, // argument / return value + 1 => Regs::A1, // argument / return value + 2 => Regs::A2, // argument value + 3 => Regs::A3, // argument value + 4 => Regs::A4, // argument value + 5 => Regs::A5, // argument value + 6 => Regs::A6, // argument value + 7 => Regs::A7, // argument value + r => { + return Err(QemuRWError::new_argument_error( + QemuRWErrorKind::Read, + i32::from(r), + )) + } + }; + + self.read_reg(reg_id) + } + + fn write_function_argument( + &self, + conv: CallingConvention, + idx: i32, + val: T, + ) -> Result<(), QemuRWError> + where + T: Into, + { + QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?; + + let val: GuestReg = val.into(); + match idx { + 0 => self.write_reg(Regs::A0, val), // argument / return value + 1 => self.write_reg(Regs::A1, val), // argument / return value + r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)), + } + } +} diff --git a/libafl_sugar/Cargo.toml b/libafl_sugar/Cargo.toml index 8c151597f1..399a825103 100644 --- a/libafl_sugar/Cargo.toml +++ b/libafl_sugar/Cargo.toml @@ -51,6 +51,10 @@ mips = ["libafl_qemu/mips"] ppc = ["libafl_qemu/ppc"] ## build qemu for hexagon hexagon = ["libafl_qemu/hexagon"] +## build qemu for riscv 32bit +riscv32 = ["libafl_qemu/riscv32"] +## build qemu for riscv 64bit +riscv64 = ["libafl_qemu/riscv64"] [build-dependencies] pyo3-build-config = { version = "0.22.3", optional = true }