Add feature to build variants/configurations automatically, with libtool/cc/cxx shims (#1322)

* Add feature to build variants/configurations automatically, with libtool/cc/cxx shims

* Fixes

* Clippy

* Add brief comment describing usage

* Fix

* Fix fuzzers: add ToolWrapper

* Clippy

* More clippy

* More clippy

* Add Compound configuration

* Clippy

* Fix

* Clippy

* Damn that Clippy

* Change names of Configurations

* Add ar wrapper

* Fix

* Clippy

* Windows build

* Clippy

* Clippy

* Clippy
This commit is contained in:
s1341 2023-06-22 17:17:33 +03:00 committed by GitHub
parent 07530fea57
commit 71aa0221a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 998 additions and 118 deletions

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper};
pub fn main() { pub fn main() {
let mut args: Vec<String> = env::args().collect(); let mut args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper};
const GRANULARITY: &str = "FUNC"; const GRANULARITY: &str = "FUNC";

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -4,6 +4,7 @@ FUZZER_NAME='fuzzer_libpng_launcher'
CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } } CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR"] } }
LIBAFL_CC = '${CARGO_TARGET_DIR}/release/libafl_cc' LIBAFL_CC = '${CARGO_TARGET_DIR}/release/libafl_cc'
LIBAFL_CXX = '${CARGO_TARGET_DIR}/release/libafl_cxx' LIBAFL_CXX = '${CARGO_TARGET_DIR}/release/libafl_cxx'
LIBAFL_LIBTOOL = '${CARGO_TARGET_DIR}/release/libafl_libtool'
FUZZER = '${CARGO_TARGET_DIR}/release/${FUZZER_NAME}' FUZZER = '${CARGO_TARGET_DIR}/release/${FUZZER_NAME}'
PROJECT_DIR = { script = ["pwd"] } PROJECT_DIR = { script = ["pwd"] }
@ -57,7 +58,7 @@ script_runner="@shell"
script=''' script='''
cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes
cd "${PROJECT_DIR}" cd "${PROJECT_DIR}"
make -C libpng-1.6.37 CC="${CARGO_TARGET_DIR}/release/libafl_cc" CXX="${CARGO_TARGET_DIR}/release/libafl_cxx" make -C libpng-1.6.37 CC="${CARGO_TARGET_DIR}/release/libafl_cc" CXX="${CARGO_TARGET_DIR}/release/libafl_cxx" LIBTOOL=${CARGO_TARGET_DIR}/release/libafl_libtool
''' '''
dependencies = [ "libpng", "cxx", "cc" ] dependencies = [ "libpng", "cxx", "cc" ]
@ -82,7 +83,7 @@ windows_alias = "unsupported"
[tasks.run_unix] [tasks.run_unix]
script_runner = "@shell" script_runner = "@shell"
script=''' script='''
./${FUZZER_NAME} --cores 0 --input ./corpus ./${FUZZER_NAME}.coverage --broker-port 21337 --cores 0 --input ./corpus
''' '''
dependencies = [ "fuzzer" ] dependencies = [ "fuzzer" ]
@ -96,7 +97,7 @@ windows_alias = "unsupported"
script_runner = "@shell" script_runner = "@shell"
script=''' script='''
rm -rf libafl_unix_shmem_server || true rm -rf libafl_unix_shmem_server || true
timeout 11s ./${FUZZER_NAME} --cores 0 --input ./corpus 2>/dev/null >fuzz_stdout.log || true timeout 11s ./${FUZZER_NAME}.coverage --broker-port 21337 --cores 0 --input ./corpus 2>/dev/null >fuzz_stdout.log || true
if [ -z "$(grep "corpus: 30" fuzz_stdout.log)" ]; then if [ -z "$(grep "corpus: 30" fuzz_stdout.log)" ]; then
echo "Fuzzer does not generate any testcases or any crashes" echo "Fuzzer does not generate any testcases or any crashes"
exit 1 exit 1

View File

@ -0,0 +1,31 @@
use std::env;
use libafl_cc::{ArWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
let mut cc = ArWrapper::new();
if let Some(code) = cc
// silence the compiler wrapper output, needed for some configure scripts.
.silence(true)
.parse_args(&args)
.expect("Failed to parse the command line")
.add_configuration(Configuration::GenerateCoverageMap)
.add_configuration(Configuration::Compound(vec![
Configuration::GenerateCoverageMap,
Configuration::CmpLog,
]))
.add_configuration(Configuration::UndefinedBehaviorSanitizer)
.add_configuration(Configuration::AddressSanitizer)
// .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp")
// .add_arg("-fsanitize=address")
.run()
.expect("Failed to run the wrapped libtool")
{
std::process::exit(code);
}
} else {
panic!("LibAFL libtool: No Arguments given");
}
}

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
@ -24,7 +24,15 @@ pub fn main() {
.parse_args(&args) .parse_args(&args)
.expect("Failed to parse the command line") .expect("Failed to parse the command line")
.link_staticlib(&dir, "libfuzzer_libpng") .link_staticlib(&dir, "libfuzzer_libpng")
.add_arg("-fsanitize-coverage=trace-pc-guard") .add_configuration(Configuration::GenerateCoverageMap)
.add_configuration(Configuration::Compound(vec![
Configuration::GenerateCoverageMap,
Configuration::CmpLog,
]))
.add_configuration(Configuration::UndefinedBehaviorSanitizer)
.add_configuration(Configuration::AddressSanitizer)
// .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp")
// .add_arg("-fsanitize=address")
.run() .run()
.expect("Failed to run the wrapped compiler") .expect("Failed to run the wrapped compiler")
{ {

View File

@ -0,0 +1,31 @@
use std::env;
use libafl_cc::{Configuration, LibtoolWrapper, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
let mut cc = LibtoolWrapper::new();
if let Some(code) = cc
// silence the compiler wrapper output, needed for some configure scripts.
.silence(true)
.parse_args(&args)
.expect("Failed to parse the command line")
.add_configuration(Configuration::GenerateCoverageMap)
.add_configuration(Configuration::Compound(vec![
Configuration::GenerateCoverageMap,
Configuration::CmpLog,
]))
.add_configuration(Configuration::UndefinedBehaviorSanitizer)
.add_configuration(Configuration::AddressSanitizer)
// .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp")
// .add_arg("-fsanitize=address")
.run()
.expect("Failed to run the wrapped libtool")
{
std::process::exit(code);
}
} else {
panic!("LibAFL libtool: No Arguments given");
}
}

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::{env, process::Command, str}; use std::{env, process::Command, str};
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
fn find_libpython() -> Result<String, String> { fn find_libpython() -> Result<String, String> {
match Command::new("python3") match Command::new("python3")

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

View File

@ -1,6 +1,6 @@
use std::env; use std::env;
use libafl_cc::{ClangWrapper, CompilerWrapper}; use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper};
pub fn main() { pub fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();

254
libafl_cc/src/ar.rs Normal file
View File

@ -0,0 +1,254 @@
//! Ar Wrapper from `LibAFL`
// pass to e.g. cmake with -DCMAKE_AR=/path/to/fuzzer/target/release/libafl_ar
use std::{convert::Into, env, path::PathBuf, str::FromStr, string::String, vec::Vec};
use crate::{Error, ToolWrapper, LIB_EXT, LIB_PREFIX};
/// Wrap Clang
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct ArWrapper {
is_silent: bool,
name: String,
linking: bool,
need_libafl_arg: bool,
has_libafl_arg: bool,
configurations: Vec<crate::Configuration>,
parse_args_called: bool,
base_args: Vec<String>,
}
#[allow(clippy::match_same_arms)] // for the linking = false wip for "shared"
impl ToolWrapper for ArWrapper {
#[allow(clippy::too_many_lines)]
fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error>
where
S: AsRef<str>,
{
let mut new_args: Vec<String> = vec![];
if args.is_empty() {
return Err(Error::InvalidArguments(
"The number of arguments cannot be 0".to_string(),
));
}
if self.parse_args_called {
return Err(Error::Unknown(
"ToolWrapper::parse_args cannot be called twice on the same instance".to_string(),
));
}
self.parse_args_called = true;
if args.len() == 1 {
return Err(Error::InvalidArguments(
"LibAFL Tool wrapper - no commands specified. Use me as compiler.".to_string(),
));
}
self.name = args[0].as_ref().to_string();
let mut linking = true;
// Detect stray -v calls from ./configure scripts.
if args.len() > 1 && args[1].as_ref() == "-v" {
if args.len() == 2 {
self.base_args.push(args[1].as_ref().into());
return Ok(self);
}
linking = false;
}
let mut suppress_linking = 0;
let mut i = 1;
while i < args.len() {
match args[i].as_ref() {
"--libafl-no-link" => {
suppress_linking += 1;
self.has_libafl_arg = true;
i += 1;
continue;
}
"--libafl" => {
suppress_linking += 1337;
self.has_libafl_arg = true;
i += 1;
continue;
}
"-fsanitize=fuzzer-no-link" => {
suppress_linking += 1;
self.has_libafl_arg = true;
i += 1;
continue;
}
"-fsanitize=fuzzer" => {
suppress_linking += 1337;
self.has_libafl_arg = true;
i += 1;
continue;
}
"--libafl-configurations" => {
if i + 1 < args.len() {
self.configurations.extend(
args[i + 1]
.as_ref()
.split(',')
.map(|x| crate::Configuration::from_str(x).unwrap()),
);
i += 2;
continue;
}
}
_ => (),
};
new_args.push(args[i].as_ref().to_string());
i += 1;
}
if linking
&& (suppress_linking > 0 || (self.has_libafl_arg && suppress_linking == 0))
&& suppress_linking < 1337
{
linking = false;
new_args.push(
PathBuf::from(env!("OUT_DIR"))
.join(format!("{LIB_PREFIX}no-link-rt.{LIB_EXT}"))
.into_os_string()
.into_string()
.unwrap(),
);
}
self.linking = linking;
// Libraries needed by libafl on Windows
self.base_args.extend(new_args);
Ok(self)
}
fn add_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>,
{
self.base_args.push(arg.as_ref().to_string());
self
}
fn add_configuration(&mut self, configuration: crate::Configuration) -> &'_ mut Self {
self.configurations.push(configuration);
self
}
fn configurations(&self) -> Result<Vec<crate::Configuration>, Error> {
let configs = self.configurations.clone();
Ok(configs)
}
fn ignore_configurations(&self) -> Result<bool, Error> {
Ok(false)
}
fn command(&mut self) -> Result<Vec<String>, Error> {
self.command_for_configuration(crate::Configuration::Default)
}
fn command_for_configuration(
&mut self,
configuration: crate::Configuration,
) -> Result<Vec<String>, Error> {
let mut args = vec![];
let base_args = self
.base_args
.iter()
.map(|r| {
let arg_as_path = std::path::PathBuf::from(r);
if r.ends_with('.') {
r.to_string()
} else {
if let Some(extension) = arg_as_path.extension() {
let extension = extension.to_str().unwrap();
let extension_lowercase = extension.to_lowercase();
match &extension_lowercase[..] {
"o" | "lo" | "a" | "la" | "so" => {
configuration.replace_extension(&arg_as_path)
}
_ => arg_as_path,
}
} else {
arg_as_path
}
.into_os_string()
.into_string()
.unwrap()
}
})
.collect::<Vec<_>>();
let Ok(ar_path) = std::env::var("LLVM_AR_PATH") else {
panic!("Couldn't find llvm-ar. Specify the `LLVM_AR_PATH` environment variable");
};
args.push(ar_path);
args.extend_from_slice(base_args.as_slice());
if self.need_libafl_arg && !self.has_libafl_arg {
return Ok(args);
}
Ok(args)
}
fn is_linking(&self) -> bool {
self.linking
}
fn filter(&self, _args: &mut Vec<String>) {}
fn silence(&mut self, value: bool) -> &'_ mut Self {
self.is_silent = value;
self
}
fn is_silent(&self) -> bool {
self.is_silent
}
}
impl Default for ArWrapper {
/// Create a new Clang Wrapper
#[must_use]
fn default() -> Self {
Self::new()
}
}
impl ArWrapper {
/// Create a new Clang Wrapper
#[must_use]
pub fn new() -> Self {
Self {
name: String::new(),
linking: false,
need_libafl_arg: false,
has_libafl_arg: false,
configurations: vec![crate::Configuration::Default],
parse_args_called: false,
base_args: vec![],
is_silent: false,
}
}
/// Set if linking
pub fn linking(&mut self, value: bool) -> &'_ mut Self {
self.linking = value;
self
}
/// Set if it needs the --libafl arg to add the custom arguments to clang
pub fn need_libafl_arg(&mut self, value: bool) -> &'_ mut Self {
self.need_libafl_arg = value;
self
}
}

View File

@ -4,11 +4,12 @@ use std::{
convert::Into, convert::Into,
env, env,
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr,
string::String, string::String,
vec::Vec, vec::Vec,
}; };
use crate::{CompilerWrapper, Error, LIB_EXT, LIB_PREFIX}; use crate::{CompilerWrapper, Error, ToolWrapper, LIB_EXT, LIB_PREFIX};
/// The `OUT_DIR` for `LLVM` compiler passes /// The `OUT_DIR` for `LLVM` compiler passes
pub const OUT_DIR: &str = env!("OUT_DIR"); pub const OUT_DIR: &str = env!("OUT_DIR");
@ -83,6 +84,9 @@ pub struct ClangWrapper {
has_libafl_arg: bool, has_libafl_arg: bool,
use_new_pm: bool, use_new_pm: bool,
output: Option<PathBuf>,
configurations: Vec<crate::Configuration>,
ignoring_configurations: bool,
parse_args_called: bool, parse_args_called: bool,
base_args: Vec<String>, base_args: Vec<String>,
cc_args: Vec<String>, cc_args: Vec<String>,
@ -93,7 +97,7 @@ pub struct ClangWrapper {
} }
#[allow(clippy::match_same_arms)] // for the linking = false wip for "shared" #[allow(clippy::match_same_arms)] // for the linking = false wip for "shared"
impl CompilerWrapper for ClangWrapper { impl ToolWrapper for ClangWrapper {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error> fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error>
where where
@ -108,15 +112,14 @@ impl CompilerWrapper for ClangWrapper {
if self.parse_args_called { if self.parse_args_called {
return Err(Error::Unknown( return Err(Error::Unknown(
"CompilerWrapper::parse_args cannot be called twice on the same instance" "ToolWrapper::parse_args cannot be called twice on the same instance".to_string(),
.to_string(),
)); ));
} }
self.parse_args_called = true; self.parse_args_called = true;
if args.len() == 1 { if args.len() == 1 {
return Err(Error::InvalidArguments( return Err(Error::InvalidArguments(
"LibAFL Compiler wrapper - no commands specified. Use me as compiler.".to_string(), "LibAFL Tool wrapper - no commands specified. Use me as compiler.".to_string(),
)); ));
} }
@ -145,7 +148,9 @@ impl CompilerWrapper for ClangWrapper {
let mut suppress_linking = 0; let mut suppress_linking = 0;
let mut i = 1; let mut i = 1;
while i < args.len() { while i < args.len() {
if std::path::Path::new(args[i].as_ref()) let arg_as_path = std::path::Path::new(args[i].as_ref());
if arg_as_path
.extension() .extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case("s")) .map_or(false, |ext| ext.eq_ignore_ascii_case("s"))
{ {
@ -189,6 +194,30 @@ impl CompilerWrapper for ClangWrapper {
continue; continue;
} }
} }
"--libafl-ignore-configurations" => {
self.ignoring_configurations = true;
i += 1;
continue;
}
"--libafl-configurations" => {
if i + 1 < args.len() {
self.configurations.extend(
args[i + 1]
.as_ref()
.split(',')
.map(|x| crate::Configuration::from_str(x).unwrap()),
);
i += 2;
continue;
}
}
"-o" => {
if i + 1 < args.len() {
self.output = Some(PathBuf::from(args[i + 1].as_ref()));
i += 2;
continue;
}
}
"-x" => self.x_set = true, "-x" => self.x_set = true,
"-m32" => self.bit_mode = 32, "-m32" => self.bit_mode = 32,
"-m64" => self.bit_mode = 64, "-m64" => self.bit_mode = 64,
@ -259,49 +288,30 @@ impl CompilerWrapper for ClangWrapper {
self self
} }
fn add_cc_arg<S>(&mut self, arg: S) -> &'_ mut Self fn add_configuration(&mut self, configuration: crate::Configuration) -> &'_ mut Self {
where self.configurations.push(configuration);
S: AsRef<str>,
{
self.cc_args.push(arg.as_ref().to_string());
self self
} }
fn add_link_arg<S>(&mut self, arg: S) -> &'_ mut Self fn configurations(&self) -> Result<Vec<crate::Configuration>, Error> {
where let mut configs = self.configurations.clone();
S: AsRef<str>, configs.reverse();
{ Ok(configs)
self.link_args.push(arg.as_ref().to_string());
self
} }
fn link_staticlib<S>(&mut self, dir: &Path, name: S) -> &'_ mut Self fn ignore_configurations(&self) -> Result<bool, Error> {
where Ok(self.ignoring_configurations)
S: AsRef<str>,
{
let lib_file = dir
.join(format!("{LIB_PREFIX}{}.{LIB_EXT}", name.as_ref()))
.into_os_string()
.into_string()
.unwrap();
if cfg!(unix) {
if cfg!(target_vendor = "apple") {
// Same as --whole-archive on linux
// Without this option, the linker picks the first symbols it finds and does not care if it's a weak or a strong symbol
// See: <https://stackoverflow.com/questions/13089166/how-to-make-gcc-link-strong-symbol-in-static-library-to-overwrite-weak-symbol>
self.add_link_arg("-Wl,-force_load").add_link_arg(lib_file)
} else {
self.add_link_arg("-Wl,--whole-archive")
.add_link_arg(lib_file)
.add_link_arg("-Wl,--no-whole-archive")
}
} else {
self.add_link_arg(format!("-Wl,-wholearchive:{lib_file}"))
}
} }
fn command(&mut self) -> Result<Vec<String>, Error> { fn command(&mut self) -> Result<Vec<String>, Error> {
self.command_for_configuration(crate::Configuration::Default)
}
#[allow(clippy::too_many_lines)]
fn command_for_configuration(
&mut self,
configuration: crate::Configuration,
) -> Result<Vec<String>, Error> {
let mut args = vec![]; let mut args = vec![];
let mut use_pass = false; let mut use_pass = false;
@ -310,7 +320,71 @@ impl CompilerWrapper for ClangWrapper {
} else { } else {
args.push(self.wrapped_cc.clone()); args.push(self.wrapped_cc.clone());
} }
args.extend_from_slice(self.base_args.as_slice());
let base_args = self
.base_args
.iter()
.map(|r| {
let arg_as_path = std::path::PathBuf::from(r);
if r.ends_with('.') {
r.to_string()
} else {
if let Some(extension) = arg_as_path.extension() {
let extension = extension.to_str().unwrap();
let extension_lowercase = extension.to_lowercase();
match &extension_lowercase[..] {
"a" | "la" => configuration.replace_extension(&arg_as_path),
_ => arg_as_path,
}
} else {
arg_as_path
}
.into_os_string()
.into_string()
.unwrap()
}
})
.collect::<Vec<_>>();
if let Some(output) = self.output.clone() {
let output = configuration.replace_extension(&output);
let new_filename = output.into_os_string().into_string().unwrap();
args.push("-o".to_string());
args.push(new_filename);
args.extend_from_slice(base_args.as_slice());
} else {
// No output specified, we need to rewrite the single .c file's name.
args.extend(
base_args
.iter()
.map(|r| {
let arg_as_path = std::path::PathBuf::from(r);
if r.ends_with('.') {
r.to_string()
} else {
if let Some(extension) = arg_as_path.extension() {
let extension = extension.to_str().unwrap();
let extension_lowercase = extension.to_lowercase();
match &extension_lowercase[..] {
"c" | "cc" | "cxx" | "cpp" => {
configuration.replace_extension(&arg_as_path)
}
_ => arg_as_path,
}
} else {
arg_as_path
}
.into_os_string()
.into_string()
.unwrap()
}
})
.collect::<Vec<_>>(),
);
}
args.extend_from_slice(&configuration.to_flags()?);
if self.need_libafl_arg && !self.has_libafl_arg { if self.need_libafl_arg && !self.has_libafl_arg {
return Ok(args); return Ok(args);
} }
@ -369,6 +443,7 @@ impl CompilerWrapper for ClangWrapper {
if cfg!(unix) { if cfg!(unix) {
args.push("-pthread".into()); args.push("-pthread".into());
args.push("-ldl".into()); args.push("-ldl".into());
args.push("-lm".into());
} }
} else { } else {
args.extend_from_slice(self.cc_args.as_slice()); args.extend_from_slice(self.cc_args.as_slice());
@ -398,6 +473,49 @@ impl CompilerWrapper for ClangWrapper {
} }
} }
impl CompilerWrapper for ClangWrapper {
fn add_cc_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>,
{
self.cc_args.push(arg.as_ref().to_string());
self
}
fn add_link_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>,
{
self.link_args.push(arg.as_ref().to_string());
self
}
fn link_staticlib<S>(&mut self, dir: &Path, name: S) -> &'_ mut Self
where
S: AsRef<str>,
{
let lib_file = dir
.join(format!("{LIB_PREFIX}{}.{LIB_EXT}", name.as_ref()))
.into_os_string()
.into_string()
.unwrap();
if cfg!(unix) {
if cfg!(target_vendor = "apple") {
// Same as --whole-archive on linux
// Without this option, the linker picks the first symbols it finds and does not care if it's a weak or a strong symbol
// See: <https://stackoverflow.com/questions/13089166/how-to-make-gcc-link-strong-symbol-in-static-library-to-overwrite-weak-symbol>
self.add_link_arg("-Wl,-force_load").add_link_arg(lib_file)
} else {
self.add_link_arg("-Wl,--whole-archive")
.add_link_arg(lib_file)
.add_link_arg("-Wl,--no-whole-archive")
}
} else {
self.add_link_arg(format!("-Wl,-wholearchive:{lib_file}"))
}
}
}
impl Default for ClangWrapper { impl Default for ClangWrapper {
/// Create a new Clang Wrapper /// Create a new Clang Wrapper
#[must_use] #[must_use]
@ -432,6 +550,9 @@ impl ClangWrapper {
need_libafl_arg: false, need_libafl_arg: false,
has_libafl_arg: false, has_libafl_arg: false,
use_new_pm, use_new_pm,
output: None,
configurations: vec![crate::Configuration::Default],
ignoring_configurations: false,
parse_args_called: false, parse_args_called: false,
base_args: vec![], base_args: vec![],
cc_args: vec![], cc_args: vec![],
@ -512,7 +633,7 @@ impl ClangWrapper {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ClangWrapper, CompilerWrapper}; use crate::{ClangWrapper, ToolWrapper};
#[test] #[test]
#[cfg_attr(miri, ignore)] #[cfg_attr(miri, ignore)]

View File

@ -60,10 +60,14 @@
use std::{convert::Into, path::Path, process::Command, string::String, vec::Vec}; use std::{convert::Into, path::Path, process::Command, string::String, vec::Vec};
pub mod ar;
pub use ar::ArWrapper;
pub mod cfg; pub mod cfg;
pub use cfg::{CfgEdge, ControlFlowGraph, EntryBasicBlockInfo, HasWeight}; pub use cfg::{CfgEdge, ControlFlowGraph, EntryBasicBlockInfo, HasWeight};
pub mod clang; pub mod clang;
pub use clang::{ClangWrapper, LLVMPasses}; pub use clang::{ClangWrapper, LLVMPasses};
pub mod libtool;
pub use libtool::LibtoolWrapper;
/// `LibAFL` CC Error Type /// `LibAFL` CC Error Type
#[derive(Debug)] #[derive(Debug)]
@ -76,6 +80,112 @@ pub enum Error {
Unknown(String), Unknown(String),
} }
/// `LibAFL` target configuration
#[derive(Debug, Clone)]
pub enum Configuration {
/// Default uninstrumented configurations
Default,
/// Sanitizing addresses
AddressSanitizer,
/// Sanitizing undefined behavior
UndefinedBehaviorSanitizer,
/// Generating a coverage map
GenerateCoverageMap,
/// Generating coverage profile data for `llvm-cov`
GenerateCoverageProfile,
/// Instrumenting for cmplog/redqueen
CmpLog,
/// A compound `Configuration`, made up of a list of other `Configuration`s
Compound(Vec<Self>),
}
impl Configuration {
/// Get compiler flags for this `Configuration`
pub fn to_flags(&self) -> Result<Vec<String>, Error> {
Ok(match self {
Configuration::Default => vec![],
Configuration::AddressSanitizer => vec!["-fsanitize=address".to_string()],
Configuration::UndefinedBehaviorSanitizer => vec!["-fsanitize=undefined".to_string()],
Configuration::GenerateCoverageMap => {
vec!["-fsanitize-coverage=trace-pc-guard".to_string()]
}
Configuration::CmpLog => vec!["-fsanitize-coverage=trace-cmp".to_string()],
Configuration::GenerateCoverageProfile => {
vec![
"-fprofile-instr-generate".to_string(),
"-fcoverage-mapping".to_string(),
]
}
Configuration::Compound(configurations) => {
let mut result: Vec<String> = vec![];
for configuration in configurations {
result.extend(configuration.to_flags()?);
}
result
}
})
}
/// Insert a `Configuration` specific 'tag' in the extension of the given file
#[must_use]
pub fn replace_extension(&self, path: &Path) -> std::path::PathBuf {
let mut parent = if let Some(parent) = path.parent() {
parent.to_path_buf()
} else {
std::path::PathBuf::from("")
};
let output = path.file_name().unwrap();
let output = output.to_str().unwrap();
let new_filename = if let Some((filename, extension)) = output.split_once('.') {
if let crate::Configuration::Default = self {
format!("{filename}.{extension}")
} else {
format!("{filename}.{self}.{extension}")
}
} else if let crate::Configuration::Default = self {
output.to_string()
} else {
format!("{output}.{self}")
};
parent.push(new_filename);
parent
}
}
impl std::str::FromStr for Configuration {
type Err = ();
fn from_str(input: &str) -> Result<Configuration, Self::Err> {
Ok(match input {
"asan" => Configuration::AddressSanitizer,
"ubsan" => Configuration::UndefinedBehaviorSanitizer,
"coverage" => Configuration::GenerateCoverageMap,
"llvm-cov" => Configuration::GenerateCoverageProfile,
"cmplog" => Configuration::CmpLog,
_ => Configuration::Default,
})
}
}
impl std::fmt::Display for Configuration {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Configuration::Default => write!(f, ""),
Configuration::AddressSanitizer => write!(f, "asan"),
Configuration::UndefinedBehaviorSanitizer => write!(f, "ubsan"),
Configuration::GenerateCoverageMap => write!(f, "coverage"),
Configuration::GenerateCoverageProfile => write!(f, "llvm-cov"),
Configuration::CmpLog => write!(f, "cmplog"),
Configuration::Compound(configurations) => {
let mut result: Vec<String> = vec![];
for configuration in configurations {
result.push(format!("{configuration}"));
}
write!(f, "{}", result.join("_"))
}
}
}
}
// TODO macOS // TODO macOS
/// extension for static libraries /// extension for static libraries
#[cfg(windows)] #[cfg(windows)]
@ -91,29 +201,19 @@ pub const LIB_PREFIX: &str = "";
#[cfg(not(windows))] #[cfg(not(windows))]
pub const LIB_PREFIX: &str = "lib"; pub const LIB_PREFIX: &str = "lib";
/// Wrap a compiler hijacking its arguments /// Wrap a tool hijacking its arguments
pub trait CompilerWrapper { pub trait ToolWrapper {
/// Set the wrapper arguments parsing a command line set of arguments /// Set the wrapper arguments parsing a command line set of arguments
fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error> fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error>
where where
S: AsRef<str>; S: AsRef<str>;
/// Add a compiler argument /// Add an argument
fn add_arg<S>(&mut self, arg: S) -> &'_ mut Self fn add_arg<S>(&mut self, arg: S) -> &'_ mut Self
where where
S: AsRef<str>; S: AsRef<str>;
/// Add a compiler argument only when compiling /// Add arguments
fn add_cc_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>;
/// Add a compiler argument only when linking
fn add_link_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>;
/// Add compiler arguments
fn add_args<S>(&mut self, args: &[S]) -> &'_ mut Self fn add_args<S>(&mut self, args: &[S]) -> &'_ mut Self
where where
S: AsRef<str>, S: AsRef<str>,
@ -124,6 +224,87 @@ pub trait CompilerWrapper {
self self
} }
/// Add a `Configuration`
fn add_configuration(&mut self, configuration: Configuration) -> &'_ mut Self;
/// Command to run the compiler
fn command(&mut self) -> Result<Vec<String>, Error>;
/// Command to run the compiler for a given `Configuration`
#[allow(clippy::too_many_lines)]
fn command_for_configuration(
&mut self,
configuration: Configuration,
) -> Result<Vec<String>, Error>;
/// Get the list of requested `Configuration`s
fn configurations(&self) -> Result<Vec<Configuration>, Error>;
/// Whether to ignore the configured `Configurations`. Useful for e.g. nested calls to
/// `libafl_cc` from `libafl_libtool`.
fn ignore_configurations(&self) -> Result<bool, Error>;
/// Get if in linking mode
fn is_linking(&self) -> bool;
/// Filter out argumets
fn filter(&self, _args: &mut Vec<String>) {}
/// Silences `libafl_cc` output
fn silence(&mut self, value: bool) -> &'_ mut Self;
/// Returns `true` if `silence` was called with `true`
fn is_silent(&self) -> bool;
/// Run the tool
fn run(&mut self) -> Result<Option<i32>, Error> {
let mut last_status = Ok(None);
let configurations = if self.ignore_configurations()? {
vec![Configuration::Default]
} else {
self.configurations()?
};
for configuration in configurations {
let mut args = self.command_for_configuration(configuration)?;
self.filter(&mut args);
if !self.is_silent() {
dbg!(args.clone());
}
if args.is_empty() {
last_status = Err(Error::InvalidArguments(
"The number of arguments cannot be 0".into(),
));
continue;
}
let status = match Command::new(&args[0]).args(&args[1..]).status() {
Ok(s) => s,
Err(e) => {
last_status = Err(Error::Io(e));
continue;
}
};
if !self.is_silent() {
dbg!(status);
}
last_status = Ok(status.code());
}
last_status
}
}
/// Wrap a compiler hijacking its arguments
pub trait CompilerWrapper: ToolWrapper {
/// Add a compiler argument only when compiling
fn add_cc_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>;
/// Add a compiler argument only when linking
fn add_link_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>;
/// Add compiler arguments only when compiling /// Add compiler arguments only when compiling
fn add_cc_args<S>(&mut self, args: &[S]) -> &'_ mut Self fn add_cc_args<S>(&mut self, args: &[S]) -> &'_ mut Self
where where
@ -150,42 +331,4 @@ pub trait CompilerWrapper {
fn link_staticlib<S>(&mut self, dir: &Path, name: S) -> &'_ mut Self fn link_staticlib<S>(&mut self, dir: &Path, name: S) -> &'_ mut Self
where where
S: AsRef<str>; S: AsRef<str>;
/// Command to run the compiler
fn command(&mut self) -> Result<Vec<String>, Error>;
/// Get if in linking mode
fn is_linking(&self) -> bool;
/// Filter out argumets
fn filter(&self, _args: &mut Vec<String>) {}
/// Silences `libafl_cc` output
fn silence(&mut self, value: bool) -> &'_ mut Self;
/// Returns `true` if `silence` was called with `true`
fn is_silent(&self) -> bool;
/// Run the compiler
fn run(&mut self) -> Result<Option<i32>, Error> {
let mut args = self.command()?;
self.filter(&mut args);
if !self.is_silent() {
dbg!(args.clone());
}
if args.is_empty() {
return Err(Error::InvalidArguments(
"The number of arguments cannot be 0".into(),
));
}
let status = match Command::new(&args[0]).args(&args[1..]).status() {
Ok(s) => s,
Err(e) => return Err(Error::Io(e)),
};
if !self.is_silent() {
dbg!(status);
}
Ok(status.code())
}
} }

291
libafl_cc/src/libtool.rs Normal file
View File

@ -0,0 +1,291 @@
//! Libtool Wrapper from `LibAFL`
// call make passing LIBTOOL=/path/to/target/release/libafl_libtool
use std::{convert::Into, env, path::PathBuf, str::FromStr, string::String, vec::Vec};
use crate::{Error, ToolWrapper, LIB_EXT, LIB_PREFIX};
/// Wrap Clang
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct LibtoolWrapper {
is_silent: bool,
name: String,
linking: bool,
need_libafl_arg: bool,
has_libafl_arg: bool,
output: Option<PathBuf>,
configurations: Vec<crate::Configuration>,
parse_args_called: bool,
base_args: Vec<String>,
}
#[allow(clippy::match_same_arms)] // for the linking = false wip for "shared"
impl ToolWrapper for LibtoolWrapper {
#[allow(clippy::too_many_lines)]
fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error>
where
S: AsRef<str>,
{
let mut new_args: Vec<String> = vec![];
if args.is_empty() {
return Err(Error::InvalidArguments(
"The number of arguments cannot be 0".to_string(),
));
}
if self.parse_args_called {
return Err(Error::Unknown(
"ToolWrapper::parse_args cannot be called twice on the same instance".to_string(),
));
}
self.parse_args_called = true;
if args.len() == 1 {
return Err(Error::InvalidArguments(
"LibAFL Tool wrapper - no commands specified. Use me as compiler.".to_string(),
));
}
self.name = args[0].as_ref().to_string();
let mut linking = true;
// Detect stray -v calls from ./configure scripts.
if args.len() > 1 && args[1].as_ref() == "-v" {
if args.len() == 2 {
self.base_args.push(args[1].as_ref().into());
return Ok(self);
}
linking = false;
}
let mut suppress_linking = 0;
let mut i = 1;
while i < args.len() {
match args[i].as_ref() {
"--libafl-no-link" => {
suppress_linking += 1;
self.has_libafl_arg = true;
i += 1;
continue;
}
"--libafl" => {
suppress_linking += 1337;
self.has_libafl_arg = true;
i += 1;
continue;
}
"-fsanitize=fuzzer-no-link" => {
suppress_linking += 1;
self.has_libafl_arg = true;
i += 1;
continue;
}
"-fsanitize=fuzzer" => {
suppress_linking += 1337;
self.has_libafl_arg = true;
i += 1;
continue;
}
"--libafl-configurations" => {
if i + 1 < args.len() {
self.configurations.extend(
args[i + 1]
.as_ref()
.split(',')
.map(|x| crate::Configuration::from_str(x).unwrap()),
);
i += 2;
continue;
}
}
"-o" => {
if i + 1 < args.len() {
self.output = Some(PathBuf::from(args[i + 1].as_ref()));
i += 2;
continue;
}
}
_ => (),
};
new_args.push(args[i].as_ref().to_string());
i += 1;
}
if linking
&& (suppress_linking > 0 || (self.has_libafl_arg && suppress_linking == 0))
&& suppress_linking < 1337
{
linking = false;
new_args.push(
PathBuf::from(env!("OUT_DIR"))
.join(format!("{LIB_PREFIX}no-link-rt.{LIB_EXT}"))
.into_os_string()
.into_string()
.unwrap(),
);
}
self.linking = linking;
// Libraries needed by libafl on Windows
self.base_args.extend(new_args);
Ok(self)
}
fn add_arg<S>(&mut self, arg: S) -> &'_ mut Self
where
S: AsRef<str>,
{
self.base_args.push(arg.as_ref().to_string());
self
}
fn add_configuration(&mut self, configuration: crate::Configuration) -> &'_ mut Self {
self.configurations.push(configuration);
self
}
fn configurations(&self) -> Result<Vec<crate::Configuration>, Error> {
let configs = self.configurations.clone();
Ok(configs)
}
fn ignore_configurations(&self) -> Result<bool, Error> {
Ok(false)
}
fn command(&mut self) -> Result<Vec<String>, Error> {
self.command_for_configuration(crate::Configuration::Default)
}
fn command_for_configuration(
&mut self,
configuration: crate::Configuration,
) -> Result<Vec<String>, Error> {
let mut args = vec![];
let base_args = self
.base_args
.iter()
.map(|r| {
let arg_as_path = std::path::PathBuf::from(r);
if r.ends_with('.') {
r.to_string()
} else {
if let Some(extension) = arg_as_path.extension() {
let extension = extension.to_str().unwrap();
let extension_lowercase = extension.to_lowercase();
match &extension_lowercase[..] {
"o" | "lo" | "a" | "la" | "so" => {
configuration.replace_extension(&arg_as_path)
}
_ => arg_as_path,
}
} else {
arg_as_path
}
.into_os_string()
.into_string()
.unwrap()
}
})
.collect::<Vec<_>>();
let libtool_path = if let Ok(libtool_dir) = std::env::var("LIBTOOL_DIR") {
format!("{libtool_dir}/libtool")
} else {
"./libtool".to_string()
};
assert!(
std::path::Path::new(&libtool_path).exists(),
"Couldn't find libtool. Specify the `LIBTOOL_DIR` environment variable"
);
args.push(libtool_path);
if let Some(output) = self.output.clone() {
let output = configuration.replace_extension(&output);
let new_filename = output.into_os_string().into_string().unwrap();
let dash_c_position = base_args.iter().position(|x| x == "-c");
if let Some(dash_c_position) = dash_c_position {
args.extend_from_slice(&base_args[..dash_c_position]);
args.extend_from_slice(&configuration.to_flags()?);
args.push("--libafl-ignore-configurations".to_string());
args.push("-c".to_string());
args.push("-o".to_string());
args.push(new_filename);
args.extend_from_slice(&base_args[(dash_c_position + 1)..]);
} else {
args.extend_from_slice(base_args.as_slice());
args.extend_from_slice(&configuration.to_flags()?);
args.push("--libafl-ignore-configurations".to_string());
args.push("-o".to_string());
args.push(new_filename);
}
} else {
args.extend_from_slice(base_args.as_slice());
args.extend_from_slice(&configuration.to_flags()?);
}
if self.need_libafl_arg && !self.has_libafl_arg {
return Ok(args);
}
Ok(args)
}
fn is_linking(&self) -> bool {
self.linking
}
fn filter(&self, _args: &mut Vec<String>) {}
fn silence(&mut self, value: bool) -> &'_ mut Self {
self.is_silent = value;
self
}
fn is_silent(&self) -> bool {
self.is_silent
}
}
impl Default for LibtoolWrapper {
/// Create a new Clang Wrapper
#[must_use]
fn default() -> Self {
Self::new()
}
}
impl LibtoolWrapper {
/// Create a new Clang Wrapper
#[must_use]
pub fn new() -> Self {
Self {
name: String::new(),
linking: false,
need_libafl_arg: false,
has_libafl_arg: false,
output: None,
configurations: vec![crate::Configuration::Default],
parse_args_called: false,
base_args: vec![],
is_silent: false,
}
}
/// Set if linking
pub fn linking(&mut self, value: bool) -> &'_ mut Self {
self.linking = value;
self
}
/// Set if it needs the --libafl arg to add the custom arguments to clang
pub fn need_libafl_arg(&mut self, value: bool) -> &'_ mut Self {
self.need_libafl_arg = value;
self
}
}