diff --git a/fuzzers/qemu_systemmode/Cargo.toml b/fuzzers/qemu_systemmode/Cargo.toml index 3319c7669b..1517816928 100644 --- a/fuzzers/qemu_systemmode/Cargo.toml +++ b/fuzzers/qemu_systemmode/Cargo.toml @@ -12,6 +12,8 @@ classic = [] # The classic way to interact with LibAFL QEMU, with direct calls t breakpoint = [] # Uses the command system, with breakpoints sync_exit = [] # Uses the command system, with sync exit. +shared = ["libafl_qemu/shared"] + [profile.release] incremental = true debug = true @@ -24,3 +26,6 @@ libafl_bolts = { path = "../../libafl_bolts/" } libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] } libafl_qemu_sys = { path = "../../libafl_qemu/libafl_qemu_sys", features = ["arm", "systemmode"] } env_logger = "*" + +[build-dependencies] +libafl_qemu_build = { path = "../../libafl_qemu/libafl_qemu_build" } diff --git a/fuzzers/qemu_systemmode/Makefile.toml b/fuzzers/qemu_systemmode/Makefile.toml index 49ed85fcc6..fe261e1507 100644 --- a/fuzzers/qemu_systemmode/Makefile.toml +++ b/fuzzers/qemu_systemmode/Makefile.toml @@ -45,7 +45,6 @@ args = [ "--no-default-features", "--features", "std,${FEATURE}", "--target-dir", "${TARGET_DIR}", - "-vv", ] dependencies = ["image"] diff --git a/fuzzers/qemu_systemmode/build.rs b/fuzzers/qemu_systemmode/build.rs index c7d7735ddc..9f7f8443ac 100644 --- a/fuzzers/qemu_systemmode/build.rs +++ b/fuzzers/qemu_systemmode/build.rs @@ -1,3 +1,5 @@ +use libafl_qemu_build::build_libafl_qemu; + #[macro_export] macro_rules! assert_unique_feature { () => {}; @@ -12,4 +14,6 @@ macro_rules! assert_unique_feature { fn main() { assert_unique_feature!("classic", "breakpoint", "sync_exit"); + + build_libafl_qemu(); } diff --git a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs index 4ad5ead1a3..b74c33387f 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs @@ -13,7 +13,7 @@ use libafl::{ inputs::BytesInput, monitors::MultiMonitor, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, - observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver}, + observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, stages::{CalibrationStage, StdMutationalStage}, state::{HasCorpus, StdState}, diff --git a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs index 3ca47e2784..5ec687fc03 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs @@ -13,7 +13,7 @@ use libafl::{ inputs::{BytesInput, HasTargetBytes}, monitors::MultiMonitor, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, - observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver}, + observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, stages::StdMutationalStage, state::{HasCorpus, StdState}, diff --git a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs index 2eaec74103..88ee2355e7 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs @@ -13,7 +13,7 @@ use libafl::{ inputs::BytesInput, monitors::MultiMonitor, mutators::scheduled::{havoc_mutations, StdScheduledMutator}, - observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver}, + observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, stages::{CalibrationStage, StdMutationalStage}, state::{HasCorpus, StdState}, diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index d07de3a1d3..ba3390a6b3 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -58,8 +58,8 @@ serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking) -# disabled atm, enabled when fixed with dynamic list -# shared = [ "libafl_qemu_sys/shared" ] +# Requires the binary's build.rs to call `build_libafl_qemu` +shared = [ "libafl_qemu_sys/shared" ] [dependencies] libafl = { path = "../libafl", version = "0.12.0", default-features = false, features = ["std", "derive", "regex"] } diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 44af50079f..378eea1915 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -6,9 +6,11 @@ use std::{ use which::which; +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 = "50b0c90e0aab07643ccb58cfbbef742bcfb8b7d1"; +const QEMU_REVISION: &str = "c9519ee8b6cb1ba54b7df1001f7f39f07218d514"; #[allow(clippy::module_name_repetitions)] pub struct BuildResult { @@ -22,6 +24,220 @@ fn build_dep_check(tools: &[&str]) { } } +fn get_config_signature(config_cmd: &Command) -> String { + let mut signature_string = String::new(); + + // Command env + let config_env: String = config_cmd + .get_envs() + .map(|(key, value)| { + format!( + "\t{}={}", + key.to_str().expect("Couldn't convert OsStr to str"), + if let Some(v) = value { + v.to_str().expect("Could't convert OsStr to str") + } else { + "" + } + ) + }) + .reduce(|acc, elt| format!("{acc}\n{elt}")) + .into_iter() + .collect(); + signature_string += format!("Environment:\n{config_env}").as_str(); + + // Command args + let config_args: String = config_cmd + .get_args() + .map(|arg| format!("\t{}", arg.to_str().expect("Couldn't convert OsStr to str"))) + .reduce(|acc, arg| format!("{acc}\n{arg}")) + .into_iter() + .collect(); + signature_string += format!("\n\nArguments:\n{config_args}").as_str(); + + signature_string +} + +#[allow(clippy::too_many_lines)] +fn configure_qemu( + cc_compiler: &cc::Tool, + cpp_compiler: &cc::Tool, + qemu_path: &PathBuf, + build_dir: &Path, + is_usermode: bool, + cpu_target: &String, + target_suffix: &String, +) -> Command { + let mut cmd = Command::new("./configure"); + + // 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("--as-shared-lib") + .arg(&format!("--target-list={cpu_target}-{target_suffix}")) + .arg("--disable-bsd-user") + .arg("--disable-capstone"); + + if cfg!(debug_assertions) { + // cmd.arg("--enable-debug"); + // .arg("--enable-debug-tcg"); + } + + if is_usermode { + // Usermode options + cmd.args(["--disable-fdt", "--disable-system"]); + } else { + // Systemmode options + cmd.arg(if cfg!(feature = "slirp") { + "--enable-slirp" + } else { + "--disable-slirp" + }) + .arg("--enable-fdt=internal") + .arg("--audio-drv-list=") + .arg("--disable-af-xdp") + .arg("--disable-alsa") + .arg("--disable-attr") + .arg("--disable-auth-pam") + .arg("--disable-dbus-display") + .arg("--disable-bochs") + .arg("--disable-bpf") + .arg("--disable-brlapi") + .arg("--disable-bzip2") + .arg("--disable-cap-ng") + .arg("--disable-canokey") + .arg("--disable-cloop") + .arg("--disable-cocoa") + .arg("--disable-coreaudio") + .arg("--disable-curl") + .arg("--disable-curses") + .arg("--disable-dmg") + .arg("--disable-docs") + .arg("--disable-dsound") + .arg("--disable-fuse") + .arg("--disable-fuse-lseek") + .arg("--disable-gcrypt") + .arg("--disable-gettext") + .arg("--disable-gio") + .arg("--disable-glusterfs") + .arg("--disable-gnutls") + .arg("--disable-gtk") + .arg("--disable-guest-agent") + .arg("--disable-guest-agent-msi") + .arg("--disable-hvf") + .arg("--disable-iconv") + .arg("--disable-jack") + .arg("--disable-keyring") + .arg("--disable-kvm") + .arg("--disable-libdaxctl") + .arg("--disable-libiscsi") + .arg("--disable-libnfs") + .arg("--disable-libpmem") + .arg("--disable-libssh") + .arg("--disable-libudev") + .arg("--disable-libusb") + .arg("--disable-linux-aio") + .arg("--disable-linux-io-uring") + .arg("--disable-linux-user") + .arg("--disable-live-block-migration") + .arg("--disable-lzfse") + .arg("--disable-lzo") + .arg("--disable-l2tpv3") + .arg("--disable-malloc-trim") + .arg("--disable-mpath") + .arg("--disable-multiprocess") + .arg("--disable-netmap") + .arg("--disable-nettle") + .arg("--disable-numa") + .arg("--disable-nvmm") + .arg("--disable-opengl") + .arg("--disable-oss") + .arg("--disable-pa") + .arg("--disable-parallels") + .arg("--disable-png") + .arg("--disable-pvrdma") + .arg("--disable-qcow1") + .arg("--disable-qed") + .arg("--disable-qga-vss") + .arg("--disable-rbd") + .arg("--disable-rdma") + .arg("--disable-replication") + .arg("--disable-sdl") + .arg("--disable-sdl-image") + .arg("--disable-seccomp") + .arg("--disable-selinux") + .arg("--disable-slirp-smbd") + .arg("--disable-smartcard") + .arg("--disable-snappy") + .arg("--disable-sndio") + .arg("--disable-sparse") + .arg("--disable-spice") + .arg("--disable-spice-protocol") + .arg("--disable-tools") + .arg("--disable-tpm") + .arg("--disable-usb-redir") + .arg("--disable-user") + .arg("--disable-u2f") + .arg("--disable-vde") + .arg("--disable-vdi") + .arg("--disable-vduse-blk-export") + .arg("--disable-vhost-crypto") + .arg("--disable-vhost-kernel") + .arg("--disable-vhost-net") + .arg("--disable-vhost-user-blk-server") + .arg("--disable-vhost-vdpa") + .arg("--disable-virglrenderer") + .arg("--disable-virtfs") + .arg("--disable-vmnet") + .arg("--disable-vnc") + .arg("--disable-vnc-jpeg") + .arg("--disable-vnc-sasl") + .arg("--disable-vte") + .arg("--disable-vvfat") + .arg("--disable-whpx") + .arg("--disable-xen") + .arg("--disable-xen-pci-passthrough") + .arg("--disable-xkbcommon") + .arg("--disable-zstd") + .arg("--disable-tests"); + } + + cmd +} + +fn build_qemu( + cc_compiler: &cc::Tool, + cpp_compiler: &cc::Tool, + build_dir: &PathBuf, + jobs: Option, +) -> Command { + let mut cmd = Command::new("make"); + + cmd.current_dir(build_dir) + .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("-j"); + + if let Some(j) = jobs { + cmd.arg(&format!("{j}")).env("V", "1"); + } + + cmd +} + #[allow(clippy::too_many_lines, clippy::missing_panics_doc)] #[must_use] pub fn build( @@ -32,7 +248,7 @@ pub fn build( ) -> BuildResult { let mut cpu_target = cpu_target.to_string(); // qemu-system-arm supports both big and little endian configurations and so - // therefore the "be" feature should ignored in this configuration. Also + // the "be" feature should be ignored in this configuration. Also // ignore the feature if we are running in clippy which enables all the // features at once (disabling the check for mutually exclusive options) // resulting in cpu_target being set to 'x86_64' above which obviously @@ -49,12 +265,13 @@ pub fn build( cpu_target += "el"; } - let custom_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); - let custom_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok(); - let custom_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok(); - println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_DIR"); - println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_BUILD"); - println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_CONFIGURE"); + let libafl_qemu_dir = env::var_os("LIBAFL_QEMU_DIR").map(|x| x.to_string_lossy().to_string()); + let libafl_qemu_force_configure = env::var("LIBAFL_QEMU_FORCE_CONFIGURE").is_ok(); + let libafl_qemu_no_build = env::var("LIBAFL_QEMU_NO_BUILD").is_ok(); + + println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_DIR"); + println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_FORCE_CONFIGURE"); + println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_NO_BUILD"); let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = out_dir.to_string_lossy().to_string(); @@ -69,7 +286,7 @@ pub fn build( let cc_compiler = cc::Build::new().cpp(false).get_compiler(); let cpp_compiler = cc::Build::new().cpp(true).get_compiler(); - let qemu_path = if let Some(qemu_dir) = custom_qemu_dir.as_ref() { + let qemu_path = if let Some(qemu_dir) = libafl_qemu_dir.as_ref() { Path::new(&qemu_dir).to_path_buf() } else { let qemu_path = target_dir.join(QEMU_DIRNAME); @@ -122,7 +339,8 @@ pub fn build( qemu_path }; - let build_dir = qemu_path.join("build"); + let qemu_build_dir = qemu_path.join("build"); + let config_signature_path = qemu_build_dir.join("libafl_config"); let target_suffix = if is_usermode { "linux-user".to_string() @@ -132,188 +350,71 @@ pub fn build( let (output_lib, output_lib_link) = if is_usermode { ( - build_dir.join(format!("libqemu-{cpu_target}.so")), + qemu_build_dir.join(format!("libqemu-{cpu_target}.so")), format!("qemu-{cpu_target}"), ) } else { ( - build_dir.join(format!("libqemu-system-{cpu_target}.so")), + qemu_build_dir.join(format!("libqemu-system-{cpu_target}.so")), format!("qemu-system-{cpu_target}"), ) }; - // println!("cargo:rerun-if-changed={}", output_lib.to_string_lossy()); + let libafl_config_old_signature = fs::read_to_string(&config_signature_path); - if !output_lib.is_file() || (custom_qemu_dir.is_some() && !custom_qemu_no_build) { - /*drop( - Command::new("make") - .current_dir(&qemu_path) - .arg("distclean") - .status(), - );*/ - let mut cmd = Command::new("./configure"); - cmd.current_dir(&qemu_path) - //.arg("--as-static-lib") - .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("--as-shared-lib") - .arg(&format!("--target-list={cpu_target}-{target_suffix}")); - if cfg!(feature = "debug_assertions") { - cmd.arg("--enable-debug"); - } - if is_usermode && !custom_qemu_no_configure { - cmd.args([ - "--disable-bsd-user", - "--disable-fdt", - "--disable-system", - "--disable-capstone", - ]); - } else if !custom_qemu_no_configure { - cmd.arg(if cfg!(feature = "slirp") { - "--enable-slirp" - } else { - "--disable-slirp" - }) - .arg("--enable-fdt=internal") - .arg("--audio-drv-list=") - .arg("--disable-af-xdp") - .arg("--disable-alsa") - .arg("--disable-attr") - .arg("--disable-auth-pam") - .arg("--disable-dbus-display") - .arg("--disable-bochs") - .arg("--disable-bpf") - .arg("--disable-brlapi") - .arg("--disable-bsd-user") - .arg("--disable-bzip2") - .arg("--disable-capstone") - .arg("--disable-cap-ng") - .arg("--disable-canokey") - .arg("--disable-cloop") - .arg("--disable-cocoa") - .arg("--disable-coreaudio") - .arg("--disable-curl") - .arg("--disable-curses") - .arg("--disable-dmg") - .arg("--disable-docs") - .arg("--disable-dsound") - .arg("--disable-fuse") - .arg("--disable-fuse-lseek") - .arg("--disable-gcrypt") - .arg("--disable-gettext") - .arg("--disable-gio") - .arg("--disable-glusterfs") - .arg("--disable-gnutls") - .arg("--disable-gtk") - .arg("--disable-guest-agent") - .arg("--disable-guest-agent-msi") - .arg("--disable-hvf") - .arg("--disable-iconv") - .arg("--disable-jack") - .arg("--disable-keyring") - .arg("--disable-kvm") - .arg("--disable-libdaxctl") - .arg("--disable-libiscsi") - .arg("--disable-libnfs") - .arg("--disable-libpmem") - .arg("--disable-libssh") - .arg("--disable-libudev") - .arg("--disable-libusb") - .arg("--disable-linux-aio") - .arg("--disable-linux-io-uring") - .arg("--disable-linux-user") - .arg("--disable-live-block-migration") - .arg("--disable-lzfse") - .arg("--disable-lzo") - .arg("--disable-l2tpv3") - .arg("--disable-malloc-trim") - .arg("--disable-mpath") - .arg("--disable-multiprocess") - .arg("--disable-netmap") - .arg("--disable-nettle") - .arg("--disable-numa") - .arg("--disable-nvmm") - .arg("--disable-opengl") - .arg("--disable-oss") - .arg("--disable-pa") - .arg("--disable-parallels") - .arg("--disable-png") - .arg("--disable-pvrdma") - .arg("--disable-qcow1") - .arg("--disable-qed") - .arg("--disable-qga-vss") - .arg("--disable-rbd") - .arg("--disable-rdma") - .arg("--disable-replication") - .arg("--disable-sdl") - .arg("--disable-sdl-image") - .arg("--disable-seccomp") - .arg("--disable-selinux") - .arg("--disable-slirp-smbd") - .arg("--disable-smartcard") - .arg("--disable-snappy") - .arg("--disable-sndio") - .arg("--disable-sparse") - .arg("--disable-spice") - .arg("--disable-spice-protocol") - .arg("--disable-tools") - .arg("--disable-tpm") - .arg("--disable-usb-redir") - .arg("--disable-user") - .arg("--disable-u2f") - .arg("--disable-vde") - .arg("--disable-vdi") - .arg("--disable-vduse-blk-export") - .arg("--disable-vhost-crypto") - .arg("--disable-vhost-kernel") - .arg("--disable-vhost-net") - .arg("--disable-vhost-user-blk-server") - .arg("--disable-vhost-vdpa") - .arg("--disable-virglrenderer") - .arg("--disable-virtfs") - .arg("--disable-vmnet") - .arg("--disable-vnc") - .arg("--disable-vnc-jpeg") - .arg("--disable-vnc-sasl") - .arg("--disable-vte") - .arg("--disable-vvfat") - .arg("--disable-whpx") - .arg("--disable-xen") - .arg("--disable-xen-pci-passthrough") - .arg("--disable-xkbcommon") - .arg("--disable-zstd") - .arg("--disable-tests"); - } + let mut config_cmd = configure_qemu( + &cc_compiler, + &cpp_compiler, + &qemu_path, + &qemu_build_dir, + is_usermode, + &cpu_target, + &target_suffix, + ); + let current_config_signature = get_config_signature(&config_cmd); + let must_reconfigure = if libafl_qemu_force_configure { + // If the user asked to reconfigure, do so + true + } else if let Ok(libafl_config_old_signature) = libafl_config_old_signature { + if libafl_config_old_signature == current_config_signature { + // Signature match, do not reconfigure + false + } else { + println!("cargo:warning=QEMU configuration is outdated. Reconfiguring..."); + true + } + } else { + // In worst scenario, reconfigure + true + }; + + if must_reconfigure { assert!( - cmd.status().expect("Invoking Configure failed").success(), + config_cmd + .status() + .expect("Invoking Configure failed") + .success(), "Configure didn't finish successfully" ); - let mut cmd = Command::new("make"); - cmd.current_dir(&build_dir) - .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("-j"); - if let Some(j) = jobs { - cmd.arg(&format!("{j}")).env("V", "1"); - } + // Config succeeded at this point, (over)write the signature file + fs::write(config_signature_path, current_config_signature) + .expect("Couldn't write config signature file."); + } + + // Always build by default, make will detect if it is necessary to rebuild qemu + if !libafl_qemu_no_build { + let mut build_cmd = build_qemu(&cc_compiler, &cpp_compiler, &qemu_build_dir, jobs); + assert!( - cmd.status().expect("Invoking Make Failed").success(), + build_cmd.status().expect("Invoking Make Failed").success(), "Make didn't finish successfully" ); } + assert!(output_lib.is_file()); // Sanity check + /* let mut objects = vec![]; for dir in &[ @@ -338,13 +439,12 @@ pub fn build( */ if cfg!(feature = "shared") { - println!( - "cargo:rustc-link-search=native={}", - build_dir.to_string_lossy() - ); + let qemu_build_dir_str = qemu_build_dir.to_str().expect("Could not convert to str"); + println!("cargo:rustc-link-search=native={qemu_build_dir_str}"); 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(build_dir.join("linkinfo.json")) + let compile_commands_string = &fs::read_to_string(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"); @@ -357,16 +457,27 @@ pub fn build( ); } - assert!(cpp_compiler - .to_command() - .current_dir(&build_dir) + let mut link_command = cpp_compiler.to_command(); + + link_command + .current_dir(&qemu_build_dir) .arg("-o") .arg("libqemu-partially-linked.o") .arg("-r") - .args(cmd) - .status() - .expect("Partial linked failure") - .success()); + .args(cmd); + + let link_str = format!("{link_command:?}"); + + let output = link_command.output().expect("Partial linked failure"); + + if !output.status.success() { + fs::write(qemu_build_dir.join("link.command"), link_str).expect("Link command failed."); + fs::write(qemu_build_dir.join("link.stdout"), &output.stdout) + .expect("Link stdout failed."); + fs::write(qemu_build_dir.join("link.stderr"), &output.stderr) + .expect("Link stderr failed."); + panic!("Linking failed."); + } /* // Old manual linking, kept here for reference and debugging if is_usermode { @@ -456,7 +567,7 @@ pub fn build( .current_dir(out_dir_path) .arg("crs") .arg("libqemu-partially-linked.a") - .arg(build_dir.join("libqemu-partially-linked.o")) + .arg(qemu_build_dir.join("libqemu-partially-linked.o")) .status() .expect("Ar creation"); @@ -476,6 +587,21 @@ pub fn build( .expect("linkinfo.json `libs` values must be strings"); println!("cargo:rustc-link-lib={val}"); } + + for arg in linkinfo["rpath"].members() { + let val = arg + .as_str() + .expect("linkinfo.json `libs` values must be strings") + .to_string() + .replace( + "$ORIGIN", + qemu_build_dir + .as_os_str() + .to_str() + .expect("Could not convert OsStr to str"), + ); + cargo_add_rpath(&val); + } } /* @@ -497,7 +623,7 @@ pub fn build( //} fs::create_dir_all(target_dir.join("pc-bios")).unwrap(); - for path in fs::read_dir(build_dir.join("pc-bios")).unwrap() { + for path in fs::read_dir(qemu_build_dir.join("pc-bios")).unwrap() { let path = path.unwrap().path(); if path.is_file() { if let Some(name) = path.file_name() { @@ -510,6 +636,6 @@ pub fn build( BuildResult { qemu_path, - build_dir, + build_dir: qemu_build_dir, } } diff --git a/libafl_qemu/libafl_qemu_build/src/lib.rs b/libafl_qemu/libafl_qemu_build/src/lib.rs index c112adf678..7643c11d99 100644 --- a/libafl_qemu/libafl_qemu_build/src/lib.rs +++ b/libafl_qemu/libafl_qemu_build/src/lib.rs @@ -3,6 +3,7 @@ use std::{ env, fs, path::{Path, PathBuf}, process::Command, + ptr::addr_of_mut, }; use regex::Regex; @@ -15,6 +16,43 @@ pub use build::build; const LLVM_VERSION_MAX: i32 = 33; +static mut CARGO_RPATH: Option> = None; +static CARGO_RPATH_SEPARATOR: &str = "|"; + +pub fn cargo_add_rpath(rpath: &str) { + unsafe { + if let Some(rpaths) = &mut *addr_of_mut!(CARGO_RPATH) { + rpaths.push(rpath.to_string()); + } else { + CARGO_RPATH = Some(vec![rpath.to_string()]); + } + } +} + +pub fn cargo_propagate_rpath() { + unsafe { + if let Some(cargo_cmds) = &mut *addr_of_mut!(CARGO_RPATH) { + let rpath = cargo_cmds.join(CARGO_RPATH_SEPARATOR); + println!("cargo:rpath={rpath}"); + } + } +} + +/// Must be called from final binary crates +pub fn build_libafl_qemu() { + // Add rpath if there are some + if let Some(rpaths) = env::var_os("DEP_QEMU_RPATH") { + let rpaths: Vec<&str> = rpaths + .to_str() + .expect("Cannot convert OsString to str") + .split(CARGO_RPATH_SEPARATOR) + .collect(); + for rpath in rpaths { + println!("cargo:rustc-link-arg-bins=-Wl,-rpath,{rpath}"); + } + } +} + pub fn build_with_bindings( cpu_target: &str, is_big_endian: bool, @@ -42,6 +80,8 @@ pub fn build_with_bindings( let re = Regex::new("(Option<\\s*)unsafe( extern \"C\" fn\\(data: u64)").unwrap(); let replaced = re.replace_all(&contents, "$1$2"); fs::write(bindings_file, replaced.as_bytes()).expect("Unable to write file"); + + cargo_propagate_rpath(); } // For bindgen, the llvm version must be >= of the rust llvm version diff --git a/libafl_qemu/libafl_qemu_sys/Cargo.toml b/libafl_qemu/libafl_qemu_sys/Cargo.toml index d83c9c71e3..ad812715a0 100644 --- a/libafl_qemu/libafl_qemu_sys/Cargo.toml +++ b/libafl_qemu/libafl_qemu_sys/Cargo.toml @@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0" keywords = ["fuzzing", "qemu", "instrumentation"] edition = "2021" categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"] +links = "qemu" [package.metadata.docs.rs] features = ["x86_64", "usermode"]