diff --git a/cargo-afl/build.rs b/cargo-afl/build.rs index 27d0f719d..2eed8cfb7 100644 --- a/cargo-afl/build.rs +++ b/cargo-afl/build.rs @@ -17,11 +17,16 @@ fn main() { // smoelius: Build AFLplusplus only when installing and not building on docs.rs. if installing && !building_on_docs_rs { - config::config(&config::Args { + if let Err(error) = config::config(&config::Args { build: true, force: true, plugins: cfg!(feature = "plugins"), ..Default::default() - }); + }) { + println!( + "cargo:warn=Could not build AFLplusplus; it will need to be built manually with \ + `cargo afl config --build`: {error}" + ); + } } } diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 89caeaec3..17de3bfe1 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -1,7 +1,10 @@ +#![deny(clippy::expect_used, clippy::panic, clippy::unwrap_used)] + use clap::Parser; use std::ffi::OsStr; +use std::io::{Error, Result}; use std::path::Path; -use std::process::{self, Command, Stdio}; +use std::process::{Command, ExitStatus, Stdio}; use super::common; @@ -33,29 +36,31 @@ pub struct Args { pub verbose: bool, } -pub fn config(args: &Args) { +pub fn config(args: &Args) -> Result<()> { if !args.force && common::archive_file_path().exists() { let version = common::afl_rustc_version(); - eprintln!( - "AFL LLVM runtime was already built for Rust {version}; run `cargo \ - afl config --build --force` to rebuild it." - ); - process::exit(1); + return Err(Error::other(format!( + "AFL LLVM runtime was already built for Rust {version}; run `cargo afl config --build \ + --force` to rebuild it." + ))); } let afl_src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(AFL_SRC_PATH); let afl_src_dir_str = &afl_src_dir.to_string_lossy(); - let tempdir = tempfile::tempdir().unwrap(); + let tempdir = tempfile::tempdir()?; if afl_src_dir.join(".git").is_dir() { - let status = Command::new("git") + let success = Command::new("git") .args(["clone", afl_src_dir_str, &*tempdir.path().to_string_lossy()]) .status() - .expect("could not run 'git'"); - assert!(status.success()); + .as_ref() + .map_or(false, ExitStatus::success); + if !success { + return Err(Error::other("could not run 'git'")); + } } else { - fs_extra::dir::copy( + let _: u64 = fs_extra::dir::copy( afl_src_dir, tempdir.path(), &fs_extra::dir::CopyOptions { @@ -63,25 +68,27 @@ pub fn config(args: &Args) { ..Default::default() }, ) - .unwrap(); + .map_err(Error::other)?; } let work_dir = tempdir.path(); - build_afl(args, work_dir); - build_afl_llvm_runtime(args, work_dir); + build_afl(args, work_dir)?; + build_afl_llvm_runtime(args, work_dir)?; if args.plugins { - copy_afl_llvm_plugins(args, work_dir); + copy_afl_llvm_plugins(args, work_dir)?; } - eprintln!( - "Artifacts written to {}", - common::afl_dir().parent().unwrap().display() - ); + let Some(dir) = common::afl_dir().parent().map(Path::to_path_buf) else { + return Err(Error::other("could not get afl dir parent")); + }; + eprintln!("Artifacts written to {}", dir.display()); + + Ok(()) } -fn build_afl(args: &Args, work_dir: &Path) { +fn build_afl(args: &Args, work_dir: &Path) -> Result<()> { // if you had already installed cargo-afl previously you **must** clean AFL++ // smoelius: AFL++ is now copied to a temporary directory before being built. So `make clean` // is no longer necessary. @@ -96,7 +103,7 @@ fn build_afl(args: &Args, work_dir: &Path) { .env_remove("DEBUG"); if args.plugins { - let llvm_config = check_llvm_and_get_config(); + let llvm_config = check_llvm_and_get_config()?; command.env("LLVM_CONFIG", llvm_config); } else { // build just the runtime to avoid troubles with Xcode clang on macOS @@ -109,16 +116,20 @@ fn build_afl(args: &Args, work_dir: &Path) { command.stderr(Stdio::null()); } - let status = command.status().expect("could not run 'make install'"); - assert!(status.success()); + let success = command.status().as_ref().map_or(false, ExitStatus::success); + if !success { + return Err(Error::other("could not run 'make install")); + } + + Ok(()) } -fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) { - std::fs::copy( +fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) -> Result<()> { + let _: u64 = std::fs::copy( work_dir.join("afl-compiler-rt.o"), common::object_file_path(), ) - .expect("Couldn't copy object file"); + .map_err(|error| Error::other(format!("could not copy object file: {error}")))?; let mut command = Command::new(AR_CMD); command @@ -131,38 +142,52 @@ fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) { command.stderr(Stdio::null()); } - let status = command.status().expect("could not run 'ar'"); - assert!(status.success()); + let success = command.status().as_ref().map_or(false, ExitStatus::success); + if !success { + return Err(Error::other("could not run 'ar'")); + } + + Ok(()) } -fn copy_afl_llvm_plugins(_args: &Args, work_dir: &Path) { +fn copy_afl_llvm_plugins(_args: &Args, work_dir: &Path) -> Result<()> { // Iterate over the files in the directory. - for result in work_dir.read_dir().unwrap() { - let entry = result.unwrap(); + for result in work_dir.read_dir()? { + let entry = result?; let file_name = entry.file_name(); // Get the file extension. Only copy the files that are shared objects. if Path::new(&file_name).extension() == Some(OsStr::new("so")) { // Attempt to copy the shared object file. - std::fs::copy( + let _: u64 = std::fs::copy( work_dir.join(&file_name), common::afl_llvm_dir().join(&file_name), ) - .unwrap_or_else(|error| { - panic!("Couldn't copy shared object file {file_name:?}: {error}") - }); + .map_err(|error| { + Error::other(format!( + "could not copy shared object file {file_name:?}: {error}" + )) + })?; } } + + Ok(()) } -fn check_llvm_and_get_config() -> String { +fn check_llvm_and_get_config() -> Result { // Make sure we are on nightly for the -Z flags - assert!( - rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly, - "cargo-afl must be compiled with nightly for the plugins feature" - ); - let version_meta = rustc_version::version_meta().unwrap(); - let llvm_version = version_meta.llvm_version.unwrap().major.to_string(); + let version_meta = rustc_version::version_meta().map_err(Error::other)?; + if version_meta.channel != rustc_version::Channel::Nightly { + return Err(Error::other( + "cargo-afl must be compiled with nightly for the plugins feature", + )); + } + let Some(llvm_version) = version_meta + .llvm_version + .map(|llvm_version| llvm_version.major.to_string()) + else { + return Err(Error::other("could not get llvm version")); + }; // Fetch the llvm version of the rust toolchain and set the LLVM_CONFIG environment variable to the same version // This is needed to compile the llvm plugins (needed for cmplog) from afl with the right LLVM version @@ -173,19 +198,36 @@ fn check_llvm_and_get_config() -> String { }; // check if llvm tools are installed and with the good version for the plugin compilation - let mut command = Command::new(llvm_config.clone()); + let mut command = Command::new(&llvm_config); command.args(["--version"]); - let out = command - .output() - .unwrap_or_else(|_| panic!("could not run {llvm_config} --version")); - - let version = String::from_utf8(out.stdout) - .expect("could not convert llvm-config --version output to utf8"); - let major = version - .split('.') - .next() - .expect("could not get major from llvm-config --version output"); - assert!(major == llvm_version); - - llvm_config + let out = match command.output() { + Ok(out) => out, + Err(error) => { + return Err(Error::other(format!( + "could not run {llvm_config} --version: {error}" + ))); + } + }; + + let version = match String::from_utf8(out.stdout) { + Ok(version) => version, + Err(error) => { + return Err(Error::other(format!( + "could not convert {llvm_config} --version output to utf8: {error}" + ))); + } + }; + let Some(major) = version.split('.').next() else { + return Err(Error::other(format!( + "could not get major from {llvm_config} --version output", + ))); + }; + if major != llvm_version { + return Err(Error::other(format!( + "{llvm_config} --version output does not contain expected major version \ + ({llvm_version})", + ))); + } + + Ok(llvm_config) } diff --git a/cargo-afl/src/main.rs b/cargo-afl/src/main.rs index 5d605bdca..5e24f6820 100644 --- a/cargo-afl/src/main.rs +++ b/cargo-afl/src/main.rs @@ -141,7 +141,7 @@ fn main() { run_afl("afl-analyze", args); } Some(AflSubcommand::Config(args)) => { - config::config(args); + config::config(args).unwrap(); } Some(AflSubcommand::Cmin { args }) => { run_afl("afl-cmin", args);