From ddeeb9514c6896f7eda3d2708ff68dd99229f858 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Tue, 22 Sep 2026 14:58:32 +0200 Subject: [PATCH 1/2] smitebot: support bin/ layout --- smitebot/src/commands/bench_exec.rs | 4 ++-- smitebot/src/commands/corpus.rs | 4 ++-- smitebot/src/commands/doctor.rs | 14 +++++++------- smitebot/src/commands/start.rs | 4 ++-- smitebot/src/utils.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/smitebot/src/commands/bench_exec.rs b/smitebot/src/commands/bench_exec.rs index 8704c7d3..f90882b3 100644 --- a/smitebot/src/commands/bench_exec.rs +++ b/smitebot/src/commands/bench_exec.rs @@ -22,7 +22,7 @@ use crate::commands::build::{BuildInputs, run_build}; use crate::config::CampaignConfig; use crate::latency_stats::{LatencyStats, avg_duration, mean_stddev}; use crate::libnyx::{Libnyx, NyxReturn, PAYLOAD_HEADER_SIZE}; -use crate::utils::{pin_to_cpu, setup_nyx}; +use crate::utils::{afl_bin_dir, pin_to_cpu, setup_nyx}; /// Default number of timed executions when `--iterations` is not given. const DEFAULT_ITERATIONS: u64 = 1000; @@ -487,7 +487,7 @@ fn locate_libnyx(config: &CampaignConfig) -> Option { return None; } - let libnyx_path = config.aflpp_path.join("libnyx.so"); + let libnyx_path = afl_bin_dir(&config.aflpp_path).join("libnyx.so"); if !libnyx_path.exists() { log::error!( "{} not found; build AFL++ with Nyx support (see nyx_mode/README.md)", diff --git a/smitebot/src/commands/corpus.rs b/smitebot/src/commands/corpus.rs index 93fdcdd7..8342e0fe 100644 --- a/smitebot/src/commands/corpus.rs +++ b/smitebot/src/commands/corpus.rs @@ -10,7 +10,7 @@ use std::process::Command; use clap::{Args, Subcommand}; use crate::state::CampaignState; -use crate::utils::{find_in_path, is_executable}; +use crate::utils::{afl_bin_dir, find_in_path, is_executable}; /// Command handler for `smitebot corpus`. pub struct CorpusCommand; @@ -373,7 +373,7 @@ fn output_dir_occupied(output: &Path) -> bool { /// searching `$PATH`. fn find_afl_cmin(aflpp_path: Option<&Path>) -> Option { if let Some(path) = aflpp_path { - let candidate = path.join("afl-cmin"); + let candidate = afl_bin_dir(path).join("afl-cmin"); if is_executable(&candidate) { return Some(candidate); } diff --git a/smitebot/src/commands/doctor.rs b/smitebot/src/commands/doctor.rs index edf06bc2..ab0a912b 100644 --- a/smitebot/src/commands/doctor.rs +++ b/smitebot/src/commands/doctor.rs @@ -10,7 +10,7 @@ use clap::Args; use serde::Serialize; use crate::config::CampaignConfig; -use crate::utils::{find_in_path, is_executable}; +use crate::utils::{afl_bin_dir, find_in_path, is_executable}; /// AFL++ binaries required for campaign execution and corpus minimization. const AFL_TOOLS: &[&str] = &["afl-fuzz", "afl-cmin", "afl-tmin", "afl-whatsup"]; @@ -178,7 +178,7 @@ impl DoctorCommand { None => None, }; let inputs = DoctorInputs::resolve(config.as_ref(), args); - let aflpp_root = &inputs.aflpp_root; + let afl_bin = afl_bin_dir(&inputs.aflpp_root); let smite_dir = &inputs.smite_dir; // Keep a predictable order for operator readability and stable JSON output. @@ -190,14 +190,14 @@ impl DoctorCommand { ), DoctorCheck::new("/dev/kvm accessible", check_kvm_access()), DoctorCheck::new("Docker daemon reachable", check_docker_daemon()), - DoctorCheck::new("AFL++ built with Nyx support", check_libnyx(aflpp_root)), + DoctorCheck::new("AFL++ built with Nyx support", check_libnyx(&afl_bin)), DoctorCheck::new("VMware backdoor enabled", check_vmware_backdoor_enabled()), ]; for &tool in AFL_TOOLS { checks.push(DoctorCheck::new( tool, - require_executable(&aflpp_root.join(tool)), + require_executable(&afl_bin.join(tool)), )); } @@ -315,9 +315,9 @@ fn check_docker_daemon() -> Result<(), CheckFailure> { } } -/// Checks whether `libnyx.so` exists under the AFL++ root used for fuzzing. -fn check_libnyx(aflpp_root: &Path) -> Result<(), CheckFailure> { - if aflpp_root.join("libnyx.so").exists() { +/// Checks whether `libnyx.so` exists in AFL++'s binary directory. +fn check_libnyx(afl_bin: &Path) -> Result<(), CheckFailure> { + if afl_bin.join("libnyx.so").exists() { Ok(()) } else { Err(CheckFailure::LibnyxNotFound) diff --git a/smitebot/src/commands/start.rs b/smitebot/src/commands/start.rs index d6e21731..6e74e9ee 100644 --- a/smitebot/src/commands/start.rs +++ b/smitebot/src/commands/start.rs @@ -16,7 +16,7 @@ use crate::commands::build::{BuildInputs, run_build}; use crate::config::CampaignConfig; use crate::state::{CampaignState, RunnerState, Status}; use crate::tmux; -use crate::utils::{command_stdout, docker_image_id, setup_nyx, shell_quote}; +use crate::utils::{afl_bin_dir, command_stdout, docker_image_id, setup_nyx, shell_quote}; /// How long to wait for `fuzzer_stats` before treating alive runners as started. /// @@ -503,7 +503,7 @@ fn build_runner_shell_cmd( seed_dir: &Path, testcache_mb: Option, ) -> String { - let afl_fuzz = config.aflpp_path.join("afl-fuzz"); + let afl_fuzz = afl_bin_dir(&config.aflpp_path).join("afl-fuzz"); // -L (MOpt) is incompatible with custom mutators; runner_strategy skips it // when an IR scenario or the user's afl_env supplies AFL_CUSTOM_MUTATOR_LIBRARY. let has_custom_mutator = config.scenario.starts_with("ir") diff --git a/smitebot/src/utils.rs b/smitebot/src/utils.rs index d5adae1e..78f90e1e 100644 --- a/smitebot/src/utils.rs +++ b/smitebot/src/utils.rs @@ -23,6 +23,19 @@ pub fn is_executable(path: &Path) -> bool { fs::metadata(path).is_ok_and(|metadata| metadata.permissions().mode() & 0o111 != 0) } +/// Returns the directory holding AFL++'s binaries and `libnyx.so`. +/// +/// A source build keeps them at the tree root; an installed package puts them +/// under `bin/`. +pub fn afl_bin_dir(aflpp_path: &Path) -> PathBuf { + let bin = aflpp_path.join("bin"); + if bin.join("afl-fuzz").is_file() { + bin + } else { + aflpp_path.to_path_buf() + } +} + /// Wraps a string in single quotes for safe interpolation into a shell command. /// /// Embedded single quotes are escaped with the standard `'\''` idiom. Used for @@ -201,6 +214,22 @@ mod tests { assert!(found.is_none()); } + #[test] + fn afl_bin_dir_is_root_for_source_tree() { + let tempdir = tempfile::tempdir().unwrap(); + fs::write(tempdir.path().join("afl-fuzz"), "").unwrap(); + assert_eq!(afl_bin_dir(tempdir.path()), tempdir.path()); + } + + #[test] + fn afl_bin_dir_is_bin_for_installed_package() { + let tempdir = tempfile::tempdir().unwrap(); + let bin = tempdir.path().join("bin"); + fs::create_dir(&bin).unwrap(); + fs::write(bin.join("afl-fuzz"), "").unwrap(); + assert_eq!(afl_bin_dir(tempdir.path()), bin); + } + #[test] fn shell_quote_wraps_in_single_quotes() { assert_eq!(shell_quote("hello"), "'hello'"); From 1030c514f8af27f0f8afaec64a38d602a3d57711 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Thu, 24 Sep 2026 00:30:50 +0200 Subject: [PATCH 2/2] smitebot: check if Nyx packer is available --- smitebot/README.md | 2 +- smitebot/src/commands/doctor.rs | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/smitebot/README.md b/smitebot/README.md index 5ebf22d6..e27f9002 100644 --- a/smitebot/README.md +++ b/smitebot/README.md @@ -187,7 +187,7 @@ Checks performed: - CPU virtualization enabled (`vmx` or `svm`) - `/dev/kvm` is present and openable - Docker daemon is reachable (`docker version`) -- AFL++ built with Nyx support (`libnyx.so` under `--aflpp-path`) +- AFL++ built with Nyx support (`libnyx.so` and Nyx packer available under `--aflpp-path`) - VMware backdoor is enabled - AFL++ tools (`afl-fuzz`, `afl-cmin`, `afl-tmin`, `afl-whatsup`) are executable - Required host tools (`bash`, `python`, `python3`, `tmux`) diff --git a/smitebot/src/commands/doctor.rs b/smitebot/src/commands/doctor.rs index ab0a912b..41ca2b43 100644 --- a/smitebot/src/commands/doctor.rs +++ b/smitebot/src/commands/doctor.rs @@ -143,6 +143,10 @@ enum CheckFailure { "libnyx.so not found under --aflpp-path; rebuild AFL++ with Nyx support (see nyx_mode/README.md in your AFL++ source tree)" )] LibnyxNotFound, + #[error( + "nyx_mode/packer/packer not found under --aflpp-path; Nyx packer is required for scripts/setup-nyx.sh" + )] + NyxPackerNotFound, #[error("backdoor disabled; run ./scripts/enable-vmware-backdoor.sh to enable")] VMwareBackdoorDisabled, } @@ -191,6 +195,7 @@ impl DoctorCommand { DoctorCheck::new("/dev/kvm accessible", check_kvm_access()), DoctorCheck::new("Docker daemon reachable", check_docker_daemon()), DoctorCheck::new("AFL++ built with Nyx support", check_libnyx(&afl_bin)), + DoctorCheck::new("Nyx packer available", check_nyx_packer(&inputs.aflpp_root)), DoctorCheck::new("VMware backdoor enabled", check_vmware_backdoor_enabled()), ]; @@ -324,6 +329,23 @@ fn check_libnyx(afl_bin: &Path) -> Result<(), CheckFailure> { } } +/// Checks for the Nyx packer tree `scripts/setup-nyx.sh` needs to build a sharedir. +/// +/// The packer lives in the AFL++ source tree rather than next to the binaries, +/// so it is resolved from the configured root instead of [`afl_bin_dir`]. +fn check_nyx_packer(aflpp_root: &Path) -> Result<(), CheckFailure> { + if aflpp_root + .join("nyx_mode") + .join("packer") + .join("packer") + .is_dir() + { + Ok(()) + } else { + Err(CheckFailure::NyxPackerNotFound) + } +} + /// Checks whether the KVM `VMware` backdoor needed by Nyx is enabled. fn check_vmware_backdoor_enabled() -> Result<(), CheckFailure> { let path = Path::new("/sys/module/kvm/parameters/enable_vmware_backdoor"); @@ -501,6 +523,25 @@ mod tests { assert!(require_executable(&tempdir.path().join("afl-fuzz")).is_ok()); } + #[test] + fn check_nyx_packer_accepts_aflpp_root_with_packer() { + let tempdir = tempfile::tempdir().unwrap(); + fs::create_dir_all(tempdir.path().join("nyx_mode/packer/packer")).unwrap(); + + assert!(check_nyx_packer(tempdir.path()).is_ok()); + } + + #[test] + fn check_nyx_packer_rejects_bin_dir_of_installed_package() { + let tempdir = tempfile::tempdir().unwrap(); + fs::create_dir_all(tempdir.path().join("nyx_mode/packer/packer")).unwrap(); + let bin = tempdir.path().join("bin"); + fs::create_dir(&bin).unwrap(); + + let err = check_nyx_packer(&bin).unwrap_err(); + assert!(err.to_string().contains("nyx_mode/packer/packer not found")); + } + #[test] fn doctor_report_json_is_machine_readable() { let report = DoctorReport {