From 31f466979444bce2aa920d05e2bf0be0af96b4b5 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Tue, 10 Oct 2023 15:26:32 +0200 Subject: [PATCH] Autodetect llvm-config for QEMU bindings generation (#1610) * Autodetect llvm-config for QEMU bindings generation * fix ci * Fix signal handlers without ucontext pointer * ci --- .github/workflows/build_and_test.yml | 6 +- libafl/src/events/mod.rs | 6 +- libafl/src/executors/inprocess.rs | 48 +++++++++----- libafl_bolts/src/llmp.rs | 7 ++- libafl_bolts/src/minibsod.rs | 12 ++-- libafl_bolts/src/os/unix_signals.rs | 4 +- libafl_qemu/libafl_qemu_build/src/lib.rs | 79 +++++++++++++++++++++++- 7 files changed, 133 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5b4cf42be5..29fda2c9ab 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -145,7 +145,7 @@ jobs: # Skipping `python` as it has to be built with the `maturin` tool # `agpl`, `nautilus` require nightly # `sancov_pcguard_edges` is tested seperately - run: cargo hack check --each-feature --clean-per-run --exclude-features=prelude,agpl,nautilus,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode --no-dev-deps + run: LLVM_CONFIG=llvm-config-15 cargo hack check --each-feature --clean-per-run --exclude-features=prelude,agpl,nautilus,python,sancov_pcguard_edges,arm,aarch64,i386,be,systemmode --no-dev-deps - name: Check nightly features run: cargo +nightly check --features=agpl && cargo +nightly check --features=nautilus @@ -179,7 +179,7 @@ jobs: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 - name: Run a maturin build - run: LLVM_CONFIG_PATH=llvm-config-15 cd ./bindings/pylibafl && python3 -m venv .env && . .env/bin/activate && pip install --upgrade --force-reinstall . && ./test.sh + run: export LLVM_CONFIG=llvm-config-15 && cd ./bindings/pylibafl && python3 -m venv .env && . .env/bin/activate && pip install --upgrade --force-reinstall . && ./test.sh - name: Run python test run: . ./bindings/pylibafl/.env/bin/activate && cd ./fuzzers/baby_fuzzer && python3 baby_fuzzer.py 2>&1 | grep "Bye" @@ -270,7 +270,7 @@ jobs: run: sudo ln -s /usr/include/asm-generic /usr/include/asm - name: Build and run example fuzzers (Linux) if: runner.os == 'Linux' - run: RUN_ON_CI=1 ./scripts/test_all_fuzzers.sh + run: RUN_ON_CI=1 LLVM_CONFIG=llvm-config-15 ./scripts/test_all_fuzzers.sh - name: Build and run example fuzzers (macOS) if: runner.os == 'macOS' # use bash v4 run: /usr/local/bin/bash -c 'RUN_ON_CI=1 ./scripts/test_all_fuzzers.sh' diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 67b3781a81..88b310f64b 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -68,7 +68,7 @@ pub struct ShutdownSignalData { /// Type for shutdown handler #[cfg(all(unix, feature = "std"))] pub type ShutdownFuncPtr = - unsafe fn(Signal, &mut siginfo_t, &mut ucontext_t, data: &mut ShutdownSignalData); + unsafe fn(Signal, &mut siginfo_t, Option<&mut ucontext_t>, data: &mut ShutdownSignalData); /// Shutdown handler. `SigTerm`, `SigInterrupt`, `SigQuit` call this /// We can't handle SIGKILL in the signal handler, this means that you shouldn't kill your fuzzer with `kill -9` because then the shmem segments are never freed @@ -80,7 +80,7 @@ pub type ShutdownFuncPtr = pub unsafe fn shutdown_handler( signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, data: &ShutdownSignalData, ) where SP: ShMemProvider, @@ -106,7 +106,7 @@ pub unsafe fn shutdown_handler( #[cfg(all(unix, feature = "std"))] impl Handler for ShutdownSignalData { - fn handle(&mut self, signal: Signal, info: &mut siginfo_t, context: &mut ucontext_t) { + fn handle(&mut self, signal: Signal, info: &mut siginfo_t, context: Option<&mut ucontext_t>) { unsafe { let data = &mut SHUTDOWN_SIGHANDLER_DATA; if !data.shutdown_handler.is_null() { diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 9b961e1c85..d5ad6f2f31 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -659,21 +659,30 @@ pub mod unix_signal_handler { state::{HasClientPerfMonitor, HasCorpus, HasSolutions}, }; - pub(crate) type HandlerFuncPtr = - unsafe fn(Signal, &mut siginfo_t, &mut ucontext_t, data: &mut InProcessExecutorHandlerData); + pub(crate) type HandlerFuncPtr = unsafe fn( + Signal, + &mut siginfo_t, + Option<&mut ucontext_t>, + data: &mut InProcessExecutorHandlerData, + ); /// A handler that does nothing. /*pub fn nop_handler( _signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, _data: &mut InProcessExecutorHandlerData, ) { }*/ #[cfg(unix)] impl Handler for InProcessExecutorHandlerData { - fn handle(&mut self, signal: Signal, info: &mut siginfo_t, context: &mut ucontext_t) { + fn handle( + &mut self, + signal: Signal, + info: &mut siginfo_t, + context: Option<&mut ucontext_t>, + ) { unsafe { let data = &mut GLOBAL_STATE; let in_handler = data.set_in_handler(true); @@ -759,7 +768,7 @@ pub mod unix_signal_handler { pub unsafe fn inproc_timeout_handler( _signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, data: &mut InProcessExecutorHandlerData, ) where E: HasObservers, @@ -809,7 +818,7 @@ pub mod unix_signal_handler { pub unsafe fn inproc_crash_handler( signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, data: &mut InProcessExecutorHandlerData, ) where E: Executor + HasObservers, @@ -840,8 +849,13 @@ pub mod unix_signal_handler { { let mut writer = std::io::BufWriter::new(&mut bsod); writeln!(writer, "input: {:?}", input.generate_name(0)).unwrap(); - libafl_bolts::minibsod::generate_minibsod(&mut writer, signal, _info, _context) - .unwrap(); + libafl_bolts::minibsod::generate_minibsod( + &mut writer, + signal, + _info, + _context.as_deref(), + ) + .unwrap(); writer.flush().unwrap(); } log::error!("{}", std::str::from_utf8(&bsod).unwrap()); @@ -864,7 +878,7 @@ pub mod unix_signal_handler { let si_addr = { _info.si_addr() as usize }; log::error!( - "We crashed at addr 0x{si_addr:x}, but are not in the target... Bug in the fuzzer? Exiting." + "We crashed at addr 0x{si_addr:x}, but are not in the target... Bug in the fuzzer? Exiting." ); #[cfg(all(feature = "std", unix))] @@ -876,7 +890,7 @@ pub mod unix_signal_handler { &mut writer, signal, _info, - _context, + _context.as_deref(), ) .unwrap(); writer.flush().unwrap(); @@ -1322,8 +1336,12 @@ pub mod windows_exception_handler { /// The signature of the crash handler function #[cfg(all(feature = "std", unix))] -pub(crate) type ForkHandlerFuncPtr = - unsafe fn(Signal, &mut siginfo_t, &mut ucontext_t, data: &mut InProcessForkExecutorGlobalData); +pub(crate) type ForkHandlerFuncPtr = unsafe fn( + Signal, + &mut siginfo_t, + Option<&mut ucontext_t>, + data: &mut InProcessForkExecutorGlobalData, +); /// The inmem fork executor's handlers. #[cfg(all(feature = "std", unix))] @@ -1463,7 +1481,7 @@ pub(crate) static mut FORK_EXECUTOR_GLOBAL_DATA: InProcessForkExecutorGlobalData #[cfg(all(feature = "std", unix))] impl Handler for InProcessForkExecutorGlobalData { - fn handle(&mut self, signal: Signal, info: &mut siginfo_t, context: &mut ucontext_t) { + fn handle(&mut self, signal: Signal, info: &mut siginfo_t, context: Option<&mut ucontext_t>) { match signal { Signal::SigUser2 | Signal::SigAlarm => unsafe { if !FORK_EXECUTOR_GLOBAL_DATA.timeout_handler.is_null() { @@ -2076,7 +2094,7 @@ pub mod child_signal_handlers { pub(crate) unsafe fn child_crash_handler( _signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, data: &mut InProcessForkExecutorGlobalData, ) where E: HasObservers, @@ -2098,7 +2116,7 @@ pub mod child_signal_handlers { pub(crate) unsafe fn child_timeout_handler( _signal: Signal, _info: &mut siginfo_t, - _context: &mut ucontext_t, + _context: Option<&mut ucontext_t>, data: &mut InProcessForkExecutorGlobalData, ) where E: HasObservers, diff --git a/libafl_bolts/src/llmp.rs b/libafl_bolts/src/llmp.rs index 8e3cb1bf97..c42ada0eef 100644 --- a/libafl_bolts/src/llmp.rs +++ b/libafl_bolts/src/llmp.rs @@ -1939,7 +1939,12 @@ pub struct LlmpShutdownSignalHandler { #[cfg(unix)] impl Handler for LlmpShutdownSignalHandler { - fn handle(&mut self, _signal: Signal, _info: &mut siginfo_t, _context: &mut ucontext_t) { + fn handle( + &mut self, + _signal: Signal, + _info: &mut siginfo_t, + _context: Option<&mut ucontext_t>, + ) { unsafe { ptr::write_volatile(&mut self.shutting_down, true); } diff --git a/libafl_bolts/src/minibsod.rs b/libafl_bolts/src/minibsod.rs index 76f79ffa80..48eb80ab9a 100644 --- a/libafl_bolts/src/minibsod.rs +++ b/libafl_bolts/src/minibsod.rs @@ -837,12 +837,16 @@ pub fn generate_minibsod( writer: &mut BufWriter, signal: Signal, _siginfo: &siginfo_t, - ucontext: &ucontext_t, + ucontext: Option<&ucontext_t>, ) -> Result<(), std::io::Error> { writeln!(writer, "{:━^100}", " CRASH ")?; - write_crash(writer, signal, ucontext)?; - writeln!(writer, "{:━^100}", " REGISTERS ")?; - dump_registers(writer, ucontext)?; + if let Some(uctx) = ucontext { + write_crash(writer, signal, uctx)?; + writeln!(writer, "{:━^100}", " REGISTERS ")?; + dump_registers(writer, uctx)?; + } else { + writeln!(writer, "Received signal {}", signal)?; + } writeln!(writer, "{:━^100}", " BACKTRACE ")?; writeln!(writer, "{:?}", backtrace::Backtrace::new())?; writeln!(writer, "{:━^100}", " MAPS ")?; diff --git a/libafl_bolts/src/os/unix_signals.rs b/libafl_bolts/src/os/unix_signals.rs index b0ff5a91cc..ddbcc7486a 100644 --- a/libafl_bolts/src/os/unix_signals.rs +++ b/libafl_bolts/src/os/unix_signals.rs @@ -378,7 +378,7 @@ impl Display for Signal { #[cfg(feature = "alloc")] pub trait Handler { /// Handle a signal - fn handle(&mut self, signal: Signal, info: &mut siginfo_t, _context: &mut ucontext_t); + fn handle(&mut self, signal: Signal, info: &mut siginfo_t, _context: Option<&mut ucontext_t>); /// Return a list of signals to handle fn signals(&self) -> Vec; } @@ -425,7 +425,7 @@ unsafe fn handle_signal(sig: c_int, info: *mut siginfo_t, void: *mut c_void) { handler.handle( *signal, &mut ptr::read_unaligned(info), - &mut ptr::read_unaligned(void as *mut ucontext_t), + (void as *mut ucontext_t).as_mut(), ); } diff --git a/libafl_qemu/libafl_qemu_build/src/lib.rs b/libafl_qemu/libafl_qemu_build/src/lib.rs index cb73ef51b3..ed2c432094 100644 --- a/libafl_qemu/libafl_qemu_build/src/lib.rs +++ b/libafl_qemu/libafl_qemu_build/src/lib.rs @@ -1,14 +1,19 @@ #![allow(clippy::missing_panics_doc)] use std::{ - fs, + env, fs, path::{Path, PathBuf}, + process::Command, }; +use which::which; + mod bindings; mod build; pub use build::build; +const LLVM_VERSION_MAX: i32 = 33; + pub fn build_with_bindings( cpu_target: &str, is_big_endian: bool, @@ -25,6 +30,73 @@ pub fn build_with_bindings( .expect("Faield to write to the bindings file"); } +// For bindgen, the llvm version must be >= of the rust llvm version +fn find_llvm_config() -> Result { + if let Ok(var) = env::var("LLVM_CONFIG") { + return Ok(var); + } + + let rustc_llvm_ver = find_rustc_llvm_version().unwrap(); + for version in (rustc_llvm_ver..=LLVM_VERSION_MAX).rev() { + let llvm_config_name: String = format!("llvm-config-{version}"); + if which(&llvm_config_name).is_ok() { + return Ok(llvm_config_name); + } + } + + if which("llvm-config").is_ok() { + if let Some(ver) = find_llvm_version("llvm-config".to_owned()) { + if ver >= rustc_llvm_ver { + return Ok("llvm-config".to_owned()); + } + } + } + + Err("could not find llvm-config".to_owned()) +} + +fn exec_llvm_config(llvm_config: String, args: &[&str]) -> String { + match Command::new(llvm_config).args(args).output() { + Ok(output) => String::from_utf8(output.stdout) + .expect("Unexpected llvm-config output") + .trim() + .to_string(), + Err(e) => panic!("Could not execute llvm-config: {e}"), + } +} + +fn find_llvm_version(llvm_config: String) -> Option { + let output = exec_llvm_config(llvm_config, &["--version"]); + if let Some(major) = output.split('.').collect::>().first() { + if let Ok(res) = major.parse::() { + return Some(res); + } + } + None +} + +fn exec_rustc(args: &[&str]) -> String { + let rustc = env::var("RUSTC").unwrap(); + match Command::new(rustc).args(args).output() { + Ok(output) => String::from_utf8(output.stdout) + .expect("Unexpected rustc output") + .trim() + .to_string(), + Err(e) => panic!("Could not execute rustc: {e}"), + } +} + +fn find_rustc_llvm_version() -> Option { + let output = exec_rustc(&["--verbose", "--version"]); + let ver = output.split(':').last().unwrap().trim(); + if let Some(major) = ver.split('.').collect::>().first() { + if let Ok(res) = major.parse::() { + return Some(res); + } + } + None +} + //linux-user_main.c.o libqemu-x86_64-linux-user.fa.p fn qemu_bindgen_clang_args( @@ -33,6 +105,11 @@ fn qemu_bindgen_clang_args( cpu_target: &str, is_usermode: bool, ) -> Vec { + if env::var("LLVM_CONFIG_PATH").is_err() { + let found = find_llvm_config().expect("Cannot find a suitable llvm-config, it must be a version equal or greater than the rustc LLVM version"); + env::set_var("LLVM_CONFIG_PATH", found); + } + // load compile commands let compile_commands_string = &fs::read_to_string(build_dir.join("compile_commands.json")) .expect("failed to read compile commands");