Skip to content
Draft
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
134 changes: 37 additions & 97 deletions fuzzamoto-cli/src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,49 @@ use std::path::PathBuf;
pub struct InitCommand;

impl InitCommand {
pub fn execute(
sharedir: PathBuf,
crash_handler: PathBuf,
bitcoind: PathBuf,
secondary_bitcoind: Option<PathBuf>,
scenario: PathBuf,
nyx_dir: Option<PathBuf>,
) -> Result<()> {
pub fn execute(sharedir: PathBuf, image: String, nyx_dir: Option<PathBuf>) -> Result<()> {
file_ops::ensure_sharedir_not_exists(&sharedir)?;
file_ops::create_dir_all(&sharedir)?;

file_ops::ensure_file_exists(&crash_handler)?;
file_ops::ensure_file_exists(&bitcoind)?;
file_ops::ensure_file_exists(&scenario)?;

if let Some(ref secondary) = secondary_bitcoind {
file_ops::ensure_file_exists(secondary)?;
// Check if the Docker image exists locally
log::info!("Checking if Docker image exists locally: {}", image);
let image_exists =
process::run_command_with_status("docker", &["image", "inspect", &image], None).is_ok();

if image_exists {
log::info!("Docker image already exists locally, skipping pull");
} else {
// Pull the Docker image
log::info!("Pulling Docker image: {}", image);
process::run_command_with_status("docker", &["pull", &image], None)?;
}

let mut all_deps = Vec::new();
let mut binary_names = Vec::new();

// Copy each binary and its dependencies
let mut binaries = vec![bitcoind, scenario.clone()];
if let Some(secondary) = secondary_bitcoind.clone() {
binaries.push(secondary);
}

for binary in &binaries {
let binary_name = binary
.file_name()
.ok_or_else(|| CliError::InvalidInput("Invalid binary path".to_string()))?
.to_str()
.ok_or_else(|| CliError::InvalidInput("Invalid binary name".to_string()))?;

file_ops::copy_file_to_dir(binary, &sharedir)?;
all_deps.push(binary_name.to_string());
binary_names.push(binary_name.to_string());

// Get and copy dependencies using lddtree
let output =
process::run_command_with_output("lddtree", &[binary.to_str().unwrap()], None)?;

// Parse lddtree output and copy dependencies
let deps = String::from_utf8_lossy(&output.stdout)
.lines()
.skip(1) // Skip first line
.filter_map(|line| {
let parts: Vec<&str> = line.split("=>").collect();
if parts.len() == 2 {
let name = parts[0].trim();
let path = parts[1].trim();

// Copy the dependency
if let Err(e) = std::fs::copy(path, sharedir.join(name)) {
log::warn!("Failed to copy {}: {}", name, e);
} else {
log::info!("Copied dependency of {}: {}", binary_name, name);
}

Some(name.to_string())
} else {
None
}
})
.collect::<Vec<String>>();

all_deps.extend(deps);
}

// Add crash handler to dependencies
let crash_handler_name = crash_handler
.file_name()
.ok_or_else(|| CliError::InvalidInput("Invalid crash handler path".to_string()))?
.to_str()
.ok_or_else(|| CliError::InvalidInput("Invalid crash handler name".to_string()))?
.to_string();
// Create a container from the image with a name
let container_name = "fuzzamoto-temp-container";
log::info!("Creating container from image: {}", image);
process::run_command_with_status(
"docker",
&["create", "--name", container_name, &image],
None,
)?;

file_ops::copy_file_to_dir(&crash_handler, &sharedir)?;
all_deps.push(crash_handler_name.clone());
all_deps.sort();
all_deps.dedup();
// Export the container to a tar file
let container_tar_path = sharedir.join("container.tar");
log::info!("Exporting container to: {}", container_tar_path.display());
process::run_command_with_status(
"docker",
&[
"export",
container_name,
"-o",
container_tar_path.to_str().unwrap(),
],
None,
)?;

log::info!("Created share directory: {}", sharedir.display());
// Clean up: remove the container
log::info!("Removing temporary container: {}", container_name);
process::run_command_with_status("docker", &["rm", container_name], None)?;

let nyx_dir = match nyx_dir {
Some(nyx_dir) => nyx_dir,
Expand All @@ -99,26 +58,7 @@ impl InitCommand {
nyx::copy_packer_binaries(&nyx_dir, &sharedir)?;
nyx::generate_nyx_config(&nyx_dir, &sharedir)?;

// Create fuzz_no_pt.sh script
let scenario_name = scenario
.file_name()
.ok_or_else(|| CliError::InvalidInput("Invalid scenario path".to_string()))?
.to_str()
.ok_or_else(|| CliError::InvalidInput("Invalid scenario name".to_string()))?;

let secondary_name = secondary_bitcoind
.as_ref()
.and_then(|p| p.file_name())
.and_then(|name| name.to_str());

nyx::create_nyx_script(
&sharedir,
&all_deps,
&binary_names,
&crash_handler_name,
scenario_name,
secondary_name,
)?;
nyx::create_nyx_script(&sharedir)?;

Ok(())
}
Expand Down
37 changes: 5 additions & 32 deletions fuzzamoto-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,9 @@ enum Commands {
Init {
#[arg(long, help = "Path to the nyx share directory that should be created")]
sharedir: PathBuf,
#[arg(
long,
help = "Path to the crash handler that should be copied into the share directory"
)]
crash_handler: PathBuf,
#[arg(
long,
help = "Path to the bitcoind binary that should be copied into the share directory"
)]
bitcoind: PathBuf,
#[arg(
long,
help = "Path to the secondary bitcoind binary that should be copied into the share directory"
)]
secondary_bitcoind: Option<PathBuf>,
#[arg(
long,
help = "Path to the fuzzamoto scenario binary that should be copied into the share directory"
)]
scenario: PathBuf,

#[arg(long, help = "Container image containing the test workload")]
image: String,

#[arg(long, help = "Path to the nyx installation")]
nyx_dir: Option<PathBuf>,
Expand Down Expand Up @@ -81,19 +64,9 @@ fn main() -> Result<()> {
match &cli.command {
Commands::Init {
sharedir,
crash_handler,
bitcoind,
secondary_bitcoind,
scenario,
image,
nyx_dir,
} => InitCommand::execute(
sharedir.clone(),
crash_handler.clone(),
bitcoind.clone(),
secondary_bitcoind.clone(),
scenario.clone(),
nyx_dir.clone(),
),
} => InitCommand::execute(sharedir.clone(), image.clone(), nyx_dir.clone()),
Commands::Coverage {
output,
corpus,
Expand Down
78 changes: 18 additions & 60 deletions fuzzamoto-cli/src/utils/nyx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,7 @@ pub fn generate_nyx_config(nyx_path: &Path, sharedir: &Path) -> Result<()> {
Ok(())
}

pub fn create_nyx_script(
sharedir: &Path,
all_deps: &[String],
binary_names: &[String],
crash_handler_name: &str,
scenario_name: &str,
secondary_bitcoind: Option<&str>,
) -> Result<()> {
pub fn create_nyx_script(sharedir: &Path) -> Result<()> {
let mut script = Vec::new();

script.push("chmod +x hget".to_string());
Expand All @@ -105,63 +98,28 @@ pub fn create_nyx_script(
script.push("echo 0 > /proc/sys/kernel/printk".to_string());
script.push("./hget hcat_no_pt hcat".to_string());
script.push("./hget habort_no_pt habort".to_string());
script.push("chmod +x ./hcat".to_string());
script.push("chmod +x ./habort".to_string());

// Add dependencies
for dep in all_deps {
script.push(format!("./hget {} {}", dep, dep));
}

// Make executables
for exe in &["habort", "hcat", "ld-linux-x86-64.so.2", crash_handler_name] {
script.push(format!("chmod +x {}", exe));
}

for binary_name in binary_names {
script.push(format!("chmod +x {}", binary_name));
}
script.push("./hget container.tar container.tar".to_string());

script.push("export __AFL_DEFER_FORKSRV=1".to_string());
script.push("export __AFL_DEFER_FORKSRV=1".to_string()); // TODO why is this needed again?

// Network setup
// Enable networking through localhost
script.push("ip addr add 127.0.0.1/8 dev lo".to_string());
script.push("ip link set lo up".to_string());
script.push("ip a | ./hcat".to_string());

// Create bitcoind proxy script
let asan_options = [
"detect_leaks=1",
"detect_stack_use_after_return=1",
"check_initialization_order=1",
"strict_init_order=1",
"log_path=/tmp/asan.log",
"abort_on_error=1",
"handle_abort=1",
]
.join(":");

let asan_options = format!("ASAN_OPTIONS={}", asan_options);
let crash_handler_preload = format!("LD_PRELOAD=./{}", crash_handler_name);
let proxy_script = format!(
"{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./bitcoind \\$@",
asan_options, crash_handler_preload,
);

script.push("echo \"#!/bin/sh\" > ./bitcoind_proxy".to_string());
script.push(format!("echo \"{}\" >> ./bitcoind_proxy", proxy_script));
script.push("chmod +x ./bitcoind_proxy".to_string());

// Run the scenario
script.push(format!(
"RUST_LOG=debug LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 ./{} ./bitcoind_proxy {} > log.txt 2>&1",
scenario_name,
secondary_bitcoind.unwrap_or("")
));

// Debug info
script.push("cat log.txt | ./hcat".to_string());
script.push(
"./habort \"target has terminated without initializing the fuzzing agent ...\"".to_string(),
);
script.push("ip a | ./hcat".to_string()); // Maybe useful for debugging

// Unpack the container
script.push("mkdir rootfs/ && tar -xf container.tar -C /tmp/rootfs".to_string());
script.push("mount -t proc /proc rootfs/proc/".to_string());
script.push("mount --rbind /sys rootfs/sys/".to_string());
script.push("mount --rbind /dev rootfs/dev/".to_string());
// Launch the init script (init.sh is expected to exist)
script.push("chroot /tmp/rootfs /init.sh".to_string());
script.push("cat rootfs/init.log | ./hcat".to_string());

script.push("./habort \"$(tail rootfs/init.log)\"".to_string());

let script_path = sharedir.join("fuzz_no_pt.sh");
let script_content = script.join("\n");
Expand Down
Loading