Nyx hypercall API support for LibAFL QEMU (#2801)

* Nyx hypercall API support

* fix linux kernel fuzzer

* hash_me -> hash_64_fast

* fix multiple bug in kernel harness

* do not check libmozjpeg's C files format.
This commit is contained in:
Romain Malmain 2025-01-06 16:58:57 +01:00 committed by GitHub
parent 7c8708d4b1
commit 17336dcf57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 5254 additions and 1154 deletions

View File

@ -30,8 +30,8 @@ use libafl_bolts::{
};
use libafl_qemu::{
elf::EasyElf,
modules::{drcov::DrCovModule, StdAddressFilter},
ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, QemuExecutor,
modules::{drcov::DrCovModule, utils::filters::StdAddressFilter},
ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor,
QemuExitReason, QemuRWError, QemuShutdownCause, Regs,
};

View File

@ -39,8 +39,10 @@ use libafl_bolts::{
use libafl_qemu::{
elf::EasyElf,
modules::{
cmplog::CmpLogObserver, edges::EdgeCoverageFullVariant, EdgeCoverageModule, EmulatorModule,
EmulatorModuleTuple, NopPageFilter, StdAddressFilter, StdEdgeCoverageModule,
cmplog::CmpLogObserver,
edges::EdgeCoverageFullVariant,
utils::filters::{NopPageFilter, StdAddressFilter},
EdgeCoverageModule, EmulatorModule, EmulatorModuleTuple, StdEdgeCoverageModule,
},
Emulator, GuestAddr, Qemu, QemuExecutor,
};

View File

@ -17,19 +17,15 @@ lto = "fat"
codegen-units = 1
[dependencies]
libafl = { path = "../../../../../libafl" }
libafl_bolts = { path = "../../../../../libafl_bolts" }
libafl_qemu = { path = "../../../../../libafl_qemu", features = [
"x86_64",
"systemmode",
#"paranoid_debug"
] }
libafl_qemu_sys = { path = "../../../../../libafl_qemu/libafl_qemu_sys", features = [
libafl = { path = "../../../libafl" }
libafl_bolts = { path = "../../../libafl_bolts" }
libafl_qemu = { path = "../../../libafl_qemu", default-features = false, features = [
"x86_64",
"systemmode",
#"paranoid_debug"
] }
libafl_targets = { path = "../../../libafl_targets" }
env_logger = "0.11.5"
[build-dependencies]
libafl_qemu_build = { path = "../../../../../libafl_qemu/libafl_qemu_build" }
libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" }

View File

@ -100,20 +100,23 @@ else
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
fi
${TARGET_DIR}/${PROFILE_DIR}/qemu_systemmode_linux_kernel \
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_kernel \
-accel tcg \
-m 4G \
-drive if=pflash,format=raw,readonly=on,file="${LINUX_BUILDER_OUT}/OVMF_CODE.fd" \
-drive if=pflash,format=raw,snapshot=off,file="${LINUX_BUILDER_OUT}/OVMF_VARS.fd" \
-blockdev filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage,driver=file \
-blockdev driver=qcow2,file=storage,node-name=disk \
-device virtio-scsi-pci,id=scsi0 \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \
-blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \
-L "${LIBAFL_QEMU_BIOS_DIR}" \
-nographic \
-monitor null \
-serial null \
-snapshot
-serial null
'''
[tasks.debug]

View File

@ -1,9 +1,9 @@
obj-m += harness.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
make -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
gcc -Wall -Werror -o user user.c
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm user
make -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) clean
rm -f user

View File

@ -91,6 +91,8 @@ static int harness_find_kallsyms_lookup(void) {
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kln_addr);
if (kln_addr == 0) { return -1; }
kln_pointer = (unsigned long (*)(const char *name))kln_addr;
return ret;
@ -108,6 +110,8 @@ static int __init harness_init(void) {
err = alloc_chrdev_region(&dev, 0, 1, "harness");
if (err < 0) { return err; }
dev_major = MAJOR(dev);
harness_class = class_create("harness");
@ -120,7 +124,9 @@ static int __init harness_init(void) {
device_create(harness_class, NULL, MKDEV(dev_major, 0), NULL, "harness");
harness_find_kallsyms_lookup();
err = harness_find_kallsyms_lookup();
if (err < 0) { return err; }
return 0;
}
@ -135,7 +141,6 @@ static void __exit harness_exit(void) {
}
static int harness_open(struct inode *inode, struct file *file) {
int ret;
lqprintf("harness: Device open\n");
char *data = kzalloc(BUF_SIZE, GFP_KERNEL);
@ -144,6 +149,11 @@ static int harness_open(struct inode *inode, struct file *file) {
unsigned long x509_fn_addr = kln_pointer("x509_cert_parse");
lqprintf("harness: x509 fn addr: 0x%lx\n", x509_fn_addr);
if (x509_fn_addr == 0) {
lqprintf("harness: Error: x509 function not found.\n");
return -1;
}
// TODO: better filtering...
libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000);

View File

@ -1,6 +1,8 @@
#!/bin/bash
LINUX_MODULES=$(pacman -Ql linux-headers | grep -m 1 -E '/usr/lib/modules/[^/]*/' | sed 's|.*/usr/lib/modules/\([^/]*\)/.*|\1|')
export LINUX_MODULES
cd /setup
make clean
make -j
ls /setup

View File

@ -12,10 +12,7 @@ use libafl::{
fuzzer::{Fuzzer, StdFuzzer},
inputs::BytesInput,
monitors::MultiMonitor,
mutators::{
scheduled::{havoc_mutations, StdScheduledMutator},
I2SRandReplaceBinonly,
},
mutators::{havoc_mutations, scheduled::StdScheduledMutator, I2SRandReplaceBinonly},
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
stages::{ShadowTracingStage, StdMutationalStage},
@ -33,16 +30,10 @@ use libafl_bolts::{
use libafl_qemu::{
emu::Emulator,
executor::QemuExecutor,
modules::{
cmplog::CmpLogObserver,
edges::{
edges_map_mut_ptr, StdEdgeCoverageClassicModule, EDGES_MAP_ALLOCATED_SIZE,
MAX_EDGES_FOUND,
},
CmpLogModule,
},
modules::{cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule},
// StdEmulatorDriver
};
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
pub fn fuzz() {
env_logger::init();
@ -61,9 +52,21 @@ pub fn fuzz() {
// Initialize QEMU
let args: Vec<String> = env::args().collect();
// Create an observation channel using the coverage map
let mut edges_observer = unsafe {
HitcountsMapObserver::new(VariableMapObserver::from_mut_slice(
"edges",
OwnedMutSlice::from_raw_parts_mut(edges_map_mut_ptr(), EDGES_MAP_DEFAULT_SIZE),
&raw mut MAX_EDGES_FOUND,
))
.track_indices()
};
// Choose modules to use
let modules = tuple_list!(
StdEdgeCoverageClassicModule::builder().build(),
StdEdgeCoverageClassicModule::builder()
.map_observer(edges_observer.as_mut())
.build()?,
CmpLogModule::default(),
);
@ -81,16 +84,6 @@ pub fn fuzz() {
emulator.run(state, input).unwrap().try_into().unwrap()
};
// Create an observation channel using the coverage map
let edges_observer = unsafe {
HitcountsMapObserver::new(VariableMapObserver::from_mut_slice(
"edges",
OwnedMutSlice::from_raw_parts_mut(edges_map_mut_ptr(), EDGES_MAP_ALLOCATED_SIZE),
&raw mut MAX_EDGES_FOUND,
))
.track_indices()
};
// Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time");

View File

@ -5,6 +5,10 @@ authors = ["Romain Malmain <rmalmain@pm.me>"]
edition = "2021"
[features]
## Build and run the target with the Nyx API instead of the built-in LibAFL QEMU API.
nyx = []
shared = ["libafl_qemu/shared"]
[profile.release]
@ -16,15 +20,10 @@ codegen-units = 1
[dependencies]
libafl = { path = "../../../libafl" }
libafl_bolts = { path = "../../../libafl_bolts" }
libafl_qemu = { path = "../../../libafl_qemu", features = [
libafl_qemu = { path = "../../../libafl_qemu", default-features = false, features = [
"x86_64",
"systemmode",
# "paranoid_debug"
] }
libafl_qemu_sys = { path = "../../../libafl_qemu/libafl_qemu_sys", features = [
"x86_64",
"systemmode",
# "paranoid_debug"
# "paranoid_debug",
] }
env_logger = "0.11.5"
libafl_targets = { path = "../../../libafl_targets" }

View File

@ -1,12 +1,20 @@
env_scripts = ['''
#!@duckscript
profile = get_env PROFILE
harness_api = get_env HARNESS_API
if eq ${profile} "dev"
set_env PROFILE_DIR debug
else
set_env PROFILE_DIR ${profile}
end
if eq ${harness_api} "nyx"
set_env FEATURE nyx
else
set_env FEATURE ""
end
''', '''
#!@duckscript
runs_on_ci = get_env RUN_ON_CI
@ -25,12 +33,12 @@ TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}"
LIBAFL_QEMU_CLONE_DIR = { value = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/qemu-libafl-bridge", condition = { env_not_set = [
"LIBAFL_QEMU_DIR",
] } }
LINUX_BUILDER_URL = "git@github.com:AFLplusplus/linux-qemu-image-builder.git"
LINUX_BUILDER_DIR = { value = "${TARGET_DIR}/linux_builder", condition = { env_not_set = [
"LINUX_BUILDER_DIR",
] } }
LINUX_BUILDER_OUT = "${LINUX_BUILDER_DIR}/output"
HARNESS_API = { value = "lqemu", condition = { env_not_set = ["HARNESS_API"] } }
[tasks.target_dir]
condition = { files_not_exist = [
@ -51,7 +59,22 @@ script = '''
git clone ${LINUX_BUILDER_URL} ${LINUX_BUILDER_DIR}
'''
[tasks.compile_target]
[tasks.compile_target_nyx]
condition = { env = { "HARNESS_API" = "nyx" } }
dependencies = ["target_dir", "linux_builder_dir"]
command = "clang"
args = [
"-O0",
"-static",
"${WORKING_DIR}/example/harness_nyx.c",
"-o",
"${TARGET_DIR}/runtime/harness",
"-I",
"${TARGET_DIR}/${PROFILE_DIR}/include",
]
[tasks.compile_target_native]
condition = { env = { "HARNESS_API" = "lqemu" } }
dependencies = ["target_dir", "linux_builder_dir"]
command = "clang"
args = [
@ -64,6 +87,9 @@ args = [
"${TARGET_DIR}/${PROFILE_DIR}/include",
]
[tasks.compile_target]
dependencies = ["compile_target_native", "compile_target_nyx"]
[tasks.target]
dependencies = ["build", "compile_target"]
script_runner = "@shell"
@ -96,7 +122,15 @@ ${LINUX_BUILDER_DIR}/update.sh
[tasks.build]
dependencies = ["target_dir"]
command = "cargo"
args = ["build", "--profile", "${PROFILE}", "--target-dir", "${TARGET_DIR}"]
args = [
"build",
"--profile",
"${PROFILE}",
"--target-dir",
"${TARGET_DIR}",
"--features",
"${FEATURE}",
]
[tasks.run]
dependencies = ["build"]
@ -111,15 +145,15 @@ else
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
fi
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_process \
-accel tcg \
-m 4G \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.fd" `# OVMF code pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.fd" `# OVMF vars pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \

View File

@ -0,0 +1,137 @@
// Adapted from
// https://github.com/google/fuzzing/blob/master/tutorial/libFuzzer/fuzz_me.cc
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>
#include <nyx_api.h>
#define PAGE_SIZE 4096
#define PAYLOAD_MAX_SIZE (1 * 1024 * 1024)
bool FuzzMe(const uint8_t *Data, size_t DataSize) {
if (DataSize > 3) {
if (Data[0] == 'F') {
if (Data[1] == 'U') {
if (Data[2] == 'Z') {
if (Data[3] == 'Z') { return true; }
}
}
}
}
return false;
}
/**
* Allocate page-aligned memory
*/
void *malloc_resident_pages(size_t num_pages) {
size_t data_size = PAGE_SIZE * num_pages;
void *ptr = NULL;
if ((ptr = aligned_alloc(PAGE_SIZE, data_size)) == NULL) {
fprintf(stderr, "Allocation failure: %s\n", strerror(errno));
goto err_out;
}
// ensure pages are aligned and resident
memset(ptr, 0x42, data_size);
if (mlock(ptr, data_size) == -1) {
fprintf(stderr, "Error locking scratch buffer: %s\n", strerror(errno));
goto err_out;
}
assert(((uintptr_t)ptr % PAGE_SIZE) == 0);
return ptr;
err_out:
free(ptr);
return NULL;
}
int agent_init(int verbose) {
host_config_t host_config;
// set ready state
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
kAFL_hypercall(HYPERCALL_KAFL_GET_HOST_CONFIG, (uintptr_t)&host_config);
if (verbose) {
fprintf(stderr, "GET_HOST_CONFIG\n");
fprintf(stderr, "\thost magic: 0x%x, version: 0x%x\n",
host_config.host_magic, host_config.host_version);
fprintf(stderr, "\tbitmap size: 0x%x, ijon: 0x%x\n",
host_config.bitmap_size, host_config.ijon_bitmap_size);
fprintf(stderr, "\tpayload size: %u KB\n",
host_config.payload_buffer_size / 1024);
fprintf(stderr, "\tworker id: %d\n", host_config.worker_id);
}
if (host_config.host_magic != NYX_HOST_MAGIC) {
hprintf("HOST_MAGIC mismatch: %08x != %08x\n", host_config.host_magic,
NYX_HOST_MAGIC);
habort("HOST_MAGIC mismatch!");
return -1;
}
if (host_config.host_version != NYX_HOST_VERSION) {
hprintf("HOST_VERSION mismatch: %08x != %08x\n", host_config.host_version,
NYX_HOST_VERSION);
habort("HOST_VERSION mismatch!");
return -1;
}
if (host_config.payload_buffer_size > PAYLOAD_MAX_SIZE) {
hprintf("Fuzzer payload size too large: %lu > %lu\n",
host_config.payload_buffer_size, PAYLOAD_MAX_SIZE);
habort("Host payload size too large!");
return -1;
}
agent_config_t agent_config = {0};
agent_config.agent_magic = NYX_AGENT_MAGIC;
agent_config.agent_version = NYX_AGENT_VERSION;
// agent_config.agent_timeout_detection = 0; // timeout by host
// agent_config.agent_tracing = 0; // trace by host
// agent_config.agent_ijon_tracing = 0; // no IJON
agent_config.agent_non_reload_mode = 0; // no persistent mode
// agent_config.trace_buffer_vaddr = 0xdeadbeef;
// agent_config.ijon_trace_buffer_vaddr = 0xdeadbeef;
agent_config.coverage_bitmap_size = host_config.bitmap_size;
// agent_config.input_buffer_size;
// agent_config.dump_payloads; // set by hypervisor (??)
kAFL_hypercall(HYPERCALL_KAFL_SET_AGENT_CONFIG, (uintptr_t)&agent_config);
return 0;
}
int main() {
kAFL_payload *pbuf = malloc_resident_pages(PAYLOAD_MAX_SIZE / PAGE_SIZE);
assert(pbuf);
agent_init(1);
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uint64_t)pbuf);
hprintf("payload size addr: %p", &pbuf->size);
hprintf("payload addr: %p", &pbuf->data);
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
// Call the target
bool ret = FuzzMe(pbuf->data, pbuf->size);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
habort("Error: post-release code has been triggered.");
}

View File

@ -3,6 +3,8 @@
use core::time::Duration;
use std::{env, path::PathBuf, process};
#[cfg(not(feature = "nyx"))]
use libafl::state::{HasExecutions, State};
use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig},
@ -10,7 +12,7 @@ use libafl::{
feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer},
inputs::BytesInput,
inputs::{BytesInput, HasTargetBytes, UsesInput},
monitors::MultiMonitor,
mutators::{havoc_mutations, I2SRandReplaceBinonly, StdScheduledMutator},
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
@ -27,13 +29,64 @@ use libafl_bolts::{
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
};
#[cfg(feature = "nyx")]
use libafl_qemu::{command::nyx::NyxCommandManager, NyxEmulatorDriver};
#[cfg(not(feature = "nyx"))]
use libafl_qemu::{command::StdCommandManager, StdEmulatorDriver};
use libafl_qemu::{
emu::Emulator,
executor::QemuExecutor,
modules::{cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule},
modules::{
cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule,
EmulatorModuleTuple,
},
FastSnapshotManager, QemuInitError,
};
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
#[cfg(feature = "nyx")]
fn get_emulator<ET, S>(
args: Vec<String>,
modules: ET,
) -> Result<
Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, FastSnapshotManager>,
QemuInitError,
>
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
<S as UsesInput>::Input: HasTargetBytes,
{
Emulator::empty()
.qemu_parameters(args)
.modules(modules)
.driver(
NyxEmulatorDriver::builder()
.allow_page_on_start(true)
.process_only(true)
.build(),
)
.command_manager(NyxCommandManager::default())
.snapshot_manager(FastSnapshotManager::default())
.build()
}
#[cfg(not(feature = "nyx"))]
fn get_emulator<ET, S>(
args: Vec<String>,
modules: ET,
) -> Result<
Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>,
QemuInitError,
>
where
ET: EmulatorModuleTuple<S>,
S: State + HasExecutions + Unpin,
<S as UsesInput>::Input: HasTargetBytes,
{
Emulator::builder().qemu_cli(args).modules(modules).build()
}
pub fn fuzz() {
env_logger::init();
@ -69,10 +122,7 @@ pub fn fuzz() {
CmpLogModule::default(),
);
let emu = Emulator::builder()
.qemu_parameters(args)
.modules(modules)
.build()?;
let emu = get_emulator(args, modules)?;
// The wrapped harness function, calling out to the LLVM-style harness
let mut harness =

View File

@ -7,9 +7,9 @@
#ifdef _WIN32
#define posix_memalign(p, a, s) \
(((*(p)) = _aligned_malloc((s), (a))), *(p) ? 0 : errno)
#define RETADDR (uintptr_t)_ReturnAddress()
#define RETADDR (uintptr_t) _ReturnAddress()
#else
#define RETADDR (uintptr_t)__builtin_return_address(0)
#define RETADDR (uintptr_t) __builtin_return_address(0)
#endif
#ifdef __GNUC__

View File

@ -265,6 +265,19 @@ pub fn hash_std(input: &[u8]) -> u64 {
}
}
/// Fast hash function for 64 bits integers minimizing collisions.
/// Adapted from <https://xorshift.di.unimi.it/splitmix64.c>
#[must_use]
pub fn hash_64_fast(mut x: u64) -> u64 {
x = (x ^ (x.overflowing_shr(30).0))
.overflowing_mul(0xbf58476d1ce4e5b9)
.0;
x = (x ^ (x.overflowing_shr(27).0))
.overflowing_mul(0x94d049bb133111eb)
.0;
x ^ (x.overflowing_shr(31).0)
}
/// Hashes the input with a given hash
///
/// Hashes the input with a given hash, depending on features:

View File

@ -41,7 +41,7 @@ use bitbybit::bitfield;
use caps::{CapSet, Capability};
#[cfg(target_os = "linux")]
use libafl_bolts::ownedref::OwnedRefMut;
use libafl_bolts::Error;
use libafl_bolts::{hash_64_fast, Error};
use libipt::PtError;
#[cfg(target_os = "linux")]
use libipt::{
@ -407,7 +407,7 @@ impl IntelPT {
let offset = decoder.offset().map_err(error_from_pt_error)?;
if b.ninsn() > 0 && skip < offset {
let id = hash_me(*previous_block_end_ip) ^ hash_me(b.ip());
let id = hash_64_fast(*previous_block_end_ip) ^ hash_64_fast(b.ip());
// SAFETY: the index is < map.len() since the modulo operation is applied
let map_loc = unsafe { map.get_unchecked_mut(id as usize % map.len()) };
*map_loc = (*map_loc).saturating_add(&1u8.into());
@ -961,21 +961,6 @@ const fn next_page_aligned_addr(address: u64) -> u64 {
(address + PAGE_SIZE as u64 - 1) & !(PAGE_SIZE as u64 - 1)
}
// copy pasted from libafl_qemu/src/modules/edges.rs
// adapted from https://xorshift.di.unimi.it/splitmix64.c
#[cfg(target_os = "linux")]
#[inline]
#[must_use]
const fn hash_me(mut x: u64) -> u64 {
x = (x ^ (x.overflowing_shr(30).0))
.overflowing_mul(0xbf58476d1ce4e5b9)
.0;
x = (x ^ (x.overflowing_shr(27).0))
.overflowing_mul(0x94d049bb133111eb)
.0;
x ^ (x.overflowing_shr(31).0)
}
#[cfg(target_os = "linux")]
#[inline]
fn smp_rmb() {

View File

@ -34,6 +34,9 @@ default = [
"injections",
]
document-features = ["dep:document-features"]
qemu_sanitizers = ["libafl_qemu_sys/qemu_sanitizers"]
paranoid_debug = [
"libafl_qemu_sys/paranoid_debug",
] # Will perform as many checks as possible. The target will be greatly slowed down.

View File

@ -51,6 +51,8 @@ pub fn build() {
let libafl_qemu_defs_hdr_name = "libafl_qemu_defs.h";
let libafl_qemu_impl_hdr_name = "libafl_qemu_impl.h";
let nyx_hdr_name = "nyx_api.h";
let libafl_runtime_dir = src_dir.join("runtime");
let libafl_qemu_hdr = libafl_runtime_dir.join(libafl_qemu_hdr_name);
@ -58,6 +60,8 @@ pub fn build() {
let libafl_qemu_defs_hdr = libafl_runtime_dir.join(libafl_qemu_defs_hdr_name);
let libafl_qemu_impl_hdr = libafl_runtime_dir.join(libafl_qemu_impl_hdr_name);
let nyx_hdr = libafl_runtime_dir.join(nyx_hdr_name);
let libafl_runtime_testfile = out_dir.join("runtime_test.c");
fs::write(&libafl_runtime_testfile, LIBAFL_QEMU_RUNTIME_TEST)
.expect("Could not write runtime test file");
@ -76,6 +80,9 @@ pub fn build() {
let runtime_bindings_file = out_dir.join("libafl_qemu_bindings.rs");
let stub_runtime_bindings_file = src_dir.join("runtime/libafl_qemu_stub_bindings.rs");
let nyx_bindings_file = out_dir.join("nyx_bindings.rs");
let stub_nyx_bindings_file = src_dir.join("runtime/nyx_stub_bindings.rs");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=build_linux.rs");
println!("cargo:rerun-if-changed={}", libafl_runtime_dir.display());
@ -121,6 +128,8 @@ pub fn build() {
if env::var("DOCS_RS").is_ok() || cfg!(feature = "clippy") {
fs::copy(&stub_runtime_bindings_file, &runtime_bindings_file)
.expect("Could not copy stub bindings file");
fs::copy(&stub_nyx_bindings_file, &nyx_bindings_file)
.expect("Could not copy stub bindings file");
return; // only build when we're not generating docs
}
@ -150,6 +159,12 @@ pub fn build() {
)
.expect("Could not copy libafl_qemu_impl.h to out directory.");
fs::copy(
nyx_hdr.clone(),
include_dir.join(nyx_hdr_name),
)
.expect("Could not copy libafl_qemu_impl.h to out directory.");
bindgen::Builder::default()
.derive_debug(true)
.derive_default(true)
@ -165,6 +180,21 @@ pub fn build() {
.write_to_file(&runtime_bindings_file)
.expect("Could not write bindings.");
bindgen::Builder::default()
.derive_debug(true)
.derive_default(true)
.impl_debug(true)
.generate_comments(true)
.default_enum_style(bindgen::EnumVariation::NewType {
is_global: true,
is_bitfield: true,
})
.header(nyx_hdr.display().to_string())
.generate()
.expect("Exit bindings generation failed.")
.write_to_file(&nyx_bindings_file)
.expect("Could not write bindings.");
maybe_generate_stub_bindings(
&cpu_target,
emulation_mode,
@ -172,6 +202,13 @@ pub fn build() {
runtime_bindings_file.as_path(),
);
maybe_generate_stub_bindings(
&cpu_target,
emulation_mode,
stub_nyx_bindings_file.as_path(),
nyx_bindings_file.as_path(),
);
if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) {
let qasan_dir = Path::new("libqasan");
let qasan_dir = fs::canonicalize(qasan_dir).unwrap();

View File

@ -29,6 +29,8 @@ slirp = [] # build qemu with host libslirp (for user networking)
clippy = [] # special feature for clippy, don't use in normal projects§
qemu_sanitizers = [] # Enable QEMU sanitizers
paranoid_debug = [
] # Will perform as many checks as possible. The target will be greatly slowed down.

View File

@ -149,10 +149,8 @@ pub fn generate(
.allowlist_type("MemOp")
.allowlist_type("DeviceSnapshotKind")
.allowlist_type("ShutdownCause")
.allowlist_type("libafl_exit_reason")
.allowlist_type("libafl_exit_reason_kind")
.allowlist_type("libafl_exit_reason_sync_backdoor")
.allowlist_type("libafl_exit_reason_breakpoint")
.allowlist_type("libafl.*")
.allowlist_type("LIBAFL.*")
.allowlist_type("Syx.*")
.allowlist_type("libafl_mapinfo")
.allowlist_type("IntervalTreeRoot")

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_DIRNAME: &str = "qemu-libafl-bridge";
pub const QEMU_REVISION: &str = "06bf8facec33548840413fba1b20858f58e38e2d";
pub const QEMU_REVISION: &str = "ace364678aef879514e150e626695c4793db41e9";
pub struct BuildResult {
pub qemu_path: PathBuf,
@ -96,9 +96,11 @@ fn configure_qemu(
.arg("--disable-tools");
if cfg!(feature = "paranoid_debug") {
cmd.arg("--enable-debug")
.arg("--enable-debug-tcg")
.arg("--enable-sanitizers");
cmd.arg("--enable-debug").arg("--enable-debug-tcg");
}
if cfg!(feature = "qemu_sanitizers") {
cmd.arg("--enable-sanitizers");
}
if is_usermode {
@ -433,29 +435,6 @@ pub fn build(
assert!(output_lib.is_file()); // Make sure this isn't very very wrong
/*
let mut objects = vec![];
for dir in [
build_dir.join("libcommon.fa.p"),
build_dir.join(format!("libqemu-{cpu_target}-{target_suffix}.fa.p")),
] {
for path in fs::read_dir(dir).unwrap() {
let path = path.unwrap().path();
if path.is_file() {
if let Some(name) = path.file_name() {
if name.to_string_lossy().starts_with("stubs") {
continue;
} else if let Some(ext) = path.extension() {
if ext == "o" {
objects.push(path);
}
}
}
}
}
}
*/
let compile_commands_string = &fs::read_to_string(libafl_qemu_build_dir.join("linkinfo.json"))
.expect("Failed to read linkinfo.json");
@ -515,90 +494,6 @@ pub fn build(
panic!("Linking failed.");
}
/* // Old manual linking, kept here for reference and debugging
if is_usermode {
Command::new("ld")
.current_dir(out_dir_path)
.arg("-o")
.arg("libqemu-partially-linked.o")
.arg("-r")
.args(objects)
.arg("--start-group")
.arg("--whole-archive")
.arg(format!("{}/libhwcore.fa", build_dir.display()))
.arg(format!("{}/libqom.fa", build_dir.display()))
.arg(format!("{}/libevent-loop-base.a", build_dir.display()))
.arg(format!("{}/gdbstub/libgdb_user.fa", build_dir.display()))
.arg("--no-whole-archive")
.arg(format!("{}/libqemuutil.a", build_dir.display()))
.arg(format!("{}/libhwcore.fa", build_dir.display()))
.arg(format!("{}/libqom.fa", build_dir.display()))
.arg(format!("{}/gdbstub/libgdb_user.fa", build_dir.display()))
.arg(format!(
"--dynamic-list={}/plugins/qemu-plugins.symbols",
qemu_path.display()
))
.arg("--end-group")
.status()
.expect("Partial linked failure");
} else {
Command::new("ld")
.current_dir(out_dir_path)
.arg("-o")
.arg("libqemu-partially-linked.o")
.arg("-r")
.args(objects)
.arg("--start-group")
.arg("--whole-archive")
.arg(format!("{}/libhwcore.fa", build_dir.display()))
.arg(format!("{}/libqom.fa", build_dir.display()))
.arg(format!("{}/libevent-loop-base.a", build_dir.display()))
.arg(format!("{}/gdbstub/libgdb_softmmu.fa", build_dir.display()))
.arg(format!("{}/libio.fa", build_dir.display()))
.arg(format!("{}/libcrypto.fa", build_dir.display()))
.arg(format!("{}/libauthz.fa", build_dir.display()))
.arg(format!("{}/libblockdev.fa", build_dir.display()))
.arg(format!("{}/libblock.fa", build_dir.display()))
.arg(format!("{}/libchardev.fa", build_dir.display()))
.arg(format!("{}/libqmp.fa", build_dir.display()))
.arg("--no-whole-archive")
.arg(format!("{}/libqemuutil.a", build_dir.display()))
.arg(format!(
"{}/subprojects/dtc/libfdt/libfdt.a",
build_dir.display()
))
.arg(format!(
"{}/subprojects/libvhost-user/libvhost-user-glib.a",
build_dir.display()
))
.arg(format!(
"{}/subprojects/libvhost-user/libvhost-user.a",
build_dir.display()
))
.arg(format!(
"{}/subprojects/libvduse/libvduse.a",
build_dir.display()
))
.arg(format!("{}/libmigration.fa", build_dir.display()))
.arg(format!("{}/libhwcore.fa", build_dir.display()))
.arg(format!("{}/libqom.fa", build_dir.display()))
.arg(format!("{}/gdbstub/libgdb_softmmu.fa", build_dir.display()))
.arg(format!("{}/libio.fa", build_dir.display()))
.arg(format!("{}/libcrypto.fa", build_dir.display()))
.arg(format!("{}/libauthz.fa", build_dir.display()))
.arg(format!("{}/libblockdev.fa", build_dir.display()))
.arg(format!("{}/libblock.fa", build_dir.display()))
.arg(format!("{}/libchardev.fa", build_dir.display()))
.arg(format!("{}/libqmp.fa", build_dir.display()))
.arg(format!(
"--dynamic-list={}/plugins/qemu-plugins.symbols",
qemu_path.display()
))
.arg("--end-group")
.status()
.expect("Partial linked failure");
}*/
Command::new("ar")
.current_dir(out_dir_path)
.arg("crs")
@ -640,29 +535,12 @@ pub fn build(
}
}
if cfg!(feature = "paranoid_debug") {
if cfg!(feature = "qemu_sanitizers") {
println!("cargo:rustc-link-lib=ubsan");
println!("cargo:rustc-link-lib=asan");
}
/*
println!("cargo:rustc-link-lib=rt");
println!("cargo:rustc-link-lib=gmodule-2.0");
println!("cargo:rustc-link-lib=glib-2.0");
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=z");
// if keyutils is available, qemu meson script will compile code with keyutils.
// therefore, we need to link with keyutils if our system have libkeyutils.
let _: Result<pkg_config::Library, pkg_config::Error> =
pkg_config::Config::new().probe("libkeyutils");
*/
if !is_usermode {
//println!("cargo:rustc-link-lib=pixman-1");
//if env::var("LINK_SLIRP").is_ok() || cfg!(feature = "slirp") {
// println!("cargo:rustc-link-lib=slirp");
//}
fs::create_dir_all(target_dir.join("pc-bios")).unwrap();
for path in fs::read_dir(libafl_qemu_build_dir.join("pc-bios")).unwrap() {
let path = path.unwrap().path();

View File

@ -56,6 +56,10 @@ clippy = [
"libafl_qemu_build/clippy",
] # special feature for clippy, don't use in normal projects
qemu_sanitizers = [
"libafl_qemu_build/qemu_sanitizers",
] # Compile QEMU with sanitizers enabled
paranoid_debug = [
"libafl_qemu_build/paranoid_debug",
] # Will perform as many checks as possible. The target will be greatly slowed down.

View File

@ -78,23 +78,28 @@
#endif
#endif
#define LIBAFL_DECLARE(name) \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0(libafl_word action); \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call1(libafl_word action, \
libafl_word arg1); \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call2(libafl_word action, \
libafl_word arg1, \
libafl_word arg2);
#ifdef _WIN32
#define LIBAFL_DEFINE_FUNCTIONS(name, _opcode) \
#ifdef __cplusplus \
extern "C" { \
#endif \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0(libafl_word action); \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call1(libafl_word action, \
##name## libafl_word arg1); \
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call2(libafl_word action, \
libafl_word arg1, \
libafl_word arg2); \
LIBAFL_DECLARE(name) \
#ifdef __cplusplus \
} \
#endif
#else
#if defined(__x86_64__)
#define LIBAFL_DEFINE_FUNCTIONS(name, opcode) \
LIBAFL_DECLARE(name) \
\
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0( \
libafl_word action) { \
libafl_word ret; \
@ -142,6 +147,8 @@
#elif defined(__arm__)
#define LIBAFL_DEFINE_FUNCTIONS(name, opcode) \
LIBAFL_DECLARE(name) \
\
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0( \
libafl_word action) { \
libafl_word ret; \
@ -189,6 +196,8 @@
#elif defined(__aarch64__)
#define LIBAFL_DEFINE_FUNCTIONS(name, opcode) \
LIBAFL_DECLARE(name) \
\
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0( \
libafl_word action) { \
libafl_word ret; \
@ -235,6 +244,8 @@
}
#elif defined(__riscv)
#define LIBAFL_DEFINE_FUNCTIONS(name, opcode) \
LIBAFL_DECLARE(name) \
\
libafl_word LIBAFL_CALLING_CONVENTION _libafl_##name##_call0( \
libafl_word action) { \
libafl_word ret; \

View File

@ -1,6 +1,6 @@
/* 1.84.0-nightly */
/* qemu git hash: 805b14ffc44999952562e8f219d81c21a4fa50b9 */
/* automatically generated by rust-bindgen 0.70.1 */
/* 1.85.0-nightly */
/* qemu git hash: 81e52dc60f83c3ae191c1a07b39bb255e633c234 */
/* automatically generated by rust-bindgen 0.71.1 */
pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607;
pub const LIBAFL_BACKDOOR_OPCODE: u32 = 1156725263;
@ -562,25 +562,25 @@ pub type cookie_io_functions_t = _IO_cookie_io_functions_t;
pub type va_list = __gnuc_va_list;
pub type off_t = __off_t;
pub type fpos_t = __fpos_t;
extern "C" {
unsafe extern "C" {
pub static mut stdin: *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub static mut stdout: *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub static mut stderr: *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn remove(__filename: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn rename(
__old: *const ::std::os::raw::c_char,
__new: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn renameat(
__oldfd: ::std::os::raw::c_int,
__old: *const ::std::os::raw::c_char,
@ -588,71 +588,71 @@ extern "C" {
__new: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fclose(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn tmpfile() -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn tmpnam(arg1: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char;
}
extern "C" {
unsafe extern "C" {
pub fn tmpnam_r(__s: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char;
}
extern "C" {
unsafe extern "C" {
pub fn tempnam(
__dir: *const ::std::os::raw::c_char,
__pfx: *const ::std::os::raw::c_char,
) -> *mut ::std::os::raw::c_char;
}
extern "C" {
unsafe extern "C" {
pub fn fflush(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fflush_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fopen(
__filename: *const ::std::os::raw::c_char,
__modes: *const ::std::os::raw::c_char,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn freopen(
__filename: *const ::std::os::raw::c_char,
__modes: *const ::std::os::raw::c_char,
__stream: *mut FILE,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn fdopen(__fd: ::std::os::raw::c_int, __modes: *const ::std::os::raw::c_char)
-> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn fopencookie(
__magic_cookie: *mut ::std::os::raw::c_void,
__modes: *const ::std::os::raw::c_char,
__io_funcs: cookie_io_functions_t,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn fmemopen(
__s: *mut ::std::os::raw::c_void,
__len: usize,
__modes: *const ::std::os::raw::c_char,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn open_memstream(
__bufloc: *mut *mut ::std::os::raw::c_char,
__sizeloc: *mut usize,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn setbuf(__stream: *mut FILE, __buf: *mut ::std::os::raw::c_char);
}
extern "C" {
unsafe extern "C" {
pub fn setvbuf(
__stream: *mut FILE,
__buf: *mut ::std::os::raw::c_char,
@ -660,50 +660,50 @@ extern "C" {
__n: usize,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn setbuffer(__stream: *mut FILE, __buf: *mut ::std::os::raw::c_char, __size: usize);
}
extern "C" {
unsafe extern "C" {
pub fn setlinebuf(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn fprintf(
__stream: *mut FILE,
__format: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn printf(__format: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn sprintf(
__s: *mut ::std::os::raw::c_char,
__format: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vfprintf(
__s: *mut FILE,
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vprintf(
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vsprintf(
__s: *mut ::std::os::raw::c_char,
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn snprintf(
__s: *mut ::std::os::raw::c_char,
__maxlen: ::std::os::raw::c_ulong,
@ -711,7 +711,7 @@ extern "C" {
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vsnprintf(
__s: *mut ::std::os::raw::c_char,
__maxlen: ::std::os::raw::c_ulong,
@ -719,52 +719,52 @@ extern "C" {
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vasprintf(
__ptr: *mut *mut ::std::os::raw::c_char,
__f: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn __asprintf(
__ptr: *mut *mut ::std::os::raw::c_char,
__fmt: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn asprintf(
__ptr: *mut *mut ::std::os::raw::c_char,
__fmt: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vdprintf(
__fd: ::std::os::raw::c_int,
__fmt: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn dprintf(
__fd: ::std::os::raw::c_int,
__fmt: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fscanf(
__stream: *mut FILE,
__format: *const ::std::os::raw::c_char,
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn scanf(__format: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn sscanf(
__s: *const ::std::os::raw::c_char,
__format: *const ::std::os::raw::c_char,
@ -775,7 +775,7 @@ pub type _Float32 = f32;
pub type _Float64 = f64;
pub type _Float32x = f64;
pub type _Float64x = u128;
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_fscanf"]
pub fn fscanf1(
__stream: *mut FILE,
@ -783,11 +783,11 @@ extern "C" {
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_scanf"]
pub fn scanf1(__format: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_sscanf"]
pub fn sscanf1(
__s: *const ::std::os::raw::c_char,
@ -795,27 +795,27 @@ extern "C" {
...
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vfscanf(
__s: *mut FILE,
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vscanf(
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn vsscanf(
__s: *const ::std::os::raw::c_char,
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_vfscanf"]
pub fn vfscanf1(
__s: *mut FILE,
@ -823,14 +823,14 @@ extern "C" {
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_vscanf"]
pub fn vscanf1(
__format: *const ::std::os::raw::c_char,
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
#[link_name = "\u{1}__isoc99_vsscanf"]
pub fn vsscanf1(
__s: *const ::std::os::raw::c_char,
@ -838,57 +838,57 @@ extern "C" {
__arg: *mut __va_list_tag,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fgetc(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn getc(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn getchar() -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn getc_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn getchar_unlocked() -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fgetc_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fputc(__c: ::std::os::raw::c_int, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn putc(__c: ::std::os::raw::c_int, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn putchar(__c: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fputc_unlocked(__c: ::std::os::raw::c_int, __stream: *mut FILE)
-> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn putc_unlocked(__c: ::std::os::raw::c_int, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn putchar_unlocked(__c: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn getw(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn putw(__w: ::std::os::raw::c_int, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fgets(
__s: *mut ::std::os::raw::c_char,
__n: ::std::os::raw::c_int,
__stream: *mut FILE,
) -> *mut ::std::os::raw::c_char;
}
extern "C" {
unsafe extern "C" {
pub fn __getdelim(
__lineptr: *mut *mut ::std::os::raw::c_char,
__n: *mut usize,
@ -896,7 +896,7 @@ extern "C" {
__stream: *mut FILE,
) -> __ssize_t;
}
extern "C" {
unsafe extern "C" {
pub fn getdelim(
__lineptr: *mut *mut ::std::os::raw::c_char,
__n: *mut usize,
@ -904,23 +904,23 @@ extern "C" {
__stream: *mut FILE,
) -> __ssize_t;
}
extern "C" {
unsafe extern "C" {
pub fn getline(
__lineptr: *mut *mut ::std::os::raw::c_char,
__n: *mut usize,
__stream: *mut FILE,
) -> __ssize_t;
}
extern "C" {
unsafe extern "C" {
pub fn fputs(__s: *const ::std::os::raw::c_char, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn puts(__s: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn ungetc(__c: ::std::os::raw::c_int, __stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fread(
__ptr: *mut ::std::os::raw::c_void,
__size: ::std::os::raw::c_ulong,
@ -928,7 +928,7 @@ extern "C" {
__stream: *mut FILE,
) -> ::std::os::raw::c_ulong;
}
extern "C" {
unsafe extern "C" {
pub fn fwrite(
__ptr: *const ::std::os::raw::c_void,
__size: ::std::os::raw::c_ulong,
@ -936,7 +936,7 @@ extern "C" {
__s: *mut FILE,
) -> ::std::os::raw::c_ulong;
}
extern "C" {
unsafe extern "C" {
pub fn fread_unlocked(
__ptr: *mut ::std::os::raw::c_void,
__size: usize,
@ -944,7 +944,7 @@ extern "C" {
__stream: *mut FILE,
) -> usize;
}
extern "C" {
unsafe extern "C" {
pub fn fwrite_unlocked(
__ptr: *const ::std::os::raw::c_void,
__size: usize,
@ -952,87 +952,87 @@ extern "C" {
__stream: *mut FILE,
) -> usize;
}
extern "C" {
unsafe extern "C" {
pub fn fseek(
__stream: *mut FILE,
__off: ::std::os::raw::c_long,
__whence: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn ftell(__stream: *mut FILE) -> ::std::os::raw::c_long;
}
extern "C" {
unsafe extern "C" {
pub fn rewind(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn fseeko(
__stream: *mut FILE,
__off: __off_t,
__whence: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn ftello(__stream: *mut FILE) -> __off_t;
}
extern "C" {
unsafe extern "C" {
pub fn fgetpos(__stream: *mut FILE, __pos: *mut fpos_t) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fsetpos(__stream: *mut FILE, __pos: *const fpos_t) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn clearerr(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn feof(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn ferror(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn clearerr_unlocked(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn feof_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn ferror_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn perror(__s: *const ::std::os::raw::c_char);
}
extern "C" {
unsafe extern "C" {
pub fn fileno(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn fileno_unlocked(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn pclose(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn popen(
__command: *const ::std::os::raw::c_char,
__modes: *const ::std::os::raw::c_char,
) -> *mut FILE;
}
extern "C" {
unsafe extern "C" {
pub fn ctermid(__s: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char;
}
extern "C" {
unsafe extern "C" {
pub fn flockfile(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn ftrylockfile(__stream: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn funlockfile(__stream: *mut FILE);
}
extern "C" {
unsafe extern "C" {
pub fn __uflow(arg1: *mut FILE) -> ::std::os::raw::c_int;
}
extern "C" {
unsafe extern "C" {
pub fn __overflow(arg1: *mut FILE, arg2: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
pub type int_least8_t = __int_least8_t;
@ -1054,26 +1054,26 @@ pub type uint_fast64_t = ::std::os::raw::c_ulong;
pub type intmax_t = __intmax_t;
pub type uintmax_t = __uintmax_t;
pub type libafl_word = u64;
extern "C" {
unsafe extern "C" {
pub fn _libafl_sync_exit_call0(action: libafl_word) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn _libafl_sync_exit_call1(action: libafl_word, arg1: libafl_word) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn _libafl_sync_exit_call2(
action: libafl_word,
arg1: libafl_word,
arg2: libafl_word,
) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn _libafl_backdoor_call0(action: libafl_word) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn _libafl_backdoor_call1(action: libafl_word, arg1: libafl_word) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn _libafl_backdoor_call2(
action: libafl_word,
arg1: libafl_word,
@ -1113,61 +1113,61 @@ impl ::std::ops::BitAndAssign for LibaflQemuEndStatus {
#[doc = " LibAFL QEMU header file.\n\n This file is a portable header file used to build target harnesses more\n conveniently. Its main purpose is to generate ready-to-use calls to\n communicate with the fuzzer. The list of commands is available at the bottom\n of this file. The rest mostly consists of macros generating the code used by\n the commands."]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct LibaflQemuEndStatus(pub ::std::os::raw::c_uint);
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_start_virt(
buf_vaddr: *mut ::std::os::raw::c_void,
max_len: libafl_word,
) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_start_phys(
buf_paddr: *mut ::std::os::raw::c_void,
max_len: libafl_word,
) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_input_virt(
buf_vaddr: *mut ::std::os::raw::c_void,
max_len: libafl_word,
) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_input_phys(
buf_paddr: *mut ::std::os::raw::c_void,
max_len: libafl_word,
) -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_end(status: LibaflQemuEndStatus);
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_save();
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_load();
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_version() -> libafl_word;
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_page_current_allow();
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_internal_error();
}
extern "C" {
unsafe extern "C" {
pub fn lqprintf(fmt: *const ::std::os::raw::c_char, ...);
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_test();
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_trace_vaddr_range(start: libafl_word, end: libafl_word);
}
extern "C" {
unsafe extern "C" {
pub fn libafl_qemu_trace_vaddr_size(start: libafl_word, size: libafl_word);
}
extern "C" {
unsafe extern "C" {
pub static mut _lqprintf_buffer: [::std::os::raw::c_char; 4096usize];
}
pub type __builtin_va_list = [__va_list_tag; 1usize];

View File

@ -0,0 +1,185 @@
/*
* kAFl/Nyx low-level interface definitions
*
* Copyright 2022 Sergej Schumilo, Cornelius Aschermann
* Copyright 2022 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
#ifndef NYX_API_H
#define NYX_API_H
#ifndef __KERNEL__
// userspace
#include <stdarg.h>
#include <stdio.h>
#ifdef __MINGW64__
#ifndef uint64_t
#define uint64_t UINT64
#endif
#ifndef int32_t
#define int32_t INT32
#endif
#ifndef uint32_t
#define uint32_t UINT32
#endif
#ifndef u_long
#define u_long UINT64
#endif
#ifndef uint8_t
#define uint8_t UINT8
#endif
#else
#include <stdint.h>
#endif
#else
// Linux kernel
#include <linux/stdarg.h>
#include <linux/types.h>
#endif
#define HYPERCALL_KAFL_RAX_ID 0x01f
#define HYPERCALL_KAFL_ACQUIRE 0
#define HYPERCALL_KAFL_GET_PAYLOAD 1
#define HYPERCALL_KAFL_GET_PROGRAM 2 /* deprecated */
#define HYPERCALL_KAFL_GET_ARGV 3 /* deprecated */
#define HYPERCALL_KAFL_RELEASE 4
#define HYPERCALL_KAFL_SUBMIT_CR3 5
#define HYPERCALL_KAFL_SUBMIT_PANIC 6
#define HYPERCALL_KAFL_SUBMIT_KASAN 7
#define HYPERCALL_KAFL_PANIC 8
#define HYPERCALL_KAFL_KASAN 9
#define HYPERCALL_KAFL_LOCK 10
#define HYPERCALL_KAFL_INFO 11 /* deprecated */
#define HYPERCALL_KAFL_NEXT_PAYLOAD 12
#define HYPERCALL_KAFL_PRINTF 13
#define HYPERCALL_KAFL_PRINTK_ADDR 14 /* deprecated */
#define HYPERCALL_KAFL_PRINTK 15 /* deprecated */
/* user space only hypercalls */
#define HYPERCALL_KAFL_USER_RANGE_ADVISE 16
#define HYPERCALL_KAFL_USER_SUBMIT_MODE 17
#define HYPERCALL_KAFL_USER_FAST_ACQUIRE 18
/* 19 is already used for exit reason KVM_EXIT_KAFL_TOPA_MAIN_FULL */
#define HYPERCALL_KAFL_USER_ABORT 20
#define HYPERCALL_KAFL_TIMEOUT 21 /* deprecated */
#define HYPERCALL_KAFL_RANGE_SUBMIT 29
#define HYPERCALL_KAFL_REQ_STREAM_DATA 30
#define HYPERCALL_KAFL_PANIC_EXTENDED 32
#define HYPERCALL_KAFL_CREATE_TMP_SNAPSHOT 33
#define HYPERCALL_KAFL_DEBUG_TMP_SNAPSHOT \
34 /* hypercall for debugging / development purposes */
#define HYPERCALL_KAFL_GET_HOST_CONFIG 35
#define HYPERCALL_KAFL_SET_AGENT_CONFIG 36
#define HYPERCALL_KAFL_DUMP_FILE 37
#define HYPERCALL_KAFL_REQ_STREAM_DATA_BULK 38
#define HYPERCALL_KAFL_PERSIST_PAGE_PAST_SNAPSHOT 39
/* hypertrash only hypercalls */
#define HYPERTRASH_HYPERCALL_MASK 0xAA000000
#define HYPERCALL_KAFL_NESTED_PREPARE (0 | HYPERTRASH_HYPERCALL_MASK)
#define HYPERCALL_KAFL_NESTED_CONFIG (1 | HYPERTRASH_HYPERCALL_MASK)
#define HYPERCALL_KAFL_NESTED_ACQUIRE (2 | HYPERTRASH_HYPERCALL_MASK)
#define HYPERCALL_KAFL_NESTED_RELEASE (3 | HYPERTRASH_HYPERCALL_MASK)
#define HYPERCALL_KAFL_NESTED_HPRINTF (4 | HYPERTRASH_HYPERCALL_MASK)
#define HPRINTF_MAX_SIZE 0x1000 /* up to 4KB hprintf strings */
#define KAFL_MODE_64 0
#define KAFL_MODE_32 1
#define KAFL_MODE_16 2
typedef struct {
int32_t size;
uint8_t data[];
} kAFL_payload;
typedef struct {
uint64_t ip[4];
uint64_t size[4];
uint8_t enabled[4];
} kAFL_ranges;
#if defined(__i386__)
static inline uint32_t kAFL_hypercall(uint32_t p1, uint32_t p2) {
uint32_t nr = HYPERCALL_KAFL_RAX_ID;
asm volatile("vmcall" : "=a"(nr) : "a"(nr), "b"(p1), "c"(p2));
return nr;
}
#elif defined(__x86_64__)
static inline uint64_t kAFL_hypercall(uint64_t p1, uint64_t p2) {
uint64_t nr = HYPERCALL_KAFL_RAX_ID;
asm volatile("vmcall" : "=a"(nr) : "a"(nr), "b"(p1), "c"(p2));
return nr;
}
#endif
static void habort(char *msg) __attribute__((unused));
static void habort(char *msg) {
kAFL_hypercall(HYPERCALL_KAFL_USER_ABORT, (uintptr_t)msg);
}
static void hprintf(const char *format, ...) __attribute__((unused));
static void hprintf(const char *format, ...) {
static char hprintf_buffer[HPRINTF_MAX_SIZE] __attribute__((aligned(4096)));
va_list args;
va_start(args, format);
vsnprintf((char *)hprintf_buffer, HPRINTF_MAX_SIZE, format, args);
// printf("%s", hprintf_buffer);
kAFL_hypercall(HYPERCALL_KAFL_PRINTF, (uintptr_t)hprintf_buffer);
va_end(args);
}
#define NYX_HOST_MAGIC 0x4878794e
#define NYX_AGENT_MAGIC 0x4178794e
#define NYX_HOST_VERSION 2
#define NYX_AGENT_VERSION 1
typedef struct {
uint32_t host_magic;
uint32_t host_version;
uint32_t bitmap_size;
uint32_t ijon_bitmap_size;
uint32_t payload_buffer_size;
uint32_t worker_id;
/* more to come */
} __attribute__((packed)) host_config_t;
typedef struct {
uint32_t agent_magic;
uint32_t agent_version;
uint8_t agent_timeout_detection;
uint8_t agent_tracing;
uint8_t agent_ijon_tracing;
uint8_t agent_non_reload_mode;
uint64_t trace_buffer_vaddr;
uint64_t ijon_trace_buffer_vaddr;
uint32_t coverage_bitmap_size;
uint32_t input_buffer_size;
uint8_t dump_payloads; /* set by hypervisor */
/* more to come */
} __attribute__((packed)) agent_config_t;
typedef struct {
uint64_t file_name_str_ptr;
uint64_t data_ptr;
uint64_t bytes;
uint8_t append;
} __attribute__((packed)) kafl_dump_file_t;
typedef struct {
char file_name[256];
uint64_t num_addresses;
uint64_t addresses[479];
} __attribute__((packed)) req_data_bulk_t;
#endif /* NYX_API_H */

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,8 @@ use crate::{
IsSnapshotManager, Qemu, QemuMemoryChunk, QemuRWError, Regs, StdEmulatorDriver, CPU,
};
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
pub mod nyx;
pub mod parser;
mod bindings {

View File

@ -0,0 +1,532 @@
//! # Nyx API Command handlers
//!
//! Nyx command handlers.
//! Makes it possible to run Nyx targets in `LibAFL` QEMU.
//! The [Nyx API](https://github.com/IntelLabs/kafl.targets/blob/master/nyx_api.h) refers to the hypercalls used in Nyx to communicate with the fuzzer, not to the fuzzer itself.
//! This is mostly a convenient way to run Nyx-compatible targets in `LibAFL` QEMU directly, without having to change a single bit of the target files.
use std::{
fmt,
fmt::{Debug, Formatter},
marker::PhantomData,
mem::offset_of,
ptr,
slice::from_raw_parts,
};
use enum_map::EnumMap;
use libafl::{
executors::ExitKind,
inputs::{HasTargetBytes, UsesInput},
};
use libafl_qemu_sys::GuestVirtAddr;
use libc::c_uint;
use paste::paste;
use crate::{
command::{
parser::nyx::{
AcquireCommandParser, GetHostConfigCommandParser, GetPayloadCommandParser,
NextPayloadCommandParser, PrintfCommandParser, ReleaseCommandParser,
SetAgentConfigCommandParser,
},
CommandError, CommandManager, IsCommand, NativeCommandParser,
},
get_exit_arch_regs,
modules::EmulatorModuleTuple,
sync_exit::ExitArgs,
Emulator, EmulatorDriverError, EmulatorDriverResult, GuestReg, InputLocation,
IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs,
};
pub(crate) mod bindings {
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(improper_ctypes)]
#![allow(unused_mut)]
#![allow(unused)]
#![allow(unused_variables)]
#![allow(clippy::all)]
#![allow(clippy::pedantic)]
include!(concat!(env!("OUT_DIR"), "/nyx_bindings.rs"));
}
macro_rules! define_nyx_command_manager {
($name:ident, [$($command:ty),+], [$($native_command_parser:ty),+]) => {
paste! {
pub struct $name<S> {
has_started: bool,
phantom: PhantomData<S>,
}
impl<S> Clone for $name<S> {
fn clone(&self) -> Self {
Self {
has_started: self.has_started,
phantom: PhantomData,
}
}
}
impl<S> Debug for $name<S> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} (has started? {:?})", stringify!($name), self.has_started)
}
}
impl<S> Default for $name<S> {
fn default() -> Self {
Self {
has_started: false,
phantom: PhantomData,
}
}
}
impl<S> $name<S> {
fn start(&mut self) -> bool {
let tmp = self.has_started;
self.has_started = true;
tmp
}
fn has_started(&self) -> bool {
self.has_started
}
}
impl<ET, S, SM> CommandManager<NyxEmulatorDriver, ET, S, SM> for $name<S>
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type Commands = [<$name Commands>];
#[deny(unreachable_patterns)]
fn parse(&self, qemu: Qemu) -> Result<Self::Commands, CommandError> {
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs();
let nyx_backdoor = qemu.read_reg(arch_regs_map[ExitArgs::Cmd])? as c_uint;
// Check nyx backdoor correctness
debug_assert_eq!(nyx_backdoor, 0x1f);
let cmd_id = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])? as c_uint;
match cmd_id {
// <StartPhysCommandParser as NativeCommandParser<S>>::COMMAND_ID => Ok(StdCommandManagerCommands::StartPhysCommandParserCmd(<StartPhysCommandParser as NativeCommandParser<S>>::parse(qemu, arch_regs_map)?)),
$(<$native_command_parser as NativeCommandParser<Self, NyxEmulatorDriver, ET, S, SM>>::COMMAND_ID => Ok(<$native_command_parser as NativeCommandParser<Self, NyxEmulatorDriver, ET, S, SM>>::parse(qemu, arch_regs_map)?.into())),+,
_ => Err(CommandError::UnknownCommand(cmd_id.into())),
}
}
}
#[derive(Clone, Debug)]
pub enum [<$name Commands>]
{
// StartPhysCommand(StartPhysCommand)
$($command($command)),+,
}
impl<ET, S, SM> IsCommand<$name<S>, NyxEmulatorDriver, ET, S, SM> for [<$name Commands>]
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
match self {
$([<$name Commands>]::$command(cmd) => <$command as IsCommand<$name<S>, NyxEmulatorDriver, ET, S, SM>>::usable_at_runtime(cmd)),+
}
}
fn run(&self,
emu: &mut Emulator<$name<S>, NyxEmulatorDriver, ET, S, SM>,
state: &mut S,
input: &S::Input,
ret_reg: Option<Regs>
) -> Result<Option<EmulatorDriverResult<$name<S>, NyxEmulatorDriver, ET, S, SM>>, EmulatorDriverError> {
match self {
$([<$name Commands>]::$command(cmd) => cmd.run(emu, state, input, ret_reg)),+
}
}
}
$(
impl From<$command> for [<$name Commands>] {
fn from(cmd: $command) -> [<$name Commands>] {
[<$name Commands>]::$command(cmd)
}
}
)+
}
};
}
define_nyx_command_manager!(
NyxCommandManager,
[
AcquireCommand,
ReleaseCommand,
GetHostConfigCommand,
SetAgentConfigCommand,
PrintfCommand,
GetPayloadCommand,
NextPayloadCommand
],
[
AcquireCommandParser,
ReleaseCommandParser,
GetHostConfigCommandParser,
SetAgentConfigCommandParser,
PrintfCommandParser,
GetPayloadCommandParser,
NextPayloadCommandParser
]
);
#[derive(Debug, Clone)]
pub struct AcquireCommand;
impl<CM, ED, ET, S, SM> IsCommand<CM, ED, ET, S, SM> for AcquireCommand
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
_emu: &mut Emulator<CM, ED, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<EmulatorDriverResult<CM, ED, ET, S, SM>>, EmulatorDriverError> {
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct GetPayloadCommand {
input_struct_location: GuestVirtAddr,
}
impl GetPayloadCommand {
#[must_use]
pub fn new(input_struct_location: GuestVirtAddr) -> Self {
Self {
input_struct_location,
}
}
}
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for GetPayloadCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
EmulatorDriverError,
> {
let qemu = emu.qemu();
let struct_addr = self.input_struct_location;
let input_addr =
self.input_struct_location + offset_of!(bindings::kAFL_payload, data) as GuestVirtAddr;
let payload_struct_mem_chunk = QemuMemoryChunk::virt(
struct_addr,
size_of::<bindings::kAFL_payload>() as GuestReg,
qemu.current_cpu().unwrap(),
);
let payload_mem_chunk = QemuMemoryChunk::virt(
input_addr,
emu.driver().max_input_size() as GuestReg,
qemu.current_cpu().unwrap(),
);
// Save input struct location for next runs
emu.driver_mut()
.set_input_struct_location(InputLocation::new(
payload_struct_mem_chunk,
qemu.current_cpu().unwrap(),
None,
))
.unwrap();
// Save input location for next runs
emu.driver_mut()
.set_input_location(InputLocation::new(
payload_mem_chunk,
qemu.current_cpu().unwrap(),
None,
))
.unwrap();
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct NextPayloadCommand;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for NextPayloadCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
state: &mut S,
input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
EmulatorDriverError,
> {
if emu.command_manager_mut().start() {
return Err(EmulatorDriverError::CommandError(
CommandError::StartedTwice,
));
}
let qemu = emu.qemu();
// Snapshot VM
let snapshot_id = emu.snapshot_manager_mut().save(qemu);
// Set snapshot ID to restore to after fuzzing ends
emu.driver_mut()
.set_snapshot_id(snapshot_id)
.map_err(|_| EmulatorDriverError::MultipleSnapshotDefinition)?;
// write nyx input to vm
emu.driver().write_input(qemu, input)?;
// Unleash hooks if locked
if emu.driver_mut().unlock_hooks() {
// Prepare hooks
emu.modules_mut().first_exec_all(qemu, state);
emu.modules_mut().pre_exec_all(qemu, state, input);
}
// Auto page filtering if option is enabled
#[cfg(feature = "systemmode")]
if emu.driver_mut().allow_page_on_start() {
if let Some(page_id) = qemu.current_cpu().unwrap().current_paging_id() {
emu.modules_mut().modules_mut().allow_page_id_all(page_id);
}
}
#[cfg(feature = "x86_64")]
if emu.driver_mut().is_process_only() {
emu.modules_mut()
.modules_mut()
.allow_address_range_all(crate::PROCESS_ADDRESS_RANGE);
}
// Make sure JIT cache is empty just before starting
qemu.flush_jit();
log::info!("Fuzzing starts");
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct ReleaseCommand;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for ReleaseCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &mut Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
EmulatorDriverError,
> {
let qemu = emu.qemu();
if emu.command_manager().has_started() {
let snapshot_id = emu
.driver_mut()
.snapshot_id()
.ok_or(EmulatorDriverError::SnapshotNotFound)?;
emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?;
#[cfg(feature = "paranoid_debug")]
emu.snapshot_manager_mut().check(qemu, &snapshot_id)?;
Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Ok)))
} else {
Ok(None)
}
}
}
#[derive(Debug, Clone)]
pub struct GetHostConfigCommand {
host_config_location: QemuMemoryChunk,
}
impl GetHostConfigCommand {
#[must_use]
pub fn new(host_config_location: QemuMemoryChunk) -> Self {
Self {
host_config_location,
}
}
}
impl<CM, ED, ET, S, SM> IsCommand<CM, ED, ET, S, SM> for GetHostConfigCommand
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
emu: &mut Emulator<CM, ED, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<EmulatorDriverResult<CM, ED, ET, S, SM>>, EmulatorDriverError> {
// TODO: check this against fuzzer code
let host_config = bindings::host_config_t {
bitmap_size: 0,
ijon_bitmap_size: 0,
payload_buffer_size: 0,
worker_id: 0,
host_magic: bindings::NYX_HOST_MAGIC,
host_version: bindings::NYX_HOST_VERSION,
};
let host_config_buf = unsafe {
from_raw_parts(
ptr::from_ref(&host_config) as *const u8,
size_of::<bindings::host_config_t>(),
)
};
let qemu = emu.qemu();
self.host_config_location
.write(qemu, host_config_buf)
.unwrap();
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct PrintfCommand {
content: String,
}
impl PrintfCommand {
#[must_use]
pub fn new(content: String) -> Self {
Self { content }
}
}
impl<CM, ED, ET, S, SM> IsCommand<CM, ED, ET, S, SM> for PrintfCommand
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
_emu: &mut Emulator<CM, ED, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<EmulatorDriverResult<CM, ED, ET, S, SM>>, EmulatorDriverError> {
println!("hprintf: {}", self.content);
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct SetAgentConfigCommand {
agent_config: bindings::agent_config_t,
}
impl SetAgentConfigCommand {
#[must_use]
pub fn new(agent_config: bindings::agent_config_t) -> Self {
Self { agent_config }
}
}
impl<CM, ED, ET, S, SM> IsCommand<CM, ED, ET, S, SM> for SetAgentConfigCommand
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
{
fn usable_at_runtime(&self) -> bool {
false
}
fn run(
&self,
_emu: &mut Emulator<CM, ED, ET, S, SM>,
_state: &mut S,
_input: &S::Input,
_ret_reg: Option<Regs>,
) -> Result<Option<EmulatorDriverResult<CM, ED, ET, S, SM>>, EmulatorDriverError> {
let agent_magic = self.agent_config.agent_magic;
let agent_version = self.agent_config.agent_version;
assert_eq!(agent_magic, bindings::NYX_AGENT_MAGIC);
assert_eq!(agent_version, bindings::NYX_AGENT_VERSION);
// TODO: use agent config
Ok(None)
}
}

View File

@ -20,6 +20,9 @@ use crate::{
GuestReg, IsSnapshotManager, Qemu, QemuMemoryChunk, Regs, StdEmulatorDriver,
};
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
pub mod nyx;
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
pub trait NativeCommandParser<CM, ED, ET, S, SM>

View File

@ -0,0 +1,189 @@
use std::{ffi::CStr, mem::transmute, sync::OnceLock};
use enum_map::EnumMap;
use libafl::{
executors::ExitKind,
inputs::{HasTargetBytes, UsesInput},
};
use libafl_qemu_sys::GuestVirtAddr;
use libc::c_uint;
use crate::{
command::{
nyx::{
bindings, AcquireCommand, GetHostConfigCommand, GetPayloadCommand, NextPayloadCommand,
NyxCommandManager, PrintfCommand, ReleaseCommand, SetAgentConfigCommand,
},
parser::NativeCommandParser,
CommandError, CommandManager, NativeExitKind,
},
modules::EmulatorModuleTuple,
sync_exit::ExitArgs,
IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs,
};
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
pub struct AcquireCommandParser;
impl<CM, ED, ET, S, SM> NativeCommandParser<CM, ED, ET, S, SM> for AcquireCommandParser
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
S::Input: HasTargetBytes,
{
type OutputCommand = AcquireCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_ACQUIRE;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(AcquireCommand)
}
}
pub struct GetPayloadCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for GetPayloadCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = GetPayloadCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_GET_PAYLOAD;
fn parse(
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let payload_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2]).unwrap() as GuestVirtAddr;
Ok(GetPayloadCommand::new(payload_addr))
}
}
pub struct NextPayloadCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for NextPayloadCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = NextPayloadCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_NEXT_PAYLOAD;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(NextPayloadCommand)
}
}
pub struct ReleaseCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for ReleaseCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = ReleaseCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_RELEASE;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(ReleaseCommand)
}
}
pub struct GetHostConfigCommandParser;
impl<CM, ED, ET, S, SM> NativeCommandParser<CM, ED, ET, S, SM> for GetHostConfigCommandParser
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
S::Input: HasTargetBytes,
{
type OutputCommand = GetHostConfigCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_GET_HOST_CONFIG;
fn parse(
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let host_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
Ok(GetHostConfigCommand::new(QemuMemoryChunk::virt(
host_config_addr,
GuestVirtAddr::try_from(size_of::<bindings::host_config_t>()).unwrap(),
qemu.current_cpu().unwrap(),
)))
}
}
pub struct SetAgentConfigCommandParser;
impl<CM, ED, ET, S, SM> NativeCommandParser<CM, ED, ET, S, SM> for SetAgentConfigCommandParser
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
S::Input: HasTargetBytes,
{
type OutputCommand = SetAgentConfigCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_SET_AGENT_CONFIG;
fn parse(
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let agent_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
let mut agent_config_buf: [u8; size_of::<bindings::agent_config_t>()] =
[0; size_of::<bindings::agent_config_t>()];
qemu.read_mem(agent_config_addr, &mut agent_config_buf)?;
let agent_config: bindings::agent_config_t = unsafe { transmute(agent_config_buf) };
Ok(SetAgentConfigCommand::new(agent_config))
}
}
pub struct PrintfCommandParser;
impl<CM, ED, ET, S, SM> NativeCommandParser<CM, ED, ET, S, SM> for PrintfCommandParser
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
S::Input: HasTargetBytes,
{
type OutputCommand = PrintfCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_PRINTF;
fn parse(
qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let str_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr;
let mut msg_chunk: [u8; bindings::HPRINTF_MAX_SIZE as usize] =
[0; bindings::HPRINTF_MAX_SIZE as usize];
qemu.read_mem(str_addr, &mut msg_chunk)?;
let cstr = CStr::from_bytes_until_nul(&msg_chunk).unwrap();
Ok(PrintfCommand::new(cstr.to_str().unwrap().to_string()))
}
}

View File

@ -14,10 +14,15 @@ use typed_builder::TypedBuilder;
use crate::{
command::{CommandError, CommandManager, InputCommand, IsCommand},
modules::EmulatorModuleTuple,
Emulator, EmulatorExitError, EmulatorExitResult, InputLocation, IsSnapshotManager,
Emulator, EmulatorExitError, EmulatorExitResult, InputLocation, IsSnapshotManager, QemuError,
QemuShutdownCause, Regs, SnapshotId, SnapshotManagerCheckError, SnapshotManagerError,
};
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
pub mod nyx;
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))]
pub use nyx::{NyxEmulatorDriver, NyxEmulatorDriverBuilder};
#[derive(Debug, Clone)]
pub enum EmulatorDriverResult<CM, ED, ET, S, SM>
where
@ -33,6 +38,7 @@ where
#[derive(Debug, Clone)]
pub enum EmulatorDriverError {
QemuError(QemuError),
QemuExitReasonError(EmulatorExitError),
SMError(SnapshotManagerError),
SMCheckError(SnapshotManagerCheckError),
@ -43,6 +49,12 @@ pub enum EmulatorDriverError {
SnapshotNotFound,
}
impl From<QemuError> for EmulatorDriverError {
fn from(error: QemuError) -> Self {
EmulatorDriverError::QemuError(error)
}
}
/// An Emulator Driver.
// TODO remove 'static when specialization will be stable
pub trait EmulatorDriver<CM, ET, S, SM>: 'static + Sized
@ -252,9 +264,9 @@ where
return Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Timeout)))
}
EmulatorExitResult::Breakpoint(bp) => (bp.trigger(qemu), None),
EmulatorExitResult::SyncExit(sync_backdoor) => {
let command = sync_backdoor.command().clone();
(Some(command), Some(sync_backdoor.ret_reg()))
EmulatorExitResult::CustomInsn(custom_insn) => {
let command = custom_insn.command().clone();
(Some(command), Some(custom_insn.ret_reg()))
}
};

View File

@ -0,0 +1,223 @@
use std::{cell::OnceCell, cmp::min, ptr, slice::from_raw_parts};
use libafl::{
executors::ExitKind,
inputs::{HasTargetBytes, UsesInput},
observers::ObserversTuple,
};
use libafl_bolts::os::CTRL_C_EXIT;
use typed_builder::TypedBuilder;
use crate::{
command::{nyx::bindings, CommandManager, IsCommand},
modules::EmulatorModuleTuple,
Emulator, EmulatorDriver, EmulatorDriverError, EmulatorDriverResult, EmulatorExitError,
EmulatorExitResult, InputLocation, IsSnapshotManager, Qemu, QemuError, QemuShutdownCause, Regs,
SnapshotId,
};
#[derive(Clone, Debug, TypedBuilder)]
#[allow(clippy::struct_excessive_bools)] // cfg dependent
pub struct NyxEmulatorDriver {
#[builder(default = OnceCell::new())]
snapshot_id: OnceCell<SnapshotId>,
#[builder(default = OnceCell::new())]
input_struct_location: OnceCell<InputLocation>,
#[builder(default = OnceCell::new())]
input_location: OnceCell<InputLocation>,
#[builder(default = true)]
hooks_locked: bool,
#[cfg(feature = "systemmode")]
#[builder(default = false)]
allow_page_on_start: bool, // when fuzzing starts, module filters will only allow the current page table.
#[cfg(feature = "x86_64")]
#[builder(default = false)]
process_only: bool, // adds x86_64 process address space in the address filters of every module.
#[builder(default = false)]
print_commands: bool,
#[builder(default = (1024 * 1024))]
max_input_size: usize,
}
impl NyxEmulatorDriver {
pub fn max_input_size(&self) -> usize {
self.max_input_size
}
pub fn write_input<I>(&self, qemu: Qemu, input: &I) -> Result<(), QemuError>
where
I: HasTargetBytes,
{
let input_len =
i32::try_from(min(self.max_input_size, input.target_bytes().len())).unwrap();
let kafl_payload = bindings::kAFL_payload {
size: input_len,
..Default::default()
};
let kafl_payload_buf = unsafe {
from_raw_parts(
ptr::from_ref(&kafl_payload) as *const u8,
size_of::<bindings::kAFL_payload>(),
)
};
let input_struct_mem_chunk = &self.input_struct_location.get().unwrap().mem_chunk;
// TODO: manage endianness correctly.
input_struct_mem_chunk.write(qemu, kafl_payload_buf)?;
// write struct first
self.input_location
.get()
.unwrap()
.mem_chunk
.write(qemu, input.target_bytes().as_ref())?;
Ok(())
}
pub fn set_input_location(&self, input_location: InputLocation) -> Result<(), InputLocation> {
self.input_location.set(input_location)
}
pub fn set_input_struct_location(
&self,
input_struct_location: InputLocation,
) -> Result<(), InputLocation> {
self.input_struct_location.set(input_struct_location)
}
pub fn set_snapshot_id(&self, snapshot_id: SnapshotId) -> Result<(), SnapshotId> {
self.snapshot_id.set(snapshot_id)
}
pub fn snapshot_id(&self) -> Option<SnapshotId> {
Some(*self.snapshot_id.get()?)
}
// return if was locked or not
pub fn unlock_hooks(&mut self) -> bool {
let was_locked = self.hooks_locked;
self.hooks_locked = false;
was_locked
}
#[cfg(feature = "systemmode")]
pub fn allow_page_on_start(&self) -> bool {
self.allow_page_on_start
}
#[cfg(feature = "x86_64")]
pub fn is_process_only(&self) -> bool {
self.process_only
}
}
impl<CM, ET, S, SM> EmulatorDriver<CM, ET, S, SM> for NyxEmulatorDriver
where
CM: CommandManager<NyxEmulatorDriver, ET, S, SM>,
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn first_harness_exec(emulator: &mut Emulator<CM, Self, ET, S, SM>, state: &mut S) {
if !emulator.driver.hooks_locked {
emulator.modules.first_exec_all(emulator.qemu, state);
}
}
fn pre_harness_exec(
emulator: &mut Emulator<CM, Self, ET, S, SM>,
state: &mut S,
input: &S::Input,
) {
if !emulator.driver.hooks_locked {
emulator.modules.pre_exec_all(emulator.qemu, state, input);
}
if emulator.driver.input_location.get().is_some() {
let qemu = emulator.qemu();
emulator.driver.write_input(qemu, input).unwrap();
}
}
fn post_harness_exec<OT>(
emulator: &mut Emulator<CM, Self, ET, S, SM>,
input: &S::Input,
observers: &mut OT,
state: &mut S,
exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<S::Input, S>,
{
if !emulator.driver.hooks_locked {
emulator
.modules
.post_exec_all(emulator.qemu, state, input, observers, exit_kind);
}
}
fn pre_qemu_exec(_emulator: &mut Emulator<CM, Self, ET, S, SM>, _input: &S::Input) {}
fn post_qemu_exec(
emulator: &mut Emulator<CM, Self, ET, S, SM>,
state: &mut S,
exit_reason: &mut Result<EmulatorExitResult<CM, Self, ET, S, SM>, EmulatorExitError>,
input: &S::Input,
) -> Result<Option<EmulatorDriverResult<CM, Self, ET, S, SM>>, EmulatorDriverError> {
let qemu = emulator.qemu();
let mut exit_reason = match exit_reason {
Ok(exit_reason) => exit_reason,
Err(exit_error) => match exit_error {
EmulatorExitError::UnexpectedExit => {
if let Some(snapshot_id) = emulator.driver.snapshot_id.get() {
emulator.snapshot_manager.restore(qemu, snapshot_id)?;
}
return Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Crash)));
}
_ => Err(exit_error.clone())?,
},
};
let (command, ret_reg): (Option<CM::Commands>, Option<Regs>) = match &mut exit_reason {
EmulatorExitResult::QemuExit(shutdown_cause) => match shutdown_cause {
QemuShutdownCause::HostSignal(signal) => {
signal.handle();
return Err(EmulatorDriverError::UnhandledSignal(*signal));
}
QemuShutdownCause::GuestPanic => {
return Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Crash)))
}
QemuShutdownCause::GuestShutdown | QemuShutdownCause::HostQmpQuit => {
log::warn!("Guest shutdown. Stopping fuzzing...");
std::process::exit(CTRL_C_EXIT);
}
_ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."),
},
EmulatorExitResult::Timeout => {
return Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Timeout)))
}
EmulatorExitResult::Breakpoint(bp) => (bp.trigger(qemu), None),
EmulatorExitResult::CustomInsn(sync_backdoor) => {
let command = sync_backdoor.command().clone();
(Some(command), Some(sync_backdoor.ret_reg()))
}
};
if let Some(cmd) = command {
if emulator.driver.print_commands {
println!("Received command: {cmd:?}");
}
cmd.run(emulator, state, input, ret_reg)
} else {
Ok(Some(EmulatorDriverResult::ReturnToHarness(
exit_reason.clone(),
)))
}
}
}

View File

@ -20,7 +20,7 @@ use crate::{
breakpoint::{Breakpoint, BreakpointId},
command::{CommandError, CommandManager, NopCommandManager, StdCommandManager},
modules::EmulatorModuleTuple,
sync_exit::SyncExit,
sync_exit::CustomInsn,
Qemu, QemuExitError, QemuExitReason, QemuHooks, QemuInitError, QemuMemoryChunk, QemuParams,
QemuShutdownCause, Regs, CPU,
};
@ -62,8 +62,8 @@ where
{
QemuExit(QemuShutdownCause), // QEMU ended for some reason.
Breakpoint(Breakpoint<CM, ED, ET, S, SM>), // Breakpoint triggered. Contains the address of the trigger.
SyncExit(SyncExit<CM, ED, ET, S, SM>), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
Timeout, // Timeout
CustomInsn(CustomInsn<CM, ED, ET, S, SM>), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
Timeout, // Timeout
}
impl<CM, ED, ET, S, SM> Clone for EmulatorExitResult<CM, ED, ET, S, SM>
@ -77,8 +77,8 @@ where
EmulatorExitResult::QemuExit(qemu_exit.clone())
}
EmulatorExitResult::Breakpoint(bp) => EmulatorExitResult::Breakpoint(bp.clone()),
EmulatorExitResult::SyncExit(sync_exit) => {
EmulatorExitResult::SyncExit(sync_exit.clone())
EmulatorExitResult::CustomInsn(sync_exit) => {
EmulatorExitResult::CustomInsn(sync_exit.clone())
}
EmulatorExitResult::Timeout => EmulatorExitResult::Timeout,
}
@ -98,7 +98,7 @@ where
EmulatorExitResult::Breakpoint(bp) => {
write!(f, "{bp:?}")
}
EmulatorExitResult::SyncExit(sync_exit) => {
EmulatorExitResult::CustomInsn(sync_exit) => {
write!(f, "{sync_exit:?}")
}
EmulatorExitResult::Timeout => {
@ -220,6 +220,16 @@ impl InputLocation {
ret_register,
}
}
#[must_use]
pub fn mem_chunk(&self) -> &QemuMemoryChunk {
&self.mem_chunk
}
#[must_use]
pub fn ret_register(&self) -> &Option<Regs> {
&self.ret_register
}
}
impl From<EmulatorExitError> for EmulatorDriverError {
@ -243,7 +253,7 @@ where
match self {
EmulatorExitResult::QemuExit(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"),
EmulatorExitResult::Breakpoint(bp) => write!(f, "{bp}"),
EmulatorExitResult::SyncExit(sync_exit) => {
EmulatorExitResult::CustomInsn(sync_exit) => {
write!(f, "Sync exit: {sync_exit:?}")
}
EmulatorExitResult::Timeout => {
@ -445,7 +455,9 @@ where
ED::pre_qemu_exec(self, input);
// Run QEMU
log::debug!("Running QEMU...");
let mut exit_reason = self.run_qemu();
log::debug!("QEMU stopped.");
// Handle QEMU exit
if let Some(exit_handler_result) =
@ -479,7 +491,7 @@ where
.clone();
EmulatorExitResult::Breakpoint(bp.clone())
}
QemuExitReason::SyncExit => EmulatorExitResult::SyncExit(SyncExit::new(
QemuExitReason::SyncExit => EmulatorExitResult::CustomInsn(CustomInsn::new(
self.command_manager.parse(self.qemu)?,
)),
}),

View File

@ -62,6 +62,7 @@ impl Default for NopSnapshotManager {
impl IsSnapshotManager for NopSnapshotManager {
fn save(&mut self, _qemu: Qemu) -> SnapshotId {
log::warn!("Saving snapshot with the NopSnapshotManager");
SnapshotId { id: 0 }
}
@ -70,6 +71,7 @@ impl IsSnapshotManager for NopSnapshotManager {
_qemu: Qemu,
_snapshot_id: &SnapshotId,
) -> Result<(), SnapshotManagerError> {
log::warn!("Restoring snapshot with the NopSnapshotManager");
Ok(())
}

View File

@ -11,11 +11,12 @@ use libafl_qemu_sys::GuestAddr;
use thread_local::ThreadLocal;
#[cfg(feature = "systemmode")]
use crate::modules::{NopPageFilter, NOP_PAGE_FILTER};
use crate::modules::utils::filters::{NopPageFilter, NOP_PAGE_FILTER};
use crate::{
capstone,
modules::{
AddressFilter, EmulatorModule, EmulatorModuleTuple, EmulatorModules, StdAddressFilter,
utils::filters::StdAddressFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple,
EmulatorModules,
},
qemu::{ArchExtras, Hook},
Qemu,

View File

@ -2,6 +2,7 @@
use capstone::{arch::BuildsCapstone, Capstone, InsnDetail};
use hashbrown::HashMap;
use libafl::{inputs::UsesInput, HasMetadata};
use libafl_bolts::hash_64_fast;
use libafl_qemu_sys::GuestAddr;
pub use libafl_targets::{
cmps::{
@ -12,12 +13,14 @@ pub use libafl_targets::{
use serde::{Deserialize, Serialize};
#[cfg(feature = "systemmode")]
use crate::modules::{NopPageFilter, NOP_PAGE_FILTER};
use crate::modules::utils::filters::{NopPageFilter, NOP_PAGE_FILTER};
#[cfg(feature = "usermode")]
use crate::{capstone, qemu::ArchExtras, CallingConvention};
use crate::{
emu::EmulatorModules,
modules::{hash_me, AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter},
modules::{
utils::filters::StdAddressFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple,
},
qemu::Hook,
Qemu,
};
@ -229,7 +232,7 @@ where
return None;
}
}
Some(hash_me(pc.into()) & (CMPLOG_MAP_W as u64 - 1))
Some(hash_64_fast(pc.into()) & (CMPLOG_MAP_W as u64 - 1))
}
pub extern "C" fn trace_cmp1_cmplog(_: *const (), id: u64, v0: u8, v1: u8) {
@ -358,7 +361,7 @@ impl CmpLogRoutinesModule {
for detail in insn_detail.groups() {
match u32::from(detail.0) {
capstone::InsnGroupType::CS_GRP_CALL => {
let k = (hash_me(pc.into())) & (CMPLOG_MAP_W as u64 - 1);
let k = (hash_64_fast(pc.into())) & (CMPLOG_MAP_W as u64 - 1);
qemu.hooks().add_instruction_hooks(
k,
insn.address() as GuestAddr,

View File

@ -8,10 +8,12 @@ use rangemap::RangeMap;
use serde::{Deserialize, Serialize};
#[cfg(feature = "systemmode")]
use crate::modules::{NopPageFilter, NOP_PAGE_FILTER};
use crate::modules::utils::filters::{NopPageFilter, NOP_PAGE_FILTER};
use crate::{
emu::EmulatorModules,
modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, NopAddressFilter},
modules::{
utils::filters::NopAddressFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple,
},
qemu::Hook,
Qemu,
};

View File

@ -6,8 +6,9 @@ use super::{
};
use crate::{
modules::{
utils::filters::{StdAddressFilter, StdPageFilter},
AddressFilter, EdgeCoverageModule, EdgeCoverageModuleBuilder, EmulatorModuleTuple,
PageFilter, StdAddressFilter, StdPageFilter,
PageFilter,
},
EmulatorModules, Hook,
};

View File

@ -8,8 +8,9 @@ use super::{
};
use crate::{
modules::{
utils::filters::{StdAddressFilter, StdPageFilter},
AddressFilter, EdgeCoverageModule, EdgeCoverageModuleBuilder, EmulatorModuleTuple,
PageFilter, StdAddressFilter, StdPageFilter,
PageFilter,
},
EmulatorModules, Hook,
};

View File

@ -6,8 +6,9 @@ use super::{
};
use crate::{
modules::{
utils::filters::{StdAddressFilter, StdPageFilter},
AddressFilter, EdgeCoverageModule, EdgeCoverageModuleBuilder, EmulatorModuleTuple,
PageFilter, StdAddressFilter, StdPageFilter,
PageFilter,
},
EmulatorModules, Hook,
};

View File

@ -52,6 +52,7 @@ mod generators {
use hashbrown::hash_map::Entry;
use libafl::{inputs::UsesInput, HasMetadata};
use libafl_bolts::hash_64_fast;
use libafl_qemu_sys::GuestAddr;
use super::{
@ -59,7 +60,7 @@ mod generators {
LIBAFL_QEMU_EDGES_MAP_SIZE_PTR,
};
use crate::{
modules::{hash_me, AddressFilter, EdgeCoverageModule, EmulatorModuleTuple, PageFilter},
modules::{AddressFilter, EdgeCoverageModule, EmulatorModuleTuple, PageFilter},
EmulatorModules, Qemu,
};
@ -192,7 +193,7 @@ mod generators {
let mask = get_mask::<IS_CONST_MAP, MAP_SIZE>() as u64;
#[expect(clippy::unnecessary_cast)]
let id = (hash_me(src as u64) ^ hash_me(dest as u64)) & mask;
let id = (hash_64_fast(src as u64) ^ hash_64_fast(dest as u64)) & mask;
if !IS_CONST_MAP {
unsafe {
@ -245,7 +246,7 @@ mod generators {
let mask = get_mask::<IS_CONST_MAP, MAP_SIZE>() as u64;
let id = hash_me(pc as u64) & mask;
let id = hash_64_fast(pc as u64) & mask;
if !IS_CONST_MAP {
unsafe {

View File

@ -1,10 +1,16 @@
use core::{fmt::Debug, ops::Range};
use std::cell::UnsafeCell;
use hashbrown::HashSet;
use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple};
use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr};
use libafl_qemu_sys::GuestAddr;
#[cfg(feature = "systemmode")]
use libafl_qemu_sys::GuestPhysAddr;
use crate::{
emu::EmulatorModules,
modules::utils::filters::{AddressFilter, PageFilter},
Qemu, QemuParams,
};
#[cfg(feature = "usermode")]
pub mod usermode;
@ -40,7 +46,7 @@ pub mod drcov;
#[cfg(not(cpu_target = "hexagon"))]
pub use drcov::{DrCovMetadata, DrCovModule, DrCovModuleBuilder};
use crate::{emu::EmulatorModules, Qemu, QemuParams};
pub mod utils;
/// A module for `libafl_qemu`.
// TODO remove 'static when specialization will be stable
@ -374,224 +380,3 @@ where
self.1.allow_page_id_all(page_id);
}
}
#[derive(Debug, Clone)]
pub enum FilterList<T> {
AllowList(T),
DenyList(T),
None,
}
impl<T> AddressFilter for FilterList<T>
where
T: AddressFilter,
{
fn register(&mut self, address_range: Range<GuestAddr>) {
match self {
FilterList::AllowList(allow_list) => allow_list.register(address_range),
FilterList::DenyList(deny_list) => deny_list.register(address_range),
FilterList::None => {}
}
}
fn allowed(&self, address: &GuestAddr) -> bool {
match self {
FilterList::AllowList(allow_list) => allow_list.allowed(address),
FilterList::DenyList(deny_list) => !deny_list.allowed(address),
FilterList::None => true,
}
}
}
impl<T> PageFilter for FilterList<T>
where
T: PageFilter,
{
fn register(&mut self, page_id: GuestPhysAddr) {
match self {
FilterList::AllowList(allow_list) => allow_list.register(page_id),
FilterList::DenyList(deny_list) => deny_list.register(page_id),
FilterList::None => {}
}
}
fn allowed(&self, page: &GuestPhysAddr) -> bool {
match self {
FilterList::AllowList(allow_list) => allow_list.allowed(page),
FilterList::DenyList(deny_list) => !deny_list.allowed(page),
FilterList::None => true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct AddressFilterVec {
// ideally, we should use a tree
registered_addresses: Vec<Range<GuestAddr>>,
}
#[derive(Clone, Debug)]
pub struct StdAddressFilter(FilterList<AddressFilterVec>);
impl Default for StdAddressFilter {
fn default() -> Self {
Self(FilterList::None)
}
}
impl StdAddressFilter {
#[must_use]
pub fn allow_list(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
StdAddressFilter(FilterList::AllowList(AddressFilterVec::new(
registered_addresses,
)))
}
#[must_use]
pub fn deny_list(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
StdAddressFilter(FilterList::DenyList(AddressFilterVec::new(
registered_addresses,
)))
}
}
impl AddressFilterVec {
#[must_use]
pub fn new(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
Self {
registered_addresses,
}
}
}
impl AddressFilter for AddressFilterVec {
fn register(&mut self, address_range: Range<GuestAddr>) {
self.registered_addresses.push(address_range);
Qemu::get().unwrap().flush_jit();
}
fn allowed(&self, addr: &GuestAddr) -> bool {
if self.registered_addresses.is_empty() {
return true;
}
for addr_range in &self.registered_addresses {
if addr_range.contains(addr) {
return true;
}
}
false
}
}
impl AddressFilter for StdAddressFilter {
fn register(&mut self, address_range: Range<GuestAddr>) {
self.0.register(address_range);
}
fn allowed(&self, address: &GuestAddr) -> bool {
self.0.allowed(address)
}
}
#[derive(Clone, Debug)]
pub struct PageFilterVec {
registered_pages: HashSet<GuestPhysAddr>,
}
#[cfg(feature = "systemmode")]
#[derive(Clone, Debug)]
pub struct StdPageFilter(FilterList<PageFilterVec>);
#[cfg(feature = "usermode")]
pub type StdPageFilter = NopPageFilter;
impl Default for PageFilterVec {
fn default() -> Self {
Self {
registered_pages: HashSet::new(),
}
}
}
#[cfg(feature = "systemmode")]
impl Default for StdPageFilter {
fn default() -> Self {
Self(FilterList::None)
}
}
impl PageFilter for PageFilterVec {
fn register(&mut self, page_id: GuestPhysAddr) {
self.registered_pages.insert(page_id);
Qemu::get().unwrap().flush_jit();
}
fn allowed(&self, paging_id: &GuestPhysAddr) -> bool {
// if self.allowed_pages.is_empty() {
// return true;
// }
self.registered_pages.contains(paging_id)
}
}
#[cfg(feature = "systemmode")]
impl PageFilter for StdPageFilter {
fn register(&mut self, page_id: GuestPhysAddr) {
self.0.register(page_id);
}
fn allowed(&self, page_id: &GuestPhysAddr) -> bool {
self.0.allowed(page_id)
}
}
// adapted from https://xorshift.di.unimi.it/splitmix64.c
#[must_use]
pub fn hash_me(mut x: u64) -> u64 {
x = (x ^ (x.overflowing_shr(30).0))
.overflowing_mul(0xbf58476d1ce4e5b9)
.0;
x = (x ^ (x.overflowing_shr(27).0))
.overflowing_mul(0x94d049bb133111eb)
.0;
x ^ (x.overflowing_shr(31).0)
}
pub trait AddressFilter: 'static + Debug {
fn register(&mut self, address_range: Range<GuestAddr>);
fn allowed(&self, address: &GuestAddr) -> bool;
}
#[derive(Debug)]
pub struct NopAddressFilter;
impl AddressFilter for NopAddressFilter {
fn register(&mut self, _address: Range<GuestAddr>) {}
fn allowed(&self, _address: &GuestAddr) -> bool {
true
}
}
pub trait PageFilter: 'static + Debug {
fn register(&mut self, page_id: GuestPhysAddr);
fn allowed(&self, page_id: &GuestPhysAddr) -> bool;
}
#[derive(Clone, Debug, Default)]
pub struct NopPageFilter;
impl PageFilter for NopPageFilter {
fn register(&mut self, _page_id: GuestPhysAddr) {}
fn allowed(&self, _page_id: &GuestPhysAddr) -> bool {
true
}
}
#[cfg(feature = "usermode")]
static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> = UnsafeCell::new(NopAddressFilter);
#[cfg(feature = "systemmode")]
static mut NOP_PAGE_FILTER: UnsafeCell<NopPageFilter> = UnsafeCell::new(NopPageFilter);

View File

@ -153,7 +153,7 @@ use object::{Object, ObjectSection};
use crate::{
emu::EmulatorModules,
modules::{AddressFilter, StdAddressFilter},
modules::{utils::filters::StdAddressFilter, AddressFilter},
qemu::{Hook, QemuHooks, SyscallHookResult},
};

View File

@ -14,7 +14,9 @@ use libafl_qemu_sys::{GuestAddr, MapInfo};
use crate::sys::libafl_tcg_gen_asan;
use crate::{
emu::EmulatorModules,
modules::{AddressFilter, EmulatorModule, EmulatorModuleTuple, StdAddressFilter},
modules::{
utils::filters::StdAddressFilter, AddressFilter, EmulatorModule, EmulatorModuleTuple,
},
qemu::{Hook, MemAccessInfo, Qemu},
sys::TCGTemp,
QemuParams,

View File

@ -23,7 +23,10 @@ use crate::SYS_execve;
use crate::{
elf::EasyElf,
emu::EmulatorModules,
modules::{EmulatorModule, EmulatorModuleTuple, NopAddressFilter, NOP_ADDRESS_FILTER},
modules::{
utils::filters::{NopAddressFilter, NOP_ADDRESS_FILTER},
EmulatorModule, EmulatorModuleTuple,
},
qemu::{ArchExtras, Hook, SyscallHookResult},
CallingConvention, Qemu,
};

View File

@ -24,8 +24,9 @@ use crate::SYS_newfstatat;
use crate::{
emu::EmulatorModules,
modules::{
asan::AsanModule, EmulatorModule, EmulatorModuleTuple, NopAddressFilter, Range,
NOP_ADDRESS_FILTER,
asan::AsanModule,
utils::filters::{NopAddressFilter, NOP_ADDRESS_FILTER},
EmulatorModule, EmulatorModuleTuple, Range,
},
qemu::{Hook, SyscallHookResult},
Qemu, SYS_brk, SYS_mprotect, SYS_mremap, SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat,

View File

@ -0,0 +1,221 @@
//! Helpers to get filtering for modules
//!
//! It is not a module by itself, but instead used as helper to have filters
//! in other modules.
use std::{cell::UnsafeCell, fmt::Debug, ops::Range};
use hashbrown::HashSet;
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr};
use crate::Qemu;
#[derive(Debug, Clone)]
pub enum FilterList<T> {
AllowList(T),
DenyList(T),
None,
}
impl<T> AddressFilter for FilterList<T>
where
T: AddressFilter,
{
fn register(&mut self, address_range: Range<GuestAddr>) {
match self {
FilterList::AllowList(allow_list) => allow_list.register(address_range),
FilterList::DenyList(deny_list) => deny_list.register(address_range),
FilterList::None => {}
}
}
fn allowed(&self, address: &GuestAddr) -> bool {
match self {
FilterList::AllowList(allow_list) => allow_list.allowed(address),
FilterList::DenyList(deny_list) => !deny_list.allowed(address),
FilterList::None => true,
}
}
}
impl<T> PageFilter for FilterList<T>
where
T: PageFilter,
{
fn register(&mut self, page_id: GuestPhysAddr) {
match self {
FilterList::AllowList(allow_list) => allow_list.register(page_id),
FilterList::DenyList(deny_list) => deny_list.register(page_id),
FilterList::None => {}
}
}
fn allowed(&self, page: &GuestPhysAddr) -> bool {
match self {
FilterList::AllowList(allow_list) => allow_list.allowed(page),
FilterList::DenyList(deny_list) => !deny_list.allowed(page),
FilterList::None => true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct AddressFilterVec {
// ideally, we should use a tree
registered_addresses: Vec<Range<GuestAddr>>,
}
#[derive(Clone, Debug)]
pub struct StdAddressFilter(FilterList<AddressFilterVec>);
impl Default for StdAddressFilter {
fn default() -> Self {
Self(FilterList::None)
}
}
impl StdAddressFilter {
#[must_use]
pub fn allow_list(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
StdAddressFilter(FilterList::AllowList(AddressFilterVec::new(
registered_addresses,
)))
}
#[must_use]
pub fn deny_list(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
StdAddressFilter(FilterList::DenyList(AddressFilterVec::new(
registered_addresses,
)))
}
}
impl AddressFilterVec {
#[must_use]
pub fn new(registered_addresses: Vec<Range<GuestAddr>>) -> Self {
Self {
registered_addresses,
}
}
}
impl AddressFilter for AddressFilterVec {
fn register(&mut self, address_range: Range<GuestAddr>) {
self.registered_addresses.push(address_range);
Qemu::get().unwrap().flush_jit();
}
fn allowed(&self, addr: &GuestAddr) -> bool {
if self.registered_addresses.is_empty() {
return true;
}
for addr_range in &self.registered_addresses {
if addr_range.contains(addr) {
return true;
}
}
false
}
}
impl AddressFilter for StdAddressFilter {
fn register(&mut self, address_range: Range<GuestAddr>) {
self.0.register(address_range);
}
fn allowed(&self, address: &GuestAddr) -> bool {
self.0.allowed(address)
}
}
#[derive(Clone, Debug)]
pub struct PageFilterVec {
registered_pages: HashSet<GuestPhysAddr>,
}
#[cfg(feature = "systemmode")]
#[derive(Clone, Debug)]
pub struct StdPageFilter(FilterList<PageFilterVec>);
#[cfg(feature = "usermode")]
pub type StdPageFilter = NopPageFilter;
impl Default for PageFilterVec {
fn default() -> Self {
Self {
registered_pages: HashSet::new(),
}
}
}
#[cfg(feature = "systemmode")]
impl Default for StdPageFilter {
fn default() -> Self {
Self(FilterList::None)
}
}
impl PageFilter for PageFilterVec {
fn register(&mut self, page_id: GuestPhysAddr) {
self.registered_pages.insert(page_id);
Qemu::get().unwrap().flush_jit();
}
fn allowed(&self, paging_id: &GuestPhysAddr) -> bool {
// if self.allowed_pages.is_empty() {
// return true;
// }
self.registered_pages.contains(paging_id)
}
}
#[cfg(feature = "systemmode")]
impl PageFilter for StdPageFilter {
fn register(&mut self, page_id: GuestPhysAddr) {
self.0.register(page_id);
}
fn allowed(&self, page_id: &GuestPhysAddr) -> bool {
self.0.allowed(page_id)
}
}
pub trait AddressFilter: 'static + Debug {
fn register(&mut self, address_range: Range<GuestAddr>);
fn allowed(&self, address: &GuestAddr) -> bool;
}
#[derive(Debug)]
pub struct NopAddressFilter;
impl AddressFilter for NopAddressFilter {
fn register(&mut self, _address: Range<GuestAddr>) {}
fn allowed(&self, _address: &GuestAddr) -> bool {
true
}
}
pub trait PageFilter: 'static + Debug {
fn register(&mut self, page_id: GuestPhysAddr);
fn allowed(&self, page_id: &GuestPhysAddr) -> bool;
}
#[derive(Clone, Debug, Default)]
pub struct NopPageFilter;
impl PageFilter for NopPageFilter {
fn register(&mut self, _page_id: GuestPhysAddr) {}
fn allowed(&self, _page_id: &GuestPhysAddr) -> bool {
true
}
}
#[cfg(feature = "usermode")]
pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> =
UnsafeCell::new(NopAddressFilter);
#[cfg(feature = "systemmode")]
pub(crate) static mut NOP_PAGE_FILTER: UnsafeCell<NopPageFilter> = UnsafeCell::new(NopPageFilter);

View File

@ -0,0 +1 @@
pub mod filters;

View File

@ -5,14 +5,14 @@ use libafl_qemu_sys::{CPUStatePtr, GuestAddr};
use crate::CallingConvention;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum QemuError {
Init(QemuInitError),
Exit(QemuExitError),
RW(QemuRWError),
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum QemuInitError {
MultipleInstances,
NoParametersProvided,
@ -21,19 +21,19 @@ pub enum QemuInitError {
TooManyArgs(usize),
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub enum QemuExitError {
UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen.
UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example.
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub enum QemuRWErrorKind {
Read,
Write,
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub enum QemuRWErrorCause {
WrongCallingConvention(CallingConvention, CallingConvention), // expected, given
WrongArgument(i32),
@ -42,7 +42,7 @@ pub enum QemuRWErrorCause {
WrongMemoryLocation(GuestAddr, usize), // addr, size
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
#[expect(dead_code)]
pub struct QemuRWError {
kind: QemuRWErrorKind,
@ -142,6 +142,12 @@ impl QemuRWError {
}
}
impl From<QemuRWError> for QemuError {
fn from(qemu_rw_error: QemuRWError) -> Self {
QemuError::RW(qemu_rw_error)
}
}
impl From<QemuError> for libafl::Error {
fn from(qemu_error: QemuError) -> Self {
libafl::Error::runtime(qemu_error)

View File

@ -675,7 +675,7 @@ impl Qemu {
let bp_addr = exit_reason.data.breakpoint.addr;
QemuExitReason::Breakpoint(bp_addr)
},
libafl_qemu_sys::libafl_exit_reason_kind_SYNC_EXIT => QemuExitReason::SyncExit,
libafl_qemu_sys::libafl_exit_reason_kind_CUSTOM_INSN => QemuExitReason::SyncExit,
#[cfg(feature = "systemmode")]
libafl_qemu_sys::libafl_exit_reason_kind_TIMEOUT => QemuExitReason::Timeout,

View File

@ -286,9 +286,7 @@ pub mod pybind {
a6: u64,
a7: u64,
) -> SyscallHookResult {
// If we don't deref_addrof we run into the "static-mut-references" lint which is worse.
#[expect(clippy::deref_addrof)]
unsafe { (*(&raw const PY_SYSCALL_HOOK)).as_ref() }.map_or_else(
unsafe { (&raw const PY_SYSCALL_HOOK).read() }.map_or_else(
|| SyscallHookResult::new(None),
|obj| {
let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7);

View File

@ -17,7 +17,7 @@ pub enum ExitArgs {
Arg6,
}
pub struct SyncExit<CM, ED, ET, S, SM>
pub struct CustomInsn<CM, ED, ET, S, SM>
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
@ -25,7 +25,7 @@ where
command: CM::Commands,
}
impl<CM, ED, ET, S, SM> Clone for SyncExit<CM, ED, ET, S, SM>
impl<CM, ED, ET, S, SM> Clone for CustomInsn<CM, ED, ET, S, SM>
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
@ -37,7 +37,7 @@ where
}
}
impl<CM, ED, ET, S, SM> Debug for SyncExit<CM, ED, ET, S, SM>
impl<CM, ED, ET, S, SM> Debug for CustomInsn<CM, ED, ET, S, SM>
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,
@ -47,7 +47,7 @@ where
}
}
impl<CM, ED, ET, S, SM> SyncExit<CM, ED, ET, S, SM>
impl<CM, ED, ET, S, SM> CustomInsn<CM, ED, ET, S, SM>
where
CM: CommandManager<ED, ET, S, SM>,
S: UsesInput,

View File

@ -223,6 +223,7 @@ async fn main() -> io::Result<()> {
r".*Little-CMS.*",
r".*cms_transform_fuzzer.cc.*",
r".*sqlite3.*",
r".*libfuzzer_libmozjpeg.*",
])
.expect("Could not create the regex set from the given regex");