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 <romain.malmain@pm.me>
This commit is contained in:
saibotk 2024-10-30 10:33:03 +01:00 committed by GitHub
parent 6eb2dafd34
commit 83c87acd5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 194 additions and 7 deletions

View File

@ -59,6 +59,8 @@ mips = [
] # build qemu for mips (el, use with the 'be' feature of mips be) ] # build qemu for mips (el, use with the 'be' feature of mips be)
ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc
hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon 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 ## Big Endian mode
be = ["libafl_qemu_sys/be"] be = ["libafl_qemu_sys/be"]

View File

@ -17,7 +17,7 @@ void __libafl_qemu_testfile() {}
pub fn build() { pub fn build() {
// Note: Unique features are checked in libafl_qemu_sys // Note: Unique features are checked in libafl_qemu_sys
println!( 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") { let emulation_mode = if cfg!(feature = "usermode") {
@ -92,6 +92,10 @@ pub fn build() {
"mips".to_string() "mips".to_string()
} else if cfg!(feature = "ppc") { } else if cfg!(feature = "ppc") {
"ppc".to_string() "ppc".to_string()
} else if cfg!(feature = "riscv32") {
"riscv32".to_string()
} else if cfg!(feature = "riscv64") {
"riscv64".to_string()
} else if cfg!(feature = "hexagon") { } else if cfg!(feature = "hexagon") {
"hexagon".to_string() "hexagon".to_string()
} else { } else {
@ -99,7 +103,7 @@ pub fn build() {
}; };
println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rerun-if-env-changed=CPU_TARGET");
println!("cargo:rustc-cfg=cpu_target=\"{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) { 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) // TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)

View File

@ -194,6 +194,10 @@ pub fn generate(
bindings bindings
.allowlist_type("ARMCPU") .allowlist_type("ARMCPU")
.allowlist_type("ARMv7MState") .allowlist_type("ARMv7MState")
} else if cpu_target == "riscv32" || cpu_target == "riscv64" {
bindings
.allowlist_type("RISCVCPU")
.allowlist_type("CPURISCVState")
} else { } else {
bindings bindings
}; };

View File

@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
pub const QEMU_DIRNAME: &str = "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)] #[allow(clippy::module_name_repetitions)]
pub struct BuildResult { pub struct BuildResult {

View File

@ -223,6 +223,7 @@ fn qemu_bindgen_clang_args(
let target_arch_dir = match cpu_target { let target_arch_dir = match cpu_target {
"x86_64" => format!("-I{}/target/i386", qemu_dir.display()), "x86_64" => format!("-I{}/target/i386", qemu_dir.display()),
"aarch64" => format!("-I{}/target/arm", 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()), _ => format!("-I{}/target/{cpu_target}", qemu_dir.display()),
}; };

View File

@ -33,6 +33,8 @@ aarch64 = [] # build qemu for aarch64
mips = [] # build qemu for mips (el, use with the 'be' feature of mips be) mips = [] # build qemu for mips (el, use with the 'be' feature of mips be)
ppc = [] # build qemu for powerpc ppc = [] # build qemu for powerpc
hexagon = [] # build qemu for hexagon hexagon = [] # build qemu for hexagon
riscv32 = [] # build qemu for riscv 32bit
riscv64 = [] # build qemu for riscv 64bit
be = [] be = []

View File

@ -41,12 +41,14 @@ pub fn build() {
// Make sure we have at most one architecutre feature set // Make sure we have at most one architecutre feature set
// Else, we default to `x86_64` - having a default makes CI easier :) // 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 // 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 // Sure aarch64 may support BE, but its not in common usage and we don't
// need it yet and so haven't tested it // 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") { let cpu_target = if cfg!(feature = "x86_64") {
"x86_64".to_string() "x86_64".to_string()
@ -60,12 +62,16 @@ pub fn build() {
"mips".to_string() "mips".to_string()
} else if cfg!(feature = "ppc") { } else if cfg!(feature = "ppc") {
"ppc".to_string() "ppc".to_string()
} else if cfg!(feature = "riscv32") {
"riscv32".to_string()
} else if cfg!(feature = "riscv64") {
"riscv64".to_string()
} else if cfg!(feature = "hexagon") { } else if cfg!(feature = "hexagon") {
"hexagon".to_string() "hexagon".to_string()
} else { } else {
env::var("CPU_TARGET").unwrap_or_else(|_| { env::var("CPU_TARGET").unwrap_or_else(|_| {
println!( 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() "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=CPU_TARGET");
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_GEN_STUBS"); println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_GEN_STUBS");
println!("cargo:rustc-cfg=cpu_target=\"{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 jobs = env::var("NUM_JOBS") let jobs = env::var("NUM_JOBS")
.ok() .ok()

View File

@ -32,3 +32,8 @@ pub use ppc::*;
pub mod hexagon; pub mod hexagon;
#[cfg(cpu_target = "hexagon")] #[cfg(cpu_target = "hexagon")]
pub use 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::*;

View File

@ -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<EnumMap<ExitArgs, Regs>> = OnceLock::new();
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
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<T>(&self) -> Result<T, QemuRWError>
where
T: From<GuestReg>,
{
self.read_reg(Regs::Ra)
}
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
self.write_reg(Regs::Ra, val)
}
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where
T: From<GuestReg>,
{
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<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
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)),
}
}
}

View File

@ -51,6 +51,10 @@ mips = ["libafl_qemu/mips"]
ppc = ["libafl_qemu/ppc"] ppc = ["libafl_qemu/ppc"]
## build qemu for hexagon ## build qemu for hexagon
hexagon = ["libafl_qemu/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] [build-dependencies]
pyo3-build-config = { version = "0.22.3", optional = true } pyo3-build-config = { version = "0.22.3", optional = true }