Skip to content

Commit

Permalink
Don't panic in config::config
Browse files Browse the repository at this point in the history
  • Loading branch information
smoelius committed Mar 10, 2024
1 parent 462eff5 commit 3d742fc
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 60 deletions.
9 changes: 7 additions & 2 deletions cargo-afl/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
);
}
}
}
156 changes: 99 additions & 57 deletions cargo-afl/src/config.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -33,55 +36,59 @@ 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 {
content_only: true,
..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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<String> {
// 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
Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion cargo-afl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 3d742fc

Please sign in to comment.