From 83c87acd5b193f93775e34e52b9a9c4009d88682 Mon Sep 17 00:00:00 2001 From: saibotk Date: Wed, 30 Oct 2024 10:33:03 +0100 Subject: [PATCH] libafl_qemu: Add RISCV support (#2367) * libafl_qemu: Add RISCV support Adds the following targets (as features): - riscv32 - riscv64 Added `RISCVCPU` and `CPURISCVState` to the bindings allow list. Added riscv.rs to the arch module, with all necessary functions and registers implemented and mapped. The registers are the same as the ones found in qemus gdbstub xml found after a build. Additionally we added all syscall numbers for riscv 64 bit (already supported by the `syscall_numbers` crate) and also added the missing ones for riscv 32 bit. We compared both lists and their differences / equalities with a simple python script and generated a list of the missing ones, to be complete. We might PR those to the `syscall_numbers` crate later on. --------- Co-authored-by: Romain Malmain --- libafl_qemu/Cargo.toml | 2 + libafl_qemu/build_linux.rs | 8 +- libafl_qemu/libafl_qemu_build/src/bindings.rs | 4 + libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- libafl_qemu/libafl_qemu_build/src/lib.rs | 1 + libafl_qemu/libafl_qemu_sys/Cargo.toml | 2 + libafl_qemu/libafl_qemu_sys/build_linux.rs | 14 +- libafl_qemu/src/arch/mod.rs | 5 + libafl_qemu/src/arch/riscv.rs | 159 ++++++++++++++++++ libafl_sugar/Cargo.toml | 4 + 10 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 libafl_qemu/src/arch/riscv.rs 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 }