From 7ab934607013440cc2bdd2e05b7de00257254c42 Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Thu, 13 Apr 2023 05:35:40 +0200 Subject: [PATCH 1/6] commit: remove deprecated fields from the config / add runtime options - With this patch we remove some deprecated options from the config struct without introducing any breaking changes (yet). In further patches we might want to switch to an enirely new config struct to get rid of all technical debts and make the code more readable. - Runtime options are configurable options that can be changed via the API before the fuzz runner is spawned. These options can not be set via a config file. --- config/Cargo.toml | 3 +- config/src/config.rs | 112 ++++++++++++++++++++++++++++++++++++++++--- config/src/lib.rs | 1 + config/src/loader.rs | 3 -- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/config/Cargo.toml b/config/Cargo.toml index 1ab9280..d0225d4 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] serde ="1.0.104" serde_derive ="1.0.104" -ron="0.6.2" \ No newline at end of file +ron="0.6.2" +libc = "0.2" \ No newline at end of file diff --git a/config/src/config.rs b/config/src/config.rs index ec6f4bc..e2d1767 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -5,6 +5,8 @@ use std::fs::File; use std::path::{Path}; use crate::loader::*; +use libc::fcntl; + fn into_absolute_path(path_to_sharedir: &str, path_to_file: String) -> String { let path_to_default_config = Path::new(&path_to_file); @@ -128,9 +130,6 @@ pub struct FuzzerConfig { pub input_buffer_size: usize, pub mem_limit: usize, pub time_limit: Duration, - pub threads: usize, - pub thread_id: usize, - pub cpu_pin_start_at: usize, pub seed_path: Option, pub dict: Vec>, pub snapshot_placement: SnapshotPlacement, @@ -158,9 +157,6 @@ impl FuzzerConfig{ input_buffer_size: config.input_buffer_size, mem_limit: config.mem_limit.or(default.mem_limit).expect("no mem_limit specified"), time_limit: config.time_limit.or(default.time_limit).expect("no time_limit specified"), - threads: config.threads.or(default.threads).expect("no threads specified"), - thread_id: config.thread_id.or(default.thread_id).expect("no thread_id specified"), - cpu_pin_start_at: config.cpu_pin_start_at.or(default.cpu_pin_start_at).expect("no cpu_pin_start_at specified"), seed_path: seed_path_value, dict: config.dict.or(default.dict).expect("no dict specified"), snapshot_placement: config.snapshot_placement.or(default.snapshot_placement).expect("no snapshot_placement specified"), @@ -178,10 +174,113 @@ impl FuzzerConfig{ } } +#[derive(Clone, Debug)] +pub enum QemuNyxRole { + /* Standalone mode, snapshot is kept in memory and not serialized. */ + StandAlone, + + /* Serialize the VM snapshot after the root snapshot has been created. + * The serialized snapshot will be stored in the workdir and the snapshot + * will later be used by the child processes. */ + Parent, + + /* Wait for the snapshot to be created by the parent process and + * deserialize it from the workdir. This way all child processes can + * mmap() the snapshot files and access the snapshot directly via shared memory. + * Consequently, this will result in a much lower memory usage compared to spawning + * multiple StandAlone-type instances. */ + Child, +} + +#[derive(Clone, Debug)] +/* runtime specific configuration */ +pub struct RuntimeConfig { + /* Configurable option to redirect hprintf to a file descriptor. + * If None, hprintf will be redirected to stdout via println!(). + */ + hprintf_fd: Option, + + /* Configurable option to specify the role of the process. + * If StandAlone, the process will not serialize the snapshot and keep everything in memory. + * If Parent, the process will create a snapshot and serialize it. + * If Child, the process will wait for the parent to create a snapshot and deserialize it. */ + process_role: QemuNyxRole, + + /* Configurable option to reuse a snapshot from a previous run (useful to avoid VM bootstrapping). */ + reuse_snapshot_path: Option, + + /* enable advanced VM debug mode (such as spawning a VNC server per VM) */ + debug_mode: bool, + + /* worker_id of the current QEMU Nyx instance */ + worker_id: usize, +} + +impl RuntimeConfig{ + pub fn new() -> Self { + Self{ + hprintf_fd: None, + process_role: QemuNyxRole::StandAlone, + reuse_snapshot_path: None, + debug_mode: false, + worker_id: 0, + } + } + + pub fn hprintf_fd(&self) -> Option { + self.hprintf_fd + } + + pub fn process_role(&self) -> &QemuNyxRole { + &self.process_role + } + + pub fn set_hpintf_fd(&mut self, fd: i32){ + /* sanitiy check to prevent invalid file descriptors via F_GETFD */ + unsafe { + /* TODO: return error instead of panicking */ + assert!(fcntl(fd, libc::F_GETFD) != -1); + }; + + self.hprintf_fd = Some(fd); + } + + pub fn set_process_role(&mut self, role: QemuNyxRole){ + self.process_role = role; + } + + pub fn reuse_root_snapshot_path(&self) -> Option { + self.reuse_snapshot_path.clone() + } + + pub fn set_reuse_snapshot_path(&mut self, path: String){ + let path = Path::new(&path).canonicalize().unwrap().to_str().unwrap().to_string(); + self.reuse_snapshot_path = Some(path); + } + + pub fn debug_mode(&self) -> bool { + self.debug_mode + } + + pub fn set_debug_mode(&mut self, debug_mode: bool){ + self.debug_mode = debug_mode; + } + + pub fn worker_id(&self) -> usize { + self.worker_id + } + + pub fn set_worker_id(&mut self, thread_id: usize){ + self.worker_id = thread_id; + } + +} + #[derive(Clone, Debug)] pub struct Config { pub runner: FuzzRunnerConfig, pub fuzz: FuzzerConfig, + pub runtime: RuntimeConfig, } impl Config{ @@ -189,6 +288,7 @@ impl Config{ Self{ runner: FuzzRunnerConfig::new_from_loader(&default_config_folder, default.runner, config.runner), fuzz: FuzzerConfig::new_from_loader(&sharedir, default.fuzz, config.fuzz), + runtime: RuntimeConfig::new(), } } diff --git a/config/src/lib.rs b/config/src/lib.rs index 225983a..7c8f15b 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -1,6 +1,7 @@ extern crate serde; extern crate serde_derive; extern crate ron; +extern crate libc; mod loader; mod config; diff --git a/config/src/loader.rs b/config/src/loader.rs index 98b91cb..51f7c3a 100644 --- a/config/src/loader.rs +++ b/config/src/loader.rs @@ -59,9 +59,6 @@ pub struct FuzzerConfigLoader { pub mem_limit: Option, pub time_limit: Option, pub target_binary: Option, - pub threads: Option, - pub thread_id: Option, - pub cpu_pin_start_at: Option, pub seed_path: Option, pub dict: Option>>, pub snapshot_placement: Option, From e767dbb8006d4bf6021c73f79b06cfeb118bfb2e Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Thu, 13 Apr 2023 05:45:30 +0200 Subject: [PATCH 2/6] refactor the qemu runner code and add new features - With this commit we delete both tons of duplicated as well as dead code. Additonally, we add new features such as hprintf FD redirection and fix the snapshot reuse option for Nyx kernel-type VMs. --- fuzz_runner/src/nyx/mod.rs | 107 +---------- fuzz_runner/src/nyx/params.rs | 269 +++++++++++----------------- fuzz_runner/src/nyx/qemu_process.rs | 75 +++++--- 3 files changed, 151 insertions(+), 300 deletions(-) diff --git a/fuzz_runner/src/nyx/mod.rs b/fuzz_runner/src/nyx/mod.rs index 444262c..5184d7b 100644 --- a/fuzz_runner/src/nyx/mod.rs +++ b/fuzz_runner/src/nyx/mod.rs @@ -10,7 +10,6 @@ use std::fs; use std::path::PathBuf; extern crate config; -use crate::config::{QemuKernelConfig, QemuSnapshotConfig, FuzzerConfig, SnapshotPath}; fn into_absolute_path(sharedir: &str) -> String{ @@ -24,107 +23,9 @@ fn into_absolute_path(sharedir: &str) -> String{ } } -pub fn qemu_process_new_from_kernel(sharedir: String, cfg: &QemuKernelConfig, fuzz_cfg: &FuzzerConfig) -> Result { - let params = params::KernelVmParams { - qemu_binary: cfg.qemu_binary.to_string(), - kernel: cfg.kernel.to_string(), - sharedir: into_absolute_path(&sharedir), - ramfs: cfg.ramfs.to_string(), - ram_size: fuzz_cfg.mem_limit, - bitmap_size: fuzz_cfg.bitmap_size, - debug: cfg.debug, - dump_python_code_for_inputs: match fuzz_cfg.dump_python_code_for_inputs{ - None => false, - Some(x) => x, - }, - write_protected_input_buffer: fuzz_cfg.write_protected_input_buffer, - cow_primary_size: fuzz_cfg.cow_primary_size, - ipt_filters: fuzz_cfg.ipt_filters, - input_buffer_size: fuzz_cfg.input_buffer_size, - }; - let qemu_id = fuzz_cfg.thread_id; - let qemu_params = params::QemuParams::new_from_kernel(&fuzz_cfg.workdir_path, qemu_id, ¶ms, fuzz_cfg.threads > 1); - - /* - if qemu_id == 0{ - qemu_process::QemuProcess::prepare_workdir(&fuzz_cfg.workdir_path, fuzz_cfg.seed_pattern.clone()); - } - */ +pub fn qemu_process_new(sharedir: String, cfg: &config::Config) -> Result { + + + let qemu_params = params::QemuParams::new(into_absolute_path(&sharedir), cfg); return qemu_process::QemuProcess::new(qemu_params); } - -pub fn qemu_process_new_from_snapshot(sharedir: String, cfg: &QemuSnapshotConfig, fuzz_cfg: &FuzzerConfig) -> Result { - - let snapshot_path = match &cfg.snapshot_path{ - SnapshotPath::Create(_x) => panic!(), - SnapshotPath::Reuse(x) => SnapshotPath::Reuse(x.to_string()), - SnapshotPath::DefaultPath => { - if fuzz_cfg.thread_id == 0 { - SnapshotPath::Create(format!("{}/snapshot/",fuzz_cfg.workdir_path)) - } else { - SnapshotPath::Reuse(format!("{}/snapshot/",fuzz_cfg.workdir_path)) - } - } - }; - - let params = params::SnapshotVmParams { - qemu_binary: cfg.qemu_binary.to_string(), - hda: cfg.hda.to_string(), - sharedir: into_absolute_path(&sharedir), - presnapshot: cfg.presnapshot.to_string(), - ram_size: fuzz_cfg.mem_limit, - bitmap_size: fuzz_cfg.bitmap_size, - debug: cfg.debug, - snapshot_path, - dump_python_code_for_inputs: match fuzz_cfg.dump_python_code_for_inputs{ - None => false, - Some(x) => x, - }, - write_protected_input_buffer: fuzz_cfg.write_protected_input_buffer, - cow_primary_size: fuzz_cfg.cow_primary_size, - ipt_filters: fuzz_cfg.ipt_filters, - input_buffer_size: fuzz_cfg.input_buffer_size, - }; - let qemu_id = fuzz_cfg.thread_id; - let qemu_params = params::QemuParams::new_from_snapshot(&fuzz_cfg.workdir_path, qemu_id, fuzz_cfg.cpu_pin_start_at, ¶ms, fuzz_cfg.threads > 1); - - return qemu_process::QemuProcess::new(qemu_params); -} - - -#[cfg(test)] -mod tests { - //use crate::aux_buffer::*; - use super::params::*; - use super::qemu_process::*; - //use std::{thread, time}; - - #[test] - fn it_works() { - let workdir = "/tmp/workdir_test"; - let params = KernelVmParams { - qemu_binary: "/home/kafl/NEW2/QEMU-PT_4.2.0/x86_64-softmmu/qemu-system-x86_64" - .to_string(), - kernel: "/home/kafl/Target-Components/linux_initramfs/bzImage-linux-4.15-rc7" - .to_string(), - ramfs: "/home/kafl/Target-Components/linux_initramfs/init.cpio.gz".to_string(), - sharedir: "foo! invalid".to_string(), - ram_size: 1000, - bitmap_size: 0x1 << 16, - debug: false, - dump_python_code_for_inputs: false, - write_protected_input_buffer: false, - }; - let qemu_id = 1; - let qemu_params = QemuParams::new_from_kernel(workdir, qemu_id, ¶ms); - - QemuProcess::prepare_workdir(&workdir, None); - - let mut qemu_process = QemuProcess::new(qemu_params); - - for _i in 0..100 { - qemu_process.send_payload(); - } - println!("test done"); - } -} diff --git a/fuzz_runner/src/nyx/params.rs b/fuzz_runner/src/nyx/params.rs index df63da5..2574b44 100644 --- a/fuzz_runner/src/nyx/params.rs +++ b/fuzz_runner/src/nyx/params.rs @@ -1,38 +1,4 @@ -use crate::config::SnapshotPath; -use crate::config::IptFilter; - -pub struct KernelVmParams { - pub qemu_binary: String, - pub kernel: String, - pub sharedir: String, - pub ramfs: String, - pub ram_size: usize, - pub bitmap_size: usize, - pub debug: bool, - - pub dump_python_code_for_inputs: bool, - pub write_protected_input_buffer: bool, - pub cow_primary_size: Option, - pub ipt_filters: [IptFilter; 4], - pub input_buffer_size: usize, -} - -pub struct SnapshotVmParams{ - pub qemu_binary: String, - pub hda: String, - pub sharedir: String, - pub presnapshot: String, - pub snapshot_path: SnapshotPath, - pub ram_size: usize, - pub bitmap_size: usize, - pub debug: bool, - - pub dump_python_code_for_inputs: bool, - pub write_protected_input_buffer: bool, - pub cow_primary_size: Option, - pub ipt_filters: [IptFilter; 4], - pub input_buffer_size: usize, -} +use crate::{config::{Config, FuzzRunnerConfig, QemuNyxRole}, QemuProcess}; pub struct QemuParams { pub cmd: Vec, @@ -46,31 +12,54 @@ pub struct QemuParams { pub dump_python_code_for_inputs: bool, pub write_protected_input_buffer: bool, pub cow_primary_size: Option, + pub hprintf_fd: Option, + } impl QemuParams { - pub fn new_from_snapshot(workdir: &str, qemu_id: usize, cpu: usize, params: &SnapshotVmParams, create_snapshot_file: bool) -> QemuParams{ - + + pub fn new(sharedir: String, fuzzer_config: &Config) -> QemuParams { + + let mut cmd = vec![]; + let qemu_id = fuzzer_config.runtime.worker_id(); + let workdir = &fuzzer_config.fuzz.workdir_path; + + let debug = fuzzer_config.runtime.debug_mode(); + let qemu_aux_buffer_filename = format!("{}/aux_buffer_{}", workdir, qemu_id); let control_filename = format!("{}/interface_{}", workdir, qemu_id); - let mut cmd = vec![]; - cmd.push(params.qemu_binary.to_string()); + match fuzzer_config.runner.clone(){ + FuzzRunnerConfig::QemuKernel(x) => { + cmd.push(x.qemu_binary.to_string()); + cmd.push("-kernel".to_string()); + cmd.push(x.kernel.to_string()); + + cmd.push("-initrd".to_string()); + cmd.push(x.ramfs.to_string()); + + cmd.push("-append".to_string()); + cmd.push("nokaslr oops=panic nopti ignore_rlimit_data".to_string()); + }, + FuzzRunnerConfig::QemuSnapshot(x) => { + cmd.push(x.qemu_binary.to_string()); + cmd.push("-drive".to_string()); + cmd.push(format!("file={},format=raw,index=0,media=disk", x.hda.to_string())); + }, + } - cmd.push("-drive".to_string()); - cmd.push(format!("file={},format=raw,index=0,media=disk", params.hda.to_string())); - - if !params.debug { + /* generic QEMU-Nyx parameters */ + if !debug{ cmd.push("-display".to_string()); cmd.push("none".to_string()); } else { cmd.push("-vnc".to_string()); - cmd.push(format!(":{}",qemu_id+cpu)); + cmd.push(format!(":{}",qemu_id)); } cmd.push("-serial".to_string()); - if params.debug { + if debug { cmd.push("mon:stdio".to_string()); } else { cmd.push("stdio".to_string()); @@ -85,7 +74,7 @@ impl QemuParams { cmd.push("de".to_string()); cmd.push("-m".to_string()); - cmd.push(params.ram_size.to_string()); + cmd.push(fuzzer_config.fuzz.mem_limit.to_string()); cmd.push("-chardev".to_string()); cmd.push(format!( @@ -95,23 +84,22 @@ impl QemuParams { cmd.push("-device".to_string()); let mut nyx_ops = format!("nyx,chardev=nyx_interface"); - nyx_ops += &format!(",bitmap_size={}", params.bitmap_size); - nyx_ops += &format!(",input_buffer_size={}", params.input_buffer_size); + nyx_ops += &format!(",bitmap_size={}", fuzzer_config.fuzz.bitmap_size); + nyx_ops += &format!(",input_buffer_size={}", fuzzer_config.fuzz.input_buffer_size); nyx_ops += &format!(",worker_id={}", qemu_id); nyx_ops += &format!(",workdir={}", workdir); - nyx_ops += &format!(",sharedir={}", params.sharedir); + nyx_ops += &format!(",sharedir={}", sharedir); - let mut i = 0; - for filter in params.ipt_filters{ + for filter in fuzzer_config.fuzz.ipt_filters{ if filter.a != 0 && filter.b != 0 { nyx_ops += &format!(",ip{}_a={},ip{}_b={}", i, filter.a, i, filter.b); i += 1; } } - if params.cow_primary_size.is_some(){ - nyx_ops += &format!(",cow_primary_size={}", params.cow_primary_size.unwrap()); + if fuzzer_config.fuzz.cow_primary_size.is_some(){ + nyx_ops += &format!(",cow_primary_size={}", fuzzer_config.fuzz.cow_primary_size.unwrap()); } cmd.push(nyx_ops); @@ -122,132 +110,75 @@ impl QemuParams { cmd.push("-cpu".to_string()); cmd.push("kAFL64-Hypervisor-v1".to_string()); - match ¶ms.snapshot_path { - SnapshotPath::Create(path) => { - if create_snapshot_file { - cmd.push("-fast_vm_reload".to_string()); - cmd.push(format!("path={},load=off,pre_path={}", path,params.presnapshot)); - } - else{ - cmd.push("-fast_vm_reload".to_string()); - cmd.push(format!("path={},load=off,pre_path={},skip_serialization=on", path,params.presnapshot)); - } - }, - SnapshotPath::Reuse(path) => { - cmd.push("-fast_vm_reload".to_string()); - cmd.push(format!("path={},load=on", path)); - } - SnapshotPath::DefaultPath => panic!(), - } - - return QemuParams { - cmd, - qemu_aux_buffer_filename, - control_filename, - workdir: workdir.to_string(), - qemu_id, - bitmap_size: params.bitmap_size, - payload_size: params.input_buffer_size, - dump_python_code_for_inputs: params.dump_python_code_for_inputs, - write_protected_input_buffer: params.write_protected_input_buffer, - cow_primary_size: params.cow_primary_size, - }; - } - pub fn new_from_kernel(workdir: &str, qemu_id: usize, params: &KernelVmParams, create_snapshot_file: bool) -> QemuParams { - - let qemu_aux_buffer_filename = format!("{}/aux_buffer_{}", workdir, qemu_id); - let control_filename = format!("{}/interface_{}", workdir, qemu_id); - - let mut cmd = vec![]; - cmd.push(params.qemu_binary.to_string()); - cmd.push("-kernel".to_string()); - cmd.push(params.kernel.to_string()); - - cmd.push("-initrd".to_string()); - cmd.push(params.ramfs.to_string()); - - cmd.push("-append".to_string()); - cmd.push("nokaslr oops=panic nopti ignore_rlimit_data".to_string()); - - if !params.debug { - cmd.push("-display".to_string()); - cmd.push("none".to_string()); - } - - cmd.push("-serial".to_string()); - if params.debug { - cmd.push("mon:stdio".to_string()); - } else { - cmd.push("none".to_string()); - } - - cmd.push("-enable-kvm".to_string()); - - cmd.push("-net".to_string()); - cmd.push("none".to_string()); - - cmd.push("-k".to_string()); - cmd.push("de".to_string()); - - cmd.push("-m".to_string()); - cmd.push(params.ram_size.to_string()); - - - cmd.push("-chardev".to_string()); - cmd.push(format!( - "socket,server,path={},id=nyx_interface", - control_filename - )); - - cmd.push("-device".to_string()); - let mut nyx_ops = format!("nyx,chardev=nyx_interface"); - nyx_ops += &format!(",bitmap_size={}", params.bitmap_size); - nyx_ops += &format!(",input_buffer_size={}", params.input_buffer_size); - nyx_ops += &format!(",worker_id={}", qemu_id); - nyx_ops += &format!(",workdir={}", workdir); - nyx_ops += &format!(",sharedir={}", params.sharedir); - - let mut i = 0; - for filter in params.ipt_filters{ - if filter.a != 0 && filter.b != 0 { - nyx_ops += &format!(",ip{}_a={:x},ip{}_b={:x}", i, filter.a, i, filter.b); - i += 1; - } - } - - if params.cow_primary_size.is_some(){ - nyx_ops += &format!(",cow_primary_size={}", params.cow_primary_size.unwrap()); - } - - cmd.push(nyx_ops); - - cmd.push("-machine".to_string()); - cmd.push("kAFL64-v1".to_string()); - - cmd.push("-cpu".to_string()); - cmd.push("kAFL64-Hypervisor-v1,+vmx".to_string()); - - if create_snapshot_file { + if fuzzer_config.runtime.reuse_root_snapshot_path().is_some() { cmd.push("-fast_vm_reload".to_string()); - if qemu_id == 0{ - cmd.push(format!("path={}/snapshot/,load=off", workdir)); - } else { - cmd.push(format!("path={}/snapshot/,load=on", workdir)); + cmd.push(format!("path={},load=on", fuzzer_config.runtime.reuse_root_snapshot_path().unwrap())); + } + else{ + match fuzzer_config.runner.clone(){ + FuzzRunnerConfig::QemuKernel(_) => { + + match fuzzer_config.runtime.process_role() { + QemuNyxRole::StandAlone => {}, + QemuNyxRole::Parent => { + cmd.push("-fast_vm_reload".to_string()); + cmd.push(format!("path={}/snapshot/,load=off", workdir)); + }, + QemuNyxRole::Child => { + cmd.push("-fast_vm_reload".to_string()); + cmd.push(format!("path={}/snapshot/,load=on", workdir)); + }, + }; + }, + FuzzRunnerConfig::QemuSnapshot(x) => { + + match fuzzer_config.runtime.process_role() { + QemuNyxRole::StandAlone => { + cmd.push("-fast_vm_reload".to_string()); + cmd.push(format!("path={}/snapshot/,load=off,pre_path={},skip_serialization=on", workdir, x.presnapshot)); + + }, + QemuNyxRole::Parent => { + cmd.push("-fast_vm_reload".to_string()); + cmd.push(format!("path={}/snapshot/,load=off,pre_path={}", workdir, x.presnapshot)); + }, + QemuNyxRole::Child => { + cmd.push("-fast_vm_reload".to_string()); + cmd.push(format!("path={}/snapshot/,load=on,pre_path={}", workdir, x.presnapshot)); + }, + }; + }, } } + match fuzzer_config.runtime.process_role() { + QemuNyxRole::StandAlone | QemuNyxRole::Parent => { + assert!(qemu_id == 0); + QemuProcess::prepare_workdir(workdir, fuzzer_config.fuzz.seed_path.clone()); + }, + QemuNyxRole::Child => { + QemuProcess::wait_for_workdir(workdir); + }, + }; + + return QemuParams { cmd, qemu_aux_buffer_filename, control_filename, workdir: workdir.to_string(), qemu_id, - bitmap_size: params.bitmap_size, - payload_size: params.input_buffer_size, - dump_python_code_for_inputs: params.dump_python_code_for_inputs, - write_protected_input_buffer: params.write_protected_input_buffer, - cow_primary_size: params.cow_primary_size, - }; + bitmap_size: fuzzer_config.fuzz.bitmap_size, + payload_size: fuzzer_config.fuzz.input_buffer_size, + dump_python_code_for_inputs: match fuzzer_config.fuzz.dump_python_code_for_inputs{ + None => false, + Some(x) => x, + }, + write_protected_input_buffer: fuzzer_config.fuzz.write_protected_input_buffer, + cow_primary_size: fuzzer_config.fuzz.cow_primary_size, + hprintf_fd: fuzzer_config.runtime.hprintf_fd(), + } } + } diff --git a/fuzz_runner/src/nyx/qemu_process.rs b/fuzz_runner/src/nyx/qemu_process.rs index 12ce543..757ba05 100644 --- a/fuzz_runner/src/nyx/qemu_process.rs +++ b/fuzz_runner/src/nyx/qemu_process.rs @@ -1,4 +1,5 @@ use core::ffi::c_void; +use std::os::unix::prelude::FromRawFd; use std::path::PathBuf; use nix::sys::mman::*; use std::fs; @@ -33,8 +34,12 @@ use crate::nyx::mem_barrier::mem_barrier; use crate::nyx::params::QemuParams; pub struct QemuProcess { - pub process: Child, - pub aux: AuxBuffer, + + process: Child, + + /* ptr to the aux buffer */ + aux: AuxBuffer, + pub feedback_data: FeedbackBuffer, pub ijon_buffer: &'static mut [u8], pub ctrl: UnixStream, @@ -46,6 +51,8 @@ pub struct QemuProcess { shm_work_dir: PathBuf, #[allow(unused)] shm_file_lock: File, + + hprintf_file: Option, } fn execute_qemu(ctrl: &mut UnixStream) -> io::Result<()>{ @@ -168,12 +175,6 @@ impl QemuProcess { let ijon_shared = make_shared_data(&ijon_buffer_shm_f, 0x1000); let ijon_feedback_buffer = make_shared_ijon_data(ijon_buffer_shm_f, 0x1000); - - thread::sleep(time::Duration::from_secs(1)); - - thread::sleep(time::Duration::from_millis(200*params.qemu_id as u64)); - - let mut child = if params.dump_python_code_for_inputs{ Command::new(¶ms.cmd[0]) .args(¶ms.cmd[1..]) @@ -188,12 +189,6 @@ impl QemuProcess { .expect("failed to execute process") }; - - thread::sleep(time::Duration::from_secs(1)); - - thread::sleep(time::Duration::from_millis(200*params.qemu_id as u64)); - - let mut control = loop { match UnixStream::connect(¶ms.control_filename) { Ok(stream) => break stream, @@ -207,19 +202,15 @@ impl QemuProcess { return Err(format!("cannot launch QEMU-Nyx...")); } - let aux_shm_f = OpenOptions::new() - .read(true) - .write(true) - .open(¶ms.qemu_aux_buffer_filename) - .expect("couldn't open aux buffer file"); - aux_shm_f.set_len(0x1000).unwrap(); + let mut aux_buffer = { + let aux_shm_f = OpenOptions::new() + .read(true) + .write(true) + .open(¶ms.qemu_aux_buffer_filename) + .expect("couldn't open aux buffer file"); - let aux_shm_f = OpenOptions::new() - .write(true) - .read(true) - .open(¶ms.qemu_aux_buffer_filename) - .expect("couldn't open aux buffer file"); - let mut aux_buffer = AuxBuffer::new(aux_shm_f); + AuxBuffer::new(aux_shm_f) + }; match aux_buffer.validate_header(){ Err(x) => { @@ -237,12 +228,17 @@ impl QemuProcess { aux_buffer.config.changed = 1; } + let mut hprintf_file = match params.hprintf_fd { + Some(fd) => Some(unsafe { File::from_raw_fd(fd) }), + None => None, + }; + loop { match aux_buffer.result.exec_result_code { NYX_HPRINTF => { let len = aux_buffer.misc.len; - print!("{}", String::from_utf8_lossy(&aux_buffer.misc.data[0..len as usize]).yellow()); + QemuProcess::output_hprintf(&mut hprintf_file, &String::from_utf8_lossy(&aux_buffer.misc.data[0..len as usize]).yellow()); }, NYX_ABORT => { let len = aux_buffer.misc.len; @@ -313,9 +309,32 @@ impl QemuProcess { params, shm_work_dir, shm_file_lock: file_lock, + hprintf_file, }); } + fn output_hprintf(hprintf_file: &mut Option, msg: &str){ + match hprintf_file { + Some(ref mut f) => { + f.write_fmt(format_args!("{}", msg)).unwrap(); + }, + None => { + print!("{}", msg); + } + } + } + + pub fn aux_buffer(&self) -> &AuxBuffer{ + &self.aux + } + + pub fn aux_buffer_mut(&mut self) -> &mut AuxBuffer{ + &mut self.aux + } + + pub fn set_hprintf_fd(&mut self, fd: i32){ + self.hprintf_file = unsafe { Some(File::from_raw_fd(fd)) }; + } pub fn send_payload(&mut self) -> io::Result<()>{ let mut old_address: u64 = 0; @@ -355,7 +374,7 @@ impl QemuProcess { match self.aux.result.exec_result_code { NYX_HPRINTF => { let len = self.aux.misc.len; - print!("{}", String::from_utf8_lossy(&self.aux.misc.data[0..len as usize]).yellow()); + QemuProcess::output_hprintf(&mut self.hprintf_file, &String::from_utf8_lossy(&self.aux.misc.data[0..len as usize]).yellow()); continue; }, NYX_ABORT => { From cab84b33ab7817f9eecfe9fbaf7638da6fe8ffc6 Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Thu, 13 Apr 2023 05:58:23 +0200 Subject: [PATCH 3/6] update libnyx API - various changes to the API (both Rust and FFI) to enable more control over the fuzzing process - fixed and updated C-API sample (test.c) --- libnyx/src/ffi.rs | 251 +++++++++++++++++++++++++++------------------ libnyx/src/lib.rs | 253 ++++++++++++++++++++++++++++------------------ libnyx/test.c | 140 +++++++++++++------------ 3 files changed, 389 insertions(+), 255 deletions(-) diff --git a/libnyx/src/ffi.rs b/libnyx/src/ffi.rs index 6fb97b9..0a608a9 100644 --- a/libnyx/src/ffi.rs +++ b/libnyx/src/ffi.rs @@ -1,3 +1,20 @@ +/* + libnyx FFI API + + Copyright (C) 2021 Sergej Schumilo + This file is part of libnyx. + + libnyx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + libnyx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with libnyx. If not, see . + */ use libc::c_char; use std::ffi::CStr; use std::ffi::c_void; @@ -5,16 +22,34 @@ use std::ffi::c_void; use fuzz_runner::nyx::aux_buffer::{NYX_CRASH, NYX_HPRINTF, NYX_ABORT}; use super::*; -#[no_mangle] -pub extern "C" fn nyx_load_config(sharedir: *const c_char) -> *mut c_void { - let sharedir_c_str = unsafe { - assert!(!sharedir.is_null()); - CStr::from_ptr(sharedir) +/* Helper function to load a C string pointer and return a Rust string. */ +fn __load_c_string_ptr(pointer: *const c_char) -> String { + let c_str = unsafe { + assert!(!pointer.is_null()); + CStr::from_ptr(pointer) }; - let sharedir_r_str = sharedir_c_str.to_str().unwrap(); + c_str.to_str().unwrap().to_string() +} - let cfg: NyxConfig = match NyxConfig::load(sharedir_r_str){ +/* Helper function to check if the config pointer is valid. + * Turns the pointer into a reference to a config object. + */ +fn __nyx_config_check_ptr(config: * mut c_void) -> *mut NyxConfig { + assert!(!config.is_null()); + assert!((config as usize) % std::mem::align_of::() == 0); + + config as *mut NyxConfig +} + +/* Loads a given Nyx share-dir and returns a raw pointer to the Nyx config object. + * The pointer is later used to access the config object in other FFI config functions. + */ +#[no_mangle] +pub extern "C" fn nyx_config_load(sharedir: *const c_char) -> *mut c_void { + let sharedir_r_str = __load_c_string_ptr(sharedir); + + let cfg: NyxConfig = match NyxConfig::load(&sharedir_r_str){ Ok(x) => x, Err(msg) => { println!("[!] libnyx config reader error: {}", msg); @@ -25,32 +60,93 @@ pub extern "C" fn nyx_load_config(sharedir: *const c_char) -> *mut c_void { Box::into_raw(Box::new(cfg)) as *mut c_void } +/* Simple debug function to print the entire config object to stdout. */ #[no_mangle] -pub extern "C" fn nyx_print_config(config: * mut c_void) { - unsafe{ - assert!(!config.is_null()); - assert!((config as usize) % std::mem::align_of::() == 0); +pub extern "C" fn nyx_config_debug(config: * mut c_void) { + let cfg = __nyx_config_check_ptr(config); - let cfg = config as *mut NyxConfig; + unsafe{ println!("{}", *cfg); } } -fn nyx_process_start(sharedir: *const c_char, workdir: *const c_char, worker_id: u32, cpu_id: u32, create_snapshot: bool, input_buffer_size: Option, input_buffer_write_protection: bool) -> * mut NyxProcess { - let sharedir_c_str = unsafe { - assert!(!sharedir.is_null()); - CStr::from_ptr(sharedir) - }; - - let workdir_c_str = unsafe { - assert!(!workdir.is_null()); - CStr::from_ptr(workdir) - }; +/* Simple debug function to print a subset of configurable options to stdout. */ +#[no_mangle] +pub extern "C" fn nyx_config_print(config: * mut c_void) { + unsafe{ + NyxConfig::print(&*__nyx_config_check_ptr(config)); + } +} - let sharedir_r_str = sharedir_c_str.to_str().unwrap(); - let workdir_r_str = workdir_c_str.to_str().unwrap(); +/* FFI function to set the workdir path in the config object. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_workdir_path(config: * mut c_void, workdir: *const c_char) { + let workidr_r_str = __load_c_string_ptr(workdir); + let cfg = __nyx_config_check_ptr(config); + + unsafe{ + NyxConfig::set_workdir_path(&mut *cfg, workidr_r_str.to_string()); + } +} + +/* FFI function to set the input buffer size in the config object. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_input_buffer_size(config: * mut c_void, input_buffer_size: u32) { + let cfg = __nyx_config_check_ptr(config); + + assert_eq!(input_buffer_size > 0, true); + unsafe{ + NyxConfig::set_input_buffer_size(&mut *cfg, input_buffer_size as usize); + } +} + +/* FFI function to set the input buffer write protection in the config object. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_input_buffer_write_protection(config: * mut c_void, input_buffer_write_protection: bool) { + let cfg = __nyx_config_check_ptr(config); + + unsafe{ + NyxConfig::set_input_buffer_write_protection(&mut *cfg, input_buffer_write_protection); + } +} + +/* FFI function to set the hprintf file descriptor in the config object. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_hprintf_fd(config: * mut c_void, hprintf_fd: i32) { + let cfg = __nyx_config_check_ptr(config); + + unsafe{ + NyxConfig::set_hprintf_fd(&mut *cfg, hprintf_fd); + } +} + +/* FFI function to set the fuzz runner role in the config object. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_process_role(config: * mut c_void, role: NyxProcessRole) { + let cfg = __nyx_config_check_ptr(config); + + unsafe{ + NyxConfig::set_process_role(&mut *cfg, role); + } +} + +/* Enable snapshot reuse by setting the path to the snapshot folder. */ +#[no_mangle] +pub extern "C" fn nyx_config_set_reuse_snapshot_path(config: * mut c_void, reuse_snapshot_path: *const c_char) { + let reuse_snapshot_path_r_str = __load_c_string_ptr(reuse_snapshot_path); + let cfg = __nyx_config_check_ptr(config); + + unsafe{ + NyxConfig::set_reuse_snapshot_path(&mut *cfg, reuse_snapshot_path_r_str.to_string()); + } +} + +#[no_mangle] +pub extern "C" fn nyx_new(config: * mut c_void, worker_id: u32) -> * mut NyxProcess { - match NyxProcess::process_start(sharedir_r_str, workdir_r_str, worker_id, cpu_id, create_snapshot, input_buffer_size, input_buffer_write_protection) { + let cfg = __nyx_config_check_ptr(config); + + match NyxProcess::new(unsafe {&mut *(cfg)}, worker_id as usize) { Ok(x) => Box::into_raw(Box::new(x)), Err(msg) => { println!("[!] libnyx failed to initialize QEMU-Nyx: {}", msg); @@ -59,106 +155,68 @@ fn nyx_process_start(sharedir: *const c_char, workdir: *const c_char, worker_id: } } -#[no_mangle] -pub extern "C" fn nyx_new(sharedir: *const c_char, workdir: *const c_char, cpu_id: u32, input_buffer_size: u32, input_buffer_write_protection: bool) -> * mut NyxProcess { - nyx_process_start(sharedir, workdir, 0, cpu_id, false, Some(input_buffer_size), input_buffer_write_protection) -} - - -#[no_mangle] -pub extern "C" fn nyx_new_parent(sharedir: *const c_char, workdir: *const c_char, cpu_id: u32, input_buffer_size: u32, input_buffer_write_protection: bool) -> * mut NyxProcess { - nyx_process_start(sharedir, workdir, 0, cpu_id, true, Some(input_buffer_size), input_buffer_write_protection) -} - -#[no_mangle] -pub extern "C" fn nyx_new_child(sharedir: *const c_char, workdir: *const c_char, cpu_id: u32, worker_id: u32) -> * mut NyxProcess { - if worker_id == 0 { - println!("[!] libnyx failed -> worker_id=0 cannot be used for child processes"); - std::ptr::null_mut() as *mut NyxProcess - } - else{ - nyx_process_start(sharedir, workdir, worker_id, cpu_id, true, None, false) - } -} - +/* Helper function to check if the NyxProcess pointer is valid. + * Turns the pointer into a reference to the NyxProcess object. + */ +fn __nyx_process_check_ptr(nyx_process: * mut NyxProcess) -> *mut NyxProcess { + assert!(!nyx_process.is_null()); + assert!((nyx_process as usize) % std::mem::align_of::() == 0); + nyx_process as *mut NyxProcess +} #[no_mangle] pub extern "C" fn nyx_get_aux_buffer(nyx_process: * mut NyxProcess) -> *mut u8 { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - return (*nyx_process).aux_buffer_as_mut_ptr(); + return (*__nyx_process_check_ptr(nyx_process)).aux_buffer_as_mut_ptr(); } } #[no_mangle] pub extern "C" fn nyx_get_input_buffer(nyx_process: * mut NyxProcess) -> *mut u8 { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - return (*nyx_process).input_buffer_mut().as_mut_ptr(); + return (*__nyx_process_check_ptr(nyx_process)).input_buffer_mut().as_mut_ptr(); } } #[no_mangle] pub extern "C" fn nyx_get_bitmap_buffer(nyx_process: * mut NyxProcess) -> *mut u8 { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - return (*nyx_process).bitmap_buffer_mut().as_mut_ptr(); + return (*__nyx_process_check_ptr(nyx_process)).bitmap_buffer_mut().as_mut_ptr(); } } #[no_mangle] pub extern "C" fn nyx_get_bitmap_buffer_size(nyx_process: * mut NyxProcess) -> usize { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - //return (*nyx_process).process.bitmap.len(); - return (*nyx_process).bitmap_buffer_size(); + return (*__nyx_process_check_ptr(nyx_process)).bitmap_buffer_size(); } } #[no_mangle] pub extern "C" fn nyx_shutdown(nyx_process: * mut NyxProcess) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - (*nyx_process).shutdown(); + (*__nyx_process_check_ptr(nyx_process)).shutdown(); } } #[no_mangle] pub extern "C" fn nyx_option_set_reload_mode(nyx_process: * mut NyxProcess, enable: bool) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - (*nyx_process).option_set_reload_mode(enable); + (*__nyx_process_check_ptr(nyx_process)).option_set_reload_mode(enable); } } #[no_mangle] pub extern "C" fn nyx_option_set_timeout(nyx_process: * mut NyxProcess, timeout_sec: u8, timeout_usec: u32) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - (*nyx_process).option_set_timeout(timeout_sec, timeout_usec); + (*__nyx_process_check_ptr(nyx_process)).option_set_timeout(timeout_sec, timeout_usec); } } #[no_mangle] pub extern "C" fn nyx_option_apply(nyx_process: * mut NyxProcess) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - (*nyx_process).option_apply(); + (*__nyx_process_check_ptr(nyx_process)).option_apply(); } } @@ -166,10 +224,7 @@ pub extern "C" fn nyx_option_apply(nyx_process: * mut NyxProcess) { pub extern "C" fn nyx_exec(nyx_process: * mut NyxProcess) -> NyxReturnValue { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - - (*nyx_process).exec() + (*__nyx_process_check_ptr(nyx_process)).exec() } } @@ -177,11 +232,8 @@ pub extern "C" fn nyx_exec(nyx_process: * mut NyxProcess) -> NyxReturnValue { pub extern "C" fn nyx_set_afl_input(nyx_process: * mut NyxProcess, buffer: *mut u8, size: u32) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); assert!((buffer as usize) % std::mem::align_of::() == 0); - - (*nyx_process).set_input_ptr(buffer, size); + (*__nyx_process_check_ptr(nyx_process)).set_input_ptr(buffer, size); } } @@ -189,14 +241,14 @@ pub extern "C" fn nyx_set_afl_input(nyx_process: * mut NyxProcess, buffer: *mut #[no_mangle] pub extern "C" fn nyx_print_aux_buffer(nyx_process: * mut NyxProcess) { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); - print!("{}", format!("{:#?}", (*nyx_process).process.aux.result)); + let nyx_process = __nyx_process_check_ptr(nyx_process); - match (*nyx_process).process.aux.result.exec_result_code { + print!("{}", format!("{:#?}", (*nyx_process).process.aux_buffer().result)); + + match (*nyx_process).process.aux_buffer().result.exec_result_code { NYX_CRASH | NYX_ABORT | NYX_HPRINTF => { - println!("{}", std::str::from_utf8(&(*nyx_process).process.aux.misc.data).unwrap()); + println!("{}", std::str::from_utf8(&(*nyx_process).process.aux_buffer().misc.data).unwrap()); }, _ => {}, } @@ -207,12 +259,19 @@ pub extern "C" fn nyx_print_aux_buffer(nyx_process: * mut NyxProcess) { pub extern "C" fn nyx_get_aux_string(nyx_process: * mut NyxProcess, buffer: *mut u8, size: u32) -> u32 { unsafe{ - assert!(!nyx_process.is_null()); - assert!((nyx_process as usize) % std::mem::align_of::() == 0); + let nyx_process = __nyx_process_check_ptr(nyx_process); assert!((buffer as usize) % std::mem::align_of::() == 0); - let len = std::cmp::min( (*nyx_process).process.aux.misc.len as usize, size as usize); - std::ptr::copy((*nyx_process).process.aux.misc.data.as_mut_ptr(), buffer, len); + let len = std::cmp::min( (*nyx_process).process.aux_buffer().misc.len as usize, size as usize); + std::ptr::copy((*nyx_process).process.aux_buffer_mut().misc.data.as_mut_ptr(), buffer, len); len as u32 } } + + +#[no_mangle] +pub extern "C" fn nyx_set_hprintf_fd(nyx_process: * mut NyxProcess, fd: i32) { + unsafe{ + (*__nyx_process_check_ptr(nyx_process)).process.set_hprintf_fd(fd); + } +} diff --git a/libnyx/src/lib.rs b/libnyx/src/lib.rs index 50ae874..2d53729 100644 --- a/libnyx/src/lib.rs +++ b/libnyx/src/lib.rs @@ -1,11 +1,27 @@ +/* + libnyx Rust API + + Copyright (C) 2021 Sergej Schumilo + This file is part of libnyx. + + libnyx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + libnyx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with libnyx. If not, see . + */ extern crate libc; -use config::{Config, FuzzRunnerConfig}; +use config::{Config, FuzzRunnerConfig, QemuNyxRole}; -use fuzz_runner::nyx::qemu_process_new_from_kernel; -use fuzz_runner::nyx::qemu_process_new_from_snapshot; use fuzz_runner::nyx::qemu_process::QemuProcess; use fuzz_runner::nyx::aux_buffer::{NYX_SUCCESS, NYX_CRASH, NYX_TIMEOUT, NYX_INPUT_WRITE, NYX_ABORT}; +use libc::fcntl; use std::fmt; @@ -24,6 +40,14 @@ pub enum NyxReturnValue { Abort, // Abort hypercall called } +#[repr(C)] +#[derive(Debug)] +pub enum NyxProcessRole { + StandAlone, + Parent, + Child, +} + impl fmt::Display for NyxReturnValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -48,18 +72,42 @@ pub struct NyxProcess { #[derive(Clone, Debug)] pub struct NyxConfig { config: Config, + sharedir_path: String, } impl NyxConfig { + + /* Loads a given Nyx share-dir and returns a result object containing the config object. + * The config object is later used to access the config object via specific config functions. + */ pub fn load(sharedir: &str) -> Result { + /* TODO: perform some additional sanity checks on the sharedir (such as checking if the bootstrap scripts exist) */ match Config::new_from_sharedir(&sharedir){ Ok(x) => Ok(NyxConfig{ - config: x + config: x, + sharedir_path: sharedir.to_string() }), Err(x) => Err(x), } } + /* Simple debug function to print the entire config object to stdout. */ + pub fn print(&self){ + println!("[*] Nyx config (share-dir: {}):", self.sharedir_path()); + println!(" - workdir_path -> {}", self.workdir_path()); + println!(" - input_buffer_size -> {}", self.input_buffer_size()); + println!(" - input_buffer_write_protection -> {}", self.input_buffer_write_protection()); + println!(" - hprintf_fd -> {}", self.hprintf_fd()); + println!(" - process_role: -> {:?}", self.process_role()); + + } + + /* Returns the path to the actual sharedir. */ + pub fn sharedir_path(&self) -> String { + self.sharedir_path.clone() + } + + /* Returns the path to the configured qemu binary. */ pub fn qemu_binary_path(&self) -> Option{ let process_cfg= match self.config.runner.clone() { FuzzRunnerConfig::QemuKernel(cfg) => cfg, @@ -68,6 +116,7 @@ impl NyxConfig { return Some(process_cfg.qemu_binary); } + /* Returns the path to the configured kernel image (if Nyx is configured to run in kernel mode). */ pub fn kernel_image_path(&self) -> Option{ let process_cfg= match self.config.runner.clone() { FuzzRunnerConfig::QemuKernel(cfg) => cfg, @@ -76,6 +125,7 @@ impl NyxConfig { return Some(process_cfg.kernel); } + /* Returns the path to the configured initrd image (if Nyx is configured to run in kernel mode). */ pub fn ramfs_image_path(&self) -> Option{ let process_cfg= match self.config.runner.clone() { FuzzRunnerConfig::QemuKernel(cfg) => cfg, @@ -84,26 +134,100 @@ impl NyxConfig { return Some(process_cfg.ramfs); } + /* Returns the configured timeout threshold as a std::time::Duration object. */ pub fn timeout(&self) -> std::time::Duration { self.config.fuzz.time_limit } + /* Returns the configured spec path (deprecated). */ pub fn spec_path(&self) -> String{ self.config.fuzz.spec_path.clone() } + /* Returns the configured trace bitmap size (might be reconfigured later by the agent). */ pub fn bitmap_size(&self) -> usize{ self.config.fuzz.bitmap_size } + /* Returns the configured workdir path. */ pub fn workdir_path(&self) -> &str { &self.config.fuzz.workdir_path } + /* Returns the actual size of the input buffer. */ + pub fn input_buffer_size(&self) -> usize { + self.config.fuzz.input_buffer_size + } + + /* Returns the config value of the input buffer write protection of the agent (guest). */ + pub fn input_buffer_write_protection(&self) -> bool { + self.config.fuzz.write_protected_input_buffer + } + + /* Sets the path to the workdir. */ pub fn set_workdir_path(&mut self, path: String) { self.config.fuzz.workdir_path = path; } + /* Set the size of the input buffer (must be a multiple of x86_64_PAGE_SIZE -> 4096). */ + pub fn set_input_buffer_size(&mut self, size: usize) { + if size % 0x1000 != 0 { + /* TODO: return error */ + panic!("[ ] Input buffer size must be a multiple of x86_64_PAGE_SIZE (4096)!"); + } + self.config.fuzz.input_buffer_size = size; + } + + /* Set the input buffer write protection of the agent (guest). */ + pub fn set_input_buffer_write_protection(&mut self, write_protected: bool) { + self.config.fuzz.write_protected_input_buffer = write_protected; + } + + /* Returns the current configured FD to redirect hprintf() calls to (returns -1 if None is set). */ + pub fn hprintf_fd(&self) -> i32 { + /* TODO: fix me */ + match self.config.runtime.hprintf_fd() { + Some(fd) => fd, + None => -1, + } + } + + /* Sets the FD to redirect hprintf() calls to (must be a valid FD and must not be closed after this call). */ + pub fn set_hprintf_fd(&mut self, fd: i32) { + self.config.runtime.set_hpintf_fd(fd); + } + + /* Sets the process role of the fuzz runner */ + pub fn set_process_role(&mut self, role: NyxProcessRole) { + let _role = match role { + NyxProcessRole::Parent => QemuNyxRole::Parent, + NyxProcessRole::Child => QemuNyxRole::Child, + NyxProcessRole::StandAlone => QemuNyxRole::StandAlone, + }; + + self.config.runtime.set_process_role(_role); + } + + /* Configures the path to the snapshot file to be reused (optional). */ + pub fn set_reuse_snapshot_path(&mut self, path: String) { + self.config.runtime.set_reuse_snapshot_path(path); + } + + /* Returns the currently configured process role of the fuzz runner. */ + pub fn process_role(&self) -> &QemuNyxRole { + self.config.runtime.process_role() + } + + /* Returns the current QEMU-Nyx worker ID. */ + pub fn worker_id(&self) -> usize { + self.config.runtime.worker_id() + } + + /* Sets the QEMU-Nyx worker ID. */ + pub fn set_worker_id(&mut self, worker_id: usize) { + self.config.runtime.set_worker_id(worker_id); + } + pub fn dict(&self) -> Vec> { self.config.fuzz.dict.clone() } @@ -117,56 +241,12 @@ impl fmt::Display for NyxConfig { impl NyxProcess { - fn start_process(sharedir: &str, workdir: &str, fuzzer_config: Config, worker_id: u32) -> Result { + pub fn new(config: &mut NyxConfig, worker_id: usize) -> Result { - let mut config = fuzzer_config.fuzz; - let runner_cfg = fuzzer_config.runner; - - config.workdir_path = format!("{}", workdir); - - let sdir = sharedir.clone(); - - if worker_id == 0 { - QemuProcess::prepare_workdir(&config.workdir_path, config.seed_path.clone()); - } - else{ - QemuProcess::wait_for_workdir(&config.workdir_path); - } - - match runner_cfg.clone() { - FuzzRunnerConfig::QemuSnapshot(cfg) => { - qemu_process_new_from_snapshot(sdir.to_string(), &cfg, &config) - }, - FuzzRunnerConfig::QemuKernel(cfg) => { - qemu_process_new_from_kernel(sdir.to_string(), &cfg, &config) - } - } - } + let sharedir = config.sharedir_path(); + config.set_worker_id(worker_id); - fn process_start(sharedir: &str, workdir: &str, worker_id: u32, cpu_id: u32, create_snapshot: bool, input_buffer_size: Option, input_buffer_write_protection: bool) -> Result { - let mut cfg: Config = match Config::new_from_sharedir(&sharedir){ - Ok(x) => x, - Err(msg) => { - return Err(format!("[!] libnyx config reader error: {}", msg)); - } - }; - - cfg.fuzz.write_protected_input_buffer = input_buffer_write_protection; - - /* todo: add sanity check */ - cfg.fuzz.cpu_pin_start_at = cpu_id as usize; - - match input_buffer_size{ - Some(x) => { cfg.fuzz.input_buffer_size = x as usize; }, - None => {}, - } - - cfg.fuzz.thread_id = worker_id as usize; - cfg.fuzz.threads = if create_snapshot { 2 as usize } else { 1 as usize }; - - cfg.fuzz.workdir_path = format!("{}", workdir); - - match Self::start_process(sharedir, workdir, cfg, worker_id){ + match fuzz_runner::nyx::qemu_process_new(sharedir.to_string(), &config.config){ Ok(x) => Ok(NyxProcess{ process: x, }), @@ -174,41 +254,9 @@ impl NyxProcess { } } - pub fn from_config(sharedir: &str, config: &NyxConfig, worker_id: u32, create_snapshot: bool) -> Result{ - let workdir = config.config.fuzz.workdir_path.clone(); - - let mut config= config.clone(); - config.config.fuzz.threads = if create_snapshot { 2 as usize } else { 1 as usize }; - config.config.fuzz.thread_id = worker_id as usize; - - match Self::start_process(sharedir, &workdir, config.config.clone(), worker_id) { - Ok(x) => Ok(NyxProcess{ - process: x, - }), - Err(x) => Err(x), - } - } - - pub fn new(sharedir: &str, workdir: &str, cpu_id: u32, input_buffer_size: u32, input_buffer_write_protection: bool) -> Result { - Self::process_start(sharedir, workdir, 0, cpu_id, false, Some(input_buffer_size), input_buffer_write_protection) - } - - pub fn new_parent(sharedir: &str, workdir: &str, cpu_id: u32, input_buffer_size: u32, input_buffer_write_protection: bool) -> Result { - Self::process_start(sharedir, workdir, 0, cpu_id, true, Some(input_buffer_size), input_buffer_write_protection) - } - - pub fn new_child(sharedir: &str, workdir: &str, cpu_id: u32, worker_id: u32) -> Result { - if worker_id == 0 { - println!("[!] libnyx failed -> worker_id=0 cannot be used for child processes"); - Err("worker_id=0 cannot be used for child processes".to_string()) - } - else{ - Self::process_start(sharedir, workdir, worker_id, cpu_id, true, None, false) - } - } pub fn aux_buffer_as_mut_ptr(&self) -> *mut u8 { - std::ptr::addr_of!(self.process.aux.header.magic) as *mut u8 + std::ptr::addr_of!(self.process.aux_buffer().header.magic) as *mut u8 } pub fn input_buffer(&self) -> &[u8] { @@ -244,48 +292,48 @@ impl NyxProcess { } pub fn option_set_reload_mode(&mut self, enable: bool) { - self.process.aux.config.reload_mode = if enable {1} else {0}; + self.process.aux_buffer_mut().config.reload_mode = if enable {1} else {0}; } pub fn option_set_redqueen_mode(&mut self, enable: bool) { - self.process.aux.config.redqueen_mode = if enable {1} else {0}; + self.process.aux_buffer_mut().config.redqueen_mode = if enable {1} else {0}; } pub fn option_set_trace_mode(&mut self, enable: bool) { - self.process.aux.config.trace_mode = if enable {1} else {0}; + self.process.aux_buffer_mut().config.trace_mode = if enable {1} else {0}; } pub fn option_set_delete_incremental_snapshot(&mut self, enable: bool) { - self.process.aux.config.discard_tmp_snapshot = if enable {1} else {0}; + self.process.aux_buffer_mut().config.discard_tmp_snapshot = if enable {1} else {0}; } pub fn option_set_timeout(&mut self, timeout_sec: u8, timeout_usec: u32) { - self.process.aux.config.timeout_sec = timeout_sec; - self.process.aux.config.timeout_usec = timeout_usec; + self.process.aux_buffer_mut().config.timeout_sec = timeout_sec; + self.process.aux_buffer_mut().config.timeout_usec = timeout_usec; } pub fn option_apply(&mut self) { - self.process.aux.config.changed = 1; + self.process.aux_buffer_mut().config.changed = 1; } pub fn aux_misc(&self) -> Vec{ - self.process.aux.misc.as_slice().to_vec() + self.process.aux_buffer().misc.as_slice().to_vec() } pub fn aux_tmp_snapshot_created(&self) -> bool { - self.process.aux.result.tmp_snapshot_created != 0 + self.process.aux_buffer().result.tmp_snapshot_created != 0 } pub fn aux_string(&self) -> String { - let len = self.process.aux.misc.len; - String::from_utf8_lossy(&self.process.aux.misc.data[0..len as usize]).to_string() + let len = self.process.aux_buffer().misc.len; + String::from_utf8_lossy(&self.process.aux_buffer().misc.data[0..len as usize]).to_string() } pub fn exec(&mut self) -> NyxReturnValue { match self.process.send_payload(){ Err(_) => NyxReturnValue::IoError, Ok(_) => { - match self.process.aux.result.exec_result_code { + match self.process.aux_buffer().result.exec_result_code { NYX_SUCCESS => NyxReturnValue::Normal, NYX_CRASH => NyxReturnValue::Crash, NYX_TIMEOUT => NyxReturnValue::Timeout, @@ -307,4 +355,15 @@ impl NyxProcess { pub fn set_input(&mut self, buffer: &[u8], size: u32) { self.set_input_ptr(buffer.as_ptr(), size); } + + pub fn set_hprintf_fd(&mut self, fd: i32) { + + /* sanitiy check to prevent invalid file descriptors via F_GETFD */ + unsafe { + assert!(fcntl(fd, libc::F_GETFD) != -1); + }; + + self.process.set_hprintf_fd(fd); + } + } diff --git a/libnyx/test.c b/libnyx/test.c index ab3b048..a00d19a 100644 --- a/libnyx/test.c +++ b/libnyx/test.c @@ -1,9 +1,14 @@ +/* Simple test program to test the C-API */ #include #include "libnyx.h" #include #include + +#include +#include +#include #ifndef HEXDUMP_COLS #define HEXDUMP_COLS 16 @@ -11,90 +16,101 @@ void hexdump(void *mem, unsigned int len) { - unsigned int i, j; - - for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) + unsigned int i, j; + + for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) + { + /* print offset */ + if(i % HEXDUMP_COLS == 0) { - /* print offset */ - if(i % HEXDUMP_COLS == 0) - { - printf("0x%06x: ", i); - } - - /* print hex data */ - if(i < len) - { - printf("%02x ", 0xFF & ((char*)mem)[i]); - } - else /* end of block, just aligning for ASCII dump */ - { - printf(" "); - } - - /* print ASCII dump */ - if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) - { - for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) - { - if(j >= len) /* end of block, not really printing */ - { - putchar(' '); - } - else if(isprint(((char*)mem)[j])) /* printable char */ - { - putchar(0xFF & ((char*)mem)[j]); - } - else /* other char */ - { - putchar('.'); - } - } - putchar('\n'); - } + printf("0x%06x: ", i); } + + /* print hex data */ + if(i < len) + { + printf("%02x ", 0xFF & ((char*)mem)[i]); + } + else /* end of block, just aligning for ASCII dump */ + { + printf(" "); + } + + /* print ASCII dump */ + if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) + { + for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) + { + if(j >= len) /* end of block, not really printing */ + { + putchar(' '); + } + else if(isprint(((char*)mem)[j])) /* printable char */ + { + putchar(0xFF & ((char*)mem)[j]); + } + else /* other char */ + { + putchar('.'); + } + } + putchar('\n'); + } + } } int main(int argc, char** argv){ - printf("YO\n"); + + void* aux_buffer; - void* aux_buffer; + void* nyx_config = nyx_config_load("/tmp/nyx_libxml2/"); - void* ptr = nyx_new("/tmp/nyx_bash/"); + //nyx_config_debug(nyx_config); - printf("QEMU Rust Object Pointer: %p\n", ptr); + nyx_config_set_workdir_path(nyx_config, "/tmp/wdir"); + nyx_config_set_input_buffer_size(nyx_config, 0x2000); - void* aux = nyx_get_aux_buffer(ptr); + int fd = open("/tmp/nyx_test_output.log", O_WRONLY | O_CREAT | O_TRUNC, 0644); + printf("Log output FD: %d\n", fd); + nyx_config_set_hprintf_fd(nyx_config, fd); - printf("QEMU Rust Aux Pointer: %p\n", aux); + nyx_config_set_process_role(nyx_config, StandAlone); + + //nyx_config_set_reuse_snapshot_path(nyx_config, "/tmp/wdir/snapshot/"); - hexdump(aux, 16); + nyx_config_print(nyx_config); + nyx_config_debug(nyx_config); - void* payload = nyx_get_payload_buffer(ptr); + void* nyx_runner = nyx_new(nyx_config, 0); - nyx_set_afl_input(ptr, "HALLO", 5); + printf("Nyx runner object pointer: %p\n", nyx_runner); + void* aux = nyx_get_aux_buffer(nyx_runner); - printf("QEMU Rust Payload Pointer: %p\n", payload); + printf("QEMU rust aux pointer: %p\n", aux); + hexdump(aux, 16); - nyx_option_set_reload_mode(ptr, true); - nyx_option_apply(ptr); + void* nyx_input = nyx_get_input_buffer(nyx_runner); - hexdump(payload, 16); + nyx_set_afl_input(nyx_runner, "INPUT", 5); + printf("QEMU Rust Payload Pointer: %p\n", nyx_input); - printf("About to run init\n"); - printf("INIT -> %d\n", nyx_exec(ptr)); - printf("Init done\n"); + nyx_option_set_reload_mode(nyx_runner, true); + nyx_option_apply(nyx_runner); + hexdump(nyx_input, 16); - for(int i = 0; i < 32; i++){ - nyx_set_afl_input(ptr, "HALLO", 5); - printf("nyx_exec -> %d\n", nyx_exec(ptr)); - //nyx_print_aux_buffer(ptr); - } - - nyx_shutdown(ptr); + printf("About to run init\n"); + printf("INIT -> %d\n", nyx_exec(nyx_runner)); + printf("Init done\n"); + for(int i = 0; i < 4; i++){ + nyx_set_afl_input(nyx_runner, "INPUT", 5); + printf("nyx_exec -> %d\n", nyx_exec(nyx_runner)); + nyx_print_aux_buffer(nyx_runner); + } + nyx_shutdown(nyx_runner); } From 1c1fdf5286441f652d7a9e4d25261b95479ab9b0 Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Thu, 13 Apr 2023 06:01:52 +0200 Subject: [PATCH 4/6] bump nix version --- fuzz_runner/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz_runner/Cargo.toml b/fuzz_runner/Cargo.toml index 92df36c..afdfc29 100644 --- a/fuzz_runner/Cargo.toml +++ b/fuzz_runner/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nix = "0.17.0" +nix = "0.20.2" time = "0.2.9" subprocess = "0.2.4" libc = "0.2.68" From a9efaee0dbfc69f03fb7f7b59dcf7b91342fb9ee Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Fri, 14 Apr 2023 04:11:16 +0200 Subject: [PATCH 5/6] fix console in non-debug mode (set "-serial none") --- fuzz_runner/src/nyx/params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz_runner/src/nyx/params.rs b/fuzz_runner/src/nyx/params.rs index 2574b44..c8efde7 100644 --- a/fuzz_runner/src/nyx/params.rs +++ b/fuzz_runner/src/nyx/params.rs @@ -62,7 +62,7 @@ impl QemuParams { if debug { cmd.push("mon:stdio".to_string()); } else { - cmd.push("stdio".to_string()); + cmd.push("none".to_string()); } cmd.push("-enable-kvm".to_string()); From 186ee5f8570660c7c66c74ea7e53aa2e6da5b30e Mon Sep 17 00:00:00 2001 From: Sergej Schumilo Date: Fri, 14 Apr 2023 04:18:31 +0200 Subject: [PATCH 6/6] add helper function to delete a workdir safely --- fuzz_runner/src/nyx/qemu_process.rs | 35 +++++++++++++++++++++++++++++ libnyx/src/ffi.rs | 21 +++++++++++++++++ libnyx/src/lib.rs | 4 ++++ libnyx/test.c | 8 ++++++- 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/fuzz_runner/src/nyx/qemu_process.rs b/fuzz_runner/src/nyx/qemu_process.rs index 757ba05..d062d87 100644 --- a/fuzz_runner/src/nyx/qemu_process.rs +++ b/fuzz_runner/src/nyx/qemu_process.rs @@ -569,3 +569,38 @@ impl QemuProcess { (shm_work_dir_path, file_lock) } } + +/* Helper function to remove a Nyx workdir safely. Returns an error if + * expected sub dirs are missing or the path does not exist */ +pub fn remove_workdir_safe(workdir: &str) -> Result<(), String> { + let folders = vec![ + "/corpus/normal", + "/corpus/crash", + "/corpus/kasan", + "/corpus/timeout", + "/imports", + "/seeds", + "/snapshot", + "/forced_imports", + ]; + + if !Path::new(&format!("{}/", workdir)).exists() { + return Err(format!("\"{}/\" does not exists", workdir)); + } + + /* check if all sub dirs exists */ + for folder in folders.iter() { + if !Path::new(&format!("{}/{}", workdir, folder)).exists() { + return Err(format!("\"{}/{}\" does not exists", workdir, folder)); + } + } + + /* remove if all sub dirs exists */ + for folder in folders.iter() { + let _ = fs::remove_dir_all(format!("{}/{}", workdir, folder)); + } + + let _ = fs::remove_dir_all(workdir); + + return Ok(()); +} diff --git a/libnyx/src/ffi.rs b/libnyx/src/ffi.rs index 0a608a9..61ffa9f 100644 --- a/libnyx/src/ffi.rs +++ b/libnyx/src/ffi.rs @@ -275,3 +275,24 @@ pub extern "C" fn nyx_set_hprintf_fd(nyx_process: * mut NyxProcess, fd: i32) { (*__nyx_process_check_ptr(nyx_process)).process.set_hprintf_fd(fd); } } + +/* Helper function to remove a given Nyx workdir safely. + * This function will return an error if the path does not exist or it does + * not appear to be a Nyx workdir (e.g. specific sub directories are + * missing). */ +#[no_mangle] +pub extern "C" fn nyx_remove_work_dir(workdir: *const c_char) -> bool { + unsafe{ + let workdir = CStr::from_ptr(workdir).to_str().unwrap(); + + match remove_work_dir(workdir) { + Ok(_) => { + true + }, + Err(e) => { + eprintln!("[!] libnyx failed to remove workdir: {}", e); + false + } + } + } +} \ No newline at end of file diff --git a/libnyx/src/lib.rs b/libnyx/src/lib.rs index 2d53729..68a6949 100644 --- a/libnyx/src/lib.rs +++ b/libnyx/src/lib.rs @@ -367,3 +367,7 @@ impl NyxProcess { } } + +pub fn remove_work_dir(workdir: &str) -> Result<(), String> { + fuzz_runner::nyx::qemu_process::remove_workdir_safe(workdir) +} diff --git a/libnyx/test.c b/libnyx/test.c index a00d19a..a222e25 100644 --- a/libnyx/test.c +++ b/libnyx/test.c @@ -59,6 +59,7 @@ void hexdump(void *mem, unsigned int len) } } +#define WORKDIR_PATH "/tmp/wdir" int main(int argc, char** argv){ @@ -69,7 +70,7 @@ int main(int argc, char** argv){ //nyx_config_debug(nyx_config); - nyx_config_set_workdir_path(nyx_config, "/tmp/wdir"); + nyx_config_set_workdir_path(nyx_config, WORKDIR_PATH); nyx_config_set_input_buffer_size(nyx_config, 0x2000); int fd = open("/tmp/nyx_test_output.log", O_WRONLY | O_CREAT | O_TRUNC, 0644); @@ -113,4 +114,9 @@ int main(int argc, char** argv){ } nyx_shutdown(nyx_runner); + + if(!nyx_remove_work_dir(WORKDIR_PATH) ){ + printf("Error: Failed to remove work dir\n"); + } + }