From e25ca624bb13dfbba501d8ca575ff5eaf87ca53c Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 9 Mar 2024 20:51:21 -0500 Subject: [PATCH] Don't panic in `config::config` --- cargo-afl/build.rs | 9 +- cargo-afl/src/common.rs | 38 ++++----- cargo-afl/src/config.rs | 178 +++++++++++++++++++++++++--------------- cargo-afl/src/main.rs | 14 ++-- 4 files changed, 143 insertions(+), 96 deletions(-) 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/common.rs b/cargo-afl/src/common.rs index 7ab7f1c6f..df0138670 100644 --- a/cargo-afl/src/common.rs +++ b/cargo-afl/src/common.rs @@ -1,14 +1,18 @@ +#![deny(clippy::expect_used, clippy::panic, clippy::unwrap_used)] + use std::env; +use std::io::{Error, Result}; use std::path::{Path, PathBuf}; -fn xdg_dir() -> xdg::BaseDirectories { +fn xdg_dir() -> Result { + let afl_rustc_version = afl_rustc_version()?; let prefix = Path::new("afl.rs") - .join(afl_rustc_version()) + .join(afl_rustc_version) .join(pkg_version()); - xdg::BaseDirectories::with_prefix(prefix).unwrap() + xdg::BaseDirectories::with_prefix(prefix).map_err(Error::other) } -fn data_dir(dir_name: &str) -> PathBuf { +fn data_dir(dir_name: &str) -> Result { // For docs.rs builds, use OUT_DIR. // For other cases, use a XDG data directory. // It is necessary to use OUT_DIR for docs.rs builds, @@ -17,21 +21,21 @@ fn data_dir(dir_name: &str) -> PathBuf { // place their generated files at OUT_DIR too, but we // don't change that for now for normal builds. // smoelius: AFL++ is no longer built on docs.rs. - xdg_dir().create_data_directory(dir_name).unwrap() + let xdg_dir = xdg_dir()?; + xdg_dir.create_data_directory(dir_name) } const SHORT_COMMIT_HASH_LEN: usize = 7; -#[must_use] -pub fn afl_rustc_version() -> String { - let version_meta = rustc_version::version_meta().unwrap(); +pub fn afl_rustc_version() -> Result { + let version_meta = rustc_version::version_meta().map_err(Error::other)?; let mut ret = String::from("rustc-"); ret.push_str(&version_meta.semver.to_string()); if let Some(commit_hash) = version_meta.commit_hash { ret.push('-'); ret.push_str(&commit_hash[..SHORT_COMMIT_HASH_LEN]); } - ret + Ok(ret) } fn pkg_version() -> String { @@ -45,25 +49,21 @@ fn pkg_version() -> String { } #[allow(dead_code)] -#[must_use] -pub fn afl_dir() -> PathBuf { +pub fn afl_dir() -> Result { data_dir("afl") } #[allow(dead_code)] -#[must_use] -pub fn afl_llvm_dir() -> PathBuf { +pub fn afl_llvm_dir() -> Result { data_dir("afl-llvm") } #[allow(dead_code)] -#[must_use] -pub fn object_file_path() -> PathBuf { - afl_llvm_dir().join("libafl-llvm-rt.o") +pub fn object_file_path() -> Result { + afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.o")) } #[allow(dead_code)] -#[must_use] -pub fn archive_file_path() -> PathBuf { - afl_llvm_dir().join("libafl-llvm-rt.a") +pub fn archive_file_path() -> Result { + afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.a")) } diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 89caeaec3..ad3e1f15c 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,32 @@ pub struct Args { pub verbose: bool, } -pub fn config(args: &Args) { - 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); +pub fn config(args: &Args) -> Result<()> { + let archive_file_path = common::archive_file_path()?; + if !args.force && archive_file_path.exists() { + let version = common::afl_rustc_version()?; + 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,40 +69,44 @@ 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 afl_dir = common::afl_dir()?; + let Some(dir) = 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. + let afl_dir = common::afl_dir()?; let mut command = Command::new("make"); command .current_dir(work_dir) .arg("install") // skip the checks for the legacy x86 afl-gcc compiler .env("AFL_NO_X86", "1") - .env("DESTDIR", common::afl_dir()) + .env("DESTDIR", afl_dir) .env("PREFIX", "") .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,60 +119,75 @@ 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( - work_dir.join("afl-compiler-rt.o"), - common::object_file_path(), - ) - .expect("Couldn't copy object file"); +fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) -> Result<()> { + let object_file_path = common::object_file_path()?; + let _: u64 = std::fs::copy(work_dir.join("afl-compiler-rt.o"), &object_file_path) + .map_err(|error| Error::other(format!("could not copy object file: {error}")))?; + let archive_file_path = common::archive_file_path()?; let mut command = Command::new(AR_CMD); command .arg("r") - .arg(common::archive_file_path()) - .arg(common::object_file_path()); + .arg(archive_file_path) + .arg(object_file_path); if !args.verbose { command.stdout(Stdio::null()); 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( - 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}") - }); + let afl_llvm_dir = common::afl_llvm_dir()?; + let _: u64 = std::fs::copy(work_dir.join(&file_name), afl_llvm_dir.join(&file_name)) + .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..b4cdbec47 100644 --- a/cargo-afl/src/main.rs +++ b/cargo-afl/src/main.rs @@ -123,9 +123,9 @@ fn main() { }; if !matches!(afl_args.subcmd, Some(AflSubcommand::Config(..))) - && !common::archive_file_path().exists() + && !common::archive_file_path().unwrap().exists() { - let version = common::afl_rustc_version(); + let version = common::afl_rustc_version().unwrap(); eprintln!( "AFL LLVM runtime was not built for Rust {version}; run `cargo \ afl config --build` to build it." @@ -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); @@ -182,7 +182,7 @@ where S: AsRef, { let no_sudo = env::var("NO_SUDO").is_ok(); - let cmd_path = common::afl_dir().join("bin").join(tool); + let cmd_path = common::afl_dir().unwrap().join("bin").join(tool); let mut cmd = if !no_sudo && tool == "afl-system-config" { let mut cmd = Command::new("sudo"); cmd.args([OsStr::new("--reset-timestamp"), cmd_path.as_os_str()]); @@ -238,7 +238,7 @@ where // `-C codegen-units=1` is needed to work around link errors // https://github.com/rust-fuzz/afl.rs/pull/193#issuecomment-933550430 - let binding = common::afl_llvm_dir(); + let binding = common::afl_llvm_dir().unwrap(); let p = binding.display(); let mut rustflags = String::from( @@ -304,7 +304,7 @@ where rustflags.push_str(&format!( "-l afl-llvm-rt \ -L {} ", - common::afl_llvm_dir().display() + common::afl_llvm_dir().unwrap().display() )); // add user provided flags @@ -332,7 +332,7 @@ fn is_nightly() -> bool { } fn plugins_available() -> bool { - let afl_llvm_dir = common::afl_llvm_dir(); + let afl_llvm_dir = common::afl_llvm_dir().unwrap(); for result in afl_llvm_dir.read_dir().unwrap() { let entry = result.unwrap(); let file_name = entry.file_name();