libafl_qemu injections (#1743)

* nits

* first steps

* different approach

* fixes

* remove temps

* remove temp

* initial import

* more tests

* bug hunt

* cleanup

* yaml function target 0x.... support

* final

* update doc

* other work

* Clippy, fmt

* Removed lazystatic dependency

* More small cleanups

* optimize to_lowercase

* move funtionality to libafl_qemu

* add missing file

* ready

* remove qemu_injections

* move test files to test directory

* doc update

* add todos

* fixes

* add file comment

* add test and other platform support

* fix clippy

* Replace Emulator::new_empty by Emulator::get.
Fix visibility identifier.

* clippy

* let's try this

* cpu_target?

* fmt

* cleanup build system, enable missing fuzzers

* fix qemu_launcher

* enable hexagon in qemu_launcher

* Removed useless `any` predicate in cfg attribute.
Replaced wrong types in `syscall_hook` signature.

* format

* move to read_function_argument

* add hexagon injections support

* enable injections fuzzing everywhere

* unify error msg

* Fix build, add initial toml support

* intermediate push, wip

* fix build

* More WIP

* Fix build

* Clippy

* fix qemu

* Fix arm

* fix more wrong things

* fix testcase

* try to fix it again?

* more release?

* make makefile independent of dev/release

* trying more fix?

* More ugly more works

* more trying to fix the testcase

* allow yml as filename too

* more docs

---------

Co-authored-by: Dominik Maier <dmnk@google.com>
Co-authored-by: Romain Malmain <romain.malmain@pm.me>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
van Hauser 2024-01-09 19:56:19 +01:00 committed by GitHub
parent ba8ca6723b
commit 0f2cf80085
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 951 additions and 71 deletions

View File

@ -5,15 +5,23 @@ authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenuk
edition = "2021" edition = "2021"
[features] [features]
default = ["std"] default = ["std", "injections"]
std = [] std = []
## Enable fuzzing for injections (where supported)
injections = ["libafl_qemu/injections"]
## Set emulator to big endian
be = ["libafl_qemu/be"] be = ["libafl_qemu/be"]
#! ## Mutually exclusive architectures
arm = ["libafl_qemu/arm"] arm = ["libafl_qemu/arm"]
x86_64 = ["libafl_qemu/x86_64"] x86_64 = ["libafl_qemu/x86_64"]
i386 = ["libafl_qemu/i386"] i386 = ["libafl_qemu/i386"]
aarch64 = ["libafl_qemu/aarch64"] aarch64 = ["libafl_qemu/aarch64"]
mips = ["libafl_qemu/mips"] mips = ["libafl_qemu/mips"]
ppc = ["libafl_qemu/ppc", "be"] ppc = ["libafl_qemu/ppc", "be"]
hexagon = ["libafl_qemu/hexagon"]
[profile.release] [profile.release]
lto = true lto = true

View File

@ -71,7 +71,6 @@ script='''
echo "Qemu fuzzer not supported on windows/mac" echo "Qemu fuzzer not supported on windows/mac"
''' '''
[tasks.target_dir] [tasks.target_dir]
condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] } condition = { files_not_exist = [ "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" ] }
script_runner="@shell" script_runner="@shell"
@ -285,12 +284,26 @@ mac_alias = "unsupported"
windows_alias = "unsupported" windows_alias = "unsupported"
[tasks.test_unix] [tasks.test_unix]
# Tidy up after we've run our tests so we don't hog all the disk space script_runner="@shell"
dependencies = [ "clean" ]
script_runner = "@shell"
script=''' script='''
echo "This test is skipped" echo "Profile: ${PROFILE}"
cd injection_test || exit 1
make
mkdir in || true
echo aaaaaaaaaa > in/a
timeout 10s "$(find ${TARGET_DIR} -name 'qemu_launcher')" -o out -i in -j ../injections.toml -v -- ./static >/dev/null 2>fuzz.log || true
if [ -z "$(grep -Ei "found.*injection" fuzz.log)" ]; then
echo "Fuzzer does not generate any testcases or any crashes"
echo "Logs:"
tail fuzz.log
exit 1
else
echo "Fuzzer is working"
fi
make clean
#rm -rf in out fuzz.log || true
''' '''
dependencies = ["build_unix"]
[tasks.clean] [tasks.clean]
linux_alias = "clean_unix" linux_alias = "clean_unix"

View File

@ -11,6 +11,9 @@ The following architectures are supported:
* mips * mips
* ppc * ppc
Note that the injection feature `-y` is currently only supported on x86_64
and aarch64.
## Prerequisites ## Prerequisites
```bash ```bash
sudo apt install \ sudo apt install \

View File

@ -22,7 +22,7 @@ fn main() {
.emit() .emit()
.unwrap(); .unwrap();
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc"); assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon");
let cpu_target = if cfg!(feature = "x86_64") { let cpu_target = if cfg!(feature = "x86_64") {
"x86_64".to_string() "x86_64".to_string()
@ -36,6 +36,8 @@ fn main() {
"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 = "hexagon") {
"hexagon".to_string()
} else { } else {
println!("cargo:warning=No architecture specified defaulting to x86_64..."); println!("cargo:warning=No architecture specified defaulting to x86_64...");
println!("cargo:rustc-cfg=feature=\"x86_64\""); println!("cargo:rustc-cfg=feature=\"x86_64\"");

View File

@ -0,0 +1,13 @@
all: static sqltest
sqltest: sqltest.c
gcc -g -o sqltest sqltest.c -l sqlite3 -lm
static: sqltest.c
gcc -g -o static sqltest.c -l sqlite3 -lm -static
fuzz: sqltest.c
afl-clang-fast -o fuzz sqltest.c -l sqlite3
clean:
rm -f sqltest static fuzz

View File

@ -0,0 +1,10 @@
# Injection test setup
To build the injection test target:
`make`
To run qemu_launcher with the injection detection activated:
```
target/release/qemu_launcher -y injections.yaml -i in -o out -- injection_test/static
```

Binary file not shown.

View File

@ -0,0 +1,63 @@
#include <sqlite3.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
int i;
for (i = 0; i < argc; i++) {
printf("%s=%s ", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
int LLVMFuzzerTestOneInput(char *data, size_t len) {
sqlite3 *db;
char *err_msg = 0, query[1024];
if (data[0] % 2) {
int rc = sqlite3_open_v2("example.db", &db, SQLITE_OPEN_READONLY, 0);
if (rc != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
snprintf(
query, sizeof(query),
"SELECT * FROM MyTable where user = \"user1\" and password = \"%s\"",
data);
rc = sqlite3_exec(db, query, callback, 0, &err_msg);
if (rc != SQLITE_OK) {
sqlite3_free(err_msg);
}
sqlite3_close(db);
} else {
snprintf(query, sizeof(query), "/usr/bin/id \"%s\"", data);
system(query);
}
return 0;
}
int main(int argc, char **argv) {
char pw[16];
ssize_t len = 1;
memset(pw, 0, sizeof(pw));
if (argc > 1) {
if ((len = read(0, pw, sizeof(pw) - 1)) < 4) {
fprintf(stderr, "Error: short read from stdin\n");
return -1;
}
}
return LLVMFuzzerTestOneInput(pw, (size_t)len + 1);
}

View File

@ -0,0 +1,63 @@
# The TOML Structure:
#
# You can specify multiple different injection types if you want.
# [name] # any name you want, it is not important
# tokens = ["a string", ...] # an injection string to add to the tokens list
# matches = ["a string", ...] # if on of these substrings (case insensitive) is found
# # in the parameter of the function then crash!
# # note that this is not a regex.
#
# [name.functions]
# # multiple function targets to hook can be defined
# function_name = # name of the function you want to hook.
# # if the function name starts with 0x then
# # this is the QEMU Guest address of a
# # function you want to hook that does not
# # have a symbol.
# {param = number} # which parameter to the function contains the string
# # 0 = first, 1 = second, ... 0-5 are supported (depending on architecture)
[sql]
tokens = [ "'\"\"'\"\n", "\"1\" OR '1'=\"1\"" ]
matches = [ "'\"\"'\"", "1\" OR '1'=\"1" ]
[sql.functions]
sqlite3_exec = {param = 1}
PQexec = {param = 1}
PQexecParams = {param = 1}
mysql_query = {param = 1}
mysql_send_query = {param = 1}
# Command injection. Note that for most you will need a libc with debug symbols
# We do not need this as we watch the SYS_execve syscall, this is just an
# example.
[cmd]
tokens = [
"'\"FUZZ\"'",
"\";FUZZ;\"",
"';FUZZ;'",
"$(FUZZ)",
]
matches = ["'\"FUZZ\"'"]
[cmd.functions]
popen = {param = 0}
system = {param = 0}
# LDAP injection tests
[ldap]
tokens = ["*)(FUZZ=*))(|"]
matches = ["*)(FUZZ=*))(|"]
[ldap.functions]
ldap_search_ext = {param = 3}
ldap_search_ext_s = {param = 3}
# XSS injection tests
# This is a minimal example that only checks for libxml2
[xss]
tokens = ["'\"><FUZZ"]
matches = ["'\"><FUZZ"]
[xss.functions]
htmlReadMemory = {param = 0}

View File

@ -0,0 +1,79 @@
# The YAML Structure:
#
# You can specify multiple different injection types if you want.
# -name: "name" # any name you want, it is not important
# functions:
# # multiple function targets to hook can be defined
# - function: "function_name" # name of the function you want to hook.
# # if the function name starts with 0x then
# # this is the QEMU Guest address of a
# # function you want to hook that does not
# # have a symbol.
# parameter: number # which parameter to the function contains the string
# # 0 = first, 1 = second, ... 0-5 are supported (depending on architecture)
# tests:
# # multiple tests can be defined.
# - input_value: "a string" # the injection string to add to the tokens list
# match_value: "a string" # if this substring (case insensitive) is found
# # in the parameter of the function then crash!
# # note that this is not a regex.
#
- name: "sql"
functions:
- function: "sqlite3_exec"
parameter: 1
- function: "PQexec"
parameter: 1
- function: "PQexecParams"
parameter: 1
- function: "mysql_query"
parameter: 1
- function: "mysql_send_query"
parameter: 1
tests:
- input_value: "'\"\"'"
match_value: "'\"\"'"
# this one is not needed, just to show you can have many entries:
- input_value: "1\" OR '1'=\"1"
match_value: "1\" OR '1'=\"1"
# Command injection. Note that for most you will need a libc with debug symbols
# We do not need this as we watch the SYS_execve syscall, this is just an
# example.
- name: "cmd"
functions:
- function: "popen"
parameter: 0
- function: "system"
parameter: 0
tests:
# basically a dummy because we load the better ones at src/client.rs
- input_value: "'\"FUZZ\"'"
match_value: "'\"FUZZ\"'"
- input_value: "\";FUZZ;\""
match_value: "'\"FUZZ\"'"
- input_value: "';FUZZ;'"
match_value: "'\"FUZZ\"'"
- input_value: "$(FUZZ)"
match_value: "'\"FUZZ\"'"
# LDAP injection tests
- name: "ldap"
functions:
- function: "ldap_search_ext"
parameter: 3
- function: "ldap_search_ext_s"
parameter: 3
tests:
- input_value: "*)(FUZZ=*))(|"
match_value: "*)(FUZZ=*))(|"
# XSS injection tests
# This is a minimal example that only checks for libxml2
- name: "xss"
functions:
- function: "htmlReadMemory"
parameter: 0
tests:
- input_value: "'\"><FUZZ"
match_value: "'\"><FUZZ"

View File

@ -18,8 +18,12 @@ use libafl_qemu::{
ArchExtras, Emulator, GuestAddr, QemuInstrumentationAddressRangeFilter, ArchExtras, Emulator, GuestAddr, QemuInstrumentationAddressRangeFilter,
}; };
#[cfg(feature = "injections")]
use libafl_qemu::injections::QemuInjectionHelper;
use crate::{instance::Instance, options::FuzzerOptions}; use crate::{instance::Instance, options::FuzzerOptions};
#[allow(clippy::module_name_repetitions)]
pub type ClientState = pub type ClientState =
StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>; StdState<BytesInput, InMemoryOnDiskCorpus<BytesInput>, StdRand, OnDiskCorpus<BytesInput>>;
@ -42,11 +46,11 @@ impl<'a> Client<'a> {
Ok(args) Ok(args)
} }
fn env(&self) -> Result<Vec<(String, String)>, Error> { #[allow(clippy::unused_self)] // Api should look the same as args above
let env = env::vars() fn env(&self) -> Vec<(String, String)> {
env::vars()
.filter(|(k, _v)| k != "LD_LIBRARY_PATH") .filter(|(k, _v)| k != "LD_LIBRARY_PATH")
.collect::<Vec<(String, String)>>(); .collect::<Vec<(String, String)>>()
Ok(env)
} }
fn start_pc(emu: &Emulator) -> Result<GuestAddr, Error> { fn start_pc(emu: &Emulator) -> Result<GuestAddr, Error> {
@ -59,6 +63,7 @@ impl<'a> Client<'a> {
Ok(start_pc) Ok(start_pc)
} }
#[allow(clippy::similar_names)] // elf != self
fn coverage_filter( fn coverage_filter(
&self, &self,
emu: &Emulator, emu: &Emulator,
@ -105,7 +110,7 @@ impl<'a> Client<'a> {
let mut args = self.args()?; let mut args = self.args()?;
log::debug!("ARGS: {:#?}", args); log::debug!("ARGS: {:#?}", args);
let mut env = self.env()?; let mut env = self.env();
log::debug!("ENV: {:#?}", env); log::debug!("ENV: {:#?}", env);
let (emu, mut asan) = { let (emu, mut asan) = {
@ -120,6 +125,29 @@ impl<'a> Client<'a> {
let start_pc = Self::start_pc(&emu)?; let start_pc = Self::start_pc(&emu)?;
log::debug!("start_pc @ {start_pc:#x}"); log::debug!("start_pc @ {start_pc:#x}");
#[cfg(not(feature = "injections"))]
let extra_tokens = None;
#[cfg(feature = "injections")]
let injection_helper = self
.options
.injections
.as_ref()
.map(|injections_file| {
let lower = injections_file.to_lowercase();
if lower.ends_with("yaml") || lower.ends_with("yml") {
QemuInjectionHelper::from_yaml(injections_file)
} else if lower.ends_with("toml") {
QemuInjectionHelper::from_toml(injections_file)
} else {
todo!("No injections given, what to do?");
}
})
.unwrap()
.unwrap();
#[cfg(feature = "injections")]
let extra_tokens = Some(injection_helper.tokens.clone());
emu.entry_break(start_pc); emu.entry_break(start_pc);
let ret_addr: GuestAddr = emu let ret_addr: GuestAddr = emu
@ -137,25 +165,51 @@ impl<'a> Client<'a> {
.options(self.options) .options(self.options)
.emu(&emu) .emu(&emu)
.mgr(mgr) .mgr(mgr)
.core_id(core_id); .core_id(core_id)
.extra_tokens(extra_tokens);
if is_asan && is_cmplog { if is_asan && is_cmplog {
#[cfg(not(feature = "injections"))]
let helpers = tuple_list!( let helpers = tuple_list!(
edge_coverage_helper, edge_coverage_helper,
QemuCmpLogHelper::default(), QemuCmpLogHelper::default(),
QemuAsanHelper::default(asan.take().unwrap()), QemuAsanHelper::default(asan.take().unwrap()),
); );
#[cfg(feature = "injections")]
let helpers = tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
QemuAsanHelper::default(asan.take().unwrap()),
injection_helper,
);
instance.build().run(helpers, state) instance.build().run(helpers, state)
} else if is_asan { } else if is_asan {
#[cfg(not(feature = "injections"))]
let helpers = tuple_list!( let helpers = tuple_list!(
edge_coverage_helper, edge_coverage_helper,
QemuAsanHelper::default(asan.take().unwrap()), QemuAsanHelper::default(asan.take().unwrap()),
); );
#[cfg(feature = "injections")]
let helpers = tuple_list!(
edge_coverage_helper,
QemuAsanHelper::default(asan.take().unwrap()),
injection_helper,
);
instance.build().run(helpers, state) instance.build().run(helpers, state)
} else if is_cmplog { } else if is_cmplog {
#[cfg(not(feature = "injections"))]
let helpers = tuple_list!(edge_coverage_helper, QemuCmpLogHelper::default(),); let helpers = tuple_list!(edge_coverage_helper, QemuCmpLogHelper::default(),);
#[cfg(feature = "injections")]
let helpers = tuple_list!(
edge_coverage_helper,
QemuCmpLogHelper::default(),
injection_helper,
);
instance.build().run(helpers, state) instance.build().run(helpers, state)
} else { } else {
#[cfg(not(feature = "injections"))]
let helpers = tuple_list!(edge_coverage_helper,); let helpers = tuple_list!(edge_coverage_helper,);
#[cfg(feature = "injections")]
let helpers = tuple_list!(edge_coverage_helper, injection_helper,);
instance.build().run(helpers, state) instance.build().run(helpers, state)
} }
} }

View File

@ -14,7 +14,7 @@ pub struct Harness<'a> {
ret_addr: GuestAddr, ret_addr: GuestAddr,
} }
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB pub const MAX_INPUT_SIZE: usize = 1_048_576; // 1MB
impl<'a> Harness<'a> { impl<'a> Harness<'a> {
pub fn new(emu: &Emulator) -> Result<Harness, Error> { pub fn new(emu: &Emulator) -> Result<Harness, Error> {
@ -24,7 +24,7 @@ impl<'a> Harness<'a> {
let pc: GuestReg = emu let pc: GuestReg = emu
.read_reg(Regs::Pc) .read_reg(Regs::Pc)
.map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?; .map_err(|e| Error::unknown(format!("Failed to read PC: {e:}")))?;
let stack_ptr: GuestAddr = emu let stack_ptr: GuestAddr = emu
.read_reg(Regs::Sp) .read_reg(Regs::Sp)

View File

@ -52,6 +52,7 @@ pub struct Instance<'a> {
emu: &'a Emulator, emu: &'a Emulator,
mgr: ClientMgr, mgr: ClientMgr,
core_id: CoreId, core_id: CoreId,
extra_tokens: Option<Vec<String>>,
} }
impl<'a> Instance<'a> { impl<'a> Instance<'a> {
@ -119,12 +120,21 @@ impl<'a> Instance<'a> {
let observers = tuple_list!(edges_observer, time_observer); let observers = tuple_list!(edges_observer, time_observer);
if let Some(tokenfile) = &self.options.tokens { let mut tokens = Tokens::new();
if state.metadata_map().get::<Tokens>().is_none() {
state.add_metadata(Tokens::from_file(tokenfile)?); if let Some(extra_tokens) = &self.extra_tokens {
for token in extra_tokens {
let bytes = token.as_bytes().to_vec();
let _ = tokens.add_token(&bytes);
} }
} }
if let Some(tokenfile) = &self.options.tokens {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
let harness = Harness::new(self.emu)?; let harness = Harness::new(self.emu)?;
let mut harness = |input: &BytesInput| harness.run(input); let mut harness = |input: &BytesInput| harness.run(input);
@ -213,7 +223,7 @@ impl<'a> Instance<'a> {
state state
.load_initial_inputs(fuzzer, executor, &mut self.mgr, &corpus_dirs) .load_initial_inputs(fuzzer, executor, &mut self.mgr, &corpus_dirs)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
println!("Failed to load initial corpus at {:?}", corpus_dirs); println!("Failed to load initial corpus at {corpus_dirs:?}");
process::exit(0); process::exit(0);
}); });
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());

View File

@ -11,6 +11,7 @@ use crate::version::Version;
#[readonly::make] #[readonly::make]
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
#[allow(clippy::module_name_repetitions)]
#[command( #[command(
name = format!("qemu_coverage-{}",env!("CPU_TARGET")), name = format!("qemu_coverage-{}",env!("CPU_TARGET")),
version = Version::default(), version = Version::default(),
@ -24,9 +25,17 @@ pub struct FuzzerOptions {
#[arg(short, long, help = "Output directory")] #[arg(short, long, help = "Output directory")]
pub output: String, pub output: String,
#[arg(long, help = "Tokens file")] #[arg(short = 'x', long, help = "Tokens file")]
pub tokens: Option<String>, pub tokens: Option<String>,
#[cfg(feature = "injections")]
#[arg(
short = 'j',
long,
help = "Injections TOML or YAML file definition. Filename must end in .toml or .yaml/.yml."
)]
pub injections: Option<String>,
#[arg(long, help = "Log file")] #[arg(long, help = "Log file")]
pub log: Option<String>, pub log: Option<String>,

View File

@ -1,4 +1,4 @@
use std::env; use std::{env, fmt::Write};
use clap::builder::Str; use clap::builder::Str;
@ -21,8 +21,12 @@ impl From<Version> for Str {
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")), ("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
] ]
.iter() .iter()
.map(|(k, v)| format!("{k:25}: {v}\n")) .fold(String::new(), |mut output, (k, v)| {
.collect::<String>(); // Note that write!-ing into a String can never fail, despite the return type of write! being std::fmt::Result, so it can be safely ignored or unwrapped.
// See https://rust-lang.github.io/rust-clippy/master/index.html#/format_collect
let _ = writeln!(output, "{k:25}: {v}");
output
});
format!("\n{version:}").into() format!("\n{version:}").into()
} }

View File

@ -221,7 +221,7 @@ where
// If we would assume the fuzzer loop will always exit after this, we could do this here: // If we would assume the fuzzer loop will always exit after this, we could do this here:
// manager.on_restart(state)?; // manager.on_restart(state)?;
// But as the state may grow to a few megabytes, // But as the state may grow to a few megabytes,
// for now we won' and the user has to do it (unless we find a way to do this on `Drop`). // for now we won't, and the user has to do it (unless we find a way to do this on `Drop`).
Ok(ret.unwrap()) Ok(ret.unwrap())
} }

View File

@ -105,7 +105,7 @@ impl Tokens {
/// ///
/// # Safety /// # Safety
/// The caller must ensure that the region between `token_start` and `token_stop` /// The caller must ensure that the region between `token_start` and `token_stop`
/// is a valid region, containing autotokens in the exepcted format. /// is a valid region, containing autotokens in the expected format.
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub unsafe fn from_mut_ptrs( pub unsafe fn from_mut_ptrs(
token_start: *const u8, token_start: *const u8,

View File

@ -12,17 +12,30 @@ edition = "2021"
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"] categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["document-features"]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
default = ["fork", "build_libqasan", "serdeany_autoreg"] default = ["fork", "build_libqasan", "serdeany_autoreg"]
clippy = [] # special feature for clippy, don't use in normal projects§
document-features = ["dep:document-features"]
#! # Feature Flags
#! ### General Features
## Find injections during fuzzing
injections = ["serde_yaml", "toml"]
## Python bindings support
python = ["pyo3", "pyo3-build-config"] python = ["pyo3", "pyo3-build-config"]
## Fork support
fork = ["libafl/fork"] fork = ["libafl/fork"]
## Build libqasan for address sanitization
build_libqasan = [] build_libqasan = []
# The following architecture features are mutually exclusive. #! ## The following architecture features are mutually exclusive.
x86_64 = ["libafl_qemu_sys/x86_64"] # build qemu for x86_64 (default)
## build qemu for x86_64 (default)
x86_64 = ["libafl_qemu_sys/x86_64"]
i386 = ["libafl_qemu_sys/i386"] # build qemu for i386 i386 = ["libafl_qemu_sys/i386"] # build qemu for i386
arm = ["libafl_qemu_sys/arm"] # build qemu for arm arm = ["libafl_qemu_sys/arm"] # build qemu for arm
aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64 aarch64 = ["libafl_qemu_sys/aarch64"] # build qemu for aarch64
@ -30,18 +43,22 @@ mips = ["libafl_qemu_sys/mips"] # build qemu for mips (el, use with the 'be' fea
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
## Big Endian mode
be = ["libafl_qemu_sys/be"] be = ["libafl_qemu_sys/be"]
## Usermode (mutually exclusive to Systemmode)
usermode = ["libafl_qemu_sys/usermode"] usermode = ["libafl_qemu_sys/usermode"]
## Systemmode (mutually exclusive to Usermode)
systemmode = ["libafl_qemu_sys/systemmode"] systemmode = ["libafl_qemu_sys/systemmode"]
# SerdeAny features #! ## SerdeAny features
serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] # Automatically register all `#[derive(SerdeAny)]` types at startup.
## Automatically register all `#[derive(SerdeAny)]` types at startup.
serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"]
## Automatically register all `#[derive(SerdeAny)]` types at startup.
slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking) slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking)
clippy = [] # special feature for clippy, don't use in normal projects§
[dependencies] [dependencies]
libafl = { path = "../libafl", version = "0.11.2", default-features = false, features = ["std", "derive", "regex"] } libafl = { path = "../libafl", version = "0.11.2", default-features = false, features = ["std", "derive", "regex"] }
libafl_bolts = { path = "../libafl_bolts", version = "0.11.2", default-features = false, features = ["std", "derive"] } libafl_bolts = { path = "../libafl_bolts", version = "0.11.2", default-features = false, features = ["std", "derive"] }
@ -67,8 +84,11 @@ addr2line = "0.21"
typed-arena = "2.0" typed-arena = "2.0"
paste = "1" paste = "1"
enum-map = "2.7" enum-map = "2.7"
serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file
toml = { version = "0.4.2", optional = true } # For parsing the injections toml file
pyo3 = { version = "0.18", optional = true } pyo3 = { version = "0.18", optional = true }
# Document all features of this crate (for `cargo doc`)
document-features = { version = "0.2", optional = true }
[build-dependencies] [build-dependencies]
pyo3-build-config = { version = "0.18", optional = true } pyo3-build-config = { version = "0.18", optional = true }

View File

@ -105,7 +105,7 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -113,11 +113,17 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
match idx { let reg_id = match idx {
0 => self.read_reg(Regs::X0), 0 => Regs::X0,
1 => self.read_reg(Regs::X1), 1 => Regs::X1,
_ => Err(format!("Unsupported argument: {idx:}")), 2 => Regs::X2,
} 3 => Regs::X3,
4 => Regs::X4,
5 => Regs::X5,
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(

View File

@ -102,7 +102,7 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -110,11 +110,16 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
match idx { let reg_id = match idx {
0 => self.read_reg(Regs::R0), 0 => Regs::R0,
1 => self.read_reg(Regs::R1), 1 => Regs::R1,
_ => Err(format!("Unsupported argument: {idx:}")), 2 => Regs::R2,
} 3 => Regs::R3,
// 4.. would be on the stack, let's not do this for now
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(

View File

@ -491,7 +491,7 @@ pub trait ArchExtras {
fn write_return_address<T>(&self, val: T) -> Result<(), String> fn write_return_address<T>(&self, val: T) -> Result<(), String>
where where
T: Into<GuestReg>; T: Into<GuestReg>;
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>; T: From<GuestReg>;
fn write_function_argument<T>( fn write_function_argument<T>(
@ -1004,7 +1004,7 @@ impl Emulator {
/// Should not be used if `Emulator::new` has never been used before (otherwise QEMU will not be initialized). /// Should not be used if `Emulator::new` has never been used before (otherwise QEMU will not be initialized).
/// Prefer `Emulator::get` for a safe version of this method. /// Prefer `Emulator::get` for a safe version of this method.
#[must_use] #[must_use]
unsafe fn new_empty() -> Emulator { pub unsafe fn new_empty() -> Emulator {
Emulator { _private: () } Emulator { _private: () }
} }
@ -1640,7 +1640,7 @@ impl ArchExtras for Emulator {
.write_return_address::<T>(val) .write_return_address::<T>(val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -1676,7 +1676,7 @@ pub mod pybind {
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![]; static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
extern "C" fn py_syscall_hook_wrapper( extern "C" fn py_syscall_hook_wrapper(
data: u64, _data: u64,
sys_num: i32, sys_num: i32,
a0: u64, a0: u64,
a1: u64, a1: u64,
@ -1774,7 +1774,7 @@ pub mod pybind {
fn run(&self) { fn run(&self) {
unsafe { unsafe {
self.emu.run(); self.emu.run().unwrap();
} }
} }

View File

@ -106,7 +106,7 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -114,8 +114,18 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
// TODO // Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off.
Err(format!("Unsupported argument: {idx:}")) let reg_id = match idx {
0 => Regs::R0,
1 => Regs::R1,
2 => Regs::R2,
3 => Regs::R3,
4 => Regs::R4,
5 => Regs::R5,
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(

View File

@ -567,6 +567,7 @@ where
} }
} }
#[allow(clippy::similar_names)]
pub fn reads( pub fn reads(
&self, &self,
generation_hook: Hook< generation_hook: Hook<
@ -730,6 +731,7 @@ where
write_3_exec_hook_wrapper::<QT, S>, write_3_exec_hook_wrapper::<QT, S>,
extern "C" fn(&mut HookState<5>, id: u64, addr: GuestAddr) extern "C" fn(&mut HookState<5>, id: u64, addr: GuestAddr)
); );
#[allow(clippy::similar_names)]
let execn = get_raw_hook!( let execn = get_raw_hook!(
execution_hook_n, execution_hook_n,
write_4_exec_hook_wrapper::<QT, S>, write_4_exec_hook_wrapper::<QT, S>,

View File

@ -88,7 +88,7 @@ impl crate::ArchExtras for crate::CPU {
Ok(()) Ok(())
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {

View File

@ -0,0 +1,479 @@
//! Detect injection vulnerabilities
/*
* TODOs:
* - read in export addresses of shared libraries to resolve functions
*
* Maybe:
* - return code analysis support (not needed currently)
* - regex support (not needed currently)
* - std::string and Rust String support (would need such target functions added)
*
*/
use std::{ffi::CStr, fmt::Display, fs, os::raw::c_char, path::Path};
use hashbrown::HashMap;
use libafl::{inputs::UsesInput, Error};
use serde::{Deserialize, Serialize};
use crate::{
elf::EasyElf, emu::ArchExtras, CallingConvention, Emulator, GuestAddr, Hook, QemuHelper,
QemuHelperTuple, QemuHooks, SYS_execve, SyscallHookResult,
};
/// Parses `injections.yaml`
fn parse_yaml<P: AsRef<Path> + Display>(path: P) -> Result<Vec<YamlInjectionEntry>, Error> {
serde_yaml::from_str(&fs::read_to_string(&path)?)
.map_err(|e| Error::serialize(format!("Failed to deserialize yaml at {path}: {e}")))
}
/// Parses `injections.toml`
fn parse_toml<P: AsRef<Path> + Display>(
path: P,
) -> Result<HashMap<String, InjectionDefinition>, Error> {
toml::from_str(&fs::read_to_string(&path)?)
.map_err(|e| Error::serialize(format!("Failed to deserialize toml at {path}: {e}")))
}
/// Converts the injects.yaml format to the internal toml-like format
fn yaml_entries_to_definition(
yaml_entries: &Vec<YamlInjectionEntry>,
) -> Result<HashMap<String, InjectionDefinition>, Error> {
let mut ret = HashMap::new();
for entry in yaml_entries {
let mut functions = HashMap::new();
for function in &entry.functions {
functions.insert(
function.function.clone(),
FunctionDescription {
param: function.parameter,
},
);
}
let mut matches = Vec::new();
let mut tokens = Vec::new();
for test in &entry.tests {
matches.push(test.match_value.clone());
tokens.push(test.input_value.clone());
}
if ret
.insert(
entry.name.clone(),
InjectionDefinition {
tokens,
matches,
functions,
},
)
.is_some()
{
return Err(Error::illegal_argument(format!(
"Entry {} was multiply defined!",
entry.name
)));
}
}
Ok(ret)
}
#[derive(Debug, Clone)]
struct LibInfo {
name: String,
off: GuestAddr,
}
impl LibInfo {
fn add_unique(libs: &mut Vec<LibInfo>, new_lib: LibInfo) {
if !libs.iter().any(|lib| lib.name == new_lib.name) {
libs.push(new_lib);
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Test {
input_value: String,
match_value: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Functions {
function: String,
parameter: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct YamlInjectionEntry {
name: String,
functions: Vec<Functions>,
tests: Vec<Test>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
struct FunctionDescription {
param: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InjectionDefinition {
tokens: Vec<String>,
matches: Vec<String>,
functions: HashMap<String, FunctionDescription>,
}
#[derive(Clone, Debug)]
pub struct Matches {
id: usize,
lib_name: String,
matches: Vec<Match>,
}
#[derive(Clone, Debug)]
pub struct Match {
bytes_lower: Vec<u8>,
original_value: String,
}
#[derive(Debug)]
pub struct QemuInjectionHelper {
pub tokens: Vec<String>,
definitions: HashMap<String, InjectionDefinition>,
matches_list: Vec<Matches>,
}
impl QemuInjectionHelper {
/// `configure_injections` is the main function to activate the injection
/// vulnerability detection feature.
pub fn from_yaml<P: AsRef<Path> + Display>(yaml_file: P) -> Result<Self, Error> {
let yaml_entries = parse_yaml(yaml_file)?;
let definition = yaml_entries_to_definition(&yaml_entries)?;
Self::new(definition)
}
/// `configure_injections` is the main function to activate the injection
/// vulnerability detection feature.
pub fn from_toml<P: AsRef<Path> + Display>(toml_file: P) -> Result<Self, Error> {
let definition = parse_toml(toml_file)?;
Self::new(definition)
}
pub fn new(definitions: HashMap<String, InjectionDefinition>) -> Result<Self, Error> {
let tokens = definitions
.iter()
.flat_map(|(_lib_name, definition)| &definition.tokens)
.map(ToString::to_string)
.collect();
let mut matches_list = Vec::with_capacity(definitions.len());
for (lib_name, definition) in &definitions {
let matches: Vec<Match> = definition
.matches
.iter()
.map(|match_str| {
let mut bytes_lower = match_str.as_bytes().to_vec();
bytes_lower.make_ascii_lowercase();
Match {
original_value: match_str.clone(),
bytes_lower,
}
})
.collect();
let id = matches_list.len();
matches_list.push(Matches {
lib_name: lib_name.clone(),
id,
matches,
});
}
Ok(Self {
tokens,
definitions,
matches_list,
})
}
fn on_call_check<S: UsesInput, QT: QemuHelperTuple<S>>(
hooks: &mut QemuHooks<QT, S>,
id: usize,
parameter: u8,
) {
let emu = hooks.emulator();
let reg: GuestAddr = emu
.current_cpu()
.unwrap()
.read_function_argument(CallingConvention::Cdecl, parameter)
.unwrap_or_default();
let helper = hooks.helpers_mut().match_first_type_mut::<Self>().unwrap();
let matches = &helper.matches_list[id];
//println!("reg value = {:x}", reg);
if reg != 0x00 {
let mut query = unsafe {
let c_str_ptr = reg as *const c_char;
let c_str = CStr::from_ptr(c_str_ptr);
c_str.to_bytes().to_vec()
};
query.make_ascii_lowercase();
//println!("query={}", query);
log::trace!("Checking {}", matches.lib_name);
for match_value in &matches.matches {
if match_value.bytes_lower.len() > matches.matches.len() {
continue;
}
// "crash" if we found the right value
assert!(
find_subsequence(&query, &match_value.bytes_lower).is_none(),
"Found value \"{}\" for {query:?} in {}",
match_value.original_value,
matches.lib_name
);
}
}
}
}
impl<S> QemuHelper<S> for QemuInjectionHelper
where
S: UsesInput,
{
fn init_hooks<QT>(&self, hooks: &QemuHooks<QT, S>)
where
QT: QemuHelperTuple<S>,
{
hooks.syscalls(Hook::Function(syscall_hook::<QT, S>));
}
fn first_exec<QT>(&self, hooks: &QemuHooks<QT, S>)
where
QT: QemuHelperTuple<S>,
{
let emu = hooks.emulator();
let mut libs: Vec<LibInfo> = Vec::new();
for region in emu.mappings() {
if let Some(path) = region.path().map(ToOwned::to_owned) {
if !path.is_empty() {
LibInfo::add_unique(
&mut libs,
LibInfo {
name: path.clone(),
off: region.start(),
},
);
}
}
}
for matches in &self.matches_list {
let id = matches.id;
let lib_name = &matches.lib_name;
for (name, func_definition) in &self.definitions[lib_name].functions {
let hook_addrs = if name.to_lowercase().starts_with(&"0x".to_string()) {
let func_pc = u64::from_str_radix(&name[2..], 16)
.map_err(|e| {
Error::illegal_argument(format!(
"Failed to parse hex string {name} from definition for {lib_name}: {e}"
))
})
.unwrap() as GuestAddr;
log::info!("Injections: Hooking hardcoded function {func_pc:#x}");
vec![func_pc]
} else {
libs.iter()
.filter_map(|lib| find_function(emu, &lib.name, name, lib.off).unwrap())
.map(|func_pc| {
log::info!("Injections: Function {name} found at {func_pc:#x}",);
func_pc
})
.collect()
};
if hook_addrs.is_empty() {
log::warn!("Injections: Function not found for {lib_name}: {name}",);
}
let param = func_definition.param;
for hook_addr in hook_addrs {
hooks.instruction(
hook_addr,
Hook::Closure(Box::new(move |hooks, _state, _guest_addr| {
Self::on_call_check(hooks, id, param);
})),
true,
);
}
}
}
}
}
fn syscall_hook<QT, S>(
hooks: &mut QemuHooks<QT, S>, // our instantiated QemuHooks
_state: Option<&mut S>,
syscall: i32, // syscall number
x0: GuestAddr, // registers ...
x1: GuestAddr,
_x2: GuestAddr,
_x3: GuestAddr,
_x4: GuestAddr,
_x5: GuestAddr,
_x6: GuestAddr,
_x7: GuestAddr,
) -> SyscallHookResult
where
QT: QemuHelperTuple<S>,
S: UsesInput,
{
log::trace!("syscall_hook {syscall} {SYS_execve}");
debug_assert!(i32::try_from(SYS_execve).is_ok());
if syscall == SYS_execve as i32 {
let _helper = hooks
.helpers_mut()
.match_first_type_mut::<QemuInjectionHelper>()
.unwrap();
if x0 > 0 && x1 > 0 {
let c_array = x1 as *const *const c_char;
let cmd = unsafe {
let c_str_ptr = x0 as *const c_char;
CStr::from_ptr(c_str_ptr).to_string_lossy()
};
assert_ne!(
cmd.to_lowercase(),
"fuzz",
"Found verified command injection!"
);
//println!("CMD {}", cmd);
let first_parameter = unsafe {
if (*c_array.offset(1)).is_null() {
return SyscallHookResult::new(None);
}
CStr::from_ptr(*c_array.offset(1)).to_string_lossy()
};
let second_parameter = unsafe {
if (*c_array.offset(2)).is_null() {
return SyscallHookResult::new(None);
}
CStr::from_ptr(*c_array.offset(2)).to_string_lossy()
};
if first_parameter == "-c"
&& (second_parameter.to_lowercase().contains("';fuzz;'")
|| second_parameter.to_lowercase().contains("\";fuzz;\""))
{
panic!("Found command injection!");
}
//println!("PARAMETERS First {} Second {}", first_parameter, second_
}
SyscallHookResult::new(Some(0))
} else {
SyscallHookResult::new(None)
}
}
fn find_function(
emu: &Emulator,
file: &String,
function: &str,
loadaddr: GuestAddr,
) -> Result<Option<GuestAddr>, Error> {
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(file, &mut elf_buffer)?;
let offset = if loadaddr > 0 {
loadaddr
} else {
emu.load_addr()
};
Ok(elf.resolve_symbol(function, offset))
}
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
#[cfg(test)]
mod tests {
use hashbrown::HashMap;
use super::{yaml_entries_to_definition, InjectionDefinition, YamlInjectionEntry};
#[test]
fn test_yaml_parsing() {
let injections: Vec<YamlInjectionEntry> = serde_yaml::from_str(
r#"
# LDAP injection tests
- name: "ldap"
functions:
- function: "ldap_search_ext"
parameter: 3
- function: "ldap_search_ext_s"
parameter: 3
tests:
- input_value: "*)(FUZZ=*))(|"
match_value: "*)(FUZZ=*))(|"
# XSS injection tests
# This is a minimal example that only checks for libxml2
- name: "xss"
functions:
- function: "htmlReadMemory"
parameter: 0
tests:
- input_value: "'\"><FUZZ"
match_value: "'\"><FUZZ"
"#,
)
.unwrap();
assert_eq!(injections.len(), 2);
assert_eq!(
injections.len(),
yaml_entries_to_definition(&injections)
.unwrap()
.keys()
.len(),
);
}
#[test]
fn test_toml_parsing() {
let injections: HashMap<String, InjectionDefinition> = toml::from_str(
r#"
[ldap]
tokens = ["*)(FUZZ=*))(|"]
matches = ["*)(FUZZ=*))(|"]
[ldap.functions]
ldap_search_ext = {param = 3}
ldap_search_ext_s = {param = 3}
# XSS injection tests
# This is a minimal example that only checks for libxml2
[xss]
tokens = ["'\"><FUZZ"]
matches = ["'\"><FUZZ"]
[xss.functions]
htmlReadMemory = {param = 0}
"#,
)
.unwrap();
assert_eq!(injections.len(), 2);
}
}

View File

@ -1,3 +1,8 @@
//! Welcome to `LibAFL` QEMU
//!
#![doc = include_str!("../../README.md")]
/*! */
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
// libafl_qemu only supports Linux currently // libafl_qemu only supports Linux currently
#![cfg(target_os = "linux")] #![cfg(target_os = "linux")]
// This lint triggers too often on the current GuestAddr type when emulating 64-bit targets because // This lint triggers too often on the current GuestAddr type when emulating 64-bit targets because
@ -76,6 +81,11 @@ pub mod cmplog;
#[cfg(not(any(cpu_target = "mips", cpu_target = "hexagon")))] #[cfg(not(any(cpu_target = "mips", cpu_target = "hexagon")))]
pub use cmplog::QemuCmpLogHelper; pub use cmplog::QemuCmpLogHelper;
#[cfg(feature = "injections")]
pub mod injections;
#[cfg(feature = "injections")]
pub use injections::QemuInjectionHelper;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]
pub mod snapshot; pub mod snapshot;
#[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))] #[cfg(all(emulation_mode = "usermode", not(cpu_target = "hexagon")))]

View File

@ -102,7 +102,7 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Ra, val) self.write_reg(Regs::Ra, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -110,11 +110,16 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
match idx { let reg_id = match idx {
0 => self.read_reg(Regs::A0), 0 => Regs::A0,
1 => self.read_reg(Regs::A1), 1 => Regs::A1,
_ => Err(format!("Unsupported argument: {idx:}")), 2 => Regs::A2,
} 3 => Regs::A3,
// 4.. would be on the stack, let's not do this for now
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(

View File

@ -142,7 +142,7 @@ impl crate::ArchExtras for crate::CPU {
self.write_reg(Regs::Lr, val) self.write_reg(Regs::Lr, val)
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -150,11 +150,17 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
match idx { let reg_id = match idx {
0 => self.read_reg(Regs::R3), 0 => Regs::R3,
1 => self.read_reg(Regs::R4), 1 => Regs::R4,
_ => Err(format!("Unsupported argument: {idx:}")), 2 => Regs::R5,
} 3 => Regs::R6,
4 => Regs::R7,
5 => Regs::R8,
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(

View File

@ -97,7 +97,7 @@ impl crate::ArchExtras for crate::CPU {
Ok(()) Ok(())
} }
fn read_function_argument<T>(&self, conv: CallingConvention, idx: i32) -> Result<T, String> fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, String>
where where
T: From<GuestReg>, T: From<GuestReg>,
{ {
@ -105,11 +105,17 @@ impl crate::ArchExtras for crate::CPU {
return Err(format!("Unsupported calling convention: {conv:#?}")); return Err(format!("Unsupported calling convention: {conv:#?}"));
} }
match idx { let reg_id = match idx {
0 => self.read_reg(Regs::Rdi), 0 => Regs::Rdi,
1 => self.read_reg(Regs::Rsi), 1 => Regs::Rsi,
_ => Err(format!("Unsupported argument: {idx:}")), 2 => Regs::Rdx,
} 3 => Regs::Rcx,
4 => Regs::R8,
5 => Regs::R9,
r => return Err(format!("Unsupported argument: {r:}")),
};
self.read_reg(reg_id)
} }
fn write_function_argument<T>( fn write_function_argument<T>(