From d8460d14a2872d1281ac0eb55797d0dc63a2d144 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Mon, 13 Jan 2025 17:32:23 +0100 Subject: [PATCH] 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 --- .../full_system/qemu_linux_kernel/Cargo.toml | 3 + .../qemu_linux_kernel/Makefile.toml | 37 ++- .../qemu_linux_kernel/setup/Makefile | 7 +- .../qemu_linux_kernel/setup/harness.c | 216 +++++++++++-- .../qemu_linux_kernel/setup/setup.sh | 6 +- .../qemu_linux_kernel/setup/x509-parser.h | 7 +- .../qemu_linux_kernel/src/fuzzer.rs | 81 ++++- .../full_system/qemu_linux_process/Cargo.toml | 1 + .../qemu_linux_process/Makefile.toml | 21 +- .../qemu_linux_process/example/harness_nyx.c | 32 +- .../qemu_linux_process/setup/setup.sh | 3 +- .../qemu_linux_process/src/fuzzer.rs | 68 ++-- libafl_bolts/src/lib.rs | 21 ++ libafl_qemu/libafl_qemu_build/src/build.rs | 2 +- libafl_qemu/runtime/nyx_api.h | 6 +- libafl_qemu/src/arch/x86_64.rs | 4 +- libafl_qemu/src/command/mod.rs | 18 +- libafl_qemu/src/command/nyx.rs | 304 +++++++++++++++--- libafl_qemu/src/command/parser/mod.rs | 16 +- libafl_qemu/src/command/parser/nyx.rs | 161 ++++++++-- libafl_qemu/src/emu/drivers/mod.rs | 17 +- libafl_qemu/src/emu/drivers/nyx.rs | 10 +- libafl_qemu/src/emu/hooks.rs | 291 ++++++++++------- libafl_qemu/src/emu/mod.rs | 16 +- libafl_qemu/src/emu/snapshot.rs | 4 +- libafl_qemu/src/modules/utils/filters.rs | 220 ++++++++++++- libafl_qemu/src/qemu/config.rs | 1 + libafl_qemu/src/qemu/hooks.rs | 2 +- libafl_qemu/src/qemu/mod.rs | 61 +++- 29 files changed, 1316 insertions(+), 320 deletions(-) diff --git a/fuzzers/full_system/qemu_linux_kernel/Cargo.toml b/fuzzers/full_system/qemu_linux_kernel/Cargo.toml index 3b36005bdc..d3201ecbb6 100644 --- a/fuzzers/full_system/qemu_linux_kernel/Cargo.toml +++ b/fuzzers/full_system/qemu_linux_kernel/Cargo.toml @@ -10,6 +10,8 @@ edition = "2021" [features] shared = ["libafl_qemu/shared"] +nyx = [] + [profile.release] incremental = true debug = true @@ -26,6 +28,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature ] } libafl_targets = { path = "../../../libafl_targets" } env_logger = "0.11.5" +log = "0.4.22" [build-dependencies] libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" } diff --git a/fuzzers/full_system/qemu_linux_kernel/Makefile.toml b/fuzzers/full_system/qemu_linux_kernel/Makefile.toml index 8aa6673084..9eb271f2a0 100644 --- a/fuzzers/full_system/qemu_linux_kernel/Makefile.toml +++ b/fuzzers/full_system/qemu_linux_kernel/Makefile.toml @@ -1,12 +1,22 @@ 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 +elseif eq ${harness_api} "lqemu" + set_env FEATURE "" +else + echo "Unknown harness API: ${harness_api}" + exit 1 +end ''', ''' #!@duckscript 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_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_DIR = { value = "${TARGET_DIR}/linux_builder", condition = { env_not_set = [ @@ -85,7 +96,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"] @@ -100,18 +119,18 @@ else LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu fi -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 +qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2 +qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2 +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 \ -accel tcg \ -m 4G \ - -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"` \ + -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \ + -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \ + -device ahci,id=ahci,bus=pci.0,addr=4 \ + -device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \ + -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"` \ -L "${LIBAFL_QEMU_BIOS_DIR}" \ -nographic \ diff --git a/fuzzers/full_system/qemu_linux_kernel/setup/Makefile b/fuzzers/full_system/qemu_linux_kernel/setup/Makefile index d934995017..8c7611532a 100644 --- a/fuzzers/full_system/qemu_linux_kernel/setup/Makefile +++ b/fuzzers/full_system/qemu_linux_kernel/setup/Makefile @@ -1,7 +1,12 @@ obj-m += harness.o +# harness-objs += symfinder.o 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 clean: diff --git a/fuzzers/full_system/qemu_linux_kernel/setup/harness.c b/fuzzers/full_system/qemu_linux_kernel/setup/harness.c index b17f031564..1070256d0c 100644 --- a/fuzzers/full_system/qemu_linux_kernel/setup/harness.c +++ b/fuzzers/full_system/qemu_linux_kernel/setup/harness.c @@ -11,11 +11,14 @@ #include #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_release(struct inode *inode, struct file *file); @@ -37,13 +40,16 @@ static struct mychar_device_data harness_data; #define KPROBE_PRE_HANDLER(fname) \ static int __kprobes fname(struct kprobe *p, struct pt_regs *regs) -long unsigned int kln_addr = 0; -unsigned long (*kln_pointer)(const char *name) = NULL; +// kallsyms_lookup_name function address +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; KPROBE_PRE_HANDLER(handler_pre0) { - kln_addr = (--regs->ip); + kallsyms_lookup_name_addr = (--regs->ip); return 0; } @@ -89,11 +95,16 @@ static int harness_find_kallsyms_lookup(void) { unregister_kprobe(&kp0); 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; } @@ -104,6 +115,104 @@ static int harness_uevent(const struct device *dev, 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) { int err; dev_t dev; @@ -126,7 +235,10 @@ static int __init harness_init(void) { err = harness_find_kallsyms_lookup(); - if (err < 0) { return err; } + if (err < 0) { + habort("error while trying to find kallsyms"); + return err; + } return 0; } @@ -141,35 +253,91 @@ static void __exit harness_exit(void) { } 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"); - 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... libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000); 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; } static int harness_release(struct inode *inode, struct file *file) { +#if defined(USE_LQEMU) lqprintf("harness: Device close\n"); +#elif defined(USE_NYX) + hprintf("harness: Device close"); +#endif return 0; } diff --git a/fuzzers/full_system/qemu_linux_kernel/setup/setup.sh b/fuzzers/full_system/qemu_linux_kernel/setup/setup.sh index 5090430ef2..f270862ef3 100644 --- a/fuzzers/full_system/qemu_linux_kernel/setup/setup.sh +++ b/fuzzers/full_system/qemu_linux_kernel/setup/setup.sh @@ -3,6 +3,10 @@ LINUX_MODULES=$(pacman -Ql linux-headers | grep -m 1 -E '/usr/lib/modules/[^/]*/' | sed 's|.*/usr/lib/modules/\([^/]*\)/.*|\1|') export LINUX_MODULES +# Default root password +echo "root:toor" | chpasswd + cd /setup + make clean -make -j \ No newline at end of file +make -j nyx \ No newline at end of file diff --git a/fuzzers/full_system/qemu_linux_kernel/setup/x509-parser.h b/fuzzers/full_system/qemu_linux_kernel/setup/x509-parser.h index 7d4950aad0..8469717e96 100644 --- a/fuzzers/full_system/qemu_linux_kernel/setup/x509-parser.h +++ b/fuzzers/full_system/qemu_linux_kernel/setup/x509-parser.h @@ -7,6 +7,9 @@ // From https://lore.kernel.org/kvm/20240304065703.GA24373@wunner.de/T/ +#ifndef X509_PARSER_H +#define X509_PARSER_H + #include #include #include @@ -43,4 +46,6 @@ struct x509_certificate { }; struct x509_certificate *x509_cert_parse(const void *data, size_t datalen); -void x509_free_certificate(struct x509_certificate *cert); \ No newline at end of file +void x509_free_certificate(struct x509_certificate *cert); + +#endif \ No newline at end of file diff --git a/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs b/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs index 3e3f61119a..8470f12be8 100644 --- a/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs +++ b/fuzzers/full_system/qemu_linux_kernel/src/fuzzer.rs @@ -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, scheduled::StdScheduledMutator, I2SRandReplaceBinonly}, observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, @@ -27,14 +29,82 @@ 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}, - // StdEmulatorDriver + modules::{ + cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule, + EmulatorModuleTuple, + }, + FastSnapshotManager, NopSnapshotManager, QemuInitError, }; use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND}; +#[cfg(feature = "nyx")] +fn get_emulator( + args: Vec, + modules: ET, +) -> Result< + Emulator, NyxEmulatorDriver, ET, S, NopSnapshotManager>, + QemuInitError, +> +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + ::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( + args: Vec, + mut modules: ET, +) -> Result< + Emulator, StdEmulatorDriver, ET, S, FastSnapshotManager>, + QemuInitError, +> +where + ET: EmulatorModuleTuple, + S: State + HasExecutions + Unpin, + ::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 = 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() { env_logger::init(); @@ -70,10 +140,7 @@ pub fn fuzz() { CmpLogModule::default(), ); - let emu = Emulator::builder() - .qemu_parameters(args) - .modules(modules) - .build()?; + let emu = get_emulator(args, modules)?; let devices = emu.list_devices(); println!("Devices = {:?}", devices); diff --git a/fuzzers/full_system/qemu_linux_process/Cargo.toml b/fuzzers/full_system/qemu_linux_process/Cargo.toml index 065fdeea25..e6f342d898 100644 --- a/fuzzers/full_system/qemu_linux_process/Cargo.toml +++ b/fuzzers/full_system/qemu_linux_process/Cargo.toml @@ -27,6 +27,7 @@ libafl_qemu = { path = "../../../libafl_qemu", default-features = false, feature ] } env_logger = "0.11.5" libafl_targets = { path = "../../../libafl_targets" } +log = "0.4.22" [build-dependencies] libafl_qemu_build = { path = "../../../libafl_qemu/libafl_qemu_build" } diff --git a/fuzzers/full_system/qemu_linux_process/Makefile.toml b/fuzzers/full_system/qemu_linux_process/Makefile.toml index fe298369da..5d3c2c0323 100644 --- a/fuzzers/full_system/qemu_linux_process/Makefile.toml +++ b/fuzzers/full_system/qemu_linux_process/Makefile.toml @@ -11,8 +11,11 @@ end if eq ${harness_api} "nyx" set_env FEATURE nyx -else +elseif eq ${harness_api} "lqemu" set_env FEATURE "" +else + echo "Unknown harness API: ${harness_api}" + exit 1 end ''', ''' @@ -145,18 +148,18 @@ else LIBAFL_QEMU_BIOS_DIR=${LIBAFL_QEMU_CLONE_DIR}/build/qemu-bundle/usr/local/share/qemu fi -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 +qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_CODE.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2 +qemu-img create -f qcow2 -o backing_file=${LINUX_BUILDER_OUT}/OVMF_VARS.4m.fd -F raw ${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2 +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 \ -accel tcg \ -m 4G \ - -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"` \ + -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_CODE.4m.qcow2" `# OVMF code pflash` \ + -drive if=pflash,format=qcow2,file="${LINUX_BUILDER_OUT}/OVMF_VARS.4m.qcow2" `# OVMF vars pflash` \ + -device ahci,id=ahci,bus=pci.0,addr=4 \ + -device ide-hd,bus=ahci.0,drive=disk,bootindex=1 \ + -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"` \ -L "${LIBAFL_QEMU_BIOS_DIR}" \ -nographic \ diff --git a/fuzzers/full_system/qemu_linux_process/example/harness_nyx.c b/fuzzers/full_system/qemu_linux_process/example/harness_nyx.c index 15c4eca2ae..8495dba22b 100644 --- a/fuzzers/full_system/qemu_linux_process/example/harness_nyx.c +++ b/fuzzers/full_system/qemu_linux_process/example/harness_nyx.c @@ -55,9 +55,22 @@ err_out: 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) { host_config_t host_config; + hprintf("Nyx agent init"); + // set ready state kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0); kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0); @@ -120,18 +133,25 @@ int main() { agent_init(1); + kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0); + hrange_submit(0, 0x0, 0x00007fffffffffff); + 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); + while (true) { + kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0); + kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0); - // Call the target - bool ret = FuzzMe(pbuf->data, pbuf->size); + // Call the target + 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?"); } \ No newline at end of file diff --git a/fuzzers/full_system/qemu_linux_process/setup/setup.sh b/fuzzers/full_system/qemu_linux_process/setup/setup.sh index 049b630ebd..16e33eba59 100644 --- a/fuzzers/full_system/qemu_linux_process/setup/setup.sh +++ b/fuzzers/full_system/qemu_linux_process/setup/setup.sh @@ -1,3 +1,4 @@ #!/bin/bash -# Nothing to do \ No newline at end of file +# Nothing to do +echo 'root:toor' | sudo chpasswd \ No newline at end of file diff --git a/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs b/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs index c6b4bc6b3c..d141163113 100644 --- a/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs +++ b/fuzzers/full_system/qemu_linux_process/src/fuzzer.rs @@ -8,8 +8,8 @@ use libafl::state::{HasExecutions, State}; use libafl::{ corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, - executors::ShadowExecutor, - feedback_or, feedback_or_fast, + executors::{ExitKind, ShadowExecutor}, + feedback_and_fast, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes, UsesInput}, @@ -37,10 +37,11 @@ use libafl_qemu::{ emu::Emulator, executor::QemuExecutor, modules::{ - cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, CmpLogModule, - EmulatorModuleTuple, + cmplog::CmpLogObserver, edges::StdEdgeCoverageClassicModule, + 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}; @@ -49,7 +50,7 @@ fn get_emulator( args: Vec, modules: ET, ) -> Result< - Emulator, NyxEmulatorDriver, ET, S, FastSnapshotManager>, + Emulator, NyxEmulatorDriver, ET, S, NopSnapshotManager>, QemuInitError, > where @@ -60,21 +61,16 @@ where Emulator::empty() .qemu_parameters(args) .modules(modules) - .driver( - NyxEmulatorDriver::builder() - .allow_page_on_start(true) - .process_only(true) - .build(), - ) + .driver(NyxEmulatorDriver::builder().build()) .command_manager(NyxCommandManager::default()) - .snapshot_manager(FastSnapshotManager::default()) + .snapshot_manager(NopSnapshotManager::default()) .build() } #[cfg(not(feature = "nyx"))] fn get_emulator( args: Vec, - modules: ET, + mut modules: ET, ) -> Result< Emulator, StdEmulatorDriver, ET, S, FastSnapshotManager>, QemuInitError, @@ -84,7 +80,29 @@ where S: State + HasExecutions + Unpin, ::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 = 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() { @@ -94,14 +112,16 @@ pub fn fuzz() { str::parse::(&s).expect("FUZZ_SIZE was not a number"); }; // Hardcoded parameters - let timeout = Duration::from_secs(99999999); + let timeout = Duration::from_secs(50); let broker_port = 1338; let cores = Cores::from_cmdline("1").unwrap(); let corpus_dirs = [PathBuf::from("./corpus")]; let objective_dir = PathBuf::from("./crashes"); + display_args(); + let mut run_client = |state: Option<_>, mut mgr, _client_description| { - // Initialize QEMU + // Fetch QEMU args from program's arguments let args: Vec = env::args().collect(); // 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 let mut harness = |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 @@ -145,8 +170,13 @@ pub fn fuzz() { TimeFeedback::new(&time_observer) ); + let map_feedback = MaxMapFeedback::new(&edges_observer); + // 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 let mut state = state.unwrap_or_else(|| { diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index 96b13dac89..cea08cf501 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -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::()`). +/// +/// # 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(nb_elts: usize, init_fn: F) -> Result, E> +where + F: FnOnce(&mut Vec) -> Result<(), E>, +{ + let mut new_vec: Vec = Vec::with_capacity(nb_elts); + new_vec.set_len(nb_elts); + + init_fn(&mut new_vec)?; + + Ok(new_vec) +} + #[cfg(test)] mod tests { diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 00432e424e..c7233fefb4 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -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 = "ace364678aef879514e150e626695c4793db41e9"; +pub const QEMU_REVISION: &str = "2b5e4bfcff875571b2813a9494de8b2e4c56120e"; pub struct BuildResult { pub qemu_path: PathBuf, diff --git a/libafl_qemu/runtime/nyx_api.h b/libafl_qemu/runtime/nyx_api.h index 708b8fc915..b0eaf584c8 100644 --- a/libafl_qemu/runtime/nyx_api.h +++ b/libafl_qemu/runtime/nyx_api.h @@ -96,12 +96,12 @@ #define KAFL_MODE_32 1 #define KAFL_MODE_16 2 -typedef struct { +typedef volatile struct { int32_t size; uint8_t data[]; } kAFL_payload; -typedef struct { +typedef volatile struct { uint64_t ip[4]; uint64_t size[4]; uint8_t enabled[4]; @@ -154,7 +154,7 @@ typedef struct { /* more to come */ } __attribute__((packed)) host_config_t; -typedef struct { +typedef volatile struct { uint32_t agent_magic; uint32_t agent_version; uint8_t agent_timeout_detection; diff --git a/libafl_qemu/src/arch/x86_64.rs b/libafl_qemu/src/arch/x86_64.rs index 0fbf62019f..87badd3702 100644 --- a/libafl_qemu/src/arch/x86_64.rs +++ b/libafl_qemu/src/arch/x86_64.rs @@ -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 enum_map::{enum_map, EnumMap}; @@ -65,8 +65,6 @@ pub fn capstone() -> capstone::arch::x86::ArchCapstoneBuilder { pub type GuestReg = u64; -pub const PROCESS_ADDRESS_RANGE: Range = 0..0x0000_7fff_ffff_ffff; - impl crate::ArchExtras for crate::CPU { fn read_return_address(&self) -> Result { let stack_ptr: GuestReg = self.read_reg(Regs::Rsp)?; diff --git a/libafl_qemu/src/command/mod.rs b/libafl_qemu/src/command/mod.rs index 602cef26e9..a0b517087d 100644 --- a/libafl_qemu/src/command/mod.rs +++ b/libafl_qemu/src/command/mod.rs @@ -32,7 +32,10 @@ use crate::{ 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 parser; @@ -251,6 +254,7 @@ pub enum CommandError { TestDifference(GuestReg, GuestReg), // received, expected StartedTwice, EndBeforeStart, + WrongUsage, } impl From for CommandError { @@ -461,18 +465,12 @@ where // 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); + 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); } } - #[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(); diff --git a/libafl_qemu/src/command/nyx.rs b/libafl_qemu/src/command/nyx.rs index 06b0d9fc41..4138fbd473 100644 --- a/libafl_qemu/src/command/nyx.rs +++ b/libafl_qemu/src/command/nyx.rs @@ -10,6 +10,7 @@ use std::{ fmt::{Debug, Formatter}, marker::PhantomData, mem::offset_of, + ops::Range, ptr, slice::from_raw_parts, }; @@ -19,7 +20,7 @@ use libafl::{ executors::ExitKind, inputs::{HasTargetBytes, UsesInput}, }; -use libafl_qemu_sys::GuestVirtAddr; +use libafl_qemu_sys::{GuestAddr, GuestVirtAddr}; use libc::c_uint; use paste::paste; @@ -27,8 +28,9 @@ use crate::{ command::{ parser::nyx::{ AcquireCommandParser, GetHostConfigCommandParser, GetPayloadCommandParser, - NextPayloadCommandParser, PrintfCommandParser, ReleaseCommandParser, - SetAgentConfigCommandParser, + NextPayloadCommandParser, PanicCommandParser, PrintfCommandParser, + RangeSubmitCommandParser, ReleaseCommandParser, SetAgentConfigCommandParser, + SubmitCR3CommandParser, SubmitPanicCommandParser, UserAbortCommandParser, }, CommandError, CommandManager, IsCommand, NativeCommandParser, }, @@ -109,12 +111,13 @@ macro_rules! define_nyx_command_manager { #[deny(unreachable_patterns)] fn parse(&self, qemu: Qemu) -> Result { let arch_regs_map: &'static EnumMap = 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 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 { // >::COMMAND_ID => Ok(StdCommandManagerCommands::StartPhysCommandParserCmd(>::parse(qemu, arch_regs_map)?)), @@ -176,7 +179,12 @@ define_nyx_command_manager!( SetAgentConfigCommand, PrintfCommand, GetPayloadCommand, - NextPayloadCommand + NextPayloadCommand, + SubmitCR3Command, + PanicCommand, + SubmitPanicCommand, + UserAbortCommand, + RangeSubmitCommand ], [ AcquireCommandParser, @@ -185,7 +193,12 @@ define_nyx_command_manager!( SetAgentConfigCommandParser, PrintfCommandParser, GetPayloadCommandParser, - NextPayloadCommandParser + NextPayloadCommandParser, + SubmitCR3CommandParser, + SubmitPanicCommandParser, + PanicCommandParser, + UserAbortCommandParser, + RangeSubmitCommandParser ] ); @@ -309,55 +322,251 @@ where Option, 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); + if !emu.command_manager_mut().start() { + log::debug!("Creating snapshot."); - // Set snapshot ID to restore to after fuzzing ends - emu.driver_mut() - .set_snapshot_id(snapshot_id) - .map_err(|_| EmulatorDriverError::MultipleSnapshotDefinition)?; + // 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)?; + + // 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 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 SubmitCR3Command; + +impl IsCommand, NyxEmulatorDriver, ET, S, SM> for SubmitCR3Command +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + emu: &mut Emulator, NyxEmulatorDriver, ET, S, SM>, + _state: &mut S, + _input: &S::Input, + _ret_reg: Option, + ) -> Result< + Option, 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, +} + +impl RangeSubmitCommand { + pub fn new(allowed_range: Range) -> Self { + Self { allowed_range } + } +} + +impl IsCommand, NyxEmulatorDriver, ET, S, SM> for RangeSubmitCommand +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + emu: &mut Emulator, NyxEmulatorDriver, ET, S, SM>, + _state: &mut S, + _input: &S::Input, + _ret_reg: Option, + ) -> Result< + Option, NyxEmulatorDriver, ET, S, SM>>, + EmulatorDriverError, + > { + log::info!("Allow address range: {:#x?}", self.allowed_range); + + const EMPTY_RANGE: Range = 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 IsCommand, NyxEmulatorDriver, ET, S, SM> for PanicCommand +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + emu: &mut Emulator, NyxEmulatorDriver, ET, S, SM>, + _state: &mut S, + _input: &S::Input, + _ret_reg: Option, + ) -> Result< + Option, 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 IsCommand, NyxEmulatorDriver, ET, S, SM> for SubmitPanicCommand +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &mut Emulator, NyxEmulatorDriver, ET, S, SM>, + _state: &mut S, + _input: &S::Input, + _ret_reg: Option, + ) -> Result< + Option, 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 IsCommand, NyxEmulatorDriver, ET, S, SM> for UserAbortCommand +where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, + S::Input: HasTargetBytes, + SM: IsSnapshotManager, +{ + fn usable_at_runtime(&self) -> bool { + true + } + + fn run( + &self, + _emu: &mut Emulator, NyxEmulatorDriver, ET, S, SM>, + _state: &mut S, + _input: &S::Input, + _ret_reg: Option, + ) -> Result< + Option, NyxEmulatorDriver, ET, S, SM>>, + EmulatorDriverError, + > { + log::error!("Nyx Guest Abort: {}", self.content); + + Ok(Some(EmulatorDriverResult::ShutdownRequest)) + } +} + #[derive(Debug, Clone)] pub struct ReleaseCommand; impl IsCommand, NyxEmulatorDriver, ET, S, SM> for ReleaseCommand @@ -384,11 +593,14 @@ where let qemu = emu.qemu(); if emu.command_manager().has_started() { + log::debug!("Release: end of fuzzing run. Restoring..."); + let snapshot_id = emu .driver_mut() .snapshot_id() .ok_or(EmulatorDriverError::SnapshotNotFound)?; + log::debug!("Restoring snapshot"); emu.snapshot_manager_mut().restore(qemu, &snapshot_id)?; #[cfg(feature = "paranoid_debug")] @@ -396,6 +608,8 @@ where Ok(Some(EmulatorDriverResult::EndOfRun(ExitKind::Ok))) } else { + log::debug!("Early release. Skipping..."); + Ok(None) } } diff --git a/libafl_qemu/src/command/parser/mod.rs b/libafl_qemu/src/command/parser/mod.rs index 476245678f..3a9ba121f4 100644 --- a/libafl_qemu/src/command/parser/mod.rs +++ b/libafl_qemu/src/command/parser/mod.rs @@ -5,7 +5,6 @@ use libafl::{ executors::ExitKind, inputs::{HasTargetBytes, UsesInput}, }; -use libafl_bolts::AsSliceMut; use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; use libc::c_uint; @@ -20,7 +19,10 @@ use crate::{ 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 static EMU_EXIT_KIND_MAP: OnceLock>> = OnceLock::new(); @@ -279,7 +281,6 @@ where type OutputCommand = LqprintfCommand; const COMMAND_ID: c_uint = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_LQPRINTF.0; - #[expect(clippy::uninit_vec)] fn parse( qemu: Qemu, arch_regs_map: &'static EnumMap, @@ -293,15 +294,10 @@ where let total_size = str_size + 1; - let mut str_copy: Vec = unsafe { - let mut res = Vec::::with_capacity(total_size); - res.set_len(total_size); - res - }; - let mem_chunk = QemuMemoryChunk::virt(buf_addr as GuestVirtAddr, total_size as GuestReg, cpu); - mem_chunk.read(qemu, str_copy.as_slice_mut())?; + + let str_copy: Vec = mem_chunk.read_vec(qemu)?; let c_str: &CStr = CStr::from_bytes_with_nul(str_copy.as_slice()).unwrap(); diff --git a/libafl_qemu/src/command/parser/nyx.rs b/libafl_qemu/src/command/parser/nyx.rs index 5fa074186a..8c1e359910 100644 --- a/libafl_qemu/src/command/parser/nyx.rs +++ b/libafl_qemu/src/command/parser/nyx.rs @@ -1,4 +1,4 @@ -use std::{ffi::CStr, mem::transmute, sync::OnceLock}; +use std::{ffi::CStr, sync::OnceLock}; use enum_map::EnumMap; use libafl::{ @@ -12,7 +12,8 @@ use crate::{ command::{ nyx::{ bindings, AcquireCommand, GetHostConfigCommand, GetPayloadCommand, NextPayloadCommand, - NyxCommandManager, PrintfCommand, ReleaseCommand, SetAgentConfigCommand, + NyxCommandManager, PanicCommand, PrintfCommand, RangeSubmitCommand, ReleaseCommand, + SetAgentConfigCommand, SubmitCR3Command, SubmitPanicCommand, UserAbortCommand, }, parser::NativeCommandParser, CommandError, CommandManager, NativeExitKind, @@ -22,6 +23,20 @@ use crate::{ IsSnapshotManager, NyxEmulatorDriver, Qemu, QemuMemoryChunk, Regs, }; +fn get_guest_string(qemu: Qemu, string_ptr_reg: Regs) -> Result { + 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>> = OnceLock::new(); pub struct AcquireCommandParser; @@ -58,14 +73,122 @@ where fn parse( qemu: Qemu, - arch_regs_map: &'static EnumMap, + _arch_regs_map: &'static EnumMap, ) -> Result { - 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)) } } +pub struct SubmitCR3CommandParser; +impl NativeCommandParser, NyxEmulatorDriver, ET, S, SM> + for SubmitCR3CommandParser +where + ET: EmulatorModuleTuple, + 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, + ) -> Result { + Ok(SubmitCR3Command) + } +} + +pub struct RangeSubmitCommandParser; +impl NativeCommandParser, NyxEmulatorDriver, ET, S, SM> + for RangeSubmitCommandParser +where + ET: EmulatorModuleTuple, + 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, + ) -> Result { + 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 NativeCommandParser, NyxEmulatorDriver, ET, S, SM> + for SubmitPanicCommandParser +where + ET: EmulatorModuleTuple, + 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, + ) -> Result { + Ok(SubmitPanicCommand) + } +} + +pub struct PanicCommandParser; +impl NativeCommandParser, NyxEmulatorDriver, ET, S, SM> + for PanicCommandParser +where + ET: EmulatorModuleTuple, + 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, + ) -> Result { + Ok(PanicCommand) + } +} + +pub struct UserAbortCommandParser; +impl NativeCommandParser, NyxEmulatorDriver, ET, S, SM> + for UserAbortCommandParser +where + ET: EmulatorModuleTuple, + 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, + ) -> Result { + let msg = get_guest_string(qemu, Regs::Rcx)?; + + Ok(UserAbortCommand::new(msg)) + } +} + pub struct NextPayloadCommandParser; impl NativeCommandParser, NyxEmulatorDriver, ET, S, SM> for NextPayloadCommandParser @@ -121,9 +244,9 @@ where fn parse( qemu: Qemu, - arch_regs_map: &'static EnumMap, + _arch_regs_map: &'static EnumMap, ) -> Result { - 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( host_config_addr, @@ -146,16 +269,14 @@ where fn parse( qemu: Qemu, - arch_regs_map: &'static EnumMap, + _arch_regs_map: &'static EnumMap, ) -> Result { - 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::()] = - [0; size_of::()]; - - qemu.read_mem(agent_config_addr, &mut agent_config_buf)?; - - let agent_config: bindings::agent_config_t = unsafe { transmute(agent_config_buf) }; + // # Safety + // We use the C struct directly to get the agent config + let agent_config: bindings::agent_config_t = + unsafe { qemu.read_mem_val(agent_config_addr)? }; Ok(SetAgentConfigCommand::new(agent_config)) } @@ -174,16 +295,10 @@ where fn parse( qemu: Qemu, - arch_regs_map: &'static EnumMap, + _arch_regs_map: &'static EnumMap, ) -> Result { - 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] = - [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())) + Ok(PrintfCommand::new(msg)) } } diff --git a/libafl_qemu/src/emu/drivers/mod.rs b/libafl_qemu/src/emu/drivers/mod.rs index d739fe15c2..5eb8616937 100644 --- a/libafl_qemu/src/emu/drivers/mod.rs +++ b/libafl_qemu/src/emu/drivers/mod.rs @@ -18,9 +18,15 @@ use crate::{ 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; -#[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}; #[derive(Debug, Clone)] @@ -34,6 +40,9 @@ where /// The run is over and the emulator is ready for the next iteration. EndOfRun(ExitKind), + + /// Internal shutdown request + ShutdownRequest, } #[derive(Debug, Clone)] @@ -296,6 +305,10 @@ where Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit)) } EmulatorDriverResult::EndOfRun(exit_kind) => Ok(exit_kind), + EmulatorDriverResult::ShutdownRequest => { + log::warn!("Shutdown request. Stopping fuzzing..."); + std::process::exit(CTRL_C_EXIT); + } } } } diff --git a/libafl_qemu/src/emu/drivers/nyx.rs b/libafl_qemu/src/emu/drivers/nyx.rs index 0eb9b0e75c..1a3ec39e6d 100644 --- a/libafl_qemu/src/emu/drivers/nyx.rs +++ b/libafl_qemu/src/emu/drivers/nyx.rs @@ -29,10 +29,7 @@ pub struct NyxEmulatorDriver { 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. + allow_page_on_start: bool, // when fuzzing starts, all modules will only accept the current page table #[builder(default = false)] print_commands: bool, #[builder(default = (1024 * 1024))] @@ -108,11 +105,6 @@ impl NyxEmulatorDriver { 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 EmulatorDriver for NyxEmulatorDriver diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 7a2993212d..88b8c00350 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -82,7 +82,7 @@ where let emulator_modules = EmulatorModules::::emulator_modules_mut().unwrap(); 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) { match crash_hook { @@ -103,23 +103,14 @@ where /// High-level `Emulator` modules, using `QemuHooks`. #[derive(Debug)] -pub struct EmulatorModules -where - S: UsesInput, -{ +pub struct EmulatorModules { modules: Pin>, hooks: EmulatorHooks, phantom: PhantomData, } -/// Hook collection, #[derive(Debug)] -pub struct EmulatorHooks -where - S: UsesInput, -{ - qemu_hooks: QemuHooks, - +struct EmulatorHookCollection { instruction_hooks: Vec>>, backdoor_hooks: Vec>>, edge_hooks: Vec>>>, @@ -144,6 +135,42 @@ where phantom: PhantomData<(ET, S)>, } +impl Default for EmulatorHookCollection { + 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 { + qemu_hooks: QemuHooks, + hook_collection: EmulatorHookCollection, +} + impl EmulatorHooks where S: UsesInput + Unpin, @@ -152,27 +179,7 @@ where pub fn new(qemu_hooks: QemuHooks) -> Self { Self { qemu_hooks, - phantom: PhantomData, - 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(), + hook_collection: EmulatorHookCollection::default(), } } @@ -189,11 +196,13 @@ where ) -> InstructionHookId { let fat: FatPtr = unsafe { transmute(hook) }; - self.instruction_hooks + self.hook_collection + .instruction_hooks .push(Box::pin((InstructionHookId::invalid(), fat))); unsafe { let hook_state = &raw mut self + .hook_collection .instruction_hooks .last_mut() .unwrap() @@ -207,7 +216,8 @@ where closure_instruction_hook_wrapper::, invalidate_block, ); - self.instruction_hooks + self.hook_collection + .instruction_hooks .last_mut() .unwrap() .as_mut() @@ -275,15 +285,18 @@ where unsafe extern "C" fn(&mut TcgHookState<1, EdgeHookId>, id: u64) ); - self.edge_hooks.push(Box::pin(TcgHookState::new( - EdgeHookId::invalid(), - hook_to_repr!(generation_hook), - HookRepr::Empty, - [hook_to_repr!(execution_hook)], - ))); + self.hook_collection + .edge_hooks + .push(Box::pin(TcgHookState::new( + EdgeHookId::invalid(), + hook_to_repr!(generation_hook), + HookRepr::Empty, + [hook_to_repr!(execution_hook)], + ))); let hook_state = &mut *ptr::from_mut::>( - self.edge_hooks + self.hook_collection + .edge_hooks .last_mut() .unwrap() .as_mut() @@ -292,7 +305,8 @@ where let id = self.qemu_hooks.add_edge_hooks(hook_state, gen, exec); - self.edge_hooks + self.hook_collection + .edge_hooks .last_mut() .unwrap() .as_mut() @@ -332,15 +346,18 @@ where unsafe extern "C" fn(&mut TcgHookState<1, BlockHookId>, id: u64) ); - self.block_hooks.push(Box::pin(TcgHookState::new( - BlockHookId::invalid(), - hook_to_repr!(generation_hook), - hook_to_repr!(post_generation_hook), - [hook_to_repr!(execution_hook)], - ))); + self.hook_collection + .block_hooks + .push(Box::pin(TcgHookState::new( + BlockHookId::invalid(), + hook_to_repr!(generation_hook), + hook_to_repr!(post_generation_hook), + [hook_to_repr!(execution_hook)], + ))); let hook_state = &mut *ptr::from_mut::>( - self.block_hooks + self.hook_collection + .block_hooks .last_mut() .unwrap() .as_mut() @@ -351,7 +368,8 @@ where .qemu_hooks .add_block_hooks(hook_state, gen, postgen, exec); - self.block_hooks + self.hook_collection + .block_hooks .last_mut() .unwrap() .as_mut() @@ -380,14 +398,17 @@ where unsafe extern "C" fn(&mut HookState, cpu: CPUStatePtr) ); - self.cpu_run_hooks.push(Box::pin(HookState::new( - CpuRunHookId::invalid(), - hook_to_repr!(pre_exec_hook), - hook_to_repr!(post_exec_hook), - ))); + self.hook_collection + .cpu_run_hooks + .push(Box::pin(HookState::new( + CpuRunHookId::invalid(), + hook_to_repr!(pre_exec_hook), + hook_to_repr!(post_exec_hook), + ))); let hook_state = &mut *ptr::from_mut::>( - self.cpu_run_hooks + self.hook_collection + .cpu_run_hooks .last_mut() .unwrap() .as_mut() @@ -398,7 +419,8 @@ where .qemu_hooks .add_cpu_run_hooks(hook_state, pre_run, post_run); - self.cpu_run_hooks + self.hook_collection + .cpu_run_hooks .last_mut() .unwrap() .as_mut() @@ -461,21 +483,24 @@ where ) ); - self.read_hooks.push(Box::pin(TcgHookState::new( - ReadHookId::invalid(), - hook_to_repr!(generation_hook), - HookRepr::Empty, - [ - hook_to_repr!(execution_hook_1), - hook_to_repr!(execution_hook_2), - hook_to_repr!(execution_hook_4), - hook_to_repr!(execution_hook_8), - hook_to_repr!(execution_hook_n), - ], - ))); + self.hook_collection + .read_hooks + .push(Box::pin(TcgHookState::new( + ReadHookId::invalid(), + hook_to_repr!(generation_hook), + HookRepr::Empty, + [ + hook_to_repr!(execution_hook_1), + hook_to_repr!(execution_hook_2), + 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::>( - self.read_hooks + self.hook_collection + .read_hooks .last_mut() .unwrap() .as_mut() @@ -486,7 +511,8 @@ where .qemu_hooks .add_read_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn); - self.read_hooks + self.hook_collection + .read_hooks .last_mut() .unwrap() .as_mut() @@ -549,21 +575,24 @@ where ) ); - self.write_hooks.push(Box::pin(TcgHookState::new( - WriteHookId::invalid(), - hook_to_repr!(generation_hook), - HookRepr::Empty, - [ - hook_to_repr!(execution_hook_1), - hook_to_repr!(execution_hook_2), - hook_to_repr!(execution_hook_4), - hook_to_repr!(execution_hook_8), - hook_to_repr!(execution_hook_n), - ], - ))); + self.hook_collection + .write_hooks + .push(Box::pin(TcgHookState::new( + WriteHookId::invalid(), + hook_to_repr!(generation_hook), + HookRepr::Empty, + [ + hook_to_repr!(execution_hook_1), + hook_to_repr!(execution_hook_2), + 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::>( - self.write_hooks + self.hook_collection + .write_hooks .last_mut() .unwrap() .as_mut() @@ -574,7 +603,8 @@ where .qemu_hooks .add_write_hooks(hook_state, gen, exec1, exec2, exec4, exec8, execn); - self.write_hooks + self.hook_collection + .write_hooks .last_mut() .unwrap() .as_mut() @@ -624,20 +654,23 @@ where unsafe extern "C" fn(&mut TcgHookState<4, CmpHookId>, id: u64, v0: u64, v1: u64) ); - self.cmp_hooks.push(Box::pin(TcgHookState::new( - CmpHookId::invalid(), - hook_to_repr!(generation_hook), - HookRepr::Empty, - [ - hook_to_repr!(execution_hook_1), - hook_to_repr!(execution_hook_2), - hook_to_repr!(execution_hook_4), - hook_to_repr!(execution_hook_8), - ], - ))); + self.hook_collection + .cmp_hooks + .push(Box::pin(TcgHookState::new( + CmpHookId::invalid(), + hook_to_repr!(generation_hook), + HookRepr::Empty, + [ + hook_to_repr!(execution_hook_1), + 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::>( - self.cmp_hooks + self.hook_collection + .cmp_hooks .last_mut() .unwrap() .as_mut() @@ -648,7 +681,8 @@ where .qemu_hooks .add_cmp_hooks(hook_state, gen, exec1, exec2, exec4, exec8); - self.cmp_hooks + self.hook_collection + .cmp_hooks .last_mut() .unwrap() .as_mut() @@ -664,10 +698,12 @@ where pub unsafe fn backdoor_closure(&mut self, hook: BackdoorHookClosure) -> BackdoorHookId { unsafe { let fat: FatPtr = transmute(hook); - self.backdoor_hooks + self.hook_collection + .backdoor_hooks .push(Box::pin((BackdoorHookId::invalid(), fat))); let hook_state = &raw mut self + .hook_collection .backdoor_hooks .last_mut() .unwrap() @@ -679,7 +715,8 @@ where .qemu_hooks .add_backdoor_hook(&mut *hook_state, closure_backdoor_hook_wrapper::); - self.backdoor_hooks + self.hook_collection + .backdoor_hooks .last_mut() .unwrap() .as_mut() @@ -736,10 +773,12 @@ where ) -> NewThreadHookId { unsafe { let fat: FatPtr = transmute(hook); - self.new_thread_hooks + self.hook_collection + .new_thread_hooks .push(Box::pin((NewThreadHookId::invalid(), fat))); let hook_state = &raw mut self + .hook_collection .new_thread_hooks .last_mut() .unwrap() @@ -750,7 +789,8 @@ where let id = self .qemu_hooks .add_new_thread_hook(&mut *hook_state, closure_new_thread_hook_wrapper::); - self.new_thread_hooks + self.hook_collection + .new_thread_hooks .last_mut() .unwrap() .as_mut() @@ -794,10 +834,12 @@ where unsafe { let fat: FatPtr = transmute(hook); - self.pre_syscall_hooks + self.hook_collection + .pre_syscall_hooks .push(Box::pin((PreSyscallHookId::invalid(), fat))); let hook_state = &raw mut self + .hook_collection .pre_syscall_hooks .last_mut() .unwrap() @@ -808,7 +850,9 @@ where let id = self .qemu_hooks .add_pre_syscall_hook(&mut *hook_state, closure_pre_syscall_hook_wrapper::); - self.pre_syscall_hooks + + self.hook_collection + .pre_syscall_hooks .last_mut() .unwrap() .as_mut() @@ -845,10 +889,12 @@ where ) -> PostSyscallHookId { unsafe { let fat: FatPtr = transmute(hook); - self.post_syscall_hooks + self.hook_collection + .post_syscall_hooks .push(Box::pin((PostSyscallHookId::invalid(), fat))); let hooks_state = &raw mut self + .hook_collection .post_syscall_hooks .last_mut() .unwrap() @@ -860,7 +906,8 @@ where &mut *hooks_state, closure_post_syscall_hook_wrapper::, ); - self.post_syscall_hooks + self.hook_collection + .post_syscall_hooks .last_mut() .unwrap() .as_mut() @@ -874,7 +921,8 @@ where // # Safety // Will cast the valid hook to a ptr. self.qemu_hooks.set_crash_hook(crash_hook_wrapper::); - self.crash_hooks + self.hook_collection + .crash_hooks .push(HookRepr::Function(hook as *const libc::c_void)); } @@ -883,20 +931,13 @@ where // Will cast the hook to a [`FatPtr`]. unsafe { self.qemu_hooks.set_crash_hook(crash_hook_wrapper::); - self.crash_hooks.push(HookRepr::Closure(transmute(hook))); + self.hook_collection + .crash_hooks + .push(HookRepr::Closure(transmute(hook))); } } } -impl Default for EmulatorHooks -where - S: Unpin + UsesInput, -{ - fn default() -> Self { - Self::new(QemuHooks::get().unwrap()) - } -} - impl EmulatorModules where S: UsesInput, @@ -1085,7 +1126,12 @@ where ET: EmulatorModuleTuple, S: UsesInput + Unpin, { - pub(super) fn new(emulator_hooks: EmulatorHooks, modules: ET) -> Pin> { + /// Create a new [`EmulatorModules`] + /// + /// # Safety + /// + /// Only one such struct should be ever created. + pub(super) unsafe fn new(emulator_hooks: EmulatorHooks, modules: ET) -> Pin> { let mut modules = Box::pin(Self { modules: Box::pin(modules), hooks: emulator_hooks, @@ -1262,12 +1308,11 @@ where } } -impl Drop for EmulatorModules -where - S: UsesInput, -{ +impl Drop for EmulatorModules { fn drop(&mut self) { // Make the global pointer null at drop time + // # Safety + // There can only be one EmulatorModules. unsafe { EMULATOR_MODULES = ptr::null_mut(); } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 9a75b862e9..73ad47a9bc 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -161,7 +161,6 @@ where S: UsesInput, { #[must_use] - #[expect(clippy::match_wildcard_for_single_variants)] pub fn end_of_run(&self) -> Option { match self { EmulatorDriverResult::EndOfRun(exit_kind) => Some(*exit_kind), @@ -377,9 +376,20 @@ where { 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 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 unsafe { emulator_modules.modules_mut().pre_qemu_init_all( @@ -390,6 +400,8 @@ where let qemu = Qemu::init(qemu_params)?; + // # Safety + // Pre-init hooks have been called above. unsafe { Ok(Self::new_with_qemu( qemu, diff --git a/libafl_qemu/src/emu/snapshot.rs b/libafl_qemu/src/emu/snapshot.rs index 77f754a5a8..4ea4fa109a 100644 --- a/libafl_qemu/src/emu/snapshot.rs +++ b/libafl_qemu/src/emu/snapshot.rs @@ -62,7 +62,7 @@ impl Default for NopSnapshotManager { impl IsSnapshotManager for NopSnapshotManager { fn save(&mut self, _qemu: Qemu) -> SnapshotId { - log::warn!("Saving snapshot with the NopSnapshotManager"); + log::debug!("Saving snapshot with the NopSnapshotManager"); SnapshotId { id: 0 } } @@ -71,7 +71,7 @@ impl IsSnapshotManager for NopSnapshotManager { _qemu: Qemu, _snapshot_id: &SnapshotId, ) -> Result<(), SnapshotManagerError> { - log::warn!("Restoring snapshot with the NopSnapshotManager"); + log::debug!("Restoring snapshot with the NopSnapshotManager"); Ok(()) } diff --git a/libafl_qemu/src/modules/utils/filters.rs b/libafl_qemu/src/modules/utils/filters.rs index 1601a57b00..e3b5246a44 100644 --- a/libafl_qemu/src/modules/utils/filters.rs +++ b/libafl_qemu/src/modules/utils/filters.rs @@ -10,6 +10,16 @@ use libafl_qemu_sys::{GuestAddr, GuestPhysAddr}; 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 = 0..0x0000_7fff_ffff_ffff; +#[cfg(feature = "x86_64")] +pub const LINUX_KERNEL_ADDRESS_RANGE: Range = 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)] pub enum FilterList { 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)] pub struct AddressFilterVec { // ideally, we should use a tree registered_addresses: Vec>, } + #[derive(Clone, Debug)] pub struct StdAddressFilter(FilterList); impl Default for StdAddressFilter { fn default() -> Self { - Self(FilterList::None) + Self::allow_list(Vec::new()) } } @@ -101,7 +116,10 @@ impl AddressFilterVec { impl AddressFilter for AddressFilterVec { fn register(&mut self, address_range: 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 { @@ -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)] pub struct PageFilterVec { registered_pages: HashSet, @@ -141,6 +163,19 @@ pub struct StdPageFilter(FilterList); #[cfg(feature = "usermode")] 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 { fn default() -> Self { Self { @@ -152,20 +187,23 @@ impl Default for PageFilterVec { #[cfg(feature = "systemmode")] impl Default for StdPageFilter { fn default() -> Self { - Self(FilterList::None) + Self::allow_list(PageFilterVec::default()) } } impl PageFilter for PageFilterVec { fn register(&mut self, page_id: GuestPhysAddr) { 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 { - // if self.allowed_pages.is_empty() { - // return true; - // } + if self.registered_pages.is_empty() { + return true; + } self.registered_pages.contains(paging_id) } @@ -219,3 +257,171 @@ pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell = UnsafeCell::new(NopAddressFilter); #[cfg(feature = "systemmode")] pub(crate) static mut NOP_PAGE_FILTER: UnsafeCell = 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 { + address_filter: AF, + page_filter: PF, + } + + impl EmulatorModule for DummyModule + 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: AF, + pf: PF, + ) -> impl EmulatorModule + 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, + ); + 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::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::default(), + StdPageFilter::default(), + ); + let module2 = gen_module::>( + StdAddressFilter::default(), + StdPageFilter::default(), + ); + let module3 = gen_module::>( + 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); + } +} diff --git a/libafl_qemu/src/qemu/config.rs b/libafl_qemu/src/qemu/config.rs index 661c05d1ca..f40e1865b1 100644 --- a/libafl_qemu/src/qemu/config.rs +++ b/libafl_qemu/src/qemu/config.rs @@ -337,6 +337,7 @@ pub struct QemuConfig { #[cfg(test)] mod test { use super::*; + #[cfg(feature = "usermode")] use crate::Qemu; #[test] diff --git a/libafl_qemu/src/qemu/hooks.rs b/libafl_qemu/src/qemu/hooks.rs index 212e5fe948..f7f08dcae4 100644 --- a/libafl_qemu/src/qemu/hooks.rs +++ b/libafl_qemu/src/qemu/hooks.rs @@ -863,7 +863,7 @@ impl QemuHooks { /// Should not be used out of Qemu itself. /// Prefer `Qemu::get` for a safe version of this method. #[must_use] - pub unsafe fn get_unchecked() -> Self { + pub(crate) unsafe fn get_unchecked() -> Self { QemuHooks { _private: () } } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index 1204e77bd1..512dd9d460 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -5,7 +5,7 @@ use core::{ cmp::{Ordering, PartialOrd}, - fmt, ptr, + fmt, ptr, slice, }; use std::{ ffi::{c_void, CString}, @@ -51,6 +51,7 @@ pub use systemmode::*; mod hooks; pub use hooks::*; +use libafl_bolts::{vec_init, AsSliceMut}; static mut QEMU_IS_INITIALIZED: bool = false; @@ -375,6 +376,18 @@ impl CPU { } } + pub fn read_mem_vec(&self, addr: GuestAddr, len: usize) -> Result, 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. pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> { // TODO use gdbstub's target_cpu_memory_rw_debug @@ -724,6 +737,13 @@ impl Qemu { .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, 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. pub fn write_mem(&self, addr: GuestAddr, buf: &[u8]) -> Result<(), QemuRWError> { self.current_cpu() @@ -731,6 +751,33 @@ impl Qemu { .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(&self, addr: GuestAddr) -> Result { + // let mut val_buf: [u8; size_of::()] = [0; size_of::()]; + + let val_buf: Vec = vec_init(size_of::(), |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(&self, addr: GuestAddr, val: &T) -> Result<(), QemuRWError> { + let val_buf: &[u8] = slice::from_raw_parts(ptr::from_ref(val) as *const u8, size_of::()); + self.write_mem(addr, val_buf)?; + + Ok(()) + } + /// Read a value from a guest address. /// /// # Safety @@ -1007,6 +1054,18 @@ impl QemuMemoryChunk { Ok(output_sliced.len().try_into().unwrap()) } + pub fn read_vec(&self, qemu: Qemu) -> Result, 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. /// Input will get chunked at `size` bytes. pub fn write(&self, qemu: Qemu, input: &[u8]) -> Result {