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.
This commit is contained in:
Sergej Schumilo 2023-04-13 05:45:30 +02:00
parent 7ab9346070
commit e767dbb800
3 changed files with 151 additions and 300 deletions

View File

@ -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<QemuProcess, String> {
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, &params, fuzz_cfg.threads > 1);
pub fn qemu_process_new(sharedir: String, cfg: &config::Config) -> Result<QemuProcess, String> {
/*
if qemu_id == 0{
qemu_process::QemuProcess::prepare_workdir(&fuzz_cfg.workdir_path, fuzz_cfg.seed_pattern.clone());
}
*/
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<QemuProcess, String> {
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, &params, 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, &params);
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");
}
}

View File

@ -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<u64>,
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<u64>,
pub ipt_filters: [IptFilter; 4],
pub input_buffer_size: usize,
}
use crate::{config::{Config, FuzzRunnerConfig, QemuNyxRole}, QemuProcess};
pub struct QemuParams {
pub cmd: Vec<String>,
@ -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<u64>,
pub hprintf_fd: Option<i32>,
}
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("-drive".to_string());
cmd.push(format!("file={},format=raw,index=0,media=disk", params.hda.to_string()));
cmd.push("-initrd".to_string());
cmd.push(x.ramfs.to_string());
if !params.debug {
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()));
},
}
/* 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 &params.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(),
}
}
}

View File

@ -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<File>,
}
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(&params.cmd[0])
.args(&params.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(&params.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(&params.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(&params.qemu_aux_buffer_filename)
.expect("couldn't open aux buffer file");
let aux_shm_f = OpenOptions::new()
.write(true)
.read(true)
.open(&params.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<File>, 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 => {