Skip to content

Commit 83c87ac

Browse files
saibotkrmalmain
andauthored
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>
1 parent 6eb2daf commit 83c87ac

File tree

10 files changed

+194
-7
lines changed

10 files changed

+194
-7
lines changed

libafl_qemu/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ mips = [
5959
] # build qemu for mips (el, use with the 'be' feature of mips be)
6060
ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc
6161
hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon
62+
riscv32 = ["libafl_qemu_sys/riscv32"] # build qemu for riscv 32bit
63+
riscv64 = ["libafl_qemu_sys/riscv64"] # build qemu for riscv 64bit
6264

6365
## Big Endian mode
6466
be = ["libafl_qemu_sys/be"]

libafl_qemu/build_linux.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ void __libafl_qemu_testfile() {}
1717
pub fn build() {
1818
// Note: Unique features are checked in libafl_qemu_sys
1919
println!(
20-
r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "x86_64"))"#
20+
r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "riscv32", "riscv64", "x86_64"))"#
2121
);
2222

2323
let emulation_mode = if cfg!(feature = "usermode") {
@@ -92,14 +92,18 @@ pub fn build() {
9292
"mips".to_string()
9393
} else if cfg!(feature = "ppc") {
9494
"ppc".to_string()
95+
} else if cfg!(feature = "riscv32") {
96+
"riscv32".to_string()
97+
} else if cfg!(feature = "riscv64") {
98+
"riscv64".to_string()
9599
} else if cfg!(feature = "hexagon") {
96100
"hexagon".to_string()
97101
} else {
98102
env::var("CPU_TARGET").unwrap_or_else(|_| "x86_64".to_string())
99103
};
100104
println!("cargo:rerun-if-env-changed=CPU_TARGET");
101105
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
102-
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
106+
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))");
103107

104108
let cross_cc = if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) {
105109
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)

libafl_qemu/libafl_qemu_build/src/bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ pub fn generate(
194194
bindings
195195
.allowlist_type("ARMCPU")
196196
.allowlist_type("ARMv7MState")
197+
} else if cpu_target == "riscv32" || cpu_target == "riscv64" {
198+
bindings
199+
.allowlist_type("RISCVCPU")
200+
.allowlist_type("CPURISCVState")
197201
} else {
198202
bindings
199203
};

libafl_qemu/libafl_qemu_build/src/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
1111

1212
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
1313
pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
14-
pub const QEMU_REVISION: &str = "805b14ffc44999952562e8f219d81c21a4fa50b9";
14+
pub const QEMU_REVISION: &str = "c3c9c2128566ff325aa1a2bdcedde717f7d86e2c";
1515

1616
#[allow(clippy::module_name_repetitions)]
1717
pub struct BuildResult {

libafl_qemu/libafl_qemu_build/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ fn qemu_bindgen_clang_args(
223223
let target_arch_dir = match cpu_target {
224224
"x86_64" => format!("-I{}/target/i386", qemu_dir.display()),
225225
"aarch64" => format!("-I{}/target/arm", qemu_dir.display()),
226+
"riscv32" | "riscv64" => format!("-I{}/target/riscv", qemu_dir.display()),
226227
_ => format!("-I{}/target/{cpu_target}", qemu_dir.display()),
227228
};
228229

libafl_qemu/libafl_qemu_sys/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ aarch64 = [] # build qemu for aarch64
3333
mips = [] # build qemu for mips (el, use with the 'be' feature of mips be)
3434
ppc = [] # build qemu for powerpc
3535
hexagon = [] # build qemu for hexagon
36+
riscv32 = [] # build qemu for riscv 32bit
37+
riscv64 = [] # build qemu for riscv 64bit
3638

3739
be = []
3840

libafl_qemu/libafl_qemu_sys/build_linux.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ pub fn build() {
4141

4242
// Make sure we have at most one architecutre feature set
4343
// Else, we default to `x86_64` - having a default makes CI easier :)
44-
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon");
44+
assert_unique_feature!(
45+
"arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon", "riscv32", "riscv64"
46+
);
4547

4648
// Make sure that we don't have BE set for any architecture other than arm and mips
4749
// Sure aarch64 may support BE, but its not in common usage and we don't
4850
// need it yet and so haven't tested it
49-
assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon");
51+
assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon", "riscv32", "riscv64");
5052

5153
let cpu_target = if cfg!(feature = "x86_64") {
5254
"x86_64".to_string()
@@ -60,20 +62,24 @@ pub fn build() {
6062
"mips".to_string()
6163
} else if cfg!(feature = "ppc") {
6264
"ppc".to_string()
65+
} else if cfg!(feature = "riscv32") {
66+
"riscv32".to_string()
67+
} else if cfg!(feature = "riscv64") {
68+
"riscv64".to_string()
6369
} else if cfg!(feature = "hexagon") {
6470
"hexagon".to_string()
6571
} else {
6672
env::var("CPU_TARGET").unwrap_or_else(|_| {
6773
println!(
68-
"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"
74+
"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"
6975
);
7076
"x86_64".to_string()
7177
})
7278
};
7379
println!("cargo:rerun-if-env-changed=CPU_TARGET");
7480
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_GEN_STUBS");
7581
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
76-
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
82+
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))");
7783

7884
let jobs = env::var("NUM_JOBS")
7985
.ok()

libafl_qemu/src/arch/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ pub use ppc::*;
3232
pub mod hexagon;
3333
#[cfg(cpu_target = "hexagon")]
3434
pub use hexagon::*;
35+
36+
#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))]
37+
pub mod riscv;
38+
#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))]
39+
pub use riscv::*;

libafl_qemu/src/arch/riscv.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use core::ffi::c_long;
2+
use std::sync::OnceLock;
3+
4+
use capstone::arch::BuildsCapstone;
5+
use enum_map::{enum_map, EnumMap};
6+
use num_enum::{IntoPrimitive, TryFromPrimitive};
7+
#[cfg(feature = "python")]
8+
use pyo3::prelude::*;
9+
pub use strum_macros::EnumIter;
10+
#[cfg(feature = "riscv32")]
11+
pub use syscall_numbers::riscv32::*;
12+
#[cfg(feature = "riscv64")]
13+
pub use syscall_numbers::riscv64::*;
14+
15+
// QEMU specific
16+
#[allow(non_upper_case_globals)]
17+
pub const SYS_syscalls: c_long = 447;
18+
#[allow(non_upper_case_globals)]
19+
pub const SYS_riscv_flush_icache: c_long = SYS_arch_specific_syscall + 15;
20+
#[allow(non_upper_case_globals)]
21+
pub const SYS_riscv_hwprobe: c_long = SYS_arch_specific_syscall + 14;
22+
23+
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
24+
25+
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
26+
#[repr(i32)]
27+
pub enum Regs {
28+
Zero = 0, // x0: Hardwired zero
29+
Ra = 1, // x1: Return address
30+
Sp = 2, // x2: Stack pointer
31+
Gp = 3, // x3: Global pointer
32+
Tp = 4, // x4: Thread pointer
33+
T0 = 5, // x5: Temporary register
34+
T1 = 6, // x6: Temporary register
35+
T2 = 7, // x7: Temporary register
36+
FP = 8, // x8: Saved register / frame pointer
37+
S1 = 9, // x9: Saved register
38+
A0 = 10, // x10: Function argument / return value
39+
A1 = 11, // x11: Function argument / return value
40+
A2 = 12, // x12: Function argument
41+
A3 = 13, // x13: Function argument
42+
A4 = 14, // x14: Function argument
43+
A5 = 15, // x15: Function argument
44+
A6 = 16, // x16: Function argument
45+
A7 = 17, // x17: Function argument
46+
S2 = 18, // x18: Saved register
47+
S3 = 19, // x19: Saved register
48+
S4 = 20, // x20: Saved register
49+
S5 = 21, // x21: Saved register
50+
S6 = 22, // x22: Saved register
51+
S7 = 23, // x23: Saved register
52+
S8 = 24, // x24: Saved register
53+
S9 = 25, // x25: Saved register
54+
S10 = 26, // x26: Saved register
55+
S11 = 27, // x27: Saved register
56+
T3 = 28, // x28: Temporary register
57+
T4 = 29, // x29: Temporary register
58+
T5 = 30, // x30: Temporary register
59+
T6 = 31, // x31: Temporary register
60+
Pc = 32, // Program Counter (code pointer not actual register)
61+
}
62+
63+
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
64+
65+
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
66+
EXIT_ARCH_REGS.get_or_init(|| {
67+
enum_map! {
68+
ExitArgs::Ret => Regs::A0,
69+
ExitArgs::Cmd => Regs::A0,
70+
ExitArgs::Arg1 => Regs::A1,
71+
ExitArgs::Arg2 => Regs::A2,
72+
ExitArgs::Arg3 => Regs::A3,
73+
ExitArgs::Arg4 => Regs::A4,
74+
ExitArgs::Arg5 => Regs::A5,
75+
ExitArgs::Arg6 => Regs::A6,
76+
}
77+
})
78+
}
79+
80+
#[cfg(not(feature = "riscv64"))]
81+
pub type GuestReg = u32;
82+
#[cfg(feature = "riscv64")]
83+
pub type GuestReg = u64;
84+
85+
/// Return a RISCV ArchCapstoneBuilder
86+
pub fn capstone() -> capstone::arch::riscv::ArchCapstoneBuilder {
87+
#[cfg(not(feature = "riscv64"))]
88+
return capstone::Capstone::new()
89+
.riscv()
90+
.mode(capstone::arch::riscv::ArchMode::RiscV32);
91+
#[cfg(feature = "riscv64")]
92+
return capstone::Capstone::new()
93+
.riscv()
94+
.mode(capstone::arch::riscv::ArchMode::RiscV64);
95+
}
96+
97+
impl crate::ArchExtras for crate::CPU {
98+
fn read_return_address<T>(&self) -> Result<T, QemuRWError>
99+
where
100+
T: From<GuestReg>,
101+
{
102+
self.read_reg(Regs::Ra)
103+
}
104+
105+
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
106+
where
107+
T: Into<GuestReg>,
108+
{
109+
self.write_reg(Regs::Ra, val)
110+
}
111+
112+
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
113+
where
114+
T: From<GuestReg>,
115+
{
116+
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
117+
118+
// 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.
119+
// Note: This does not consider the floating point registers.
120+
// See https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
121+
let reg_id = match idx {
122+
0 => Regs::A0, // argument / return value
123+
1 => Regs::A1, // argument / return value
124+
2 => Regs::A2, // argument value
125+
3 => Regs::A3, // argument value
126+
4 => Regs::A4, // argument value
127+
5 => Regs::A5, // argument value
128+
6 => Regs::A6, // argument value
129+
7 => Regs::A7, // argument value
130+
r => {
131+
return Err(QemuRWError::new_argument_error(
132+
QemuRWErrorKind::Read,
133+
i32::from(r),
134+
))
135+
}
136+
};
137+
138+
self.read_reg(reg_id)
139+
}
140+
141+
fn write_function_argument<T>(
142+
&self,
143+
conv: CallingConvention,
144+
idx: i32,
145+
val: T,
146+
) -> Result<(), QemuRWError>
147+
where
148+
T: Into<GuestReg>,
149+
{
150+
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
151+
152+
let val: GuestReg = val.into();
153+
match idx {
154+
0 => self.write_reg(Regs::A0, val), // argument / return value
155+
1 => self.write_reg(Regs::A1, val), // argument / return value
156+
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
157+
}
158+
}
159+
}

libafl_sugar/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ mips = ["libafl_qemu/mips"]
5151
ppc = ["libafl_qemu/ppc"]
5252
## build qemu for hexagon
5353
hexagon = ["libafl_qemu/hexagon"]
54+
## build qemu for riscv 32bit
55+
riscv32 = ["libafl_qemu/riscv32"]
56+
## build qemu for riscv 64bit
57+
riscv64 = ["libafl_qemu/riscv64"]
5458

5559
[build-dependencies]
5660
pyo3-build-config = { version = "0.22.3", optional = true }

0 commit comments

Comments
 (0)