AFLplusplus / AFLplusplus/LibAFL

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

Abierto
#2,621 1 comentario 3 reacciones 1 asignado Reclamado por @rmalmain Ver en GitHub
bug qemu
Lenguaje dominante
Rust
Estrellas
2.6k
Forks
481
Merge medio
2 d 30 min
PR fusionados (30 d)
16

Descripción

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`.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.