Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugins feature #392

Merged
merged 1 commit into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ jobs:
matrix:
environment: [ubuntu-latest, macos-latest]
toolchain: [stable, nightly]
features: [default, plugins]
cc: [cc, clang]
exclude:
- environment: macos-latest
features: plugins
- toolchain: stable
features: plugins
include:
- cc: cc
cxx: c++
Expand All @@ -45,21 +51,37 @@ jobs:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
steps:
- if: ${{ matrix.environment == 'ubuntu-latest' }}
run: sudo apt-get install llvm
- uses: actions/checkout@v4
with:
submodules: true
- name: Rustup
run: rustup default ${{ matrix.toolchain }}
- name: Install LLVM
id: install-llvm
run: |
LLVM_VERSION="$(rustc --version -v | grep '^LLVM version:' | grep -o '[0-9]\+' | head -n 1)"
if [[ ${{ matrix.environment }} = 'macos-latest' ]]; then
brew update
brew install llvm@"$LLVM_VERSION" || true
echo "PATH=/usr/local/opt/llvm/bin:$PATH" >> "$GITHUB_OUTPUT"
else
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh "$LLVM_VERSION"
echo "PATH=$PATH" >> "$GITHUB_OUTPUT"
fi
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
- name: Build
run: cargo build -vv
run: cargo build --features=${{ matrix.features }} -vv
env:
PATH: ${{ steps.install-llvm.outputs.path }}
- name: Run afl-system-config
run: cargo run -- afl system-config
run: cargo run --features=${{ matrix.features }} -- afl system-config
- name: Build examples (with AFL instrumentation)
run: cargo run -- afl build --examples -vv
run: cargo run --features=${{ matrix.features }} -- afl build --examples -vv
- name: Run tests
run: cargo test -p cargo-afl -vv
run: cargo test --features=${{ matrix.features }} -p cargo-afl -vv
all-checks:
needs: [lint, build]
runs-on: ubuntu-latest
Expand Down
9 changes: 8 additions & 1 deletion afl/examples/cmplog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ fn main() {
// This fuzz harness demonstrates the capabilities of CmpLog.
// Simply run the fuzzer and it should find the crash immediately.
afl::fuzz!(|data: &[u8]| {
if data.len() != 16 {
if data.len() < 29 {
return;
}
if data[0] != b'A' {
Expand All @@ -26,6 +26,13 @@ fn main() {
return;
};

let slice = &data[16..];
let match_string = "Hello, world!";
let compare_string = String::from_utf8(slice.to_vec()).unwrap_or_default();
if compare_string != match_string {
return;
}

panic!("BOOM");
});
}
4 changes: 4 additions & 0 deletions cargo-afl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ assert_cmd = "2.0"
ctor = "0.2"
predicates = "3.0"
tempfile = "3.8"

[features]
default = []
plugins = []
81 changes: 78 additions & 3 deletions cargo-afl/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,34 @@ fn main() {

build_afl(&work_dir, base.as_deref());
build_afl_llvm_runtime(&work_dir, base.as_deref());

if cfg!(feature = "plugins") {
copy_afl_llvm_plugins(&work_dir, base.as_deref());
}
smoelius marked this conversation as resolved.
Show resolved Hide resolved
}

fn build_afl(work_dir: &Path, base: Option<&Path>) {
// if you had already installed cargo-afl previously you **must** clean AFL++
let mut command = Command::new("make");
command
.current_dir(work_dir)
.args(["clean", "all", "install"])
.args(["clean", "install"])
// skip the checks for the legacy x86 afl-gcc compiler
.env("AFL_NO_X86", "1")
// build just the runtime to avoid troubles with Xcode clang on macOS
.env("NO_BUILD", "1")
//.env("NO_BUILD", "1")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self to revisit this.

.env("DESTDIR", common::afl_dir(base))
.env("PREFIX", "")
.env_remove("DEBUG");
let status = command.status().expect("could not run 'make'");

if cfg!(feature = "plugins") {
let llvm_config = check_llvm_and_get_config();
command.env("LLVM_CONFIG", llvm_config);
}

let status = command
.status()
.expect("could not run 'make clean install'");
assert!(status.success());
smoelius marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -94,6 +107,68 @@ fn build_afl_llvm_runtime(work_dir: &Path, base: Option<&Path>) {
assert!(status.success());
}

fn copy_afl_llvm_plugins(work_dir: &Path, base: Option<&Path>) {
// Iterate over the files in the directory.
if let Ok(entries) = work_dir.read_dir() {
for entry in entries.flatten() {
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();

// Get the file extension.
if let Some(extension) = file_name_str.split('.').last() {
// Only copy the files that are shared objects
if extension == "so" {
// Attempt to copy the shared object file.
std::fs::copy(
work_dir.join(&file_name),
common::afl_llvm_dir(base).join(&file_name),
)
.unwrap_or_else(|_| {
panic!("Couldn't copy shared object file {file_name_str}",)
});
}
}
}
} else {
eprintln!("Failed to read the work directory. Aborting.");
}
}

fn check_llvm_and_get_config() -> 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();

// 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
let llvm_config = if cfg!(target_os = "macos") {
"llvm-config".to_string()
} else {
format!("llvm-config-{llvm_version}")
};

// check if llvm tools are installed and with the good version for the plugin compilation
let mut command = Command::new(llvm_config.clone());
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
}

#[cfg(unix)]
mod sys {
use std::fs::File;
Expand Down
73 changes: 54 additions & 19 deletions cargo-afl/src/bin/cargo-afl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use clap::crate_version;

use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
use std::process::{self, Command, Stdio};
Expand Down Expand Up @@ -99,22 +98,28 @@ fn main() {
fn clap_app() -> clap::Command {
use clap::{value_parser, Arg, Command};

let help = "In addition to the subcommands above, Cargo subcommands are also \
const HELP: &str = "In addition to the subcommands above, Cargo subcommands are also \
supported (see `cargo help` for a list of all Cargo subcommands).";

const VERSION: &str = if cfg!(feature = "plugins") {
concat!(env!("CARGO_PKG_VERSION"), " [feature=plugins]")
} else {
env!("CARGO_PKG_VERSION")
};

Command::new("cargo afl")
.display_name("cargo")
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("afl")
.version(crate_version!())
.version(VERSION)
.subcommand_required(true)
.arg_required_else_help(true)
.allow_external_subcommands(true)
.external_subcommand_value_parser(value_parser!(OsString))
.override_usage("cargo afl [SUBCOMMAND or Cargo SUBCOMMAND]")
.after_help(help)
.after_help(HELP)
.subcommand(
Command::new("addseeds")
.about("Invoke afl-addseeds")
Expand Down Expand Up @@ -316,18 +321,48 @@ 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(None);
let p = binding.display();

let mut rustflags = format!(
"-C debug-assertions \
-C overflow_checks \
-C passes={passes} \
-C codegen-units=1 \
-C llvm-args=-sanitizer-coverage-level=3 \
-C llvm-args=-sanitizer-coverage-trace-pc-guard \
-C llvm-args=-sanitizer-coverage-prune-blocks=0 \
-C llvm-args=-sanitizer-coverage-trace-compares \
-C opt-level=3 \
-C target-cpu=native "
-C overflow_checks \
-C passes={passes} \
-C codegen-units=1 \
-C opt-level=3 \
-C target-cpu=native "
);
let mut environment_variables = HashMap::<&str, String>::new();
environment_variables.insert("ASAN_OPTIONS", asan_options);
environment_variables.insert("TSAN_OPTIONS", tsan_options);

if cfg!(feature = "plugins") {
// 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 CMPLOG and other advanced AFL++ features"
);

rustflags.push_str(&format!(
"-Z llvm-plugins={p}/cmplog-instructions-pass.so \
-Z llvm-plugins={p}/cmplog-routines-pass.so \
-Z llvm-plugins={p}/cmplog-switches-pass.so \
-Z llvm-plugins={p}/SanitizerCoveragePCGUARD.so \
-Z llvm-plugins={p}/afl-llvm-dict2file.so
"
));
smoelius marked this conversation as resolved.
Show resolved Hide resolved

environment_variables.insert("AFL_QUIET", "1".to_string());
} else {
rustflags.push_str(
"-C llvm-args=-sanitizer-coverage-level=3 \
-C llvm-args=-sanitizer-coverage-trace-pc-guard \
-C llvm-args=-sanitizer-coverage-prune-blocks=0 \
-C llvm-args=-sanitizer-coverage-trace-compares
",
);
}
smoelius marked this conversation as resolved.
Show resolved Hide resolved

let no_cfg_fuzzing = env::var("AFL_NO_CFG_FUZZING").is_ok();
if no_cfg_fuzzing {
Expand All @@ -353,20 +388,20 @@ where
rustflags.push_str(&format!(
"-l afl-llvm-rt \
-L {} ",
common::afl_llvm_rt_dir(None).display()
common::afl_llvm_dir(None).display()
));

// add user provided flags
rustflags.push_str(&env::var("RUSTFLAGS").unwrap_or_default());
rustdocflags.push_str(&env::var("RUSTDOCFLAGS").unwrap_or_default());

environment_variables.insert("RUSTFLAGS", rustflags);
environment_variables.insert("RUSTDOCFLAGS", rustdocflags);

let status = Command::new(cargo_path)
.arg(subcommand)
.args(args)
.env("RUSTFLAGS", &rustflags)
.env("RUSTDOCFLAGS", &rustdocflags)
.env("ASAN_OPTIONS", asan_options)
.env("TSAN_OPTIONS", tsan_options)
.envs(&environment_variables)
.status()
.unwrap();
process::exit(status.code().unwrap_or(1));
Expand Down
8 changes: 4 additions & 4 deletions cargo-afl/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ pub fn afl_dir(base: Option<&Path>) -> PathBuf {

#[allow(dead_code)]
#[must_use]
pub fn afl_llvm_rt_dir(base: Option<&Path>) -> PathBuf {
data_dir(base, "afl-llvm-rt")
pub fn afl_llvm_dir(base: Option<&Path>) -> PathBuf {
smoelius marked this conversation as resolved.
Show resolved Hide resolved
data_dir(base, "afl-llvm")
}

#[allow(dead_code)]
#[must_use]
pub fn object_file_path(base: Option<&Path>) -> PathBuf {
afl_llvm_rt_dir(base).join("libafl-llvm-rt.o")
afl_llvm_dir(base).join("libafl-llvm-rt.o")
}

#[allow(dead_code)]
#[must_use]
pub fn archive_file_path(base: Option<&Path>) -> PathBuf {
afl_llvm_rt_dir(base).join("libafl-llvm-rt.a")
afl_llvm_dir(base).join("libafl-llvm-rt.a")
}