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, 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" 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..c8efde7 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,34 +12,57 @@ 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()); + cmd.push("none".to_string()); } cmd.push("-enable-kvm".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..d062d87 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 => { @@ -550,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 6fb97b9..61ffa9f 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,40 @@ 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); + } +} + +/* 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 50ae874..68a6949 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,19 @@ 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); + } + +} + +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 ab3b048..a222e25 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,107 @@ 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'); + } + } } +#define WORKDIR_PATH "/tmp/wdir" 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, WORKDIR_PATH); + 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); - } + printf("About to run init\n"); + printf("INIT -> %d\n", nyx_exec(nyx_runner)); + printf("Init done\n"); - nyx_shutdown(ptr); + 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); + + if(!nyx_remove_work_dir(WORKDIR_PATH) ){ + printf("Error: Failed to remove work dir\n"); + } }