Skip to content
Open
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
1 change: 1 addition & 0 deletions Dockerfile.coverage
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ COPY ./fuzzamoto-libafl/src/ src/
WORKDIR /fuzzamoto/fuzzamoto-scenarios
COPY ./fuzzamoto-scenarios/Cargo.toml .
COPY ./fuzzamoto-scenarios/bin/ bin/
COPY ./fuzzamoto-scenarios/rpcs.txt rpcs.txt

WORKDIR /fuzzamoto
COPY ./Cargo.toml .
Expand Down
123 changes: 115 additions & 8 deletions fuzzamoto-cli/src/commands/coverage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::error::{CliError, Result};
use crate::utils::{file_ops, process};
use crate::utils::{
file_ops::{self, ensure_dir_exists},
process,
};
use std::path::PathBuf;

pub struct CoverageCommand;
Expand All @@ -9,17 +12,19 @@ impl CoverageCommand {
output: PathBuf,
corpus: PathBuf,
bitcoind: PathBuf,
scenario: PathBuf,
scenarios: Vec<Scenario>,
profraws: Option<Vec<PathBuf>>,
run_only: bool,
) -> Result<()> {
file_ops::ensure_file_exists(&bitcoind)?;
file_ops::ensure_file_exists(&scenario)?;
for scenario in &scenarios {
file_ops::ensure_file_exists(scenario.path())?;
}

if run_only {
let corpus_files = file_ops::read_dir_files(&corpus)?;
for corpus_file in corpus_files {
if let Err(e) = Self::run_one_input(&output, &corpus_file, &bitcoind, &scenario) {
if let Err(e) = Self::run_one_input(&output, &corpus_file, &bitcoind, &scenarios) {
log::error!("Failed to run input ({:?}): {}", corpus_file, e);
}
}
Expand All @@ -33,7 +38,8 @@ impl CoverageCommand {
log::info!("{:?}", corpus_files);
// Run scenario for each corpus file
for corpus_file in corpus_files {
if let Err(e) = Self::run_one_input(&output, &corpus_file, &bitcoind, &scenario)
if let Err(e) =
Self::run_one_input(&output, &corpus_file, &bitcoind, &scenarios)
{
log::error!("Failed to run input ({:?}): {}", corpus_file, e);
}
Expand All @@ -52,7 +58,7 @@ impl CoverageCommand {
output: &PathBuf,
input: &PathBuf,
bitcoind: &PathBuf,
scenario: &PathBuf,
scenarios: &Vec<Scenario>,
) -> Result<()> {
log::info!("Running scenario with input: {}", input.display());

Expand All @@ -66,8 +72,9 @@ impl CoverageCommand {
("FUZZAMOTO_INPUT", input.to_str().unwrap()),
("RUST_LOG", "debug"),
];

process::run_scenario_command(scenario, bitcoind, &env_vars)?;
for scenario in scenarios {
process::run_scenario_command(scenario, bitcoind, &env_vars)?;
}

Ok(())
}
Expand Down Expand Up @@ -143,3 +150,103 @@ impl CoverageCommand {
Ok(merged)
}
}

#[derive(Clone, Copy, Debug)]
pub enum ScenarioType {
CompactBlocks,
Generic,
HttpServer,
ImportMempool,
IR,
RPCGeneric,
WalletMigration,
}

impl ScenarioType {
pub fn from_filename(filename: &str) -> Option<Self> {
match filename {
"scenario-compact-blocks" => Some(Self::CompactBlocks),
"scenario-generic" => Some(Self::Generic),
"scenario-http-server" => Some(Self::HttpServer),
"scenario-import-mempool" => Some(Self::ImportMempool),
"scenario-ir" => Some(Self::IR),
"scenario-rpc-generic" => Some(Self::RPCGeneric),
"scenario-wallet-migration" => Some(Self::WalletMigration),
_ => None,
}
}
}

pub struct Scenario {
path: PathBuf,
ty: ScenarioType,
}

impl Scenario {
pub fn from_path(path: &PathBuf) -> Option<Self> {
let filename = path.file_name()?.to_str()?;
let ty = ScenarioType::from_filename(filename)?;
Some(Self {
path: path.to_path_buf(),
ty,
})
}

pub fn name(&self) -> &str {
match self.ty {
ScenarioType::CompactBlocks => "scenario-compact-blocks",
ScenarioType::Generic => "scenario-generic",
ScenarioType::HttpServer => "scenario-http-server",
ScenarioType::ImportMempool => "scenario-import-mempool",
ScenarioType::IR => "scenario-ir",
ScenarioType::RPCGeneric => "scenario-rpc-generic",
ScenarioType::WalletMigration => "scenario-wallet-migration",
}
}

pub fn path(&self) -> &PathBuf {
&self.path
}

pub fn ty(&self) -> ScenarioType {
self.ty
}
}

impl std::fmt::Display for Scenario {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}

impl std::fmt::Debug for Scenario {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}

pub fn scan_scenario_dir(dir: &PathBuf) -> Result<Vec<Scenario>> {
ensure_dir_exists(dir)?;

let mut scenarios = Vec::new();

for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if let Some(scenario) = Scenario::from_path(&path) {
scenarios.push(scenario);
}
}

log::info!(
"Found {} scenarios in {:?}: {:?}",
scenarios.len(),
dir,
scenarios
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ")
);

Ok(scenarios)
}
59 changes: 49 additions & 10 deletions fuzzamoto-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ use commands::*;
use error::Result;
use std::path::PathBuf;

use crate::commands::coverage_batch::CoverageBatchCommand;
use crate::{
commands::coverage_batch::CoverageBatchCommand,
coverage::{Scenario, scan_scenario_dir},
error::CliError,
};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
Expand Down Expand Up @@ -68,7 +72,13 @@ enum Commands {
long,
help = "Path to the fuzzamoto scenario binary that should be run with coverage measurer"
)]
scenario: PathBuf,
scenario: Option<PathBuf>,
#[arg(
long,
conflicts_with = "scenario",
help = "Path to directory containing scenario-* binaries"
)]
scenario_dir: Option<PathBuf>,
#[arg(
long,
value_name = "PROFRAWS",
Expand Down Expand Up @@ -147,16 +157,45 @@ fn main() -> Result<()> {
corpus,
bitcoind,
scenario,
scenario_dir,
profraws,
run_only,
} => CoverageCommand::execute(
output.clone(),
corpus.clone(),
bitcoind.clone(),
scenario.clone(),
profraws.clone(),
*run_only,
),
} => {
let scenarios: Vec<Scenario> = if let Some(dir) = scenario_dir {
scan_scenario_dir(&dir)?
} else if let Some(s) = scenario {
match Scenario::from_path(s) {
Some(s) => vec![s],
None => {
return Err(
CliError::InvalidInput(format!("Invalid scenario: {:?}", s)).into()
);
}
}
} else {
return Err(CliError::InvalidInput(
"Must specify either --scenario or --scenario-dir".to_string(),
)
.into());
};

if scenarios.is_empty() {
return Err(CliError::InvalidInput("No scenarios found".to_string()).into());
}

for scenario in &scenarios {
log::info!("Running coverage measurement on {:?}", scenario);
}

CoverageCommand::execute(
output.clone(),
corpus.clone(),
bitcoind.clone(),
scenarios,
profraws.clone(),
*run_only,
)
}
Commands::CoverageBatch {
output,
corpus,
Expand Down
9 changes: 8 additions & 1 deletion fuzzamoto-cli/src/utils/file_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ pub fn copy_file_to_dir(src: &Path, dst_dir: &Path) -> Result<()> {
}

pub fn ensure_file_exists(path: &Path) -> Result<()> {
if !path.exists() {
if !path.exists() || !path.is_file() {
return Err(CliError::FileNotFound(path.display().to_string()));
}
Ok(())
}

pub fn ensure_dir_exists(path: &Path) -> Result<()> {
if !path.exists() || !path.is_dir() {
return Err(CliError::FileNotFound(path.display().to_string()));
}
Ok(())
Expand Down
14 changes: 11 additions & 3 deletions fuzzamoto-cli/src/utils/process.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::error::{CliError, Result};
use crate::{
coverage::{Scenario, ScenarioType},
error::{CliError, Result},
};
use std::path::Path;
use std::process::{Command, Stdio};

Expand Down Expand Up @@ -56,13 +59,18 @@ pub fn run_command_with_output(
}

pub fn run_scenario_command(
scenario: &Path,
scenario: &Scenario,
bitcoind: &Path,
env_vars: &[(&str, &str)],
) -> Result<()> {
let mut cmd = Command::new(scenario);
let mut cmd = Command::new(scenario.path());

cmd.arg(bitcoind);

if matches!(scenario.ty(), ScenarioType::RPCGeneric) {
cmd.arg("/fuzzamoto/fuzzamoto-scenarios/rpcs.txt");
}

for (key, value) in env_vars {
cmd.env(key, value);
}
Expand Down
4 changes: 2 additions & 2 deletions fuzzamoto-scenarios/bin/rpc_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ struct RpcScenario {
impl<'a> Scenario<'a, TestCase> for RpcScenario {
fn new(args: &[String]) -> Result<Self, String> {
let target = BitcoinCoreTarget::from_path(&args[1])?;
let rpcs =
fs::read_to_string(&args[2]).map_err(|e| format!("Failed to parse file: {}", e))?;
let rpcs = fs::read_to_string(&args[2])
.map_err(|e| format!("Failed to parse file {}: {}", args[2], e))?;

// Note that any change in the file may invalidate existing seeds
let mut available_rpcs: Vec<String> = vec![];
Expand Down