More Nyx hypercalls supported in libafl qemu. add tests for filters. (#2825)

* more nyx hypercalls implemented, among them:
- panic hypercall
- range filtering hypercall

* fixed some nyx hypercalls behavior.

* added generic read / write to qemu memory

* port linux kernel example to also have nyx API, add better filtering as well.

* make nyx api structs volatile to avoid optimization issues

* Introduce a method create a Vec in place, using a closure.

* use new vec_init function in relevant places.

* removed unused unsafe keywork

* add more allocated memory r/w callbacks

* add more safety notes

* move emulator hooks to separate struct

* update QEMU version
This commit is contained in:
Romain Malmain 2025-01-13 17:32:23 +01:00 committed by GitHub
parent 02566b33cd
commit d8460d14a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1316 additions and 320 deletions

View File

@ -10,6 +10,8 @@ edition = "2021"
[features] [features]
shared = ["libafl_qemu/shared"] shared = ["libafl_qemu/shared"]
nyx = []
[profile.release] [profile.release]
incremental = true incremental = true
debug = true debug = true
@ -26,6 +28,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature
] } ] }
libafl_targets = { path = "../../../libafl_targets" } libafl_targets = { path = "../../../libafl_targets" }
env_logger = "0.11.5" env_logger = "0.11.5"
log = "0.4.22"
[build-dependencies] [build-dependencies]
libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" } libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" }

View File

@ -1,12 +1,22 @@
env_scripts = [''' env_scripts = ['''
#!@duckscript #!@duckscript
profile = get_env PROFILE profile = get_env PROFILE
harness_api = get_env HARNESS_API
if eq ${profile} "dev" if eq ${profile} "dev"
set_env PROFILE_DIR debug set_env PROFILE_DIR debug
else else
set_env PROFILE_DIR ${profile} set_env PROFILE_DIR ${profile}
end end
if eq ${harness_api} "nyx"
set_env FEATURE nyx
elseif eq ${harness_api} "lqemu"
set_env FEATURE ""
else
echo "Unknown harness API: ${harness_api}"
exit 1
end
''', ''' ''', '''
#!@duckscript #!@duckscript
runs_on_ci = get_env RUN_ON_CI runs_on_ci = get_env RUN_ON_CI
@ -24,6 +34,7 @@ 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_CLONE_DIR = { value = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/qemu-libafl-bridge", condition = { env_not_set = [
"LIBAFL_QEMU_DIR", "LIBAFL_QEMU_DIR",
] } } ] } }
HARNESS_API = { value = "lqemu", condition = { env_not_set = ["HARNESS_API"] } }
LINUX_BUILDER_URL = "git@github.com:AFLplusplus/linux-qemu-image-builder.git" 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 = { value = "${TARGET_DIR}/linux_builder", condition = { env_not_set = [
@ -85,7 +96,15 @@ ${LINUX_BUILDER_DIR}/update.sh
[tasks.build] [tasks.build]
dependencies = ["target_dir"] dependencies = ["target_dir"]
command = "cargo" command = "cargo"
args = ["build", "--profile", "${PROFILE}", "--target-dir", "${TARGET_DIR}"] args = [
"build",
"--profile",
"${PROFILE}",
"--target-dir",
"${TARGET_DIR}",
"--features",
"${FEATURE}",
]
[tasks.run] [tasks.run]
dependencies = ["build"] dependencies = ["build"]
@ -100,18 +119,18 @@ else
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
fi fi
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/linux.qcow2 -F qcow2 ${LINUX_BUILDER_OUT}/linux.tmp.qcow2
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_kernel \ ${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_kernel \
-accel tcg \ -accel tcg \
-m 4G \ -m 4G \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \ -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \ -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \ -device ahci,id=ahci,bus=pci.0,addr=4 \
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \ -device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \ -blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.tmp.qcow2",node-name=storage `# Backend file of "disk"` \
-blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \ -blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \
-L "${LIBAFL_QEMU_BIOS_DIR}" \ -L "${LIBAFL_QEMU_BIOS_DIR}" \
-nographic \ -nographic \

View File

@ -1,7 +1,12 @@
obj-m += harness.o obj-m += harness.o
# harness-objs += symfinder.o
all: all:
make -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules make EXTRA_CFLAGS="-DUSE_LQEMU=1" -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
gcc -Wall -Werror -o user user.c
nyx:
make EXTRA_CFLAGS="-DUSE_NYX=1" -C /lib/modules/$(LINUX_MODULES)/build M=$(PWD) modules
gcc -Wall -Werror -o user user.c gcc -Wall -Werror -o user user.c
clean: clean:

View File

@ -11,11 +11,14 @@
#include <linux/kallsyms.h> #include <linux/kallsyms.h>
#include "x509-parser.h" #include "x509-parser.h"
#include "libafl_qemu.h"
#define MAX_DEV 1 #if defined(USE_LQEMU)
#include "libafl_qemu.h"
#elif defined(USE_NYX)
#include "nyx_api.h"
#endif
#define BUF_SIZE 4096 #define PAYLOAD_MAX_SIZE 65536
static int harness_open(struct inode *inode, struct file *file); static int harness_open(struct inode *inode, struct file *file);
static int harness_release(struct inode *inode, struct file *file); static int harness_release(struct inode *inode, struct file *file);
@ -37,13 +40,16 @@ static struct mychar_device_data harness_data;
#define KPROBE_PRE_HANDLER(fname) \ #define KPROBE_PRE_HANDLER(fname) \
static int __kprobes fname(struct kprobe *p, struct pt_regs *regs) static int __kprobes fname(struct kprobe *p, struct pt_regs *regs)
long unsigned int kln_addr = 0; // kallsyms_lookup_name function address
unsigned long (*kln_pointer)(const char *name) = NULL; static long unsigned int kallsyms_lookup_name_addr = 0;
// kallsyms_lookup_name function
static unsigned long (*kall_syms_lookup_name_fn)(const char *name) = NULL;
static struct kprobe kp0, kp1; static struct kprobe kp0, kp1;
KPROBE_PRE_HANDLER(handler_pre0) { KPROBE_PRE_HANDLER(handler_pre0) {
kln_addr = (--regs->ip); kallsyms_lookup_name_addr = (--regs->ip);
return 0; return 0;
} }
@ -89,11 +95,16 @@ static int harness_find_kallsyms_lookup(void) {
unregister_kprobe(&kp0); unregister_kprobe(&kp0);
unregister_kprobe(&kp1); unregister_kprobe(&kp1);
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kln_addr); #ifdef USE_NYX
hprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
#elif DEFINED(USE_LQEMU)
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
#endif
if (kln_addr == 0) { return -1; } if (kallsyms_lookup_name_addr == 0) { return -1; }
kln_pointer = (unsigned long (*)(const char *name))kln_addr; kall_syms_lookup_name_fn =
(unsigned long (*)(const char *name))kallsyms_lookup_name_addr;
return ret; return ret;
} }
@ -104,6 +115,104 @@ static int harness_uevent(const struct device *dev,
return 0; return 0;
} }
#ifdef USE_NYX
/**
* Allocate page-aligned memory
*/
static void *malloc_resident_pages(size_t num_pages) {
size_t data_size = PAGE_SIZE * num_pages;
void *ptr = NULL;
if ((ptr = kzalloc(data_size, GFP_KERNEL)) == NULL) {
printk("Allocation failure\n");
goto err_out;
}
// ensure pages are aligned and resident
memset(ptr, 0x42, data_size);
// if (mlock(ptr, data_size) == -1) {
// printk("Error locking scratch buffer\n");
// goto err_out;
// }
// assert(((uintptr_t)ptr % PAGE_SIZE) == 0);
return ptr;
err_out:
// free(ptr);
return NULL;
}
static volatile __attribute__((aligned(PAGE_SIZE))) uint64_t range_args[3];
static void hrange_submit(unsigned id, unsigned long start, unsigned long end) {
range_args[0] = start;
range_args[1] = end;
range_args[2] = id;
kAFL_hypercall(HYPERCALL_KAFL_RANGE_SUBMIT, (unsigned long)range_args);
}
static int agent_init(int verbose) {
host_config_t host_config;
hprintf("Nyx agent init");
// 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) {
printk("GET_HOST_CONFIG\n");
printk("\thost magic: 0x%x, version: 0x%x\n", host_config.host_magic,
host_config.host_version);
printk("\tbitmap size: 0x%x, ijon: 0x%x\n", host_config.bitmap_size,
host_config.ijon_bitmap_size);
printk("\tpayload size: %u KB\n", host_config.payload_buffer_size / 1024);
printk("\tworker id: %d\n", host_config.worker_id);
}
if (host_config.host_magic != NYX_HOST_MAGIC) {
hprintf("HOST_MAGIC mismatch: %08x != %08x", 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;
}
#endif
static int __init harness_init(void) { static int __init harness_init(void) {
int err; int err;
dev_t dev; dev_t dev;
@ -126,7 +235,10 @@ static int __init harness_init(void) {
err = harness_find_kallsyms_lookup(); err = harness_find_kallsyms_lookup();
if (err < 0) { return err; } if (err < 0) {
habort("error while trying to find kallsyms");
return err;
}
return 0; return 0;
} }
@ -141,35 +253,91 @@ static void __exit harness_exit(void) {
} }
static int harness_open(struct inode *inode, struct file *file) { static int harness_open(struct inode *inode, struct file *file) {
unsigned long x509_fn_addr = kall_syms_lookup_name_fn("x509_cert_parse");
unsigned long asn1_ber_decoder_addr =
kall_syms_lookup_name_fn("asn1_ber_decoder");
unsigned long x509_get_sig_params_addr =
kall_syms_lookup_name_fn("x509_get_sig_params");
// hprintf("action 0: %p", x509_decoder.actions[0]);
#if defined(USE_LQEMU)
lqprintf("harness: Device open\n"); lqprintf("harness: Device open\n");
char *data = kzalloc(BUF_SIZE, GFP_KERNEL);
data[0] = 0xff; // init page
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... // TODO: better filtering...
libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000); libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000);
libafl_qemu_test(); libafl_qemu_test();
u64 buf_size = libafl_qemu_start_virt(data, BUF_SIZE); char *input_buf = kzalloc(PAYLOAD_MAX_SIZE, GFP_KERNEL);
input_buf[0] = 0xff; // init page
struct x509_certificate *cert_ret = x509_cert_parse(data, buf_size); #elif defined(USE_NYX)
hprintf("harness: Device open.");
hprintf("\tx509_cert_parse: %p", x509_fn_addr);
hprintf("\tasn1_ber_decoder: %p", asn1_ber_decoder_addr);
hprintf("\tx509_get_sig_params: %p", x509_get_sig_params_addr);
libafl_qemu_end(LIBAFL_QEMU_END_OK); if (!x509_fn_addr || !asn1_ber_decoder_addr) { habort("Invalid ranges"); }
kAFL_payload *pbuf = malloc_resident_pages(PAYLOAD_MAX_SIZE / PAGE_SIZE);
agent_init(1);
// kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0);
hrange_submit(0, x509_fn_addr, x509_fn_addr + 0x1000);
hrange_submit(1, asn1_ber_decoder_addr, asn1_ber_decoder_addr + 0x1000);
hrange_submit(2, x509_get_sig_params_addr, x509_get_sig_params_addr + 0x1000);
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uintptr_t)pbuf);
hprintf("payload size addr: %p", &pbuf->size);
hprintf("payload addr: %p", &pbuf->data);
#else
#error No API specified.
#endif
// int ret;
// uintptr_t start_addr = 0, end_addr = 0;
// ret = lqemu_symfinder_widen_range("x509_cert_parse", &start_addr,
// &end_addr); if (ret) {
// printk("error while handling range");
// return ret;
// }
while (true) {
#if defined(USE_LQEMU)
uint8_t *data = input_buf;
size_t size = libafl_qemu_start_virt(data, PAYLOAD_MAX_SIZE);
#elif defined(USE_NYX)
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
size_t size = pbuf->size;
const volatile uint8_t *data = pbuf->data;
#endif
__maybe_unused struct x509_certificate *cert_ret =
x509_cert_parse((const void *)data, size);
#if defined(USE_LQEMU)
libafl_qemu_end(LIBAFL_QEMU_END_OK);
#elif defined(USE_NYX)
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
#endif
}
return 0; return 0;
} }
static int harness_release(struct inode *inode, struct file *file) { static int harness_release(struct inode *inode, struct file *file) {
#if defined(USE_LQEMU)
lqprintf("harness: Device close\n"); lqprintf("harness: Device close\n");
#elif defined(USE_NYX)
hprintf("harness: Device close");
#endif
return 0; return 0;
} }

View File

@ -3,6 +3,10 @@
LINUX_MODULES=$(pacman -Ql linux-headers | grep -m 1 -E '/usr/lib/modules/[^/]*/' | sed 's|.*/usr/lib/modules/\([^/]*\)/.*|\1|') LINUX_MODULES=$(pacman -Ql linux-headers | grep -m 1 -E '/usr/lib/modules/[^/]*/' | sed 's|.*/usr/lib/modules/\([^/]*\)/.*|\1|')
export LINUX_MODULES export LINUX_MODULES
# Default root password
echo "root:toor" | chpasswd
cd /setup cd /setup
make clean make clean
make -j make -j nyx

View File

@ -7,6 +7,9 @@
// From https://lore.kernel.org/kvm/20240304065703.GA24373@wunner.de/T/ // From https://lore.kernel.org/kvm/20240304065703.GA24373@wunner.de/T/
#ifndef X509_PARSER_H
#define X509_PARSER_H
#include <crypto/public_key.h> #include <crypto/public_key.h>
#include <keys/asymmetric-type.h> #include <keys/asymmetric-type.h>
#include <linux/time64.h> #include <linux/time64.h>
@ -43,4 +46,6 @@ struct x509_certificate {
}; };
struct x509_certificate *x509_cert_parse(const void *data, size_t datalen); struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
void x509_free_certificate(struct x509_certificate *cert); void x509_free_certificate(struct x509_certificate *cert);
#endif

View File

@ -3,6 +3,8 @@
use core::time::Duration; use core::time::Duration;
use std::{env, path::PathBuf, process}; use std::{env, path::PathBuf, process};
#[cfg(not(feature = "nyx"))]
use libafl::state::{HasExecutions, State};
use libafl::{ use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig}, events::{launcher::Launcher, EventConfig},
@ -10,7 +12,7 @@ use libafl::{
feedback_or, feedback_or_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::BytesInput, inputs::{BytesInput, HasTargetBytes, UsesInput},
monitors::MultiMonitor, monitors::MultiMonitor,
mutators::{havoc_mutations, scheduled::StdScheduledMutator, I2SRandReplaceBinonly}, mutators::{havoc_mutations, scheduled::StdScheduledMutator, I2SRandReplaceBinonly},
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
@ -27,14 +29,82 @@ use libafl_bolts::{
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list, 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::{ use libafl_qemu::{
emu::Emulator, emu::Emulator,
executor::QemuExecutor, executor::QemuExecutor,
modules::{cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule}, modules::{
// StdEmulatorDriver cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule,
EmulatorModuleTuple,
},
FastSnapshotManager, NopSnapshotManager, QemuInitError,
}; };
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; 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, NopSnapshotManager>,
QemuInitError,
>
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
<S as UsesInput>::Input: HasTargetBytes,
{
Emulator::empty()
.qemu_parameters(args)
.modules(modules)
.driver(NyxEmulatorDriver::builder().build())
.command_manager(NyxCommandManager::default())
.snapshot_manager(NopSnapshotManager::default())
.build()
}
#[cfg(not(feature = "nyx"))]
fn get_emulator<ET, S>(
args: Vec<String>,
mut modules: ET,
) -> Result<
Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>,
QemuInitError,
>
where
ET: EmulatorModuleTuple<S>,
S: State + HasExecutions + Unpin,
<S as UsesInput>::Input: HasTargetBytes,
{
// Allow linux process address space addresses as feedback
modules.allow_address_range_all(LINUX_PROCESS_ADDRESS_RANGE);
Emulator::builder()
.qemu_parameters(args)
.modules(modules)
.build()
}
#[allow(unused)]
fn display_args() {
let args: Vec<String> = env::args().collect();
let mut arg_str = String::new();
for arg in args {
arg_str.push_str(&arg);
arg_str.push_str(" \\\n\t");
}
arg_str.pop();
arg_str.pop();
arg_str.pop();
log::info!("QEMU args:");
log::info!("\n{arg_str}");
}
pub fn fuzz() { pub fn fuzz() {
env_logger::init(); env_logger::init();
@ -70,10 +140,7 @@ pub fn fuzz() {
CmpLogModule::default(), CmpLogModule::default(),
); );
let emu = Emulator::builder() let emu = get_emulator(args, modules)?;
.qemu_parameters(args)
.modules(modules)
.build()?;
let devices = emu.list_devices(); let devices = emu.list_devices();
println!("Devices = {:?}", devices); println!("Devices = {:?}", devices);

View File

@ -27,6 +27,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature
] } ] }
env_logger = "0.11.5" env_logger = "0.11.5"
libafl_targets = { path = "../../../libafl_targets" } libafl_targets = { path = "../../../libafl_targets" }
log = "0.4.22"
[build-dependencies] [build-dependencies]
libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" } libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" }

View File

@ -11,8 +11,11 @@ end
if eq ${harness_api} "nyx" if eq ${harness_api} "nyx"
set_env FEATURE nyx set_env FEATURE nyx
else elseif eq ${harness_api} "lqemu"
set_env FEATURE "" set_env FEATURE ""
else
echo "Unknown harness API: ${harness_api}"
exit 1
end end
''', ''' ''', '''
@ -145,18 +148,18 @@ else
LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu
fi fi
cp ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd ${LINUX_BUILDER_OUT}/OVMF_CODE.fd.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2
cp ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd ${LINUX_BUILDER_OUT}/OVMF_VARS.fd.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2
cp ${LINUX_BUILDER_OUT}/linux.qcow2 ${LINUX_BUILDER_OUT}/linux.qcow2.clone qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/linux.qcow2 -F qcow2 ${LINUX_BUILDER_OUT}/linux.tmp.qcow2
${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_process \ ${TARGET_DIR}/${PROFILE_DIR}/qemu_linux_process \
-accel tcg \ -accel tcg \
-m 4G \ -m 4G \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd" `# OVMF code pflash` \ -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \
-drive if=pflash,format=raw,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd" `# OVMF vars pflash` \ -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \
-device virtio-scsi-pci,id=scsi0 `# SCSI bus` \ -device ahci,id=ahci,bus=pci.0,addr=4 \
-device scsi-hd,bus=scsi0.0,drive=disk,id=virtio-disk0,bootindex=1 \ -device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \
-blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.qcow2",node-name=storage `# Backend file of "disk"` \ -blockdev driver=file,filename="${LINUX_BUILDER_OUT}/linux.tmp.qcow2",node-name=storage `# Backend file of "disk"` \
-blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \ -blockdev driver=qcow2,file=storage,node-name=disk `# QCOW2 "disk"` \
-L "${LIBAFL_QEMU_BIOS_DIR}" \ -L "${LIBAFL_QEMU_BIOS_DIR}" \
-nographic \ -nographic \

View File

@ -55,9 +55,22 @@ err_out:
return NULL; return NULL;
} }
void hrange_submit(unsigned id, uintptr_t start, uintptr_t end) {
uint64_t range_arg[3] __attribute__((aligned(PAGE_SIZE)));
memset(range_arg, 0, sizeof(range_arg));
range_arg[0] = start;
range_arg[1] = end;
range_arg[2] = id;
kAFL_hypercall(HYPERCALL_KAFL_RANGE_SUBMIT, (uintptr_t)range_arg);
}
int agent_init(int verbose) { int agent_init(int verbose) {
host_config_t host_config; host_config_t host_config;
hprintf("Nyx agent init");
// set ready state // set ready state
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0); kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0); kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
@ -120,18 +133,25 @@ int main() {
agent_init(1); agent_init(1);
kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0);
hrange_submit(0, 0x0, 0x00007fffffffffff);
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uint64_t)pbuf); kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uint64_t)pbuf);
hprintf("payload size addr: %p", &pbuf->size); hprintf("payload size addr: %p", &pbuf->size);
hprintf("payload addr: %p", &pbuf->data); hprintf("payload addr: %p", &pbuf->data);
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0); while (true) {
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0); kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
// Call the target // Call the target
bool ret = FuzzMe(pbuf->data, pbuf->size); bool ret = FuzzMe(pbuf->data, pbuf->size);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0); if (ret) { kAFL_hypercall(HYPERCALL_KAFL_PANIC, 0); }
habort("Error: post-release code has been triggered."); kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
}
habort("post-release code has been triggered. Snapshot error?");
} }

View File

@ -1,3 +1,4 @@
#!/bin/bash #!/bin/bash
# Nothing to do # Nothing to do
echo 'root:toor' | sudo chpasswd

View File

@ -8,8 +8,8 @@ use libafl::state::{HasExecutions, State};
use libafl::{ use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig}, events::{launcher::Launcher, EventConfig},
executors::ShadowExecutor, executors::{ExitKind, ShadowExecutor},
feedback_or, feedback_or_fast, feedback_and_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes, UsesInput}, inputs::{BytesInput, HasTargetBytes, UsesInput},
@ -37,10 +37,11 @@ use libafl_qemu::{
emu::Emulator, emu::Emulator,
executor::QemuExecutor, executor::QemuExecutor,
modules::{ modules::{
cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule, cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule,
EmulatorModuleTuple, utils::filters::LINUX_PROCESS_ADDRESS_RANGE, CmpLogModule, EmulatorModuleTuple,
}, },
FastSnapshotManager, QemuInitError, EmulatorDriverError, FastSnapshotManager, NopSnapshotManager, QemuInitError,
QemuSnapshotManager,
}; };
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
@ -49,7 +50,7 @@ fn get_emulator<ET, S>(
args: Vec<String>, args: Vec<String>,
modules: ET, modules: ET,
) -> Result< ) -> Result<
Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, FastSnapshotManager>, Emulator<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, NopSnapshotManager>,
QemuInitError, QemuInitError,
> >
where where
@ -60,21 +61,16 @@ where
Emulator::empty() Emulator::empty()
.qemu_parameters(args) .qemu_parameters(args)
.modules(modules) .modules(modules)
.driver( .driver(NyxEmulatorDriver::builder().build())
NyxEmulatorDriver::builder()
.allow_page_on_start(true)
.process_only(true)
.build(),
)
.command_manager(NyxCommandManager::default()) .command_manager(NyxCommandManager::default())
.snapshot_manager(FastSnapshotManager::default()) .snapshot_manager(NopSnapshotManager::default())
.build() .build()
} }
#[cfg(not(feature = "nyx"))] #[cfg(not(feature = "nyx"))]
fn get_emulator<ET, S>( fn get_emulator<ET, S>(
args: Vec<String>, args: Vec<String>,
modules: ET, mut modules: ET,
) -> Result< ) -> Result<
Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>, Emulator<StdCommandManager<S>, StdEmulatorDriver, ET, S, FastSnapshotManager>,
QemuInitError, QemuInitError,
@ -84,7 +80,29 @@ where
S: State + HasExecutions + Unpin, S: State + HasExecutions + Unpin,
<S as UsesInput>::Input: HasTargetBytes, <S as UsesInput>::Input: HasTargetBytes,
{ {
Emulator::builder().qemu_cli(args).modules(modules).build() // Allow linux process address space addresses as feedback
modules.allow_address_range_all(LINUX_PROCESS_ADDRESS_RANGE);
Emulator::builder()
.qemu_parameters(args)
.modules(modules)
.build()
}
fn display_args() {
let args: Vec<String> = env::args().collect();
let mut arg_str = String::new();
for arg in args {
arg_str.push_str(&arg);
arg_str.push_str(" \\\n\t");
}
arg_str.pop();
arg_str.pop();
arg_str.pop();
log::info!("QEMU args:");
log::info!("\n{arg_str}");
} }
pub fn fuzz() { pub fn fuzz() {
@ -94,14 +112,16 @@ pub fn fuzz() {
str::parse::<usize>(&s).expect("FUZZ_SIZE was not a number"); str::parse::<usize>(&s).expect("FUZZ_SIZE was not a number");
}; };
// Hardcoded parameters // Hardcoded parameters
let timeout = Duration::from_secs(99999999); let timeout = Duration::from_secs(50);
let broker_port = 1338; let broker_port = 1338;
let cores = Cores::from_cmdline("1").unwrap(); let cores = Cores::from_cmdline("1").unwrap();
let corpus_dirs = [PathBuf::from("./corpus")]; let corpus_dirs = [PathBuf::from("./corpus")];
let objective_dir = PathBuf::from("./crashes"); let objective_dir = PathBuf::from("./crashes");
display_args();
let mut run_client = |state: Option<_>, mut mgr, _client_description| { let mut run_client = |state: Option<_>, mut mgr, _client_description| {
// Initialize QEMU // Fetch QEMU args from program's arguments
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
@ -127,7 +147,12 @@ pub fn fuzz() {
// The wrapped harness function, calling out to the LLVM-style harness // The wrapped harness function, calling out to the LLVM-style harness
let mut harness = let mut harness =
|emulator: &mut Emulator<_, _, _, _, _>, state: &mut _, input: &BytesInput| unsafe { |emulator: &mut Emulator<_, _, _, _, _>, state: &mut _, input: &BytesInput| unsafe {
emulator.run(state, input).unwrap().try_into().unwrap() match emulator.run(state, input) {
Ok(res) => res.try_into().unwrap(),
Err(e) => match e {
_ => panic!("{e:?}"),
},
}
}; };
// Create an observation channel to keep track of the execution time // Create an observation channel to keep track of the execution time
@ -145,8 +170,13 @@ pub fn fuzz() {
TimeFeedback::new(&time_observer) TimeFeedback::new(&time_observer)
); );
let map_feedback = MaxMapFeedback::new(&edges_observer);
// A feedback to choose if an input is a solution or not // A feedback to choose if an input is a solution or not
let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); let mut objective = feedback_and_fast!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
map_feedback,
);
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { let mut state = state.unwrap_or_else(|| {

View File

@ -1310,6 +1310,27 @@ pub mod pybind {
} }
} }
/// Create a [`Vec`] of the given type with `nb_elts` elements, initialized in place.
/// The closure must initialize [`Vec`] (of size `nb_elts` * `sizeo_of::<T>()`).
///
/// # Safety
///
/// The input closure should fully initialize the new [`Vec`], not leaving any uninitialized bytes.
// TODO: Use MaybeUninit API at some point.
#[cfg(feature = "alloc")]
#[expect(clippy::uninit_vec)]
pub unsafe fn vec_init<E, F, T>(nb_elts: usize, init_fn: F) -> Result<Vec<T>, E>
where
F: FnOnce(&mut Vec<T>) -> Result<(), E>,
{
let mut new_vec: Vec<T> = Vec::with_capacity(nb_elts);
new_vec.set_len(nb_elts);
init_fn(&mut new_vec)?;
Ok(new_vec)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
pub const QEMU_REVISION: &str = "ace364678aef879514e150e626695c4793db41e9"; pub const QEMU_REVISION: &str = "2b5e4bfcff875571b2813a9494de8b2e4c56120e";
pub struct BuildResult { pub struct BuildResult {
pub qemu_path: PathBuf, pub qemu_path: PathBuf,

View File

@ -96,12 +96,12 @@
#define KAFL_MODE_32 1 #define KAFL_MODE_32 1
#define KAFL_MODE_16 2 #define KAFL_MODE_16 2
typedef struct { typedef volatile struct {
int32_t size; int32_t size;
uint8_t data[]; uint8_t data[];
} kAFL_payload; } kAFL_payload;
typedef struct { typedef volatile struct {
uint64_t ip[4]; uint64_t ip[4];
uint64_t size[4]; uint64_t size[4];
uint8_t enabled[4]; uint8_t enabled[4];
@ -154,7 +154,7 @@ typedef struct {
/* more to come */ /* more to come */
} __attribute__((packed)) host_config_t; } __attribute__((packed)) host_config_t;
typedef struct { typedef volatile struct {
uint32_t agent_magic; uint32_t agent_magic;
uint32_t agent_version; uint32_t agent_version;
uint8_t agent_timeout_detection; uint8_t agent_timeout_detection;

View File

@ -1,4 +1,4 @@
use std::{mem::size_of, ops::Range, sync::OnceLock}; use std::{mem::size_of, sync::OnceLock};
use capstone::arch::BuildsCapstone; use capstone::arch::BuildsCapstone;
use enum_map::{enum_map, EnumMap}; use enum_map::{enum_map, EnumMap};
@ -65,8 +65,6 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder {
pub type GuestReg = u64; pub type GuestReg = u64;
pub const PROCESS_ADDRESS_RANGE: Range<u64> = 0..0x0000_7fff_ffff_ffff;
impl crate::ArchExtras for crate::CPU { impl crate::ArchExtras for crate::CPU {
fn read_return_address(&self) -> Result<GuestReg, QemuRWError> { fn read_return_address(&self) -> Result<GuestReg, QemuRWError> {
let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?; let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?;

View File

@ -32,7 +32,10 @@ use crate::{
IsSnapshotManager, Qemu, QemuMemoryChunk, QemuRWError, Regs, StdEmulatorDriver, CPU, IsSnapshotManager, Qemu, QemuMemoryChunk, QemuRWError, Regs, StdEmulatorDriver, CPU,
}; };
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))] #[cfg(all(
any(cpu_target = "i386", cpu_target = "x86_64"),
feature = "systemmode"
))]
pub mod nyx; pub mod nyx;
pub mod parser; pub mod parser;
@ -251,6 +254,7 @@ pub enum CommandError {
TestDifference(GuestReg, GuestReg), // received, expected TestDifference(GuestReg, GuestReg), // received, expected
StartedTwice, StartedTwice,
EndBeforeStart, EndBeforeStart,
WrongUsage,
} }
impl From<QemuRWError> for CommandError { impl From<QemuRWError> for CommandError {
@ -461,18 +465,12 @@ where
// Auto page filtering if option is enabled // Auto page filtering if option is enabled
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
if emu.driver_mut().allow_page_on_start() { if emu.driver_mut().allow_page_on_start() {
if let Some(page_id) = qemu.current_cpu().unwrap().current_paging_id() { if let Some(paging_id) = qemu.current_cpu().unwrap().current_paging_id() {
emu.modules_mut().modules_mut().allow_page_id_all(page_id); log::info!("Filter: allow page ID {paging_id}.");
emu.modules_mut().modules_mut().allow_page_id_all(paging_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 // Make sure JIT cache is empty just before starting
qemu.flush_jit(); qemu.flush_jit();

View File

@ -10,6 +10,7 @@ use std::{
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
marker::PhantomData, marker::PhantomData,
mem::offset_of, mem::offset_of,
ops::Range,
ptr, ptr,
slice::from_raw_parts, slice::from_raw_parts,
}; };
@ -19,7 +20,7 @@ use libafl::{
executors::ExitKind, executors::ExitKind,
inputs::{HasTargetBytes, UsesInput}, inputs::{HasTargetBytes, UsesInput},
}; };
use libafl_qemu_sys::GuestVirtAddr; use libafl_qemu_sys::{GuestAddr, GuestVirtAddr};
use libc::c_uint; use libc::c_uint;
use paste::paste; use paste::paste;
@ -27,8 +28,9 @@ use crate::{
command::{ command::{
parser::nyx::{ parser::nyx::{
AcquireCommandParser, GetHostConfigCommandParser, GetPayloadCommandParser, AcquireCommandParser, GetHostConfigCommandParser, GetPayloadCommandParser,
NextPayloadCommandParser, PrintfCommandParser, ReleaseCommandParser, NextPayloadCommandParser, PanicCommandParser, PrintfCommandParser,
SetAgentConfigCommandParser, RangeSubmitCommandParser, ReleaseCommandParser, SetAgentConfigCommandParser,
SubmitCR3CommandParser, SubmitPanicCommandParser, UserAbortCommandParser,
}, },
CommandError, CommandManager, IsCommand, NativeCommandParser, CommandError, CommandManager, IsCommand, NativeCommandParser,
}, },
@ -109,12 +111,13 @@ macro_rules! define_nyx_command_manager {
#[deny(unreachable_patterns)] #[deny(unreachable_patterns)]
fn parse(&self, qemu: Qemu) -> Result<Self::Commands, CommandError> { fn parse(&self, qemu: Qemu) -> Result<Self::Commands, CommandError> {
let arch_regs_map: &'static EnumMap<ExitArgs, Regs> = get_exit_arch_regs(); 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; let nyx_backdoor = qemu.read_reg(Regs::Rax)? as c_uint;
let cmd_id = qemu.read_reg(Regs::Rbx)? as c_uint;
// Check nyx backdoor correctness // Check nyx backdoor correctness
debug_assert_eq!(nyx_backdoor, 0x1f); debug_assert_eq!(nyx_backdoor, 0x1f);
let cmd_id = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])? as c_uint; log::debug!("Received Nyx command ID: {cmd_id}");
match cmd_id { match cmd_id {
// <StartPhysCommandParser as NativeCommandParser<S>>::COMMAND_ID => Ok(StdCommandManagerCommands::StartPhysCommandParserCmd(<StartPhysCommandParser as NativeCommandParser<S>>::parse(qemu, arch_regs_map)?)), // <StartPhysCommandParser as NativeCommandParser<S>>::COMMAND_ID => Ok(StdCommandManagerCommands::StartPhysCommandParserCmd(<StartPhysCommandParser as NativeCommandParser<S>>::parse(qemu, arch_regs_map)?)),
@ -176,7 +179,12 @@ define_nyx_command_manager!(
SetAgentConfigCommand, SetAgentConfigCommand,
PrintfCommand, PrintfCommand,
GetPayloadCommand, GetPayloadCommand,
NextPayloadCommand NextPayloadCommand,
SubmitCR3Command,
PanicCommand,
SubmitPanicCommand,
UserAbortCommand,
RangeSubmitCommand
], ],
[ [
AcquireCommandParser, AcquireCommandParser,
@ -185,7 +193,12 @@ define_nyx_command_manager!(
SetAgentConfigCommandParser, SetAgentConfigCommandParser,
PrintfCommandParser, PrintfCommandParser,
GetPayloadCommandParser, GetPayloadCommandParser,
NextPayloadCommandParser NextPayloadCommandParser,
SubmitCR3CommandParser,
SubmitPanicCommandParser,
PanicCommandParser,
UserAbortCommandParser,
RangeSubmitCommandParser
] ]
); );
@ -309,55 +322,251 @@ where
Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>, Option<EmulatorDriverResult<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>>,
EmulatorDriverError, EmulatorDriverError,
> { > {
if emu.command_manager_mut().start() {
return Err(EmulatorDriverError::CommandError(
CommandError::StartedTwice,
));
}
let qemu = emu.qemu(); let qemu = emu.qemu();
// Snapshot VM if !emu.command_manager_mut().start() {
let snapshot_id = emu.snapshot_manager_mut().save(qemu); log::debug!("Creating snapshot.");
// Set snapshot ID to restore to after fuzzing ends // Snapshot VM
emu.driver_mut() let snapshot_id = emu.snapshot_manager_mut().save(qemu);
.set_snapshot_id(snapshot_id)
.map_err(|_| EmulatorDriverError::MultipleSnapshotDefinition)?; // Set snapshot ID to restore to after fuzzing ends
emu.driver_mut()
.set_snapshot_id(snapshot_id)
.map_err(|_| EmulatorDriverError::MultipleSnapshotDefinition)?;
// 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(paging_id) = qemu.current_cpu().unwrap().current_paging_id() {
log::info!("Filter: allow page ID {paging_id}.");
emu.modules_mut().modules_mut().allow_page_id_all(paging_id);
}
}
// Make sure JIT cache is empty just before starting
qemu.flush_jit();
log::info!("Fuzzing starts");
}
// write nyx input to vm // write nyx input to vm
emu.driver().write_input(qemu, input)?; 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) Ok(None)
} }
} }
#[derive(Debug, Clone)]
pub struct SubmitCR3Command;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for SubmitCR3Command
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
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 let Some(current_cpu) = qemu.current_cpu() {
if let Some(paging_id) = current_cpu.current_paging_id() {
log::info!("Filter: allow page ID {paging_id}.");
emu.modules_mut().modules_mut().allow_page_id_all(paging_id);
Ok(None)
} else {
log::warn!("No paging id found for current cpu");
Err(EmulatorDriverError::CommandError(CommandError::WrongUsage))
}
} else {
log::error!("No current cpu found");
Err(EmulatorDriverError::CommandError(CommandError::WrongUsage))
}
}
}
#[derive(Debug, Clone)]
pub struct RangeSubmitCommand {
allowed_range: Range<GuestAddr>,
}
impl RangeSubmitCommand {
pub fn new(allowed_range: Range<GuestAddr>) -> Self {
Self { allowed_range }
}
}
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for RangeSubmitCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
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,
> {
log::info!("Allow address range: {:#x?}", self.allowed_range);
const EMPTY_RANGE: Range<GuestAddr> = 0..0;
if self.allowed_range == EMPTY_RANGE {
log::warn!("The given range is {:#x?}, which is most likely invalid. It is most likely a guest error.", EMPTY_RANGE);
log::warn!("Hint: make sure the range is not getting optimized out (the volatile keyword may help you).");
}
emu.modules_mut()
.modules_mut()
.allow_address_range_all(self.allowed_range.clone());
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct PanicCommand;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for PanicCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
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_mut().has_started() {
return Err(EmulatorDriverError::CommandError(
CommandError::EndBeforeStart,
));
}
let snapshot_id = emu
.driver_mut()
.snapshot_id()
.ok_or(EmulatorDriverError::SnapshotNotFound)?;
log::debug!("Restoring snapshot");
emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?;
emu.snapshot_manager_mut().check(qemu, &snapshot_id)?;
Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Crash)))
}
}
#[derive(Debug, Clone)]
pub struct SubmitPanicCommand;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for SubmitPanicCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
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,
> {
// TODO: add breakpoint to submit panic addr / page and associate it with a panic command
unimplemented!()
}
}
#[derive(Debug, Clone)]
pub struct UserAbortCommand {
content: String,
}
impl UserAbortCommand {
pub fn new(content: String) -> Self {
Self { content }
}
}
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for UserAbortCommand
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
fn usable_at_runtime(&self) -> bool {
true
}
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,
> {
log::error!("Nyx Guest Abort: {}", self.content);
Ok(Some(EmulatorDriverResult::ShutdownRequest))
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ReleaseCommand; pub struct ReleaseCommand;
impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for ReleaseCommand impl<ET, S, SM> IsCommand<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> for ReleaseCommand
@ -384,11 +593,14 @@ where
let qemu = emu.qemu(); let qemu = emu.qemu();
if emu.command_manager().has_started() { if emu.command_manager().has_started() {
log::debug!("Release: end of fuzzing run. Restoring...");
let snapshot_id = emu let snapshot_id = emu
.driver_mut() .driver_mut()
.snapshot_id() .snapshot_id()
.ok_or(EmulatorDriverError::SnapshotNotFound)?; .ok_or(EmulatorDriverError::SnapshotNotFound)?;
log::debug!("Restoring snapshot");
emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?; emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?;
#[cfg(feature = "paranoid_debug")] #[cfg(feature = "paranoid_debug")]
@ -396,6 +608,8 @@ where
Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Ok))) Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Ok)))
} else { } else {
log::debug!("Early release. Skipping...");
Ok(None) Ok(None)
} }
} }

View File

@ -5,7 +5,6 @@ use libafl::{
executors::ExitKind, executors::ExitKind,
inputs::{HasTargetBytes, UsesInput}, inputs::{HasTargetBytes, UsesInput},
}; };
use libafl_bolts::AsSliceMut;
use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr};
use libc::c_uint; use libc::c_uint;
@ -20,7 +19,10 @@ use crate::{
GuestReg, IsSnapshotManager, Qemu, QemuMemoryChunk, Regs, StdEmulatorDriver, GuestReg, IsSnapshotManager, Qemu, QemuMemoryChunk, Regs, StdEmulatorDriver,
}; };
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))] #[cfg(all(
any(cpu_target = "i386", cpu_target = "x86_64"),
feature = "systemmode"
))]
pub mod nyx; pub mod nyx;
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new(); pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
@ -279,7 +281,6 @@ where
type OutputCommand = LqprintfCommand; type OutputCommand = LqprintfCommand;
const COMMAND_ID: c_uint = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LQPRINTF.0; const COMMAND_ID: c_uint = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LQPRINTF.0;
#[expect(clippy::uninit_vec)]
fn parse( fn parse(
qemu: Qemu, qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>, arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
@ -293,15 +294,10 @@ where
let total_size = str_size + 1; let total_size = str_size + 1;
let mut str_copy: Vec<u8> = unsafe {
let mut res = Vec::<u8>::with_capacity(total_size);
res.set_len(total_size);
res
};
let mem_chunk = let mem_chunk =
QemuMemoryChunk::virt(buf_addr as GuestVirtAddr, total_size as GuestReg, cpu); QemuMemoryChunk::virt(buf_addr as GuestVirtAddr, total_size as GuestReg, cpu);
mem_chunk.read(qemu, str_copy.as_slice_mut())?;
let str_copy: Vec<u8> = mem_chunk.read_vec(qemu)?;
let c_str: &CStr = CStr::from_bytes_with_nul(str_copy.as_slice()).unwrap(); let c_str: &CStr = CStr::from_bytes_with_nul(str_copy.as_slice()).unwrap();

View File

@ -1,4 +1,4 @@
use std::{ffi::CStr, mem::transmute, sync::OnceLock}; use std::{ffi::CStr, sync::OnceLock};
use enum_map::EnumMap; use enum_map::EnumMap;
use libafl::{ use libafl::{
@ -12,7 +12,8 @@ use crate::{
command::{ command::{
nyx::{ nyx::{
bindings, AcquireCommand, GetHostConfigCommand, GetPayloadCommand, NextPayloadCommand, bindings, AcquireCommand, GetHostConfigCommand, GetPayloadCommand, NextPayloadCommand,
NyxCommandManager, PrintfCommand, ReleaseCommand, SetAgentConfigCommand, NyxCommandManager, PanicCommand, PrintfCommand, RangeSubmitCommand, ReleaseCommand,
SetAgentConfigCommand, SubmitCR3Command, SubmitPanicCommand, UserAbortCommand,
}, },
parser::NativeCommandParser, parser::NativeCommandParser,
CommandError, CommandManager, NativeExitKind, CommandError, CommandManager, NativeExitKind,
@ -22,6 +23,20 @@ use crate::{
IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs, IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs,
}; };
fn get_guest_string(qemu: Qemu, string_ptr_reg: Regs) -> Result<String, CommandError> {
let str_addr = qemu.read_reg(string_ptr_reg)? 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)?;
Ok(CStr::from_bytes_until_nul(&msg_chunk)
.unwrap()
.to_str()
.unwrap()
.to_string())
}
pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new(); pub static EMU_EXIT_KIND_MAP: OnceLock<EnumMap<NativeExitKind, Option<ExitKind>>> = OnceLock::new();
pub struct AcquireCommandParser; pub struct AcquireCommandParser;
@ -58,14 +73,122 @@ where
fn parse( fn parse(
qemu: Qemu, qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>, _arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> { ) -> Result<Self::OutputCommand, CommandError> {
let payload_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2]).unwrap() as GuestVirtAddr; let payload_addr = qemu.read_reg(Regs::Rcx).unwrap() as GuestVirtAddr;
Ok(GetPayloadCommand::new(payload_addr)) Ok(GetPayloadCommand::new(payload_addr))
} }
} }
pub struct SubmitCR3CommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for SubmitCR3CommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = SubmitCR3Command;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_SUBMIT_CR3;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(SubmitCR3Command)
}
}
pub struct RangeSubmitCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for RangeSubmitCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = RangeSubmitCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_RANGE_SUBMIT;
fn parse(
qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let allowed_range_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
// # Safety
// Range submit is represented with an array of 3 u64 in the Nyx API.
let allowed_range: [u64; 3] = unsafe { qemu.read_mem_val(allowed_range_addr)? };
Ok(RangeSubmitCommand::new(allowed_range[0]..allowed_range[1]))
}
}
pub struct SubmitPanicCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for SubmitPanicCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = SubmitPanicCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_SUBMIT_PANIC;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(SubmitPanicCommand)
}
}
pub struct PanicCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for PanicCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = PanicCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_PANIC;
fn parse(
_qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
Ok(PanicCommand)
}
}
pub struct UserAbortCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for UserAbortCommandParser
where
ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin,
S::Input: HasTargetBytes,
SM: IsSnapshotManager,
{
type OutputCommand = UserAbortCommand;
const COMMAND_ID: c_uint = bindings::HYPERCALL_KAFL_USER_ABORT;
fn parse(
qemu: Qemu,
_arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> {
let msg = get_guest_string(qemu, Regs::Rcx)?;
Ok(UserAbortCommand::new(msg))
}
}
pub struct NextPayloadCommandParser; pub struct NextPayloadCommandParser;
impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM> impl<ET, S, SM> NativeCommandParser<NyxCommandManager<S>, NyxEmulatorDriver, ET, S, SM>
for NextPayloadCommandParser for NextPayloadCommandParser
@ -121,9 +244,9 @@ where
fn parse( fn parse(
qemu: Qemu, qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>, _arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> { ) -> Result<Self::OutputCommand, CommandError> {
let host_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr; let host_config_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
Ok(GetHostConfigCommand::new(QemuMemoryChunk::virt( Ok(GetHostConfigCommand::new(QemuMemoryChunk::virt(
host_config_addr, host_config_addr,
@ -146,16 +269,14 @@ where
fn parse( fn parse(
qemu: Qemu, qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>, _arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> { ) -> Result<Self::OutputCommand, CommandError> {
let agent_config_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr; let agent_config_addr = qemu.read_reg(Regs::Rcx)? as GuestVirtAddr;
let mut agent_config_buf: [u8; size_of::<bindings::agent_config_t>()] = // # Safety
[0; size_of::<bindings::agent_config_t>()]; // We use the C struct directly to get the agent config
let agent_config: bindings::agent_config_t =
qemu.read_mem(agent_config_addr, &mut agent_config_buf)?; unsafe { qemu.read_mem_val(agent_config_addr)? };
let agent_config: bindings::agent_config_t = unsafe { transmute(agent_config_buf) };
Ok(SetAgentConfigCommand::new(agent_config)) Ok(SetAgentConfigCommand::new(agent_config))
} }
@ -174,16 +295,10 @@ where
fn parse( fn parse(
qemu: Qemu, qemu: Qemu,
arch_regs_map: &'static EnumMap<ExitArgs, Regs>, _arch_regs_map: &'static EnumMap<ExitArgs, Regs>,
) -> Result<Self::OutputCommand, CommandError> { ) -> Result<Self::OutputCommand, CommandError> {
let str_addr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])? as GuestVirtAddr; let msg = get_guest_string(qemu, Regs::Rcx)?;
let mut msg_chunk: [u8; bindings::HPRINTF_MAX_SIZE as usize] = Ok(PrintfCommand::new(msg))
[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

@ -18,9 +18,15 @@ use crate::{
QemuShutdownCause, Regs, SnapshotId, SnapshotManagerCheckError, SnapshotManagerError, QemuShutdownCause, Regs, SnapshotId, SnapshotManagerCheckError, SnapshotManagerError,
}; };
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))] #[cfg(all(
any(cpu_target = "i386", cpu_target = "x86_64"),
feature = "systemmode"
))]
pub mod nyx; pub mod nyx;
#[cfg(any(cpu_target = "i386", cpu_target = "x86_64"))] #[cfg(all(
any(cpu_target = "i386", cpu_target = "x86_64"),
feature = "systemmode"
))]
pub use nyx::{NyxEmulatorDriver, NyxEmulatorDriverBuilder}; pub use nyx::{NyxEmulatorDriver, NyxEmulatorDriverBuilder};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -34,6 +40,9 @@ where
/// The run is over and the emulator is ready for the next iteration. /// The run is over and the emulator is ready for the next iteration.
EndOfRun(ExitKind), EndOfRun(ExitKind),
/// Internal shutdown request
ShutdownRequest,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -296,6 +305,10 @@ where
Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit)) Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit))
} }
EmulatorDriverResult::EndOfRun(exit_kind) => Ok(exit_kind), EmulatorDriverResult::EndOfRun(exit_kind) => Ok(exit_kind),
EmulatorDriverResult::ShutdownRequest => {
log::warn!("Shutdown request. Stopping fuzzing...");
std::process::exit(CTRL_C_EXIT);
}
} }
} }
} }

View File

@ -29,10 +29,7 @@ pub struct NyxEmulatorDriver {
hooks_locked: bool, hooks_locked: bool,
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
#[builder(default = false)] #[builder(default = false)]
allow_page_on_start: bool, // when fuzzing starts, module filters will only allow the current page table. allow_page_on_start: bool, // when fuzzing starts, all modules will only accept 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)] #[builder(default = false)]
print_commands: bool, print_commands: bool,
#[builder(default = (1024 * 1024))] #[builder(default = (1024 * 1024))]
@ -108,11 +105,6 @@ impl NyxEmulatorDriver {
pub fn allow_page_on_start(&self) -> bool { pub fn allow_page_on_start(&self) -> bool {
self.allow_page_on_start 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 impl<CM, ET, S, SM> EmulatorDriver<CM, ET, S, SM> for NyxEmulatorDriver

View File

@ -82,7 +82,7 @@ where
let emulator_modules = EmulatorModules::<ET, S>::emulator_modules_mut().unwrap(); let emulator_modules = EmulatorModules::<ET, S>::emulator_modules_mut().unwrap();
let qemu = Qemu::get_unchecked(); let qemu = Qemu::get_unchecked();
let crash_hooks_ptr = &raw mut emulator_modules.hooks.crash_hooks; let crash_hooks_ptr = &raw mut emulator_modules.hooks.hook_collection.crash_hooks;
for crash_hook in &mut (*crash_hooks_ptr) { for crash_hook in &mut (*crash_hooks_ptr) {
match crash_hook { match crash_hook {
@ -103,23 +103,14 @@ where
/// High-level `Emulator` modules, using `QemuHooks`. /// High-level `Emulator` modules, using `QemuHooks`.
#[derive(Debug)] #[derive(Debug)]
pub struct EmulatorModules<ET, S> pub struct EmulatorModules<ET, S> {
where
S: UsesInput,
{
modules: Pin<Box<ET>>, modules: Pin<Box<ET>>,
hooks: EmulatorHooks<ET, S>, hooks: EmulatorHooks<ET, S>,
phantom: PhantomData<S>, phantom: PhantomData<S>,
} }
/// Hook collection,
#[derive(Debug)] #[derive(Debug)]
pub struct EmulatorHooks<ET, S> struct EmulatorHookCollection<ET, S> {
where
S: UsesInput,
{
qemu_hooks: QemuHooks,
instruction_hooks: Vec<Pin<Box<(InstructionHookId, FatPtr)>>>, instruction_hooks: Vec<Pin<Box<(InstructionHookId, FatPtr)>>>,
backdoor_hooks: Vec<Pin<Box<(BackdoorHookId, FatPtr)>>>, backdoor_hooks: Vec<Pin<Box<(BackdoorHookId, FatPtr)>>>,
edge_hooks: Vec<Pin<Box<TcgHookState<1, EdgeHookId>>>>, edge_hooks: Vec<Pin<Box<TcgHookState<1, EdgeHookId>>>>,
@ -144,6 +135,42 @@ where
phantom: PhantomData<(ET, S)>, phantom: PhantomData<(ET, S)>,
} }
impl<ET, S> Default for EmulatorHookCollection<ET, S> {
fn default() -> Self {
Self {
instruction_hooks: Vec::default(),
backdoor_hooks: Vec::default(),
edge_hooks: Vec::default(),
block_hooks: Vec::default(),
read_hooks: Vec::default(),
write_hooks: Vec::default(),
cmp_hooks: Vec::default(),
cpu_run_hooks: Vec::default(),
new_thread_hooks: Vec::default(),
#[cfg(feature = "usermode")]
pre_syscall_hooks: Vec::default(),
#[cfg(feature = "usermode")]
post_syscall_hooks: Vec::default(),
#[cfg(feature = "usermode")]
crash_hooks: Vec::default(),
phantom: PhantomData,
}
}
}
/// Hook collection,
#[derive(Debug)]
pub struct EmulatorHooks<ET, S> {
qemu_hooks: QemuHooks,
hook_collection: EmulatorHookCollection<ET, S>,
}
impl<ET, S> EmulatorHooks<ET, S> impl<ET, S> EmulatorHooks<ET, S>
where where
S: UsesInput + Unpin, S: UsesInput + Unpin,
@ -152,27 +179,7 @@ where
pub fn new(qemu_hooks: QemuHooks) -> Self { pub fn new(qemu_hooks: QemuHooks) -> Self {
Self { Self {
qemu_hooks, qemu_hooks,
phantom: PhantomData, hook_collection: EmulatorHookCollection::default(),
instruction_hooks: Vec::new(),
backdoor_hooks: Vec::new(),
edge_hooks: Vec::new(),
block_hooks: Vec::new(),
read_hooks: Vec::new(),
write_hooks: Vec::new(),
cmp_hooks: Vec::new(),
cpu_run_hooks: Vec::new(),
new_thread_hooks: Vec::new(),
#[cfg(feature = "usermode")]
pre_syscall_hooks: Vec::new(),
#[cfg(feature = "usermode")]
post_syscall_hooks: Vec::new(),
#[cfg(feature = "usermode")]
crash_hooks: Vec::new(),
} }
} }
@ -189,11 +196,13 @@ where
) -> InstructionHookId { ) -> InstructionHookId {
let fat: FatPtr = unsafe { transmute(hook) }; let fat: FatPtr = unsafe { transmute(hook) };
self.instruction_hooks self.hook_collection
.instruction_hooks
.push(Box::pin((InstructionHookId::invalid(), fat))); .push(Box::pin((InstructionHookId::invalid(), fat)));
unsafe { unsafe {
let hook_state = &raw mut self let hook_state = &raw mut self
.hook_collection
.instruction_hooks .instruction_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -207,7 +216,8 @@ where
closure_instruction_hook_wrapper::<ET, S>, closure_instruction_hook_wrapper::<ET, S>,
invalidate_block, invalidate_block,
); );
self.instruction_hooks self.hook_collection
.instruction_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -275,15 +285,18 @@ where
unsafe extern "C" fn(&mut TcgHookState<1, EdgeHookId>, id: u64) unsafe extern "C" fn(&mut TcgHookState<1, EdgeHookId>, id: u64)
); );
self.edge_hooks.push(Box::pin(TcgHookState::new( self.hook_collection
EdgeHookId::invalid(), .edge_hooks
hook_to_repr!(generation_hook), .push(Box::pin(TcgHookState::new(
HookRepr::Empty, EdgeHookId::invalid(),
[hook_to_repr!(execution_hook)], hook_to_repr!(generation_hook),
))); HookRepr::Empty,
[hook_to_repr!(execution_hook)],
)));
let hook_state = &mut *ptr::from_mut::<TcgHookState<1, EdgeHookId>>( let hook_state = &mut *ptr::from_mut::<TcgHookState<1, EdgeHookId>>(
self.edge_hooks self.hook_collection
.edge_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -292,7 +305,8 @@ where
let id = self.qemu_hooks.add_edge_hooks(hook_state, gen, exec); let id = self.qemu_hooks.add_edge_hooks(hook_state, gen, exec);
self.edge_hooks self.hook_collection
.edge_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -332,15 +346,18 @@ where
unsafe extern "C" fn(&mut TcgHookState<1, BlockHookId>, id: u64) unsafe extern "C" fn(&mut TcgHookState<1, BlockHookId>, id: u64)
); );
self.block_hooks.push(Box::pin(TcgHookState::new( self.hook_collection
BlockHookId::invalid(), .block_hooks
hook_to_repr!(generation_hook), .push(Box::pin(TcgHookState::new(
hook_to_repr!(post_generation_hook), BlockHookId::invalid(),
[hook_to_repr!(execution_hook)], hook_to_repr!(generation_hook),
))); hook_to_repr!(post_generation_hook),
[hook_to_repr!(execution_hook)],
)));
let hook_state = &mut *ptr::from_mut::<TcgHookState<1, BlockHookId>>( let hook_state = &mut *ptr::from_mut::<TcgHookState<1, BlockHookId>>(
self.block_hooks self.hook_collection
.block_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -351,7 +368,8 @@ where
.qemu_hooks .qemu_hooks
.add_block_hooks(hook_state, gen, postgen, exec); .add_block_hooks(hook_state, gen, postgen, exec);
self.block_hooks self.hook_collection
.block_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -380,14 +398,17 @@ where
unsafe extern "C" fn(&mut HookState<CpuRunHookId>, cpu: CPUStatePtr) unsafe extern "C" fn(&mut HookState<CpuRunHookId>, cpu: CPUStatePtr)
); );
self.cpu_run_hooks.push(Box::pin(HookState::new( self.hook_collection
CpuRunHookId::invalid(), .cpu_run_hooks
hook_to_repr!(pre_exec_hook), .push(Box::pin(HookState::new(
hook_to_repr!(post_exec_hook), CpuRunHookId::invalid(),
))); hook_to_repr!(pre_exec_hook),
hook_to_repr!(post_exec_hook),
)));
let hook_state = &mut *ptr::from_mut::<HookState<CpuRunHookId>>( let hook_state = &mut *ptr::from_mut::<HookState<CpuRunHookId>>(
self.cpu_run_hooks self.hook_collection
.cpu_run_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -398,7 +419,8 @@ where
.qemu_hooks .qemu_hooks
.add_cpu_run_hooks(hook_state, pre_run, post_run); .add_cpu_run_hooks(hook_state, pre_run, post_run);
self.cpu_run_hooks self.hook_collection
.cpu_run_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -461,21 +483,24 @@ where
) )
); );
self.read_hooks.push(Box::pin(TcgHookState::new( self.hook_collection
ReadHookId::invalid(), .read_hooks
hook_to_repr!(generation_hook), .push(Box::pin(TcgHookState::new(
HookRepr::Empty, ReadHookId::invalid(),
[ hook_to_repr!(generation_hook),
hook_to_repr!(execution_hook_1), HookRepr::Empty,
hook_to_repr!(execution_hook_2), [
hook_to_repr!(execution_hook_4), hook_to_repr!(execution_hook_1),
hook_to_repr!(execution_hook_8), hook_to_repr!(execution_hook_2),
hook_to_repr!(execution_hook_n), hook_to_repr!(execution_hook_4),
], hook_to_repr!(execution_hook_8),
))); hook_to_repr!(execution_hook_n),
],
)));
let hook_state = &mut *ptr::from_mut::<TcgHookState<5, ReadHookId>>( let hook_state = &mut *ptr::from_mut::<TcgHookState<5, ReadHookId>>(
self.read_hooks self.hook_collection
.read_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -486,7 +511,8 @@ where
.qemu_hooks .qemu_hooks
.add_read_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn); .add_read_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn);
self.read_hooks self.hook_collection
.read_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -549,21 +575,24 @@ where
) )
); );
self.write_hooks.push(Box::pin(TcgHookState::new( self.hook_collection
WriteHookId::invalid(), .write_hooks
hook_to_repr!(generation_hook), .push(Box::pin(TcgHookState::new(
HookRepr::Empty, WriteHookId::invalid(),
[ hook_to_repr!(generation_hook),
hook_to_repr!(execution_hook_1), HookRepr::Empty,
hook_to_repr!(execution_hook_2), [
hook_to_repr!(execution_hook_4), hook_to_repr!(execution_hook_1),
hook_to_repr!(execution_hook_8), hook_to_repr!(execution_hook_2),
hook_to_repr!(execution_hook_n), hook_to_repr!(execution_hook_4),
], hook_to_repr!(execution_hook_8),
))); hook_to_repr!(execution_hook_n),
],
)));
let hook_state = &mut *ptr::from_mut::<TcgHookState<5, WriteHookId>>( let hook_state = &mut *ptr::from_mut::<TcgHookState<5, WriteHookId>>(
self.write_hooks self.hook_collection
.write_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -574,7 +603,8 @@ where
.qemu_hooks .qemu_hooks
.add_write_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn); .add_write_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn);
self.write_hooks self.hook_collection
.write_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -624,20 +654,23 @@ where
unsafe extern "C" fn(&mut TcgHookState<4, CmpHookId>, id: u64, v0: u64, v1: u64) unsafe extern "C" fn(&mut TcgHookState<4, CmpHookId>, id: u64, v0: u64, v1: u64)
); );
self.cmp_hooks.push(Box::pin(TcgHookState::new( self.hook_collection
CmpHookId::invalid(), .cmp_hooks
hook_to_repr!(generation_hook), .push(Box::pin(TcgHookState::new(
HookRepr::Empty, CmpHookId::invalid(),
[ hook_to_repr!(generation_hook),
hook_to_repr!(execution_hook_1), HookRepr::Empty,
hook_to_repr!(execution_hook_2), [
hook_to_repr!(execution_hook_4), hook_to_repr!(execution_hook_1),
hook_to_repr!(execution_hook_8), hook_to_repr!(execution_hook_2),
], hook_to_repr!(execution_hook_4),
))); hook_to_repr!(execution_hook_8),
],
)));
let hook_state = &mut *ptr::from_mut::<TcgHookState<4, CmpHookId>>( let hook_state = &mut *ptr::from_mut::<TcgHookState<4, CmpHookId>>(
self.cmp_hooks self.hook_collection
.cmp_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -648,7 +681,8 @@ where
.qemu_hooks .qemu_hooks
.add_cmp_hooks(hook_state, gen, exec1, exec2, exec4, exec8); .add_cmp_hooks(hook_state, gen, exec1, exec2, exec4, exec8);
self.cmp_hooks self.hook_collection
.cmp_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -664,10 +698,12 @@ where
pub unsafe fn backdoor_closure(&mut self, hook: BackdoorHookClosure<ET, S>) -> BackdoorHookId { pub unsafe fn backdoor_closure(&mut self, hook: BackdoorHookClosure<ET, S>) -> BackdoorHookId {
unsafe { unsafe {
let fat: FatPtr = transmute(hook); let fat: FatPtr = transmute(hook);
self.backdoor_hooks self.hook_collection
.backdoor_hooks
.push(Box::pin((BackdoorHookId::invalid(), fat))); .push(Box::pin((BackdoorHookId::invalid(), fat)));
let hook_state = &raw mut self let hook_state = &raw mut self
.hook_collection
.backdoor_hooks .backdoor_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -679,7 +715,8 @@ where
.qemu_hooks .qemu_hooks
.add_backdoor_hook(&mut *hook_state, closure_backdoor_hook_wrapper::<ET, S>); .add_backdoor_hook(&mut *hook_state, closure_backdoor_hook_wrapper::<ET, S>);
self.backdoor_hooks self.hook_collection
.backdoor_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -736,10 +773,12 @@ where
) -> NewThreadHookId { ) -> NewThreadHookId {
unsafe { unsafe {
let fat: FatPtr = transmute(hook); let fat: FatPtr = transmute(hook);
self.new_thread_hooks self.hook_collection
.new_thread_hooks
.push(Box::pin((NewThreadHookId::invalid(), fat))); .push(Box::pin((NewThreadHookId::invalid(), fat)));
let hook_state = &raw mut self let hook_state = &raw mut self
.hook_collection
.new_thread_hooks .new_thread_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -750,7 +789,8 @@ where
let id = self let id = self
.qemu_hooks .qemu_hooks
.add_new_thread_hook(&mut *hook_state, closure_new_thread_hook_wrapper::<ET, S>); .add_new_thread_hook(&mut *hook_state, closure_new_thread_hook_wrapper::<ET, S>);
self.new_thread_hooks self.hook_collection
.new_thread_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -794,10 +834,12 @@ where
unsafe { unsafe {
let fat: FatPtr = transmute(hook); let fat: FatPtr = transmute(hook);
self.pre_syscall_hooks self.hook_collection
.pre_syscall_hooks
.push(Box::pin((PreSyscallHookId::invalid(), fat))); .push(Box::pin((PreSyscallHookId::invalid(), fat)));
let hook_state = &raw mut self let hook_state = &raw mut self
.hook_collection
.pre_syscall_hooks .pre_syscall_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -808,7 +850,9 @@ where
let id = self let id = self
.qemu_hooks .qemu_hooks
.add_pre_syscall_hook(&mut *hook_state, closure_pre_syscall_hook_wrapper::<ET, S>); .add_pre_syscall_hook(&mut *hook_state, closure_pre_syscall_hook_wrapper::<ET, S>);
self.pre_syscall_hooks
self.hook_collection
.pre_syscall_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -845,10 +889,12 @@ where
) -> PostSyscallHookId { ) -> PostSyscallHookId {
unsafe { unsafe {
let fat: FatPtr = transmute(hook); let fat: FatPtr = transmute(hook);
self.post_syscall_hooks self.hook_collection
.post_syscall_hooks
.push(Box::pin((PostSyscallHookId::invalid(), fat))); .push(Box::pin((PostSyscallHookId::invalid(), fat)));
let hooks_state = &raw mut self let hooks_state = &raw mut self
.hook_collection
.post_syscall_hooks .post_syscall_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
@ -860,7 +906,8 @@ where
&mut *hooks_state, &mut *hooks_state,
closure_post_syscall_hook_wrapper::<ET, S>, closure_post_syscall_hook_wrapper::<ET, S>,
); );
self.post_syscall_hooks self.hook_collection
.post_syscall_hooks
.last_mut() .last_mut()
.unwrap() .unwrap()
.as_mut() .as_mut()
@ -874,7 +921,8 @@ where
// # Safety // # Safety
// Will cast the valid hook to a ptr. // Will cast the valid hook to a ptr.
self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>); self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>);
self.crash_hooks self.hook_collection
.crash_hooks
.push(HookRepr::Function(hook as *const libc::c_void)); .push(HookRepr::Function(hook as *const libc::c_void));
} }
@ -883,20 +931,13 @@ where
// Will cast the hook to a [`FatPtr`]. // Will cast the hook to a [`FatPtr`].
unsafe { unsafe {
self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>); self.qemu_hooks.set_crash_hook(crash_hook_wrapper::<ET, S>);
self.crash_hooks.push(HookRepr::Closure(transmute(hook))); self.hook_collection
.crash_hooks
.push(HookRepr::Closure(transmute(hook)));
} }
} }
} }
impl<ET, S> Default for EmulatorHooks<ET, S>
where
S: Unpin + UsesInput,
{
fn default() -> Self {
Self::new(QemuHooks::get().unwrap())
}
}
impl<ET, S> EmulatorModules<ET, S> impl<ET, S> EmulatorModules<ET, S>
where where
S: UsesInput, S: UsesInput,
@ -1085,7 +1126,12 @@ where
ET: EmulatorModuleTuple<S>, ET: EmulatorModuleTuple<S>,
S: UsesInput + Unpin, S: UsesInput + Unpin,
{ {
pub(super) fn new(emulator_hooks: EmulatorHooks<ET, S>, modules: ET) -> Pin<Box<Self>> { /// Create a new [`EmulatorModules`]
///
/// # Safety
///
/// Only one such struct should be ever created.
pub(super) unsafe fn new(emulator_hooks: EmulatorHooks<ET, S>, modules: ET) -> Pin<Box<Self>> {
let mut modules = Box::pin(Self { let mut modules = Box::pin(Self {
modules: Box::pin(modules), modules: Box::pin(modules),
hooks: emulator_hooks, hooks: emulator_hooks,
@ -1262,12 +1308,11 @@ where
} }
} }
impl<ET, S> Drop for EmulatorModules<ET, S> impl<ET, S> Drop for EmulatorModules<ET, S> {
where
S: UsesInput,
{
fn drop(&mut self) { fn drop(&mut self) {
// Make the global pointer null at drop time // Make the global pointer null at drop time
// # Safety
// There can only be one EmulatorModules.
unsafe { unsafe {
EMULATOR_MODULES = ptr::null_mut(); EMULATOR_MODULES = ptr::null_mut();
} }

View File

@ -161,7 +161,6 @@ where
S: UsesInput, S: UsesInput,
{ {
#[must_use] #[must_use]
#[expect(clippy::match_wildcard_for_single_variants)]
pub fn end_of_run(&self) -> Option<ExitKind> { pub fn end_of_run(&self) -> Option<ExitKind> {
match self { match self {
EmulatorDriverResult::EndOfRun(exit_kind) => Some(*exit_kind), EmulatorDriverResult::EndOfRun(exit_kind) => Some(*exit_kind),
@ -377,9 +376,20 @@ where
{ {
let mut qemu_params = qemu_params.into(); let mut qemu_params = qemu_params.into();
// # Safety
// `QemuHooks` can be used without QEMU being fully initialized, we make sure to only call
// functions that do not depend on whether QEMU is well-initialized or not.
let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) }; let emulator_hooks = unsafe { EmulatorHooks::new(QemuHooks::get_unchecked()) };
let mut emulator_modules = EmulatorModules::new(emulator_hooks, modules);
// # Safety
// This is the only call to `EmulatorModules::new`.
// Since Emulator can only be created once, we fulfil the conditions to call this function.
let mut emulator_modules = unsafe { EmulatorModules::new(emulator_hooks, modules) };
// # Safety
// This is mostly safe, but can cause issues if module hooks call to emulator_modules.modules_mut().
// In that case, it would cause the creation of a double mutable reference.
// We need to refactor Modules to avoid such problem in the future at some point.
// TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called // TODO: fix things there properly. The biggest issue being that it creates 2 mut ref to the module with the callback being called
unsafe { unsafe {
emulator_modules.modules_mut().pre_qemu_init_all( emulator_modules.modules_mut().pre_qemu_init_all(
@ -390,6 +400,8 @@ where
let qemu = Qemu::init(qemu_params)?; let qemu = Qemu::init(qemu_params)?;
// # Safety
// Pre-init hooks have been called above.
unsafe { unsafe {
Ok(Self::new_with_qemu( Ok(Self::new_with_qemu(
qemu, qemu,

View File

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

View File

@ -10,6 +10,16 @@ use libafl_qemu_sys::{GuestAddr, GuestPhysAddr};
use crate::Qemu; use crate::Qemu;
// TODO: make a better arch-specific / os-specific system. only works for x86_64 for now.
#[cfg(feature = "x86_64")]
pub const LINUX_PROCESS_ADDRESS_RANGE: Range<u64> = 0..0x0000_7fff_ffff_ffff;
#[cfg(feature = "x86_64")]
pub const LINUX_KERNEL_ADDRESS_RANGE: Range<u64> = 0xFFFFFFFF80000000..0xFFFFFFFF9FFFFFFF;
/// Generic Filter that can be:
/// - an allow list: allow nothing but the given list
/// - a deny list: allow anything but the given list
/// - none: allow everything (no filtering applied)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum FilterList<T> { pub enum FilterList<T> {
AllowList(T), AllowList(T),
@ -59,17 +69,22 @@ where
} }
} }
/// An address filter list.
///
/// It will allow anything in the registered ranges, and deny anything else.
/// If there is no range registered, it will allow anything.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct AddressFilterVec { pub struct AddressFilterVec {
// ideally, we should use a tree // ideally, we should use a tree
registered_addresses: Vec<Range<GuestAddr>>, registered_addresses: Vec<Range<GuestAddr>>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct StdAddressFilter(FilterList<AddressFilterVec>); pub struct StdAddressFilter(FilterList<AddressFilterVec>);
impl Default for StdAddressFilter { impl Default for StdAddressFilter {
fn default() -> Self { fn default() -> Self {
Self(FilterList::None) Self::allow_list(Vec::new())
} }
} }
@ -101,7 +116,10 @@ impl AddressFilterVec {
impl AddressFilter for AddressFilterVec { impl AddressFilter for AddressFilterVec {
fn register(&mut self, address_range: Range<GuestAddr>) { fn register(&mut self, address_range: Range<GuestAddr>) {
self.registered_addresses.push(address_range); self.registered_addresses.push(address_range);
Qemu::get().unwrap().flush_jit();
if let Some(qemu) = Qemu::get() {
qemu.flush_jit();
}
} }
fn allowed(&self, addr: &GuestAddr) -> bool { fn allowed(&self, addr: &GuestAddr) -> bool {
@ -129,6 +147,10 @@ impl AddressFilter for StdAddressFilter {
} }
} }
/// A page filter list.
///
/// It will allow anything in the registered pages, and deny anything else.
/// If there is no page registered, it will allow anything.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PageFilterVec { pub struct PageFilterVec {
registered_pages: HashSet<GuestPhysAddr>, registered_pages: HashSet<GuestPhysAddr>,
@ -141,6 +163,19 @@ pub struct StdPageFilter(FilterList<PageFilterVec>);
#[cfg(feature = "usermode")] #[cfg(feature = "usermode")]
pub type StdPageFilter = NopPageFilter; pub type StdPageFilter = NopPageFilter;
#[cfg(feature = "systemmode")]
impl StdPageFilter {
#[must_use]
pub fn allow_list(registered_pages: PageFilterVec) -> Self {
StdPageFilter(FilterList::AllowList(registered_pages))
}
#[must_use]
pub fn deny_list(registered_pages: PageFilterVec) -> Self {
StdPageFilter(FilterList::DenyList(registered_pages))
}
}
impl Default for PageFilterVec { impl Default for PageFilterVec {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -152,20 +187,23 @@ impl Default for PageFilterVec {
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
impl Default for StdPageFilter { impl Default for StdPageFilter {
fn default() -> Self { fn default() -> Self {
Self(FilterList::None) Self::allow_list(PageFilterVec::default())
} }
} }
impl PageFilter for PageFilterVec { impl PageFilter for PageFilterVec {
fn register(&mut self, page_id: GuestPhysAddr) { fn register(&mut self, page_id: GuestPhysAddr) {
self.registered_pages.insert(page_id); self.registered_pages.insert(page_id);
Qemu::get().unwrap().flush_jit();
if let Some(qemu) = Qemu::get() {
qemu.flush_jit();
}
} }
fn allowed(&self, paging_id: &GuestPhysAddr) -> bool { fn allowed(&self, paging_id: &GuestPhysAddr) -> bool {
// if self.allowed_pages.is_empty() { if self.registered_pages.is_empty() {
// return true; return true;
// } }
self.registered_pages.contains(paging_id) self.registered_pages.contains(paging_id)
} }
@ -219,3 +257,171 @@ pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> =
UnsafeCell::new(NopAddressFilter); UnsafeCell::new(NopAddressFilter);
#[cfg(feature = "systemmode")] #[cfg(feature = "systemmode")]
pub(crate) static mut NOP_PAGE_FILTER: UnsafeCell<NopPageFilter> = UnsafeCell::new(NopPageFilter); pub(crate) static mut NOP_PAGE_FILTER: UnsafeCell<NopPageFilter> = UnsafeCell::new(NopPageFilter);
#[cfg(all(feature = "systemmode", test))]
mod tests {
use libafl::{
inputs::{NopInput, UsesInput},
state::NopState,
HasMetadata,
};
use libafl_bolts::tuples::tuple_list;
use crate::modules::{
utils::filters::{
AddressFilter, NopAddressFilter, NopPageFilter, PageFilter, StdAddressFilter,
StdPageFilter,
},
EmulatorModule, EmulatorModuleTuple,
};
#[derive(Clone, Debug)]
struct DummyModule<AF, PF> {
address_filter: AF,
page_filter: PF,
}
impl<S, AF, PF> EmulatorModule<S> for DummyModule<AF, PF>
where
AF: AddressFilter + 'static,
PF: PageFilter + 'static,
S: Unpin + UsesInput + HasMetadata,
{
type ModuleAddressFilter = AF;
type ModulePageFilter = PF;
fn address_filter(&self) -> &Self::ModuleAddressFilter {
&self.address_filter
}
fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter {
&mut self.address_filter
}
fn page_filter(&self) -> &Self::ModulePageFilter {
&self.page_filter
}
fn page_filter_mut(&mut self) -> &mut Self::ModulePageFilter {
&mut self.page_filter
}
}
fn gen_module<AF, PF, S>(
af: AF,
pf: PF,
) -> impl EmulatorModule<S, ModuleAddressFilter = AF, ModulePageFilter = PF>
where
AF: AddressFilter,
PF: PageFilter,
S: HasMetadata + UsesInput + Unpin,
{
DummyModule {
address_filter: af,
page_filter: pf,
}
}
macro_rules! test_module {
($modules:ident, $Id:tt) => {
assert!($modules.$Id.address_filter().allowed(&0x100));
assert!($modules.$Id.address_filter().allowed(&0x101));
assert!($modules.$Id.address_filter().allowed(&0x1ff));
assert!($modules.$Id.address_filter().allowed(&0x301));
assert!(!$modules.$Id.address_filter().allowed(&0xff));
assert!(!$modules.$Id.address_filter().allowed(&0x200));
assert!(!$modules.$Id.address_filter().allowed(&0x201));
assert!($modules.$Id.page_filter().allowed(&0xaaaa));
assert!($modules.$Id.page_filter().allowed(&0xbbbb));
assert!(!$modules.$Id.page_filter().allowed(&0xcccc));
};
}
#[test]
fn test_filter_nop() {
let module = gen_module::<NopAddressFilter, NopPageFilter, NopState<NopInput>>(
NopAddressFilter,
NopPageFilter,
);
let mut modules = tuple_list!(module);
modules.allow_address_range_all(0x100..0x200);
modules.allow_address_range_all(0x300..0x400);
modules.allow_page_id_all(0xaaaa);
modules.allow_page_id_all(0xbbbb);
assert!(modules.0.address_filter().allowed(&0xff));
assert!(modules.0.address_filter().allowed(&0x200));
assert!(modules.0.address_filter().allowed(&0x201));
assert!(modules.0.address_filter().allowed(&0x300));
assert!(modules.0.page_filter().allowed(&0xaaaa));
assert!(modules.0.page_filter().allowed(&0xbbbb));
assert!(modules.0.page_filter().allowed(&0xcccc));
}
#[test]
fn test_filters_simple() {
let module = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
StdAddressFilter::default(),
StdPageFilter::default(),
);
let mut modules = tuple_list!(module);
assert!(modules.0.address_filter().allowed(&0x000));
assert!(modules.0.address_filter().allowed(&0x100));
assert!(modules.0.address_filter().allowed(&0x200));
assert!(modules.0.address_filter().allowed(&0xffffffff));
assert!(modules.0.page_filter().allowed(&0xabcd));
modules.allow_address_range_all(0x100..0x200);
modules.allow_address_range_all(0x300..0x400);
modules.allow_page_id_all(0xaaaa);
modules.allow_page_id_all(0xbbbb);
assert!(modules.0.address_filter().allowed(&0x100));
assert!(modules.0.address_filter().allowed(&0x101));
assert!(modules.0.address_filter().allowed(&0x1ff));
assert!(modules.0.address_filter().allowed(&0x301));
assert!(!modules.0.address_filter().allowed(&0xff));
assert!(!modules.0.address_filter().allowed(&0x200));
assert!(!modules.0.address_filter().allowed(&0x201));
assert!(modules.0.page_filter().allowed(&0xaaaa));
assert!(modules.0.page_filter().allowed(&0xbbbb));
assert!(!modules.0.page_filter().allowed(&0xcccc));
}
#[test]
fn test_filters_multiple() {
let module1 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
StdAddressFilter::default(),
StdPageFilter::default(),
);
let module2 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
StdAddressFilter::default(),
StdPageFilter::default(),
);
let module3 = gen_module::<StdAddressFilter, StdPageFilter, NopState<NopInput>>(
StdAddressFilter::default(),
StdPageFilter::default(),
);
let mut modules = tuple_list!(module1, module2, module3);
modules.allow_address_range_all(0x100..0x200);
modules.allow_address_range_all(0x300..0x400);
modules.allow_page_id_all(0xaaaa);
modules.allow_page_id_all(0xbbbb);
test_module!(modules, 0);
}
}

View File

@ -337,6 +337,7 @@ pub struct QemuConfig {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[cfg(feature = "usermode")]
use crate::Qemu; use crate::Qemu;
#[test] #[test]

View File

@ -863,7 +863,7 @@ impl QemuHooks {
/// Should not be used out of Qemu itself. /// Should not be used out of Qemu itself.
/// Prefer `Qemu::get` for a safe version of this method. /// Prefer `Qemu::get` for a safe version of this method.
#[must_use] #[must_use]
pub unsafe fn get_unchecked() -> Self { pub(crate) unsafe fn get_unchecked() -> Self {
QemuHooks { _private: () } QemuHooks { _private: () }
} }

View File

@ -5,7 +5,7 @@
use core::{ use core::{
cmp::{Ordering, PartialOrd}, cmp::{Ordering, PartialOrd},
fmt, ptr, fmt, ptr, slice,
}; };
use std::{ use std::{
ffi::{c_void, CString}, ffi::{c_void, CString},
@ -51,6 +51,7 @@ pub use systemmode::*;
mod hooks; mod hooks;
pub use hooks::*; pub use hooks::*;
use libafl_bolts::{vec_init, AsSliceMut};
static mut QEMU_IS_INITIALIZED: bool = false; static mut QEMU_IS_INITIALIZED: bool = false;
@ -375,6 +376,18 @@ impl CPU {
} }
} }
pub fn read_mem_vec(&self, addr: GuestAddr, len: usize) -> Result<Vec<u8>, QemuRWError> {
// # Safety
// This is safe because we read exactly `len` bytes from QEMU.
unsafe {
vec_init(len, |buf| {
self.read_mem(addr, buf.as_slice_mut())?;
Ok::<(), QemuRWError>(())
})
}
}
/// Write a value to a guest address, taking into account the potential MMU / MPU. /// Write a value to a guest address, taking into account the potential MMU / MPU.
pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> { pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> {
// TODO use gdbstub's target_cpu_memory_rw_debug // TODO use gdbstub's target_cpu_memory_rw_debug
@ -724,6 +737,13 @@ impl Qemu {
.read_mem(addr, buf) .read_mem(addr, buf)
} }
/// Read a value from a guest address, taking into account the potential indirections with the current CPU.
pub fn read_mem_vec(&self, addr: GuestAddr, len: usize) -> Result<Vec<u8>, QemuRWError> {
self.current_cpu()
.unwrap_or_else(|| self.cpu_from_index(0))
.read_mem_vec(addr, len)
}
/// Write a value to a guest address, taking into account the potential indirections with the current CPU. /// Write a value to a guest address, taking into account the potential indirections with the current CPU.
pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> { pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> {
self.current_cpu() self.current_cpu()
@ -731,6 +751,33 @@ impl Qemu {
.write_mem(addr, buf) .write_mem(addr, buf)
} }
/// Read a value from memory to a guest addr, taking into account the potential indirections with the current CPU.
///
/// # Safety
///
/// The read object should have the same layout as the type of val.
/// No checked is performed to check whether the returned object makes sense or not.
// TODO: Use sized array when const generics are stabilized.
pub unsafe fn read_mem_val<T>(&self, addr: GuestAddr) -> Result<T, QemuRWError> {
// let mut val_buf: [u8; size_of::<T>()] = [0; size_of::<T>()];
let val_buf: Vec<u8> = vec_init(size_of::<T>(), |buf| self.read_mem(addr, buf))?;
Ok(ptr::read(val_buf.as_ptr() as *const T))
}
/// Write a value to memory at a guest addr, taking into account the potential indirections with the current CPU.
///
/// # Safety
///
/// val will be used as parameter of [`slice::from_raw_parts`], and thus must enforce the same requirements.
pub unsafe fn write_mem_val<T>(&self, addr: GuestAddr, val: &T) -> Result<(), QemuRWError> {
let val_buf: &[u8] = slice::from_raw_parts(ptr::from_ref(val) as *const u8, size_of::<T>());
self.write_mem(addr, val_buf)?;
Ok(())
}
/// Read a value from a guest address. /// Read a value from a guest address.
/// ///
/// # Safety /// # Safety
@ -1007,6 +1054,18 @@ impl QemuMemoryChunk {
Ok(output_sliced.len().try_into().unwrap()) Ok(output_sliced.len().try_into().unwrap())
} }
pub fn read_vec(&self, qemu: Qemu) -> Result<Vec<u8>, QemuRWError> {
// # Safety
// This is safe because we read exactly `self.size` bytes from QEMU.
unsafe {
vec_init(self.size as usize, |buf| {
self.read(qemu, buf.as_slice_mut())?;
Ok::<(), QemuRWError>(())
})
}
}
/// Returns the number of bytes effectively written. /// Returns the number of bytes effectively written.
/// Input will get chunked at `size` bytes. /// Input will get chunked at `size` bytes.
pub fn write(&self, qemu: Qemu, input: &[u8]) -> Result<GuestReg, QemuRWError> { pub fn write(&self, qemu: Qemu, input: &[u8]) -> Result<GuestReg, QemuRWError> {