Skip to content

Commit

Permalink
Qemu update to latest version + various fixes (#2119)
Browse files Browse the repository at this point in the history
* Fix maps iterator

* Added paranoid debug feature.

* Fix snapshot bugs.

* Update qemu-libafl-bridge.

* Added auto-generation of `x86_64_stub_bindings.rs` .

* Fix sync_exit calling to stopped CPU.
  • Loading branch information
rmalmain authored May 2, 2024
1 parent 2f7c19e commit 935100e
Show file tree
Hide file tree
Showing 16 changed files with 587 additions and 351 deletions.
1 change: 0 additions & 1 deletion fuzzers/qemu_cmin/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use libafl_bolts::{
use libafl_qemu::{
edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE_IN_USE},
elf::EasyElf,
emu::Emulator,
ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitReason,
QemuExitReasonError, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs,
};
Expand Down
1 change: 1 addition & 0 deletions libafl_qemu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"]
default = ["fork", "build_libgasan", "build_libqasan", "serdeany_autoreg", "injections"]
clippy = [] # special feature for clippy, don't use in normal projects§
document-features = ["dep:document-features"]
paranoid_debug = ["libafl_qemu_sys/paranoid_debug"] # Will perform as many checks as possible. The target will be greatly slowed down.

#! # Feature Flags
#! ### General Features
Expand Down
2 changes: 2 additions & 0 deletions libafl_qemu/libafl_qemu_build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ slirp = [] # build qemu with host libslirp (for user networking)

clippy = [] # special feature for clippy, don't use in normal projects§

paranoid_debug = [] # Will perform as many checks as possible. The target will be greatly slowed down.

[dependencies]
bindgen = "0.69.4"
which = "4.4"
Expand Down
30 changes: 5 additions & 25 deletions libafl_qemu/libafl_qemu_build/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{collections::hash_map, fs, hash::Hasher, io::Read, path::Path};
use std::path::Path;

use bindgen::{BindgenError, Bindings};

use crate::store_generated_content_if_different;

const WRAPPER_HEADER: &str = r#"
// https://github.com/rust-lang/rust-bindgen/issues/2500
Expand Down Expand Up @@ -91,31 +93,8 @@ pub fn generate(
clang_args: Vec<String>,
) -> Result<Bindings, BindgenError> {
let wrapper_h = build_dir.join("wrapper.h");
let existing_wrapper_h = fs::File::open(&wrapper_h);
let mut must_rewrite_wrapper = true;

// Check if equivalent wrapper file already exists without relying on filesystem timestamp.
if let Ok(mut wrapper_file) = existing_wrapper_h {
let mut existing_wrapper_content = Vec::with_capacity(WRAPPER_HEADER.len());
wrapper_file
.read_to_end(existing_wrapper_content.as_mut())
.unwrap();

let mut existing_wrapper_hasher = hash_map::DefaultHasher::new();
existing_wrapper_hasher.write(existing_wrapper_content.as_ref());

let mut wrapper_h_hasher = hash_map::DefaultHasher::new();
wrapper_h_hasher.write(WRAPPER_HEADER.as_bytes());

// Check if wrappers are the same
if existing_wrapper_hasher.finish() == wrapper_h_hasher.finish() {
must_rewrite_wrapper = false;
}
}

if must_rewrite_wrapper {
fs::write(&wrapper_h, WRAPPER_HEADER).expect("Unable to write wrapper.h");
}
store_generated_content_if_different(&wrapper_h, WRAPPER_HEADER.as_bytes());

let bindings = bindgen::Builder::default()
.derive_debug(true)
Expand Down Expand Up @@ -166,6 +145,7 @@ pub fn generate(
.allowlist_function("libafl_.*")
.allowlist_function("read_self_maps")
.allowlist_function("free_self_maps")
.allowlist_function("pageflags_get_root")
.blocklist_function("main_loop_wait") // bindgen issue #1313
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

Expand Down
62 changes: 42 additions & 20 deletions libafl_qemu/libafl_qemu_build/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
env, fs,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};

use which::which;
Expand All @@ -10,7 +11,7 @@ use crate::cargo_add_rpath;

const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "c9519ee8b6cb1ba54b7df1001f7f39f07218d514";
const QEMU_REVISION: &str = "538e6b02c36838e0de469b39dd03fef05678444f";

#[allow(clippy::module_name_repetitions)]
pub struct BuildResult {
Expand Down Expand Up @@ -70,28 +71,32 @@ fn configure_qemu(
) -> Command {
let mut cmd = Command::new("./configure");

let linker_interceptor = qemu_path.join("linker_interceptor.py");
let linker_interceptor_plus_plus = qemu_path.join("linker_interceptor++.py");

println!("cargo:rerun-if-changed={}", linker_interceptor.display());
println!(
"cargo:rerun-if-changed={}",
linker_interceptor_plus_plus.display()
);

// Set common options for usermode and systemmode
cmd.current_dir(qemu_path)
.env("__LIBAFL_QEMU_CONFIGURE", "")
.env("__LIBAFL_QEMU_BUILD_OUT", build_dir.join("linkinfo.json"))
.env("__LIBAFL_QEMU_BUILD_CC", cc_compiler.path())
.env("__LIBAFL_QEMU_BUILD_CXX", cpp_compiler.path())
.arg(&format!(
"--cc={}",
qemu_path.join("linker_interceptor.py").display()
))
.arg(&format!(
"--cxx={}",
qemu_path.join("linker_interceptor++.py").display()
))
.arg(&format!("--cc={}", linker_interceptor.display()))
.arg(&format!("--cxx={}", linker_interceptor_plus_plus.display()))
.arg("--as-shared-lib")
.arg(&format!("--target-list={cpu_target}-{target_suffix}"))
.arg("--disable-bsd-user")
.arg("--disable-capstone");
// .arg("--disable-capstone")
.arg("--disable-bsd-user");

if cfg!(debug_assertions) {
// cmd.arg("--enable-debug");
// .arg("--enable-debug-tcg");
if cfg!(feature = "paranoid_debug") {
cmd.arg("--enable-debug")
.arg("--enable-debug-tcg")
.arg("--enable-sanitizers");
}

if is_usermode {
Expand Down Expand Up @@ -273,6 +278,7 @@ pub fn build(

println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_DIR");
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_CLONE_DIR");
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_FORCE_BUILD");
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_FORCE_CONFIGURE");
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_NO_BUILD");

Expand Down Expand Up @@ -449,6 +455,23 @@ pub fn build(
}
*/

let compile_commands_string = &fs::read_to_string(libafl_qemu_build_dir.join("linkinfo.json"))
.expect("Failed to read linkinfo.json");

let linkinfo = json::parse(compile_commands_string).expect("Failed to parse linkinfo.json");

for source in linkinfo["sources"].members() {
let source_path = PathBuf::from_str(source.as_str().unwrap()).unwrap();

let source_path = if source_path.is_relative() {
libafl_qemu_build_dir.join(source_path)
} else {
source_path
};

println!("cargo:rerun-if-changed={}", source_path.display());
}

if cfg!(feature = "shared") {
let qemu_build_dir_str = libafl_qemu_build_dir
.to_str()
Expand All @@ -457,12 +480,6 @@ pub fn build(
println!("cargo:rustc-link-lib=dylib={output_lib_link}");
cargo_add_rpath(qemu_build_dir_str);
} else {
let compile_commands_string =
&fs::read_to_string(libafl_qemu_build_dir.join("linkinfo.json"))
.expect("Failed to read linkinfo.json");

let linkinfo = json::parse(compile_commands_string).expect("Failed to parse linkinfo.json");

let mut cmd = vec![];
for arg in linkinfo["cmd"].members() {
cmd.push(
Expand Down Expand Up @@ -619,6 +636,11 @@ pub fn build(
}
}

if cfg!(feature = "paranoid_debug") {
println!("cargo:rustc-link-lib=ubsan");
println!("cargo:rustc-link-lib=asan");
}

/*
println!("cargo:rustc-link-lib=rt");
println!("cargo:rustc-link-lib=gmodule-2.0");
Expand Down
47 changes: 47 additions & 0 deletions libafl_qemu/libafl_qemu_build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#![allow(clippy::missing_panics_doc)]
use std::{
collections::hash_map,
env, fs,
fs::File,
hash::Hasher,
io::{Read, Seek, SeekFrom, Write},
path::{Path, PathBuf},
process::Command,
ptr::addr_of_mut,
Expand Down Expand Up @@ -243,3 +247,46 @@ fn include_path(build_dir: &Path, path: &str) -> String {
build_dir.join(include_path).display().to_string()
}
}

pub fn store_generated_content_if_different(file_to_update: &PathBuf, fresh_content: &[u8]) {
let mut must_rewrite_file = true;

// Check if equivalent file already exists without relying on filesystem timestamp.
let mut file_to_check =
if let Ok(mut wrapper_file) = File::options().read(true).write(true).open(file_to_update) {
let mut existing_file_content = Vec::with_capacity(fresh_content.len());
wrapper_file
.read_to_end(existing_file_content.as_mut())
.unwrap();

let mut existing_wrapper_hasher = hash_map::DefaultHasher::new();
existing_wrapper_hasher.write(existing_file_content.as_ref());

let mut wrapper_h_hasher = hash_map::DefaultHasher::new();
wrapper_h_hasher.write(fresh_content);

// Check if wrappers are the same
if existing_wrapper_hasher.finish() == wrapper_h_hasher.finish() {
must_rewrite_file = false;
}

// Reset file cursor if it's going to be rewritten
if must_rewrite_file {
wrapper_file.set_len(0).expect("Could not set file len");
wrapper_file
.seek(SeekFrom::Start(0))
.expect("Could not seek file to beginning");
}

wrapper_file
} else {
File::create(file_to_update)
.unwrap_or_else(|_| panic!("Could not create {}", file_to_update.display()))
};

if must_rewrite_file {
file_to_check
.write_all(fresh_content)
.unwrap_or_else(|_| panic!("Unable to write in {}", file_to_update.display()));
}
}
3 changes: 3 additions & 0 deletions libafl_qemu/libafl_qemu_sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ shared = [ "libafl_qemu_build/shared" ]

clippy = [ "libafl_qemu_build/clippy" ] # special feature for clippy, don't use in normal projects

paranoid_debug = ["libafl_qemu_build/paranoid_debug"] # Will perform as many checks as possible. The target will be greatly slowed down.

[dependencies]
paste = "1"
num_enum = "0.7"
Expand All @@ -49,3 +51,4 @@ pyo3 = { version = "0.18", optional = true }
[build-dependencies]
libafl_qemu_build = { path = "../libafl_qemu_build", version = "0.12.0" }
pyo3-build-config = { version = "0.18", optional = true }
rustversion = "1.0"
32 changes: 29 additions & 3 deletions libafl_qemu/libafl_qemu_sys/build_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use std::{env, fs::copy, path::PathBuf};

use libafl_qemu_build::build_with_bindings;

#[rustversion::nightly]
use std::fs;
#[rustversion::nightly]
use libafl_qemu_build::store_generated_content_if_different;

#[macro_export]
macro_rules! assert_unique_feature {
() => {};
Expand All @@ -14,6 +19,18 @@ macro_rules! assert_unique_feature {
}
}

#[rustversion::nightly]
fn maybe_generate_stub_bindings(cpu_target: &str, emulation_mode: &str, stub_bindings_file: &PathBuf, bindings_file: &PathBuf) {
if cpu_target == "x86_64" && emulation_mode == "usermode" {
store_generated_content_if_different(stub_bindings_file, fs::read(bindings_file).expect("Could not read generated bindings file").as_slice());
}
}

#[rustversion::not(nightly)]
fn maybe_generate_stub_bindings(_cpu_target: &str, _emulation_mode: &str, _stub_bindings_file: &PathBuf, _bindings_file: &PathBuf) {
// Do nothing
}

pub fn build() {
// Make sure that exactly one qemu mode is set
assert_unique_feature!("usermode", "systemmode");
Expand Down Expand Up @@ -73,10 +90,14 @@ pub fn build() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = PathBuf::from(out_dir);
let bindings_file = out_dir.join("bindings.rs");

let src_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let src_dir = PathBuf::from(src_dir);
let stub_bindings_file = src_dir.join("src/x86_64_stub_bindings.rs");

if std::env::var("DOCS_RS").is_ok() || cfg!(feature = "clippy") {
if env::var("DOCS_RS").is_ok() || cfg!(feature = "clippy") {
// Only build when we're not generating docs and not in clippy
copy("src/x86_64_stub_bindings.rs", bindings_file).expect("Failed to copy the bindings stub");
copy(stub_bindings_file, bindings_file).expect("Failed to copy the bindings stub");
return;
}

Expand All @@ -87,4 +108,9 @@ pub fn build() {
jobs,
&bindings_file,
);
}

println!("cargo:rerun-if-changed={}", stub_bindings_file.display());

// If the bindings are built and differ from the current stub, replace it with the freshly generated bindings
maybe_generate_stub_bindings(&cpu_target, &emulation_mode, &stub_bindings_file, &bindings_file);
}
1 change: 1 addition & 0 deletions libafl_qemu/libafl_qemu_sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod bindings {
pub use bindings::*;

#[cfg(any(feature = "clippy", not(target_os = "linux")))]
#[rustfmt::skip]
mod x86_64_stub_bindings;

#[cfg(emulation_mode = "usermode")]
Expand Down
Loading

0 comments on commit 935100e

Please sign in to comment.