AFLplusplus / AFLplusplus/LibAFL

libafl-qemu: It's easy to misuse the snapshot API

Aperta
#2,621 1 commento 3 reazioni 1 assegnatario Rivendicata da @rmalmain Vedi su GitHub
bug qemu
Lingua principale
Rust
Stelle
2.6k
Fork
481
Merge medio
2g 30m
PR unite (30g)
16

Descrizione

Consider the following use of `SnapshotModule::{snapshot,reset}`:
```rust
// cargo init
// cargo add --no-default-features --git https://github.com/AFLplusplus/LibAFL libafl_qemu
// cargo run

use std::{error, fs, io::Write as _, path, process};

use libafl_qemu::{modules::SnapshotModule, Regs};

fn run_to(
qemu: &libafl_qemu::Qemu,
addr: libafl_qemu::GuestAddr,
) -> Result<(), libafl_qemu::QemuExitError> {
qemu.set_breakpoint(addr);
unsafe { qemu.run() }.unwrap();
qemu.remove_breakpoint(addr);
Ok(())
}

/// Call a function with signature `u64 (*)(void)`
fn call(qemu: &libafl_qemu::Qemu, addr: libafl_qemu::GuestAddr) -> u64 {
// Push return address
let rip: u64 = qemu.read_reg(Regs::Rip).unwrap();
let mut rsp: u64 = qemu.read_reg(Regs::Rsp).unwrap();
rsp -= 8;
qemu.write_mem(rsp, &u64::to_le_bytes(rip)).unwrap();
qemu.write_reg(Regs::Rsp, rsp).unwrap();

// Make call
println!("Calling {addr:#x}");
qemu.write_reg(Regs::Rip, addr).unwrap();
run_to(qemu, rip).unwrap();
let ret = qemu.read_reg::<_, u64>(Regs::Rax).unwrap();
println!("Got {ret:#x}");
ret
}

fn main() -> Result<(), Box> {
const PROG: &str = r#"
int global = 0xdeadbeef;

int get(void) { return global; }
void set(void) { global = 1; }

int main(int argc, char *argv[]) {
set();
return get();
}
"#;
let target = path::PathBuf::from("set");
fs::File::create("set.c")?.write_all(PROG.as_bytes())?;
let out = process::Command::new("clang")
.arg("set.c")
.arg("-O0")
.args(["-o", "set"])
.output()?;
assert!(out.status.success());

let args = &["qemu".to_string(), target.to_string_lossy().to_string()];
let qemu = libafl_qemu::Qemu::init(args)?;
let mut elf_buffer = Vec::new();
let elf = libafl_qemu::elf::EasyElf::from_file(target.as_path(), &mut elf_buffer)?;
let get = elf.resolve_symbol("get", qemu.load_addr()).unwrap();
let set = elf.resolve_symbol("set", qemu.load_addr()).unwrap();
let main = elf.resolve_symbol("main", qemu.load_addr()).unwrap();
println!("get @ {get:#x}");
println!("set @ {set:#x}");
println!("main @ {main:#x}");

// Run to entrypoint
run_to(&qemu, main).unwrap();

// Check initial value
let mut global = call(&qemu, get);
assert_eq!(global, 0xdeadbeef);

let mut snap = SnapshotModule::new();
println!("Taking snapshot");
snap.snapshot(qemu);

// Check that value changes when `set` is called
call(&qemu, set);
global = call(&qemu, get);
assert_eq!(global, 1);

// Check that value is reset by snapshot
println!("Resetting snapshot");
snap.reset(qemu);
global = call(&qemu, get);
assert_eq!(global, 0xdeadbeef); // this fails!

Ok(())
}
```
The last assertion there fails. I believe this is because `SnapshotModule::new` doesn't actually install any sort of memory tracing infrastructure, this only happens [in `init_module`](https://github.com/AFLplusplus/LibAFL/blob/23e966c702e7caf4dc65f4b775cfc0e8985ce072/libafl_qemu/src/modules/usermode/snapshot.rs#L676). I would say that this is a footgun, the above API usage is pretty reasonable looking, but doesn't do at all what one might expect. I think that this module should probably not expose `snapshot` and `reset`, and that these should only be available through the `impl EmulatorModule`.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.