use crate::clang::clang_security_x86::*;
use crate::clang::*;
use crate::sanitize::*;
use crate::sanitizer_coverage::*;
use crate::value::ValueRef;
use crate::x86::*;
use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
pub const X86_FUZZ_MAX_INPUT_LEN: u64 = 1_048_576;
pub const X86_FUZZ_DEFAULT_TIMEOUT_MS: u64 = 10_000;
pub const X86_FUZZ_DEFAULT_RSS_LIMIT_MB: u64 = 2048;
pub const X86_FUZZ_DEFAULT_JOBS: u32 = 4;
pub const X86_FUZZ_MIN_CORPUS_SIZE: usize = 10;
pub const X86_CRASH_STACK_HASH_DEPTH: usize = 5;
pub const X86_MAX_CRASHES: usize = 10_000;
pub const X86_HANG_THRESHOLD_MS: u64 = 60_000;
pub const X86_INF_LOOP_DETECT_LIMIT: u64 = 100_000_000;
pub const X86_AFL_SHM_ID_BASE: i32 = 0x41464c00;
pub const X86_HF_PERSISTENT_LOOP: u64 = 1_000_000;
pub const X86_CLUSTER_HEARTBEAT_SECS: u64 = 30;
pub const X86_DASHBOARD_REFRESH_SECS: u64 = 5;
pub const X86_WEBHOOK_RETRY_LIMIT: u32 = 3;
pub const X86_SANCOV_MAP_SIZE: usize = 65536;
#[derive(Debug, Clone)]
pub struct X86Fuzzing {
pub config: X86FuzzConfig,
pub driver: X86FuzzDriver,
pub targets: X86FuzzTargets,
pub generators: X86FuzzGenerators,
pub analyzers: X86FuzzAnalyzers,
pub mutation: X86MutationEngine,
pub corpus: X86CorpusManager,
pub server: Option<X86FuzzServer>,
pub stats: X86FuzzStats,
pub active: bool,
}
#[derive(Debug, Clone)]
pub struct X86FuzzConfig {
pub arch: X86ArchVariant,
pub engine: X86FuzzEngine,
pub sanitizers: Vec<X86FuzzSanitizer>,
pub coverage: X86FuzzCoverage,
pub max_input_len: u64,
pub timeout_ms: u64,
pub rss_limit_mb: u64,
pub jobs: u32,
pub seed: Option<u64>,
pub minimize_crashes: bool,
pub distill_corpus: bool,
pub differential_testing: bool,
pub output_dir: Option<PathBuf>,
pub extra_clang_flags: Vec<String>,
pub verbose: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzEngine {
LibFuzzer,
AFL,
AFLPlusPlus,
Honggfuzz,
Custom,
OSSFuzz,
}
impl X86FuzzEngine {
pub fn name(&self) -> &'static str {
match self {
Self::LibFuzzer => "libFuzzer",
Self::AFL => "AFL",
Self::AFLPlusPlus => "AFL++",
Self::Honggfuzz => "Honggfuzz",
Self::Custom => "Custom",
Self::OSSFuzz => "OSS-Fuzz",
}
}
pub fn compiler_flag(&self) -> &'static str {
match self {
Self::LibFuzzer => "-fsanitize=fuzzer",
Self::AFL => "",
Self::AFLPlusPlus => "",
Self::Honggfuzz => "",
Self::Custom => "",
Self::OSSFuzz => "-fsanitize=fuzzer",
}
}
pub fn is_in_process(&self) -> bool {
matches!(self, Self::LibFuzzer | Self::Custom)
}
pub fn supports_fork_server(&self) -> bool {
matches!(self, Self::AFL | Self::AFLPlusPlus | Self::Honggfuzz)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzSanitizer {
Address,
Memory,
Undefined,
Thread,
Leak,
DataFlow,
SafeStack,
CFI,
}
impl X86FuzzSanitizer {
pub fn clang_flag(&self) -> &'static str {
match self {
Self::Address => "-fsanitize=address",
Self::Memory => "-fsanitize=memory",
Self::Undefined => "-fsanitize=undefined",
Self::Thread => "-fsanitize=thread",
Self::Leak => "-fsanitize=leak",
Self::DataFlow => "-fsanitize=dataflow",
Self::SafeStack => "-fsanitize=safe-stack",
Self::CFI => "-fsanitize=cfi",
}
}
pub fn description(&self) -> &'static str {
match self {
Self::Address => "Heap/stack/global buffer overflows, use-after-free, double-free",
Self::Memory => "Reads of uninitialized memory",
Self::Undefined => {
"Undefined behavior (signed overflow, null deref, misaligned access, etc.)"
}
Self::Thread => "Data races",
Self::Leak => "Memory leaks",
Self::DataFlow => "Data-flow tracing for fuzzing guidance",
Self::SafeStack => "Stack-based memory safety",
Self::CFI => "Control-flow integrity violations",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzCoverage {
None,
Edge,
Block,
Full,
SourceBased,
}
impl X86FuzzCoverage {
pub fn sancov_flags(&self) -> Vec<&'static str> {
match self {
Self::None => vec![],
Self::Edge => vec!["-fsanitize-coverage=trace-pc-guard"],
Self::Block => vec!["-fsanitize-coverage=trace-pc"],
Self::Full => {
vec!["-fsanitize-coverage=trace-pc-guard,inline-8bit-counters,trace-cmp,trace-gep"]
}
Self::SourceBased => vec!["-fprofile-instr-generate", "-fcoverage-mapping"],
}
}
}
impl Default for X86FuzzConfig {
fn default() -> Self {
Self {
arch: X86ArchVariant::X86_64,
engine: X86FuzzEngine::LibFuzzer,
sanitizers: vec![X86FuzzSanitizer::Address, X86FuzzSanitizer::Undefined],
coverage: X86FuzzCoverage::Edge,
max_input_len: X86_FUZZ_MAX_INPUT_LEN,
timeout_ms: X86_FUZZ_DEFAULT_TIMEOUT_MS,
rss_limit_mb: X86_FUZZ_DEFAULT_RSS_LIMIT_MB,
jobs: X86_FUZZ_DEFAULT_JOBS,
seed: None,
minimize_crashes: true,
distill_corpus: true,
differential_testing: false,
output_dir: None,
extra_clang_flags: Vec::new(),
verbose: false,
}
}
}
impl Default for X86Fuzzing {
fn default() -> Self {
Self {
config: X86FuzzConfig::default(),
driver: X86FuzzDriver::default(),
targets: X86FuzzTargets::default(),
generators: X86FuzzGenerators::default(),
analyzers: X86FuzzAnalyzers::default(),
mutation: X86MutationEngine::default(),
corpus: X86CorpusManager::default(),
server: None,
stats: X86FuzzStats::default(),
active: false,
}
}
}
impl X86Fuzzing {
pub fn new() -> Self {
Self::default()
}
pub fn compiler_fuzzing() -> Self {
let mut s = Self::default();
s.config.sanitizers = vec![X86FuzzSanitizer::Address, X86FuzzSanitizer::Undefined];
s.config.coverage = X86FuzzCoverage::Full;
s.config.differential_testing = true;
s.config.max_input_len = 100_000;
s
}
pub fn afl_binary_fuzzing() -> Self {
let mut s = Self::default();
s.config.engine = X86FuzzEngine::AFLPlusPlus;
s.config.coverage = X86FuzzCoverage::Edge;
s.config.sanitizers = vec![X86FuzzSanitizer::Address];
s
}
pub fn honggfuzz_persistent() -> Self {
let mut s = Self::default();
s.config.engine = X86FuzzEngine::Honggfuzz;
s.config.coverage = X86FuzzCoverage::Full;
s
}
pub fn ossfuzz_pipeline() -> Self {
let mut s = Self::default();
s.config.engine = X86FuzzEngine::OSSFuzz;
s.config.sanitizers = vec![
X86FuzzSanitizer::Address,
X86FuzzSanitizer::Undefined,
X86FuzzSanitizer::Memory,
];
s.config.coverage = X86FuzzCoverage::Full;
s.config.differential_testing = true;
s.config.distill_corpus = true;
s.config.minimize_crashes = true;
s
}
pub fn build_clang_command(&self) -> Vec<String> {
let mut flags = Vec::new();
if let Some(f) = self
.config
.engine
.compiler_flag()
.to_string()
.into_iter()
.find(|_| true)
{
if !self.config.engine.compiler_flag().is_empty() {
flags.push(self.config.engine.compiler_flag().to_string());
}
}
for s in &self.config.sanitizers {
flags.push(s.clang_flag().to_string());
}
for cf in self.config.coverage.sancov_flags() {
flags.push(cf.to_string());
}
match self.config.arch {
X86ArchVariant::X86_64 => flags.push("-m64".to_string()),
X86ArchVariant::X86_32 => flags.push("-m32".to_string()),
X86ArchVariant::X86_32PAE => flags.push("-m32".to_string()),
X86ArchVariant::X86_X32 => flags.push("-mx32".to_string()),
X86ArchVariant::X86_16 => flags.push("-m16".to_string()),
}
flags.extend(self.config.extra_clang_flags.clone());
flags
}
pub fn start(&mut self) -> X86FuzzResult {
self.active = true;
self.stats.total_runs += 1;
self.stats.start_time = Some(Instant::now());
let result = self.driver.run(&self.config, &mut self.stats);
self.stats.end_time = Some(Instant::now());
self.active = false;
result
}
pub fn stop(&mut self) {
self.active = false;
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!("=== X86 Fuzzing Summary ===\n"));
s.push_str(&format!("Engine: {}\n", self.config.engine.name()));
s.push_str(&format!("Arch: {:?}\n", self.config.arch));
s.push_str(&format!("Active: {}\n", self.active));
s.push_str(&format!("Total runs: {}\n", self.stats.total_runs));
s.push_str(&format!("Total execs: {}\n", self.stats.total_executions));
s.push_str(&format!("Crashes: {}\n", self.stats.total_crashes));
s.push_str(&format!("Hangs: {}\n", self.stats.total_hangs));
s.push_str(&format!("OOMs: {}\n", self.stats.total_ooms));
s.push_str(&format!("Corpus size: {}\n", self.stats.corpus_size));
s.push_str(&format!("New features: {}\n", self.stats.new_features));
s
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzStats {
pub total_runs: u64,
pub total_executions: AtomicU64,
pub total_crashes: AtomicU64,
pub total_hangs: AtomicU64,
pub total_ooms: AtomicU64,
pub corpus_size: AtomicUsize,
pub new_features: AtomicU64,
pub start_time: Option<Instant>,
pub end_time: Option<Instant>,
pub peak_rss_mb: AtomicU64,
pub mutations: AtomicU64,
pub bytes_processed: AtomicU64,
}
impl Default for X86FuzzStats {
fn default() -> Self {
Self {
total_runs: 0,
total_executions: AtomicU64::new(0),
total_crashes: AtomicU64::new(0),
total_hangs: AtomicU64::new(0),
total_ooms: AtomicU64::new(0),
corpus_size: AtomicUsize::new(0),
new_features: AtomicU64::new(0),
start_time: None,
end_time: None,
peak_rss_mb: AtomicU64::new(0),
mutations: AtomicU64::new(0),
bytes_processed: AtomicU64::new(0),
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzResult {
pub success: bool,
pub engine: X86FuzzEngine,
pub total_executions: u64,
pub execs_per_second: f64,
pub crashes: Vec<X86CrashInfo>,
pub hangs: Vec<X86HangInfo>,
pub ooms: u64,
pub coverage_pct: f64,
pub corpus_size: usize,
pub peak_rss_mb: u64,
pub new_features: u64,
pub duration: Duration,
pub log: Vec<String>,
}
impl Default for X86FuzzResult {
fn default() -> Self {
Self {
success: true,
engine: X86FuzzEngine::LibFuzzer,
total_executions: 0,
execs_per_second: 0.0,
crashes: Vec::new(),
hangs: Vec::new(),
ooms: 0,
coverage_pct: 0.0,
corpus_size: 0,
peak_rss_mb: 0,
new_features: 0,
duration: Duration::from_secs(0),
log: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzDriver {
pub libfuzzer: X86LibFuzzerConfig,
pub afl: X86AFLConfig,
pub honggfuzz: X86HonggfuzzConfig,
pub custom_harness: Option<PathBuf>,
pub auto_generate_harness: bool,
}
#[derive(Debug, Clone)]
pub struct X86LibFuzzerConfig {
pub runs: Option<u64>,
pub max_total_time: Option<u64>,
pub use_value_profile: bool,
pub crossover: Option<f64>,
pub mutate_depth: Option<u32>,
pub shrink: bool,
pub reduce_inputs: bool,
pub fork_mode: Option<X86ForkMode>,
pub use_dataflow_trace: bool,
pub experimental_passthrough: bool,
}
#[derive(Debug, Clone)]
pub struct X86AFLConfig {
pub input_dir: Option<PathBuf>,
pub output_dir: Option<PathBuf>,
pub master: bool,
pub slave_id: Option<u32>,
pub power_schedule: X86AFLPowerSchedule,
pub cmplog: bool,
pub comparison_coverage: bool,
pub skip_deterministic: bool,
pub deterministic_only: bool,
pub shm_path: Option<PathBuf>,
pub forkserver_timeout_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86AFLPowerSchedule {
Explore,
Exploit,
Fast,
Coeff,
Lin,
Quad,
Rare,
MMopt,
Seek,
}
impl X86AFLPowerSchedule {
pub fn as_arg(&self) -> &'static str {
match self {
Self::Explore => "explore",
Self::Exploit => "exploit",
Self::Fast => "fast",
Self::Coeff => "coeff",
Self::Lin => "lin",
Self::Quad => "quad",
Self::Rare => "rare",
Self::MMopt => "mmopt",
Self::Seek => "seek",
}
}
}
#[derive(Debug, Clone)]
pub struct X86HonggfuzzConfig {
pub input_dir: Option<PathBuf>,
pub output_dir: Option<PathBuf>,
pub threads: u32,
pub timeout_secs: u64,
pub mutation_rate: Option<f64>,
pub dictionary: Option<PathBuf>,
pub asan: bool,
pub ubsan: bool,
pub msan: bool,
pub linux_perf_bts_edge: bool,
pub linux_perf_ipt_block: bool,
pub persistent_iterations: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ForkMode {
Fork,
Persistent,
ForkServer,
Timeout { per_input_ms: u64 },
}
impl Default for X86LibFuzzerConfig {
fn default() -> Self {
Self {
runs: None,
max_total_time: None,
use_value_profile: true,
crossover: Some(0.5),
mutate_depth: Some(5),
shrink: true,
reduce_inputs: true,
fork_mode: Some(X86ForkMode::Fork),
use_dataflow_trace: false,
experimental_passthrough: false,
}
}
}
impl Default for X86AFLConfig {
fn default() -> Self {
Self {
input_dir: None,
output_dir: None,
master: true,
slave_id: None,
power_schedule: X86AFLPowerSchedule::Explore,
cmplog: false,
comparison_coverage: true,
skip_deterministic: false,
deterministic_only: false,
shm_path: None,
forkserver_timeout_ms: 5000,
}
}
}
impl Default for X86HonggfuzzConfig {
fn default() -> Self {
Self {
input_dir: None,
output_dir: None,
threads: 4,
timeout_secs: 10,
mutation_rate: None,
dictionary: None,
asan: true,
ubsan: true,
msan: false,
linux_perf_bts_edge: true,
linux_perf_ipt_block: false,
persistent_iterations: X86_HF_PERSISTENT_LOOP,
}
}
}
impl Default for X86FuzzDriver {
fn default() -> Self {
Self {
libfuzzer: X86LibFuzzerConfig::default(),
afl: X86AFLConfig::default(),
honggfuzz: X86HonggfuzzConfig::default(),
custom_harness: None,
auto_generate_harness: true,
}
}
}
impl X86FuzzDriver {
pub fn run(&mut self, config: &X86FuzzConfig, stats: &mut X86FuzzStats) -> X86FuzzResult {
match config.engine {
X86FuzzEngine::LibFuzzer => self.run_libfuzzer(config, stats),
X86FuzzEngine::AFL | X86FuzzEngine::AFLPlusPlus => self.run_afl(config, stats),
X86FuzzEngine::Honggfuzz => self.run_honggfuzz(config, stats),
X86FuzzEngine::Custom => self.run_custom(config, stats),
X86FuzzEngine::OSSFuzz => self.run_ossfuzz(config, stats),
}
}
fn run_libfuzzer(&self, config: &X86FuzzConfig, stats: &X86FuzzStats) -> X86FuzzResult {
let start = Instant::now();
let mut result = X86FuzzResult {
engine: X86FuzzEngine::LibFuzzer,
..Default::default()
};
let harness = if self.auto_generate_harness {
self.generate_libfuzzer_harness(config)
} else {
self.custom_harness
.clone()
.unwrap_or_else(|| PathBuf::from("fuzz_target.cc"))
.to_string_lossy()
.to_string()
};
let mut libfuzzer_args = Vec::new();
if let Some(runs) = self.libfuzzer.runs {
libfuzzer_args.push(format!("-runs={}", runs));
}
if let Some(t) = self.libfuzzer.max_total_time {
libfuzzer_args.push(format!("-max_total_time={}", t));
}
if self.libfuzzer.use_value_profile {
libfuzzer_args.push("-use_value_profile=1".to_string());
}
if self.libfuzzer.shrink {
libfuzzer_args.push("-shrink=1".to_string());
}
if self.libfuzzer.reduce_inputs {
libfuzzer_args.push("-reduce_inputs=1".to_string());
}
libfuzzer_args.push(format!("-max_len={}", config.max_input_len));
libfuzzer_args.push(format!("-timeout={}", config.timeout_ms / 1000));
libfuzzer_args.push(format!("-rss_limit_mb={}", config.rss_limit_mb));
libfuzzer_args.push(format!("-jobs={}", config.jobs));
if let Some(seed) = config.seed {
libfuzzer_args.push(format!("-seed={}", seed));
}
result
.log
.push(format!("libFuzzer args: {:?}", libfuzzer_args));
result.log.push(format!("Harness: {:?}", harness));
let simulated_execs = self.libfuzzer.runs.unwrap_or(1_000_000);
stats
.total_executions
.fetch_add(simulated_execs, Ordering::Relaxed);
result.total_executions = simulated_execs;
result.execs_per_second = simulated_execs as f64 / start.elapsed().as_secs_f64().max(0.001);
result.duration = start.elapsed();
result.corpus_size = stats.corpus_size.load(Ordering::Relaxed);
result
}
fn run_afl(&self, config: &X86FuzzConfig, stats: &X86FuzzStats) -> X86FuzzResult {
let start = Instant::now();
let mut result = X86FuzzResult {
engine: config.engine,
..Default::default()
};
let afl_target = self.generate_afl_harness(config);
result
.log
.push(format!("AFL target source: {}", afl_target));
result.log.push(format!(
"AFL input dir: {:?}, output dir: {:?}",
self.afl.input_dir, self.afl.output_dir
));
result.log.push(format!(
"AFL power schedule: {}",
self.afl.power_schedule.as_arg()
));
if self.afl.cmplog {
result.log.push("CmpLog enabled".to_string());
}
let simulated_execs = 500_000;
stats
.total_executions
.fetch_add(simulated_execs, Ordering::Relaxed);
result.total_executions = simulated_execs;
result.execs_per_second = simulated_execs as f64 / start.elapsed().as_secs_f64().max(0.001);
result.duration = start.elapsed();
result
}
fn run_honggfuzz(&self, config: &X86FuzzConfig, stats: &X86FuzzStats) -> X86FuzzResult {
let start = Instant::now();
let mut result = X86FuzzResult {
engine: X86FuzzEngine::Honggfuzz,
..Default::default()
};
let hf_target = self.generate_honggfuzz_harness(config);
result
.log
.push(format!("Honggfuzz target source: {}", hf_target));
result.log.push(format!(
"Threads: {}, Timeout: {}s, Persistent: {}",
self.honggfuzz.threads,
self.honggfuzz.timeout_secs,
self.honggfuzz.persistent_iterations
));
let simulated_execs = 750_000;
stats
.total_executions
.fetch_add(simulated_execs, Ordering::Relaxed);
result.total_executions = simulated_execs;
result.execs_per_second = simulated_execs as f64 / start.elapsed().as_secs_f64().max(0.001);
result.duration = start.elapsed();
result
}
fn run_custom(&self, config: &X86FuzzConfig, stats: &X86FuzzStats) -> X86FuzzResult {
let start = Instant::now();
let mut result = X86FuzzResult {
engine: X86FuzzEngine::Custom,
..Default::default()
};
let harness_path = self
.custom_harness
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "custom_fuzz_harness.cc".to_string());
result.log.push(format!("Custom harness: {}", harness_path));
let simulated_execs = 250_000;
stats
.total_executions
.fetch_add(simulated_execs, Ordering::Relaxed);
result.total_executions = simulated_execs;
result.execs_per_second = simulated_execs as f64 / start.elapsed().as_secs_f64().max(0.001);
result.duration = start.elapsed();
result
}
fn run_ossfuzz(&self, config: &X86FuzzConfig, stats: &X86FuzzStats) -> X86FuzzResult {
let start = Instant::now();
let mut result = X86FuzzResult {
engine: X86FuzzEngine::OSSFuzz,
..Default::default()
};
let build_sh = self.generate_ossfuzz_build_script(config);
let dockerfile = self.generate_ossfuzz_dockerfile(config);
result.log.push("=== OSS-Fuzz Pipeline ===".to_string());
result.log.push(build_sh);
result.log.push(dockerfile);
let simulated_execs = 2_000_000;
stats
.total_executions
.fetch_add(simulated_execs, Ordering::Relaxed);
result.total_executions = simulated_execs;
result.execs_per_second = simulated_execs as f64 / start.elapsed().as_secs_f64().max(0.001);
result.duration = start.elapsed();
result
}
pub fn generate_libfuzzer_harness(&self, config: &X86FuzzConfig) -> String {
let mut s = String::new();
s.push_str("// Auto-generated libFuzzer harness for Clang X86\n");
s.push_str("#include <stdint.h>\n");
s.push_str("#include <stddef.h>\n");
s.push_str("#include <string.h>\n\n");
s.push_str("#ifdef __cplusplus\n");
s.push_str("extern \"C\" {\n");
s.push_str("#endif\n\n");
s.push_str("int LLVMFuzzerInitialize(int *argc, char ***argv) {\n");
s.push_str(" // Initialization: seed, options, environment\n");
s.push_str(" return 0;\n");
s.push_str("}\n\n");
s.push_str("int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n");
s.push_str(" if (size == 0) return 0;\n");
s.push_str(" // Fuzz target: process `data` of length `size`\n");
s.push_str(" // Example: parse C source, optimize IR, generate code\n");
s.push_str(" return 0;\n");
s.push_str("}\n\n");
s.push_str("#ifdef __cplusplus\n");
s.push_str("}\n");
s.push_str("#endif\n");
s
}
pub fn generate_afl_harness(&self, config: &X86FuzzConfig) -> String {
let mut s = String::new();
s.push_str("// Auto-generated AFL harness for Clang X86\n");
s.push_str("#include <stdint.h>\n");
s.push_str("#include <stddef.h>\n");
s.push_str("#include <string.h>\n");
s.push_str("#include <unistd.h>\n");
s.push_str("#include <signal.h>\n\n");
s.push_str("#ifdef __AFL_COMPILER\n");
s.push_str("int main(int argc, char **argv) {\n");
s.push_str(" uint8_t buf[1024*1024];\n");
s.push_str(" ssize_t len;\n\n");
s.push_str("#ifdef __AFL_HAVE_MANUAL_CONTROL\n");
s.push_str(" __AFL_INIT();\n");
s.push_str("#endif\n\n");
s.push_str(" while (__AFL_LOOP(10000)) {\n");
s.push_str(" len = read(0, buf, sizeof(buf));\n");
s.push_str(" if (len <= 0) continue;\n");
s.push_str(" // Fuzz target: process buf of length len\n");
s.push_str(" }\n");
s.push_str(" return 0;\n");
s.push_str("}\n");
s.push_str("#endif\n");
s
}
pub fn generate_honggfuzz_harness(&self, config: &X86FuzzConfig) -> String {
let mut s = String::new();
s.push_str("// Auto-generated Honggfuzz harness for Clang X86\n");
s.push_str("#include <stdint.h>\n");
s.push_str("#include <stddef.h>\n");
s.push_str("#include <string.h>\n");
s.push_str("#include <hfuzz/hfuzz.h>\n\n");
s.push_str("int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n");
s.push_str(" if (size == 0) return 0;\n");
s.push_str(" // Fuzz target\n");
s.push_str(" return 0;\n");
s.push_str("}\n\n");
s.push_str("int main(void) {\n");
s.push_str(" HF_ITER((uint8_t **)&buf, &len) {\n");
s.push_str(" LLVMFuzzerTestOneInput(buf, len);\n");
s.push_str(" }\n");
s.push_str(" return 0;\n");
s.push_str("}\n");
s
}
pub fn generate_ossfuzz_build_script(&self, config: &X86FuzzConfig) -> String {
let mut s = String::new();
s.push_str("#!/bin/bash -eu\n");
s.push_str("# OSS-Fuzz build.sh for Clang X86 fuzzing\n");
s.push_str("export CC=clang\n");
s.push_str("export CXX=clang++\n");
s.push_str("export CFLAGS=\"-O1 -fno-omit-frame-pointer -gline-tables-only\"\n");
s.push_str("export CXXFLAGS=\"$CFLAGS -stdlib=libc++\"\n");
s.push_str("export LIB_FUZZING_ENGINE=\"-fsanitize=fuzzer\"\n\n");
s.push_str("for fuzzer in fuzz_target_*; do\n");
s.push_str(" $CXX $CXXFLAGS $LIB_FUZZING_ENGINE \\\n");
s.push_str(" ${fuzzer}.cc -o $OUT/${fuzzer}\n");
s.push_str("done\n");
s
}
pub fn generate_ossfuzz_dockerfile(&self, config: &X86FuzzConfig) -> String {
let mut s = String::new();
s.push_str("FROM gcr.io/oss-fuzz-base/base-builder\n");
s.push_str("RUN apt-get update && apt-get install -y make autoconf automake libtool\n");
s.push_str("COPY . $SRC/project\n");
s.push_str("WORKDIR $SRC/project\n");
s.push_str("COPY build.sh $SRC/\n");
s
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzTargets {
pub compiler: X86CompilerFuzzTarget,
pub parser: X86ParserFuzzTarget,
pub optimizer: X86OptimizerFuzzTarget,
pub codegen: X86CodegenFuzzTarget,
pub linker: X86LinkerFuzzTarget,
pub assembler: X86AsmFuzzTarget,
pub disassembler: X86DisasmFuzzTarget,
pub enabled: X86FuzzTargetMask,
pub config_overrides: HashMap<String, X86FuzzTargetOverride>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86FuzzTargetMask {
pub compiler: bool,
pub parser: bool,
pub optimizer: bool,
pub codegen: bool,
pub linker: bool,
pub assembler: bool,
pub disassembler: bool,
}
impl Default for X86FuzzTargetMask {
fn default() -> Self {
Self {
compiler: true,
parser: true,
optimizer: true,
codegen: false,
linker: true,
assembler: true,
disassembler: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzTargetOverride {
pub max_len: Option<u64>,
pub timeout_ms: Option<u64>,
pub jobs: Option<u32>,
pub extra_flags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86CompilerFuzzTarget {
pub language: X86FuzzLanguage,
pub include_headers: bool,
pub max_source_chars: usize,
pub translation_units: u32,
pub opt_levels: Vec<ClangOptLevel>,
pub verify_output: bool,
pub all_standards: bool,
pub crash_only: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzLanguage {
C,
Cpp,
Both,
}
impl X86FuzzLanguage {
pub fn as_str(&self) -> &'static str {
match self {
Self::C => "c",
Self::Cpp => "c++",
Self::Both => "c,c++",
}
}
}
impl Default for X86CompilerFuzzTarget {
fn default() -> Self {
Self {
language: X86FuzzLanguage::C,
include_headers: true,
max_source_chars: 10_000,
translation_units: 1,
opt_levels: vec![ClangOptLevel::O0, ClangOptLevel::O2],
verify_output: false,
all_standards: true,
crash_only: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ParserFuzzTarget {
pub max_tokens: usize,
pub all_diagnostics: bool,
pub error_recovery: bool,
pub test_fixit: bool,
pub compare_with_gcc: bool,
}
impl Default for X86ParserFuzzTarget {
fn default() -> Self {
Self {
max_tokens: 5000,
all_diagnostics: true,
error_recovery: true,
test_fixit: false,
compare_with_gcc: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86OptimizerFuzzTarget {
pub min_blocks: u32,
pub max_blocks: u32,
pub passes: Vec<String>,
pub verify_after_pass: bool,
pub compare_output: bool,
pub translation_validation: bool,
}
impl Default for X86OptimizerFuzzTarget {
fn default() -> Self {
Self {
min_blocks: 1,
max_blocks: 100,
passes: vec![
"instcombine".to_string(),
"simplifycfg".to_string(),
"gvn".to_string(),
"sccp".to_string(),
],
verify_after_pass: true,
compare_output: true,
translation_validation: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CodegenFuzzTarget {
pub cpu_target: String,
pub check_assembly: bool,
pub execute_code: bool,
pub interpreter_verify: bool,
pub check_abi: bool,
}
impl Default for X86CodegenFuzzTarget {
fn default() -> Self {
Self {
cpu_target: "x86-64".to_string(),
check_assembly: true,
execute_code: false,
interpreter_verify: true,
check_abi: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86LinkerFuzzTarget {
pub max_objects: u32,
pub test_lto: bool,
pub test_thin_lto: bool,
pub test_linker_scripts: bool,
pub test_symbol_resolution: bool,
pub test_relocations: bool,
pub test_gc_sections: bool,
}
impl Default for X86LinkerFuzzTarget {
fn default() -> Self {
Self {
max_objects: 10,
test_lto: true,
test_thin_lto: true,
test_linker_scripts: false,
test_symbol_resolution: true,
test_relocations: true,
test_gc_sections: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86AsmFuzzTarget {
pub syntax: X86AsmSyntax,
pub fuzz_mnemonics: bool,
pub fuzz_operands: bool,
pub fuzz_directives: bool,
pub roundtrip_verify: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86AsmSyntax {
Intel,
ATT,
Both,
}
impl Default for X86AsmFuzzTarget {
fn default() -> Self {
Self {
syntax: X86AsmSyntax::Intel,
fuzz_mnemonics: true,
fuzz_operands: true,
fuzz_directives: true,
roundtrip_verify: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86DisasmFuzzTarget {
pub max_binary_len: usize,
pub verify_roundtrip: bool,
pub compare_objdump: bool,
pub start_address: u64,
pub mode: X86DisasmMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DisasmMode {
Mode16,
Mode32,
Mode64,
}
impl Default for X86DisasmFuzzTarget {
fn default() -> Self {
Self {
max_binary_len: 1024,
verify_roundtrip: true,
compare_objdump: false,
start_address: 0x1000,
mode: X86DisasmMode::Mode64,
}
}
}
impl Default for X86FuzzTargets {
fn default() -> Self {
Self {
compiler: X86CompilerFuzzTarget::default(),
parser: X86ParserFuzzTarget::default(),
optimizer: X86OptimizerFuzzTarget::default(),
codegen: X86CodegenFuzzTarget::default(),
linker: X86LinkerFuzzTarget::default(),
assembler: X86AsmFuzzTarget::default(),
disassembler: X86DisasmFuzzTarget::default(),
enabled: X86FuzzTargetMask::default(),
config_overrides: HashMap::new(),
}
}
}
impl X86FuzzTargets {
pub fn enable_all(&mut self) {
self.enabled = X86FuzzTargetMask {
compiler: true,
parser: true,
optimizer: true,
codegen: true,
linker: true,
assembler: true,
disassembler: true,
};
}
pub fn enable_compiler_only(&mut self) {
self.enabled = X86FuzzTargetMask {
compiler: true,
parser: true,
optimizer: true,
codegen: false,
linker: false,
assembler: false,
disassembler: false,
};
}
pub fn enable_binary_only(&mut self) {
self.enabled = X86FuzzTargetMask {
compiler: false,
parser: false,
optimizer: false,
codegen: false,
linker: true,
assembler: true,
disassembler: true,
};
}
pub fn enabled_count(&self) -> u32 {
let mut c = 0u32;
if self.enabled.compiler {
c += 1;
}
if self.enabled.parser {
c += 1;
}
if self.enabled.optimizer {
c += 1;
}
if self.enabled.codegen {
c += 1;
}
if self.enabled.linker {
c += 1;
}
if self.enabled.assembler {
c += 1;
}
if self.enabled.disassembler {
c += 1;
}
c
}
pub fn generate_compiler_harness(&self) -> String {
let mut s = String::new();
s.push_str("// Auto-generated compiler fuzz harness\n");
s.push_str("#include <stdint.h>\n");
s.push_str("#include <stddef.h>\n");
s.push_str("#include <stdio.h>\n");
s.push_str("#include <stdlib.h>\n");
s.push_str("#include <string.h>\n\n");
s.push_str("static char source_buffer[");
s.push_str(&self.compiler.max_source_chars.to_string());
s.push_str("];\n\n");
s.push_str("int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n");
s.push_str(" if (size < 4) return 0;\n");
s.push_str(" size = size < sizeof(source_buffer)-1 ? size : sizeof(source_buffer)-1;\n");
s.push_str(" memcpy(source_buffer, data, size);\n");
s.push_str(" source_buffer[size] = 0;\n");
s.push_str(" // Generate and compile random C program from input\n");
s.push_str(" return 0;\n");
s.push_str("}\n");
s
}
pub fn generate_parser_harness(&self) -> String {
let mut s = String::new();
s.push_str("// Auto-generated parser fuzz harness\n");
s.push_str("int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n");
s.push_str(" if (size == 0) return 0;\n");
s.push_str(" // Parse malformed C/C++ source\n");
s.push_str(" // Verify no crash, assert, or unsafe behavior\n");
s.push_str(" return 0;\n");
s.push_str("}\n");
s
}
pub fn generate_optimizer_harness(&self) -> String {
let mut s = String::new();
s.push_str("// Auto-generated optimizer fuzz harness\n");
s.push_str("int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n");
s.push_str(" // Generate valid IR, run optimization passes, verify\n");
s.push_str(" return 0;\n");
s.push_str("}\n");
s
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzGenerators {
pub grammar: X86GrammarGenerator,
pub mutation: X86MutationGenerator,
pub structured: X86StructuredGenerator,
pub coverage_guided: X86CoverageGuidedGenerator,
pub active: X86GeneratorKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GeneratorKind {
Grammar,
Mutation,
Structured,
CoverageGuided,
Hybrid,
}
#[derive(Debug, Clone)]
pub struct X86GrammarGenerator {
pub rules: Vec<X86GrammarRule>,
pub max_depth: u32,
pub max_expansion: usize,
pub biased_selection: bool,
pub seed: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct X86GrammarRule {
pub name: String,
pub productions: Vec<Vec<X86GrammarSymbol>>,
pub weight: f64,
}
#[derive(Debug, Clone)]
pub enum X86GrammarSymbol {
NonTerminal(String),
Terminal(String),
IntConstant { min: i64, max: i64 },
Identifier,
Operator(String),
Choice(Vec<X86GrammarSymbol>),
Optional(Box<X86GrammarSymbol>),
Repeat(Box<X86GrammarSymbol>),
}
impl X86GrammarGenerator {
pub fn c_grammar() -> Self {
let rules = vec![
X86GrammarRule {
name: "program".to_string(),
productions: vec![vec![X86GrammarSymbol::Repeat(Box::new(
X86GrammarSymbol::NonTerminal("decl".to_string()),
))]],
weight: 1.0,
},
X86GrammarRule {
name: "decl".to_string(),
productions: vec![
vec![X86GrammarSymbol::NonTerminal("func_def".to_string())],
vec![X86GrammarSymbol::NonTerminal("var_decl".to_string())],
vec![X86GrammarSymbol::NonTerminal("struct_def".to_string())],
],
weight: 1.0,
},
X86GrammarRule {
name: "func_def".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("param_list".to_string()),
X86GrammarSymbol::Terminal(") { ".to_string()),
X86GrammarSymbol::NonTerminal("stmt_list".to_string()),
X86GrammarSymbol::Terminal(" }".to_string()),
]],
weight: 1.0,
},
X86GrammarRule {
name: "type_spec".to_string(),
productions: vec![
vec![X86GrammarSymbol::Terminal("int".to_string())],
vec![X86GrammarSymbol::Terminal("char".to_string())],
vec![X86GrammarSymbol::Terminal("long".to_string())],
vec![X86GrammarSymbol::Terminal("float".to_string())],
vec![X86GrammarSymbol::Terminal("double".to_string())],
vec![X86GrammarSymbol::Terminal("void".to_string())],
vec![X86GrammarSymbol::Terminal("unsigned int".to_string())],
],
weight: 1.0,
},
X86GrammarRule {
name: "stmt".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Terminal("return ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(";".to_string()),
],
vec![X86GrammarSymbol::NonTerminal("var_decl".to_string())],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(";".to_string()),
],
vec![
X86GrammarSymbol::Terminal("if (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") { ".to_string()),
X86GrammarSymbol::NonTerminal("stmt_list".to_string()),
X86GrammarSymbol::Terminal(" }".to_string()),
],
vec![
X86GrammarSymbol::Terminal("while (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") { ".to_string()),
X86GrammarSymbol::NonTerminal("stmt_list".to_string()),
X86GrammarSymbol::Terminal(" }".to_string()),
],
],
weight: 1.0,
},
X86GrammarRule {
name: "expr".to_string(),
productions: vec![
vec![X86GrammarSymbol::IntConstant { min: 0, max: 1000 }],
vec![X86GrammarSymbol::Identifier],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Operator("+".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Operator("*".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("arg_list".to_string()),
X86GrammarSymbol::Terminal(")".to_string()),
],
],
weight: 1.0,
},
];
Self {
rules,
max_depth: 10,
max_expansion: 5000,
biased_selection: true,
seed: None,
}
}
pub fn generate(&self) -> String {
let mut output = String::new();
self.expand("program", 0, &mut output);
output
}
fn expand(&self, non_terminal: &str, depth: u32, output: &mut String) {
if depth > self.max_depth || output.len() > self.max_expansion {
output.push_str("0");
return;
}
if let Some(rule) = self.rules.iter().find(|r| r.name == non_terminal) {
if rule.productions.is_empty() {
return;
}
let prod_idx = if self.biased_selection {
let total_weight: f64 = self
.rules
.iter()
.filter(|r| r.name == non_terminal)
.map(|r| r.weight)
.sum();
let mut cumulative = 0.0;
let target = (depth as f64 * 0.1).fract() * total_weight;
let mut chosen = 0usize;
for (i, r) in self.rules.iter().enumerate() {
if r.name == non_terminal {
cumulative += r.weight;
if cumulative >= target || i == rule.productions.len() - 1 {
chosen = i;
break;
}
}
}
(depth as usize + chosen) % rule.productions.len()
} else {
depth as usize % rule.productions.len()
};
let production = &rule.productions[prod_idx.max(0).min(rule.productions.len() - 1)];
for symbol in self
.rules
.iter()
.find(|r| r.name == non_terminal)
.and_then(|r| r.productions.get(prod_idx))
.unwrap_or(&production)
{
match symbol {
X86GrammarSymbol::NonTerminal(name) => {
self.expand(name, depth + 1, output);
}
X86GrammarSymbol::Terminal(text) => {
output.push_str(text);
}
X86GrammarSymbol::IntConstant { min, max } => {
let val = *min + ((depth as i64 + output.len() as i64) % (*max - *min + 1));
output.push_str(&val.to_string());
}
X86GrammarSymbol::Identifier => {
output.push_str(&format!("v{}", depth % 26 + (output.len() % 26) as u32));
}
X86GrammarSymbol::Operator(op) => {
output.push(' ');
output.push_str(op);
output.push(' ');
}
X86GrammarSymbol::Choice(choices) => {
if !choices.is_empty() {
let idx = (depth as usize + output.len()) % choices.len();
match &choices[idx] {
X86GrammarSymbol::Identifier => {
output.push_str(&format!("v{}", depth));
}
X86GrammarSymbol::IntConstant { min, max } => {
let val = *min + (depth as i64 % (*max - *min + 1));
output.push_str(&val.to_string());
}
X86GrammarSymbol::Terminal(t) => output.push_str(t),
_ => {}
}
}
}
X86GrammarSymbol::Optional(sym) => {
if depth % 3 != 0 {
match sym.as_ref() {
X86GrammarSymbol::Terminal(t) => output.push_str(t),
X86GrammarSymbol::Identifier => {
output.push_str(&format!("x{}", depth));
}
_ => {}
}
}
}
X86GrammarSymbol::Repeat(sym) => {
let count = (depth + 1) as usize % 4;
for _ in 0..count {
match sym.as_ref() {
X86GrammarSymbol::NonTerminal(name) => {
self.expand(name, depth + 1, output);
}
X86GrammarSymbol::Terminal(t) => output.push_str(t),
_ => {}
}
output.push('\n');
}
}
}
}
}
}
}
impl Default for X86GrammarGenerator {
fn default() -> Self {
Self::c_grammar()
}
}
#[derive(Debug, Clone)]
pub struct X86MutationGenerator {
pub seeds: Vec<Vec<u8>>,
pub engine: X86MutationEngine,
pub mutation_sequence: u32,
pub havoc: bool,
}
impl Default for X86MutationGenerator {
fn default() -> Self {
Self {
seeds: Vec::new(),
engine: X86MutationEngine::default(),
mutation_sequence: 1,
havoc: false,
}
}
}
impl X86MutationGenerator {
pub fn set_seeds(&mut self, seeds: Vec<Vec<u8>>) {
self.seeds = seeds;
}
pub fn generate(&self, seed: &[u8], rng: &X86FuzzRng) -> Vec<u8> {
self.engine.mutate_single(seed, rng)
}
}
#[derive(Debug, Clone)]
pub struct X86StructuredGenerator {
pub schema: Option<String>,
pub use_lpm: bool,
pub use_custom_mutator: bool,
pub ast_template: Option<X86AstTemplate>,
}
#[derive(Debug, Clone)]
pub struct X86AstTemplate {
pub root: String,
pub node_types: Vec<String>,
pub max_depth: u32,
}
impl Default for X86StructuredGenerator {
fn default() -> Self {
Self {
schema: None,
use_lpm: true,
use_custom_mutator: false,
ast_template: None,
}
}
}
impl X86StructuredGenerator {
pub fn generate_protobuf_harness(&self) -> String {
let mut s = String::new();
s.push_str("// Auto-generated protobuf mutator fuzzer\n");
s.push_str("#include <libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h>\n");
s.push_str("DEFINE_PROTO_FUZZER(const MyMessage &msg) {\n");
s.push_str(" // Process structured input\n");
s.push_str("}\n");
s
}
}
#[derive(Debug, Clone)]
pub struct X86CoverageGuidedGenerator {
pub coverage_map: Vec<u8>,
pub seen_features: HashSet<u64>,
pub reward_new_coverage: bool,
pub energy_assignment: bool,
}
impl Default for X86CoverageGuidedGenerator {
fn default() -> Self {
Self {
coverage_map: vec![0u8; X86_SANCOV_MAP_SIZE],
seen_features: HashSet::new(),
reward_new_coverage: true,
energy_assignment: true,
}
}
}
impl X86CoverageGuidedGenerator {
pub fn has_new_coverage(&self, map: &[u8]) -> bool {
let hash = self.hash_map(map);
!self.seen_features.contains(&hash)
}
pub fn record_coverage(&mut self, map: &[u8]) {
let hash = self.hash_map(map);
self.seen_features.insert(hash);
self.coverage_map
.copy_from_slice(&map[..map.len().min(X86_SANCOV_MAP_SIZE)]);
}
fn hash_map(&self, map: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in map.iter().take(256) {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn compute_energy(&self, map: &[u8]) -> f64 {
let mut rare_count = 0u64;
for &b in map.iter().take(X86_SANCOV_MAP_SIZE) {
if b > 0 && b < 8 {
rare_count += 1;
}
}
if rare_count == 0 {
1.0
} else {
1.0 + (rare_count as f64).log2()
}
}
}
impl Default for X86FuzzGenerators {
fn default() -> Self {
Self {
grammar: X86GrammarGenerator::default(),
mutation: X86MutationGenerator::default(),
structured: X86StructuredGenerator::default(),
coverage_guided: X86CoverageGuidedGenerator::default(),
active: X86GeneratorKind::Grammar,
}
}
}
impl X86FuzzGenerators {
pub fn set_active(&mut self, kind: X86GeneratorKind) {
self.active = kind;
}
pub fn generate(&self, rng: &X86FuzzRng) -> Vec<u8> {
match self.active {
X86GeneratorKind::Grammar => {
let s = self.grammar.generate();
s.into_bytes()
}
X86GeneratorKind::Mutation => {
if let Some(seed) = self.mutation.seeds.first() {
self.mutation.generate(seed, rng)
} else {
b"int main() { return 0; }".to_vec()
}
}
X86GeneratorKind::Structured => {
b"// structured fuzz input".to_vec()
}
X86GeneratorKind::CoverageGuided => {
b"int main() { return 0; }".to_vec()
}
X86GeneratorKind::Hybrid => {
let base = self.grammar.generate();
self.mutation.generate(&base.into_bytes(), rng)
}
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzRng {
state: u64,
}
impl X86FuzzRng {
pub fn new(seed: u64) -> Self {
Self { state: seed.max(1) }
}
pub fn from_time() -> Self {
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
Self::new(ts.max(1))
}
pub fn next_u64(&mut self) -> u64 {
self.state ^= self.state >> 12;
self.state ^= self.state << 25;
self.state ^= self.state >> 27;
self.state.wrapping_mul(0x2545F4914F6CDD1D)
}
pub fn next_usize(&mut self, max: usize) -> usize {
if max == 0 {
return 0;
}
(self.next_u64() as usize) % max
}
pub fn next_u8(&mut self) -> u8 {
self.next_u64() as u8
}
pub fn next_bool(&mut self) -> bool {
self.next_u64() & 1 == 1
}
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() as f64) / (u64::MAX as f64)
}
pub fn next_i64_range(&mut self, min: i64, max: i64) -> i64 {
if min >= max {
return min;
}
let range = (max - min) as u64;
min + (self.next_u64() % (range + 1)) as i64
}
pub fn choose<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
if slice.is_empty() {
None
} else {
Some(&slice[self.next_usize(slice.len())])
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzAnalyzers {
pub crash_triage: X86CrashTriage,
pub hang_detection: X86HangDetection,
pub memory_error: X86MemoryErrorDetection,
pub differential: X86DifferentialTesting,
pub oracle: X86FuzzOracle,
}
#[derive(Debug, Clone)]
pub struct X86CrashTriage {
pub crash_dir: Option<PathBuf>,
pub symbolize: bool,
pub symbol_path: Option<PathBuf>,
pub asan_options: String,
pub stack_hash_depth: usize,
pub max_crashes: usize,
}
impl Default for X86CrashTriage {
fn default() -> Self {
Self {
crash_dir: None,
symbolize: true,
symbol_path: None,
asan_options: "halt_on_error=0:detect_leaks=0".to_string(),
stack_hash_depth: X86_CRASH_STACK_HASH_DEPTH,
max_crashes: X86_MAX_CRASHES,
}
}
}
impl X86CrashTriage {
pub fn deduplicate(&self, crashes: &[X86CrashInfo]) -> Vec<X86CrashInfo> {
let mut seen: HashMap<u64, usize> = HashMap::new();
let mut unique: Vec<X86CrashInfo> = Vec::new();
for crash in crashes {
let hash = crash.stack_hash;
if let Some(idx) = seen.get(&hash) {
let _ = idx;
} else if unique.len() < self.max_crashes {
seen.insert(hash, unique.len());
unique.push(crash.clone());
}
}
unique
}
pub fn classify(&self, output: &str) -> X86CrashType {
let lower = output.to_lowercase();
if lower.contains("heap-buffer-overflow") {
X86CrashType::HeapBufferOverflow
} else if lower.contains("stack-buffer-overflow") {
X86CrashType::StackBufferOverflow
} else if lower.contains("global-buffer-overflow") {
X86CrashType::GlobalBufferOverflow
} else if lower.contains("use-after-free") {
X86CrashType::UseAfterFree
} else if lower.contains("use-after-return") {
X86CrashType::UseAfterReturn
} else if lower.contains("double-free") {
X86CrashType::DoubleFree
} else if lower.contains("segmentation fault") || lower.contains("segfault") {
X86CrashType::Segfault
} else if lower.contains("abort") || lower.contains("aborted") {
X86CrashType::Abort
} else if lower.contains("assert") {
X86CrashType::AssertFail
} else if lower.contains("memory leak") {
X86CrashType::MemLeak
} else if lower.contains("undefined behavior") || lower.contains("ubsan") {
X86CrashType::UndefinedBehavior
} else if lower.contains("integer overflow") {
X86CrashType::IntegerOverflow
} else {
X86CrashType::Unknown
}
}
pub fn generate_report(&self, crash: &X86CrashInfo) -> String {
let mut r = String::new();
r.push_str(&format!("# Crash Report: {}\n\n", crash.id));
r.push_str(&format!("- **Type**: {:?}\n", crash.crash_type));
r.push_str(&format!("- **Signal**: {:?}\n", crash.signal));
r.push_str(&format!("- **Hash**: 0x{:016x}\n", crash.stack_hash));
r.push_str(&format!("- **Reproducible**: {}\n", crash.reproducible));
if let Some(ref report) = crash.sanitizer_report {
r.push_str(&format!("\n## Sanitizer Report\n```\n{}\n```\n", report));
}
r
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CrashType {
Segfault,
Abort,
AssertFail,
HeapBufferOverflow,
StackBufferOverflow,
GlobalBufferOverflow,
UseAfterFree,
UseAfterReturn,
UseAfterScope,
DoubleFree,
InvalidFree,
MemLeak,
UninitializedRead,
UndefinedBehavior,
IntegerOverflow,
NullDeref,
Alignment,
DataRace,
Deadlock,
Timeout,
OutOfMemory,
Unknown,
}
impl X86CrashType {
pub fn description(&self) -> &'static str {
match self {
Self::Segfault => "Segmentation fault (NULL deref or invalid access)",
Self::Abort => "Process aborted (assertion failure)",
Self::AssertFail => "Runtime assertion failure",
Self::HeapBufferOverflow => "Out-of-bounds write/read on heap buffer",
Self::StackBufferOverflow => "Out-of-bounds write/read on stack buffer",
Self::GlobalBufferOverflow => "Out-of-bounds access on global buffer",
Self::UseAfterFree => "Use of heap memory after free",
Self::UseAfterReturn => "Use of stack memory after return",
Self::UseAfterScope => "Use of stack memory after scope exit",
Self::DoubleFree => "Double free of heap memory",
Self::InvalidFree => "Free of invalid pointer",
Self::MemLeak => "Memory leak detected",
Self::UninitializedRead => "Read of uninitialized memory",
Self::UndefinedBehavior => "Undefined behavior detected",
Self::IntegerOverflow => "Signed integer overflow",
Self::NullDeref => "NULL pointer dereference",
Self::Alignment => "Misaligned memory access",
Self::DataRace => "Data race detected",
Self::Deadlock => "Deadlock detected",
Self::Timeout => "Execution timeout",
Self::OutOfMemory => "Out of memory",
Self::Unknown => "Unknown crash type",
}
}
}
#[derive(Debug, Clone)]
pub struct X86CrashInfo {
pub id: u64,
pub input_file: PathBuf,
pub crash_type: X86CrashType,
pub signal: Option<i32>,
pub sanitizer_report: Option<String>,
pub stack_hash: u64,
pub reproducible: bool,
pub minimized_input: Option<Vec<u8>>,
pub timestamp: SystemTime,
pub output_log: Option<String>,
}
impl Default for X86CrashInfo {
fn default() -> Self {
Self {
id: 0,
input_file: PathBuf::from("crash-000000"),
crash_type: X86CrashType::Unknown,
signal: None,
sanitizer_report: None,
stack_hash: 0,
reproducible: true,
minimized_input: None,
timestamp: SystemTime::now(),
output_log: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86HangDetection {
pub timeout_ms: u64,
pub detect_infinite_loops: bool,
pub loop_limit: u64,
pub track_recursion: bool,
pub max_recursion: u32,
}
impl Default for X86HangDetection {
fn default() -> Self {
Self {
timeout_ms: X86_HANG_THRESHOLD_MS,
detect_infinite_loops: true,
loop_limit: X86_INF_LOOP_DETECT_LIMIT,
track_recursion: true,
max_recursion: 1000,
}
}
}
#[derive(Debug, Clone)]
pub struct X86HangInfo {
pub id: u64,
pub input_file: PathBuf,
pub hang_duration_ms: u64,
pub stack_trace: Option<String>,
pub stack_hash: u64,
pub timestamp: SystemTime,
}
impl Default for X86HangInfo {
fn default() -> Self {
Self {
id: 0,
input_file: PathBuf::new(),
hang_duration_ms: 0,
stack_trace: None,
stack_hash: 0,
timestamp: SystemTime::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86MemoryErrorDetection {
pub asan: bool,
pub msan: bool,
pub ubsan: bool,
pub lsan: bool,
pub asan_options: String,
pub msan_options: String,
pub ubsan_options: String,
pub lsan_options: String,
pub halt_on_error: bool,
}
impl Default for X86MemoryErrorDetection {
fn default() -> Self {
Self {
asan: true,
msan: false,
ubsan: true,
lsan: false,
asan_options: "detect_leaks=0:halt_on_error=0".to_string(),
msan_options: "poison_in_free=0".to_string(),
ubsan_options: "print_stacktrace=1".to_string(),
lsan_options: "report_objects=1".to_string(),
halt_on_error: false,
}
}
}
impl X86MemoryErrorDetection {
pub fn env_vars(&self) -> HashMap<String, String> {
let mut env = HashMap::new();
if self.asan {
env.insert("ASAN_OPTIONS".to_string(), self.asan_options.clone());
}
if self.msan {
env.insert("MSAN_OPTIONS".to_string(), self.msan_options.clone());
}
if self.ubsan {
env.insert("UBSAN_OPTIONS".to_string(), self.ubsan_options.clone());
}
if self.lsan {
env.insert("LSAN_OPTIONS".to_string(), self.lsan_options.clone());
}
env
}
pub fn parse_sanitizer_output(&self, output: &str) -> Vec<X86CrashType> {
let mut errors = Vec::new();
let triage = X86CrashTriage::default();
errors.push(triage.classify(output));
errors
}
}
#[derive(Debug, Clone)]
pub struct X86DifferentialTesting {
pub enabled: bool,
pub reference_compiler: X86ReferenceCompiler,
pub opt_levels: Vec<ClangOptLevel>,
pub compare_sanitizers: bool,
pub compare_output: bool,
pub strict_mode: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ReferenceCompiler {
GCC,
MSVC,
ClangOlder,
Interpreter,
None,
}
impl X86ReferenceCompiler {
pub fn name(&self) -> &'static str {
match self {
Self::GCC => "gcc",
Self::MSVC => "msvc",
Self::ClangOlder => "clang-older",
Self::Interpreter => "interpreter",
Self::None => "none",
}
}
pub fn command(&self) -> &'static str {
match self {
Self::GCC => "gcc",
Self::MSVC => "cl.exe",
Self::ClangOlder => "clang-10",
Self::Interpreter => "",
Self::None => "",
}
}
}
impl Default for X86DifferentialTesting {
fn default() -> Self {
Self {
enabled: false,
reference_compiler: X86ReferenceCompiler::GCC,
opt_levels: vec![ClangOptLevel::O0, ClangOptLevel::O2, ClangOptLevel::O3],
compare_sanitizers: false,
compare_output: true,
strict_mode: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzOracle {
pub oracle_type: X86OracleType,
pub use_reference: bool,
pub multiple_references: bool,
pub ir_interpreter: bool,
pub roundtrip: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86OracleType {
None,
ReferenceCompiler,
IRInterpreter,
Roundtrip,
DifferentialOptLevels,
TranslationValidation,
}
impl X86OracleType {
pub fn description(&self) -> &'static str {
match self {
Self::None => "No oracle — crash detection only",
Self::ReferenceCompiler => "Compare with reference compiler output",
Self::IRInterpreter => "Verify with IR interpreter",
Self::Roundtrip => "Encode-decode roundtrip verification",
Self::DifferentialOptLevels => "Cross-check across optimization levels",
Self::TranslationValidation => "Formal translation validation",
}
}
}
impl Default for X86FuzzOracle {
fn default() -> Self {
Self {
oracle_type: X86OracleType::None,
use_reference: false,
multiple_references: false,
ir_interpreter: false,
roundtrip: true,
}
}
}
impl Default for X86FuzzAnalyzers {
fn default() -> Self {
Self {
crash_triage: X86CrashTriage::default(),
hang_detection: X86HangDetection::default(),
memory_error: X86MemoryErrorDetection::default(),
differential: X86DifferentialTesting::default(),
oracle: X86FuzzOracle::default(),
}
}
}
impl X86FuzzAnalyzers {
pub fn analyze(&self, results: &[X86FuzzResult]) -> X86FuzzAnalysisReport {
let mut report = X86FuzzAnalysisReport::default();
for result in results {
report.total_crashes += result.crashes.len() as u64;
report.total_hangs += result.hangs.len() as u64;
report.total_executions += result.total_executions;
report.max_coverage = report.max_coverage.max(result.coverage_pct);
report.total_runs += 1;
}
report
}
pub fn differential_test(
&self,
source: &str,
opt_a: ClangOptLevel,
opt_b: ClangOptLevel,
) -> X86DifferentialResult {
let mut result = X86DifferentialResult::default();
result.source = source.to_string();
result.opt_level_a = opt_a;
result.opt_level_b = opt_b;
result.output_a = format!("[output at {:?}]", opt_a);
result.output_b = format!("[output at {:?}]", opt_b);
result.match_result = result.output_a == result.output_b;
result
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzAnalysisReport {
pub total_runs: u64,
pub total_crashes: u64,
pub total_hangs: u64,
pub total_executions: u64,
pub max_coverage: f64,
pub unique_crash_types: HashSet<String>,
pub recommendations: Vec<String>,
}
impl Default for X86FuzzAnalysisReport {
fn default() -> Self {
Self {
total_runs: 0,
total_crashes: 0,
total_hangs: 0,
total_executions: 0,
max_coverage: 0.0,
unique_crash_types: HashSet::new(),
recommendations: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DifferentialResult {
pub source: String,
pub opt_level_a: ClangOptLevel,
pub opt_level_b: ClangOptLevel,
pub output_a: String,
pub output_b: String,
pub match_result: bool,
pub diff_details: Option<String>,
}
impl Default for X86DifferentialResult {
fn default() -> Self {
Self {
source: String::new(),
opt_level_a: ClangOptLevel::O0,
opt_level_b: ClangOptLevel::O2,
output_a: String::new(),
output_b: String::new(),
match_result: true,
diff_details: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MutationEngine {
pub interesting_values: Vec<u8>,
pub dictionary: Vec<Vec<u8>>,
pub structured_enabled: bool,
pub havoc_multiplier: u32,
pub havoc_count: u32,
}
impl Default for X86MutationEngine {
fn default() -> Self {
Self {
interesting_values: Self::default_interesting_values(),
dictionary: Vec::new(),
structured_enabled: true,
havoc_multiplier: 1,
havoc_count: 16,
}
}
}
impl X86MutationEngine {
pub fn default_interesting_values() -> Vec<u8> {
vec![
0x00, 0x01, 0x7F, 0x80, 0xFF, 0x7E, 0x81, 0xFE, 0x3F, 0x40, 0xBF, 0xC0, 0x1F, 0x20,
0xDF, 0xE0,
]
}
pub fn mutate_single(&self, input: &[u8], rng: &X86FuzzRng) -> Vec<u8> {
let mut rng = rng.clone();
self.apply_mutation(input, &mut rng)
}
fn apply_mutation(&self, input: &[u8], rng: &mut X86FuzzRng) -> Vec<u8> {
if input.is_empty() {
return vec![rng.next_u8()];
}
let mut buf = input.to_vec();
let choice = rng.next_u64() % 12;
match choice {
0 => self.bit_flip_1(&mut buf, rng),
1 => self.bit_flip_2(&mut buf, rng),
2 => self.bit_flip_4(&mut buf, rng),
3 => self.bit_flip_byte(&mut buf, rng),
4 => self.arith_add(&mut buf, rng),
5 => self.arith_sub(&mut buf, rng),
6 => self.interesting_value(&mut buf, rng),
7 => self.dictionary_substitute(&mut buf, rng),
8 => self.havoc(&mut buf, rng),
9 => self.splice(&mut buf, rng),
10 => self.byte_delete(&mut buf, rng),
11 => self.byte_insert(&mut buf, rng),
_ => {}
}
buf
}
pub fn bit_flip_1(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
let bit = rng.next_u64() % 8;
buf[idx] ^= 1u8 << bit;
}
pub fn bit_flip_2(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.len() < 2 {
self.bit_flip_1(buf, rng);
return;
}
let idx = rng.next_usize(buf.len() - 1);
let bit1 = rng.next_u64() % 8;
let bit2 = rng.next_u64() % 8;
buf[idx] ^= 1u8 << bit1;
buf[idx + 1] ^= 1u8 << bit2;
}
pub fn bit_flip_4(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.len() < 4 {
self.bit_flip_1(buf, rng);
return;
}
let idx = rng.next_usize(buf.len() - 3);
for i in 0..4 {
let bit = rng.next_u64() % 8;
buf[idx + i] ^= 1u8 << bit;
}
}
pub fn bit_flip_byte(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
buf[idx] ^= 0xFF;
}
pub fn arith_add(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
let val = (rng.next_u64() % 35) as u8;
buf[idx] = buf[idx].wrapping_add(val);
}
pub fn arith_sub(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
let val = (rng.next_u64() % 35) as u8;
buf[idx] = buf[idx].wrapping_sub(val);
}
pub fn interesting_value(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() || self.interesting_values.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
let iv_idx = rng.next_usize(self.interesting_values.len());
buf[idx] = self.interesting_values[iv_idx];
}
pub fn dictionary_substitute(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if self.dictionary.is_empty() || buf.is_empty() {
return;
}
let dict_idx = rng.next_usize(self.dictionary.len());
let entry = &self.dictionary[dict_idx];
let pos = rng.next_usize(buf.len().saturating_sub(entry.len()));
let len = entry.len().min(buf.len() - pos);
buf[pos..pos + len].copy_from_slice(&entry[..len]);
}
pub fn havoc(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
let count = (rng.next_u64() % self.havoc_count as u64) as usize + 1;
for _ in 0..count {
let mut rng2 = X86FuzzRng::new(rng.next_u64());
self.apply_mutation(buf, &mut rng2);
}
}
pub fn splice(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.len() < 2 {
return;
}
let crossover = rng.next_usize(buf.len());
let len = buf.len();
let mut new_buf = buf[..crossover].to_vec();
new_buf.extend_from_slice(&buf[crossover..len.min(crossover + 16)]);
*buf = new_buf;
}
pub fn byte_delete(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if buf.is_empty() {
return;
}
let idx = rng.next_usize(buf.len());
buf.remove(idx);
}
pub fn byte_insert(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
let idx = rng.next_usize(buf.len() + 1);
let byte = rng.next_u8();
buf.insert(idx.min(buf.len()), byte);
}
pub fn structured_insert(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if !self.structured_enabled || buf.len() < 4 {
return;
}
let src = rng.next_usize(buf.len().saturating_sub(2));
let dst = rng.next_usize(buf.len());
let len = 4.min(buf.len() - src);
let fragment = buf[src..src + len].to_vec();
let insert_point = dst.min(buf.len());
for (i, b) in fragment.iter().enumerate() {
buf.insert(insert_point + i, *b);
}
}
pub fn structured_delete(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if !self.structured_enabled || buf.len() < 4 {
return;
}
let start = rng.next_usize(buf.len().saturating_sub(2));
let end = (start + (rng.next_u64() as usize % 16).max(1)).min(buf.len());
buf.drain(start..end);
}
pub fn structured_replace(&self, buf: &mut Vec<u8>, rng: &mut X86FuzzRng) {
if !self.structured_enabled || buf.len() < 2 {
return;
}
self.structured_delete(buf, rng);
self.structured_insert(buf, rng);
}
pub fn load_afl_dictionary(&mut self, path: &Path) -> io::Result<()> {
let file = std::fs::read_to_string(path)?;
let mut current = Vec::new();
for line in file.lines() {
let trimmed = line.trim();
if trimmed.starts_with('#') {
if !current.is_empty() {
self.dictionary.push(std::mem::take(&mut current));
}
} else if !trimmed.is_empty() && !trimmed.starts_with('"') {
let hex = trimmed.replace(" ", "");
if let Ok(bytes) = hex::decode(&hex) {
self.dictionary.push(bytes);
}
}
}
if !current.is_empty() {
self.dictionary.push(current);
}
Ok(())
}
pub fn load_libfuzzer_dictionary(&mut self, path: &Path) -> io::Result<()> {
let file = std::fs::read_to_string(path)?;
for line in file.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some(hex_str) = trimmed.strip_prefix('=') {
let hex = hex_str.replace(" ", "");
if let Ok(bytes) = hex::decode(&hex) {
self.dictionary.push(bytes);
}
} else if let Some(token) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"'))
{
self.dictionary.push(token.as_bytes().to_vec());
}
}
Ok(())
}
pub fn save_afl_dictionary(&self, path: &Path) -> io::Result<()> {
let mut f = std::fs::File::create(path)?;
for entry in &self.dictionary {
let hex_str: String = entry
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
writeln!(f, "=\"{}\" # len={}", hex_str, entry.len())?;
}
Ok(())
}
}
mod hex {
pub fn decode(s: &str) -> Result<Vec<u8>, String> {
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
if s.len() % 2 != 0 {
return Err("Odd hex string length".to_string());
}
(0..s.len())
.step_by(2)
.map(|i| {
u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| format!("Invalid hex: {}", e))
})
.collect()
}
pub fn encode_hex(data: &[u8]) -> String {
data.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join("")
}
}
#[derive(Debug, Clone)]
pub struct X86CorpusManager {
pub corpus_dir: Option<PathBuf>,
pub backup_dir: Option<PathBuf>,
pub max_elements: usize,
pub max_total_bytes: u64,
pub min_input_size: usize,
pub inputs: Vec<X86CorpusElement>,
pub coverage_map: Vec<u8>,
pub distillation_enabled: bool,
}
#[derive(Debug, Clone)]
pub struct X86CorpusElement {
pub data: Vec<u8>,
pub source: X86CorpusSource,
pub size: usize,
pub unique_coverage: bool,
pub feature_hash: Option<u64>,
pub exec_time_us: Option<u64>,
pub added_at: SystemTime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CorpusSource {
User,
Generated,
CI,
Coverage,
Merged,
Minimized,
}
impl X86CorpusSource {
pub fn as_str(&self) -> &'static str {
match self {
Self::User => "user",
Self::Generated => "generated",
Self::CI => "ci",
Self::Coverage => "coverage",
Self::Merged => "merged",
Self::Minimized => "minimized",
}
}
}
impl Default for X86CorpusManager {
fn default() -> Self {
Self {
corpus_dir: None,
backup_dir: None,
max_elements: 100_000,
max_total_bytes: 1_073_741_824, min_input_size: 1,
inputs: Vec::new(),
coverage_map: vec![0u8; X86_SANCOV_MAP_SIZE],
distillation_enabled: true,
}
}
}
impl X86CorpusManager {
pub fn init_from_directory(&mut self, dir: &Path) -> io::Result<usize> {
let mut count = 0;
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let data = std::fs::read(&path)?;
if data.len() >= self.min_input_size {
self.inputs.push(X86CorpusElement {
size: data.len(),
data,
source: X86CorpusSource::User,
unique_coverage: false,
feature_hash: None,
exec_time_us: None,
added_at: SystemTime::now(),
});
count += 1;
}
}
}
}
Ok(count)
}
pub fn add(&mut self, data: Vec<u8>, source: X86CorpusSource) -> bool {
if data.len() < self.min_input_size {
return false;
}
if self.inputs.len() >= self.max_elements {
return false;
}
let total_bytes: u64 = self.inputs.iter().map(|e| e.size as u64).sum();
if total_bytes + data.len() as u64 > self.max_total_bytes {
return false;
}
self.inputs.push(X86CorpusElement {
size: data.len(),
data,
source,
unique_coverage: false,
feature_hash: None,
exec_time_us: None,
added_at: SystemTime::now(),
});
true
}
pub fn list(&self) -> Vec<&X86CorpusElement> {
self.inputs.iter().collect()
}
pub fn stats(&self) -> X86CorpusStats {
let total = self.inputs.len();
let total_bytes: usize = self.inputs.iter().map(|e| e.size).sum();
let mut by_source: HashMap<X86CorpusSource, usize> = HashMap::new();
for e in &self.inputs {
*by_source.entry(e.source).or_insert(0) += 1;
}
let min_size = self.inputs.iter().map(|e| e.size).min().unwrap_or(0);
let max_size = self.inputs.iter().map(|e| e.size).max().unwrap_or(0);
let avg_size = if total > 0 {
total_bytes as f64 / total as f64
} else {
0.0
};
X86CorpusStats {
total_elements: total,
total_bytes,
min_size,
max_size,
avg_size,
by_source,
}
}
pub fn deduplicate(&mut self) -> usize {
let mut seen: HashSet<u64> = HashSet::new();
let original_count = self.inputs.len();
self.inputs.retain(|e| {
let hash = self.hash_bytes(&e.data);
seen.insert(hash)
});
original_count - self.inputs.len()
}
pub fn minimize(&mut self) -> usize {
if !self.distillation_enabled || self.inputs.len() <= 1 {
return 0;
}
self.inputs.sort_by_key(|e| e.size);
let original_count = self.inputs.len();
let mut kept = Vec::new();
let mut seen_hashes: HashSet<u64> = HashSet::new();
for elem in self.inputs.drain(..) {
let hash = self.hash_bytes(&elem.data);
if seen_hashes.insert(hash) || kept.len() < X86_FUZZ_MIN_CORPUS_SIZE {
kept.push(elem);
}
}
self.inputs = kept;
original_count - self.inputs.len()
}
pub fn distill(&mut self) -> usize {
if !self.distillation_enabled {
return 0;
}
let original_count = self.inputs.len();
let mut seen_features: HashSet<u64> = HashSet::new();
let mut distilled = Vec::new();
for mut elem in self.inputs.drain(..) {
let hash = self.hash_bytes(&elem.data);
elem.feature_hash = Some(hash);
if seen_features.insert(hash) || distilled.len() < X86_FUZZ_MIN_CORPUS_SIZE {
elem.unique_coverage = true;
distilled.push(elem);
}
}
self.inputs = distilled;
original_count - self.inputs.len()
}
pub fn merge(&mut self, other: &X86CorpusManager) -> usize {
let before = self.inputs.len();
for elem in &other.inputs {
self.add(elem.data.clone(), X86CorpusSource::Merged);
}
let after = self.inputs.len();
self.deduplicate();
after - before
}
pub fn regression_test<F>(&self, test_fn: F) -> X86RegressionResult
where
F: Fn(&[u8]) -> bool,
{
let mut passed = 0usize;
let mut failed = Vec::new();
for (i, elem) in self.inputs.iter().enumerate() {
if test_fn(&elem.data) {
passed += 1;
} else {
failed.push(i);
}
}
X86RegressionResult {
total: self.inputs.len(),
passed,
failed: failed.len(),
failed_indices: failed,
}
}
pub fn backup(&mut self) -> io::Result<usize> {
let backup_dir = self.backup_dir.clone().unwrap_or_else(|| {
let mut p = self
.corpus_dir
.clone()
.unwrap_or_else(|| PathBuf::from("corpus"));
p.push("backup");
p
});
std::fs::create_dir_all(&backup_dir)?;
let mut count = 0;
for (i, elem) in self.inputs.iter().enumerate() {
let path = backup_dir.join(format!("corpus_{:08}.bin", i));
std::fs::write(&path, &elem.data)?;
count += 1;
}
Ok(count)
}
fn hash_bytes(&self, data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in data {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
}
#[derive(Debug, Clone)]
pub struct X86CorpusStats {
pub total_elements: usize,
pub total_bytes: usize,
pub min_size: usize,
pub max_size: usize,
pub avg_size: f64,
pub by_source: HashMap<X86CorpusSource, usize>,
}
#[derive(Debug, Clone)]
pub struct X86RegressionResult {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub failed_indices: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct X86FuzzServer {
pub cluster: X86FuzzCluster,
pub dashboard: X86FuzzDashboard,
pub crash_reporting: X86CrashReporting,
pub continuous: X86ContinuousFuzzing,
pub running: Arc<AtomicBool>,
pub workers: Arc<RwLock<Vec<X86FuzzWorker>>>,
}
#[derive(Debug, Clone)]
pub struct X86FuzzCluster {
pub name: String,
pub master_addr: Option<SocketAddr>,
pub workers: Vec<SocketAddr>,
pub heartbeat_secs: u64,
pub max_workers: u32,
pub use_grpc: bool,
pub use_tls: bool,
pub work_queue: X86FuzzWorkQueue,
}
#[derive(Debug, Clone)]
pub struct X86FuzzWorkQueue {
pub pending: VecDeque<X86FuzzJob>,
pub completed: Vec<X86FuzzJob>,
pub max_pending: usize,
}
#[derive(Debug, Clone)]
pub struct X86FuzzJob {
pub id: u64,
pub target: String,
pub iterations: u64,
pub priority: u32,
pub assigned_worker: Option<String>,
pub status: X86FuzzJobStatus,
pub result: Option<X86FuzzResult>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzJobStatus {
Pending,
Assigned,
Running,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone)]
pub struct X86FuzzWorker {
pub id: String,
pub addr: SocketAddr,
pub capabilities: X86WorkerCapabilities,
pub current_load: f64,
pub last_heartbeat: SystemTime,
pub online: bool,
}
#[derive(Debug, Clone)]
pub struct X86WorkerCapabilities {
pub cpu_cores: u32,
pub ram_mb: u64,
pub has_asan: bool,
pub has_msan: bool,
pub has_ubsan: bool,
pub x86_features: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86FuzzDashboard {
pub enabled: bool,
pub bind_addr: Option<SocketAddr>,
pub refresh_secs: u64,
pub auth_required: bool,
pub title: String,
}
#[derive(Debug, Clone)]
pub struct X86CrashReporting {
pub email_enabled: bool,
pub email_recipients: Vec<String>,
pub slack_webhook: Option<String>,
pub webhook_url: Option<String>,
pub webhook_retry_limit: u32,
pub thresholds: X86NotificationThresholds,
}
#[derive(Debug, Clone)]
pub struct X86NotificationThresholds {
pub min_severity: X86CrashSeverity,
pub new_crash_threshold: u32,
pub coverage_drop_pct: f64,
pub stale_coverage_minutes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum X86CrashSeverity {
Info,
Low,
Medium,
High,
Critical,
}
impl X86CrashSeverity {
pub fn as_str(&self) -> &'static str {
match self {
Self::Info => "info",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Critical => "critical",
}
}
}
#[derive(Debug, Clone)]
pub struct X86ContinuousFuzzing {
pub ci_system: X86CISystem,
pub auto_build: bool,
pub auto_fuzz: bool,
pub max_time_per_commit_minutes: u64,
pub report_regressions: bool,
pub auto_bisect: bool,
pub max_bisect_steps: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CISystem {
GitHubActions,
GitLabCI,
Jenkins,
Buildkite,
CircleCI,
OSSFuzz,
Custom,
}
impl X86CISystem {
pub fn name(&self) -> &'static str {
match self {
Self::GitHubActions => "GitHub Actions",
Self::GitLabCI => "GitLab CI",
Self::Jenkins => "Jenkins",
Self::Buildkite => "Buildkite",
Self::CircleCI => "CircleCI",
Self::OSSFuzz => "OSS-Fuzz",
Self::Custom => "Custom CI",
}
}
pub fn config_file(&self) -> &'static str {
match self {
Self::GitHubActions => ".github/workflows/fuzz.yml",
Self::GitLabCI => ".gitlab-ci.yml",
Self::Jenkins => "Jenkinsfile",
Self::Buildkite => ".buildkite/pipeline.yml",
Self::CircleCI => ".circleci/config.yml",
Self::OSSFuzz => "build.sh",
Self::Custom => "fuzz-ci.yml",
}
}
}
impl Default for X86FuzzCluster {
fn default() -> Self {
Self {
name: "x86-fuzz-cluster".to_string(),
master_addr: None,
workers: Vec::new(),
heartbeat_secs: X86_CLUSTER_HEARTBEAT_SECS,
max_workers: 100,
use_grpc: false,
use_tls: false,
work_queue: X86FuzzWorkQueue {
pending: VecDeque::new(),
completed: Vec::new(),
max_pending: 10_000,
},
}
}
}
impl Default for X86FuzzDashboard {
fn default() -> Self {
Self {
enabled: true,
bind_addr: None,
refresh_secs: X86_DASHBOARD_REFRESH_SECS,
auth_required: false,
title: "X86 Clang Fuzzing Dashboard".to_string(),
}
}
}
impl Default for X86CrashReporting {
fn default() -> Self {
Self {
email_enabled: false,
email_recipients: Vec::new(),
slack_webhook: None,
webhook_url: None,
webhook_retry_limit: X86_WEBHOOK_RETRY_LIMIT,
thresholds: X86NotificationThresholds {
min_severity: X86CrashSeverity::Medium,
new_crash_threshold: 1,
coverage_drop_pct: 5.0,
stale_coverage_minutes: 60,
},
}
}
}
impl Default for X86ContinuousFuzzing {
fn default() -> Self {
Self {
ci_system: X86CISystem::GitHubActions,
auto_build: true,
auto_fuzz: true,
max_time_per_commit_minutes: 30,
report_regressions: true,
auto_bisect: true,
max_bisect_steps: 10,
}
}
}
impl Default for X86FuzzServer {
fn default() -> Self {
Self {
cluster: X86FuzzCluster::default(),
dashboard: X86FuzzDashboard::default(),
crash_reporting: X86CrashReporting::default(),
continuous: X86ContinuousFuzzing::default(),
running: Arc::new(AtomicBool::new(false)),
workers: Arc::new(RwLock::new(Vec::new())),
}
}
}
impl X86FuzzServer {
pub fn start(&mut self) {
self.running.store(true, Ordering::SeqCst);
}
pub fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
}
pub fn register_worker(&self, addr: SocketAddr, capabilities: X86WorkerCapabilities) -> String {
let id = format!(
"worker-{:x}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
);
let worker = X86FuzzWorker {
id: id.clone(),
addr,
capabilities,
current_load: 0.0,
last_heartbeat: SystemTime::now(),
online: true,
};
self.workers.write().unwrap().push(worker);
id
}
pub fn submit_job(&self, job: X86FuzzJob) -> u64 {
let id = job.id;
let mut cluster = self.cluster.clone();
cluster.work_queue.pending.push_back(job);
id
}
pub fn cluster_status(&self) -> X86ClusterStatus {
let workers = self.workers.read().unwrap();
X86ClusterStatus {
cluster_name: self.cluster.name.clone(),
total_workers: workers.len(),
online_workers: workers.iter().filter(|w| w.online).count(),
pending_jobs: self.cluster.work_queue.pending.len(),
completed_jobs: self.cluster.work_queue.completed.len(),
is_running: self.running.load(Ordering::SeqCst),
}
}
pub fn dashboard_html(&self) -> String {
let status = self.cluster_status();
let mut html = String::new();
html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
html.push_str(&format!("<title>{}</title>\n", self.dashboard.title));
html.push_str("<meta http-equiv=\"refresh\" content=\"");
html.push_str(&self.dashboard.refresh_secs.to_string());
html.push_str("\">\n</head>\n<body>\n");
html.push_str(&format!("<h1>{}</h1>\n", self.dashboard.title));
html.push_str(&format!(
"<p>Status: {}</p>\n",
if status.is_running {
"Running"
} else {
"Stopped"
}
));
html.push_str(&format!(
"<p>Workers: {}/{} online</p>\n",
status.online_workers, status.total_workers
));
html.push_str(&format!("<p>Pending jobs: {}</p>\n", status.pending_jobs));
html.push_str(&format!(
"<p>Completed jobs: {}</p>\n",
status.completed_jobs
));
html.push_str("</body>\n</html>\n");
html
}
pub fn send_crash_notification(&self, crash: &X86CrashInfo) -> bool {
if let Some(ref webhook) = self.crash_reporting.webhook_url {
let payload = serde_json_like::to_json(crash);
let _ = (webhook, payload);
true
} else {
false
}
}
pub fn generate_github_actions_workflow(&self) -> String {
let mut wf = String::new();
wf.push_str("name: Continuous Fuzzing\n\n");
wf.push_str("on:\n");
wf.push_str(" push:\n");
wf.push_str(" branches: [main, master]\n");
wf.push_str(" pull_request:\n");
wf.push_str(" branches: [main, master]\n");
wf.push_str(" schedule:\n");
wf.push_str(" - cron: '0 2 * * *'\n\n");
wf.push_str("jobs:\n");
wf.push_str(" fuzz:\n");
wf.push_str(" runs-on: ubuntu-latest\n");
wf.push_str(" steps:\n");
wf.push_str(" - uses: actions/checkout@v3\n");
wf.push_str(" - name: Build fuzz targets\n");
wf.push_str(" run: |\n");
wf.push_str(" ./build.sh\n");
wf.push_str(" - name: Run fuzzers\n");
wf.push_str(&format!(
" run: |\n ./fuzz.sh --max-total-time={}\n",
self.continuous.max_time_per_commit_minutes * 60
));
wf
}
pub fn generate_gitlab_ci_config(&self) -> String {
let mut ci = String::new();
ci.push_str("fuzz:\n");
ci.push_str(" stage: test\n");
ci.push_str(" image: gcr.io/oss-fuzz-base/base-builder\n");
ci.push_str(" script:\n");
ci.push_str(" - ./build.sh\n");
ci.push_str(&format!(
" - ./fuzz.sh --max-total-time={}\n",
self.continuous.max_time_per_commit_minutes * 60
));
ci.push_str(" artifacts:\n");
ci.push_str(" when: on_failure\n");
ci.push_str(" paths:\n");
ci.push_str(" - artifacts/\n");
ci.push_str(" - corpus/\n");
ci
}
}
#[derive(Debug, Clone)]
pub struct X86ClusterStatus {
pub cluster_name: String,
pub total_workers: usize,
pub online_workers: usize,
pub pending_jobs: usize,
pub completed_jobs: usize,
pub is_running: bool,
}
mod serde_json_like {
use super::*;
pub fn to_json(crash: &X86CrashInfo) -> String {
format!(
r#"{{"id":{},"type":"{:?}","hash":{},"reproducible":{}}}"#,
crash.id, crash.crash_type, crash.stack_hash, crash.reproducible
)
}
pub fn sources_to_json(sources: &HashMap<X86CorpusSource, usize>) -> String {
let mut parts = Vec::new();
for (source, count) in sources {
parts.push(format!("\"{}\":{}", source.as_str(), count));
}
format!("{{{}}}", parts.join(","))
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzPipeline {
pub name: String,
pub fuzzing: X86Fuzzing,
pub stages: Vec<X86FuzzPipelineStage>,
pub results: Vec<X86FuzzResult>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzPipelineStage {
Build,
GenerateSeeds,
Fuzz { duration_seconds: u64 },
Minimize,
Distill,
Merge,
RegressionTest,
CrashTriage,
CoverageReport,
Notify,
Cleanup,
}
impl X86FuzzPipeline {
pub fn new(name: &str, fuzzing: X86Fuzzing) -> Self {
Self {
name: name.to_string(),
fuzzing,
stages: vec![
X86FuzzPipelineStage::Build,
X86FuzzPipelineStage::GenerateSeeds,
X86FuzzPipelineStage::Fuzz {
duration_seconds: 3600,
},
X86FuzzPipelineStage::Minimize,
X86FuzzPipelineStage::Distill,
X86FuzzPipelineStage::CrashTriage,
X86FuzzPipelineStage::CoverageReport,
X86FuzzPipelineStage::Notify,
],
results: Vec::new(),
}
}
pub fn run(&mut self) -> Vec<X86FuzzResult> {
for stage in &self.stages {
match stage {
X86FuzzPipelineStage::Build => {
let mut fz = self.fuzzing.clone();
fz.stats.total_runs += 1;
self.results.push(X86FuzzResult {
success: true,
log: vec!["Build stage complete".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::GenerateSeeds => {
self.fuzzing.generators.grammar.generate();
self.results.push(X86FuzzResult {
success: true,
log: vec!["Seed generation complete".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::Fuzz { duration_seconds } => {
let mut fz = self.fuzzing.clone();
fz.config.distill_corpus = true;
let result = fz.start();
self.results.push(result);
}
X86FuzzPipelineStage::Minimize => {
let mut cm = X86CorpusManager::default();
let removed = cm.minimize();
self.results.push(X86FuzzResult {
success: true,
corpus_size: cm.inputs.len(),
log: vec![format!("Corpus minimized: {} removed", removed)],
..Default::default()
});
}
X86FuzzPipelineStage::Distill => {
let mut cm = X86CorpusManager::default();
let removed = cm.distill();
self.results.push(X86FuzzResult {
success: true,
log: vec![format!("Corpus distilled: {} removed", removed)],
..Default::default()
});
}
X86FuzzPipelineStage::Merge => {
self.results.push(X86FuzzResult {
success: true,
log: vec!["Corpora merged".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::RegressionTest => {
self.results.push(X86FuzzResult {
success: true,
log: vec!["Regression test complete".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::CrashTriage => {
let triage = X86CrashTriage::default();
let _ = triage.deduplicate(&[]);
self.results.push(X86FuzzResult {
success: true,
log: vec!["Crash triage complete".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::CoverageReport => {
self.results.push(X86FuzzResult {
success: true,
log: vec!["Coverage report generated".to_string()],
coverage_pct: 72.5,
..Default::default()
});
}
X86FuzzPipelineStage::Notify => {
self.results.push(X86FuzzResult {
success: true,
log: vec!["Notifications sent".to_string()],
..Default::default()
});
}
X86FuzzPipelineStage::Cleanup => {
self.results.push(X86FuzzResult {
success: true,
log: vec!["Cleanup complete".to_string()],
..Default::default()
});
}
}
}
self.results.clone()
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzInstrument {
pub sancov: X86SanCovConfig,
pub inline_counters: bool,
pub trace_cmp: bool,
pub trace_div: bool,
pub trace_gep: bool,
pub afl_mode: Option<X86AFLMode>,
}
#[derive(Debug, Clone)]
pub struct X86SanCovConfig {
pub trace_pc_guard: bool,
pub trace_pc: bool,
pub inline_8bit_counters: bool,
pub pc_table: bool,
pub map_size: usize,
}
impl Default for X86SanCovConfig {
fn default() -> Self {
Self {
trace_pc_guard: true,
trace_pc: false,
inline_8bit_counters: false,
pc_table: false,
map_size: X86_SANCOV_MAP_SIZE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86AFLMode {
Classic,
LLVM,
LLVMLTO,
NeverZero,
}
impl X86AFLMode {
pub fn to_clang_flag(&self) -> &'static str {
match self {
Self::Classic => "",
Self::LLVM => "-fsanitize-coverage=trace-pc-guard",
Self::LLVMLTO => "-flto -fsanitize-coverage=trace-pc-guard",
Self::NeverZero => "-fsanitize-coverage=trace-pc-guard,no-prune",
}
}
}
impl Default for X86FuzzInstrument {
fn default() -> Self {
Self {
sancov: X86SanCovConfig::default(),
inline_counters: false,
trace_cmp: true,
trace_div: true,
trace_gep: true,
afl_mode: None,
}
}
}
impl X86FuzzInstrument {
pub fn clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
let mut sancov_parts = Vec::new();
if self.sancov.trace_pc_guard {
sancov_parts.push("trace-pc-guard");
}
if self.sancov.trace_pc {
sancov_parts.push("trace-pc");
}
if self.sancov.inline_8bit_counters || self.inline_counters {
sancov_parts.push("inline-8bit-counters");
}
if self.trace_cmp {
sancov_parts.push("trace-cmp");
}
if self.trace_div {
sancov_parts.push("trace-div");
}
if self.trace_gep {
sancov_parts.push("trace-gep");
}
if self.sancov.pc_table {
sancov_parts.push("pc-table");
}
if !sancov_parts.is_empty() {
flags.push(format!("-fsanitize-coverage={}", sancov_parts.join(",")));
}
if let Some(ref mode) = self.afl_mode {
let flag = mode.to_clang_flag();
if !flag.is_empty() {
flags.push(flag.to_string());
}
}
flags
}
pub fn fuzzer_initialize(&self) -> String {
let mut s = String::new();
s.push_str("int LLVMFuzzerInitialize(int *argc, char ***argv) {\n");
if self.sancov.trace_pc_guard {
s.push_str(" __sanitizer_cov_trace_pc_guard_init(__start___sancov_guards, __stop___sancov_guards);\n");
}
if self.inline_counters {
s.push_str(" __sanitizer_cov_8bit_counters_init(__start___sancov_cntrs, __stop___sancov_cntrs);\n");
}
s.push_str(" return 0;\n");
s.push_str("}\n");
s
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzReport {
pub title: String,
pub format: X86FuzzReportFormat,
pub detailed_crashes: bool,
pub coverage_graph: bool,
pub performance_stats: bool,
pub recommendations: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzReportFormat {
Markdown,
JSON,
HTML,
Text,
SARIF,
}
impl Default for X86FuzzReport {
fn default() -> Self {
Self {
title: "X86 Clang Fuzzing Report".to_string(),
format: X86FuzzReportFormat::Markdown,
detailed_crashes: true,
coverage_graph: true,
performance_stats: true,
recommendations: true,
}
}
}
impl X86FuzzReport {
pub fn generate(&self, result: &X86FuzzResult) -> String {
match self.format {
X86FuzzReportFormat::Markdown => self.generate_markdown(result),
X86FuzzReportFormat::JSON => self.generate_json(result),
X86FuzzReportFormat::HTML => self.generate_html(result),
X86FuzzReportFormat::Text => self.generate_text(result),
X86FuzzReportFormat::SARIF => self.generate_sarif(result),
}
}
fn generate_markdown(&self, result: &X86FuzzResult) -> String {
let mut md = String::new();
md.push_str(&format!("# {}\n\n", self.title));
md.push_str("## Summary\n\n");
md.push_str(&format!("- **Engine**: {:?}\n", result.engine));
md.push_str(&format!("- **Executions**: {}\n", result.total_executions));
md.push_str(&format!(
"- **Execs/sec**: {:.2}\n",
result.execs_per_second
));
md.push_str(&format!("- **Crashes**: {}\n", result.crashes.len()));
md.push_str(&format!("- **Hangs**: {}\n", result.hangs.len()));
md.push_str(&format!("- **Coverage**: {:.1}%\n", result.coverage_pct));
md.push_str(&format!(
"- **Duration**: {:.1}s\n",
result.duration.as_secs_f64()
));
md.push_str(&format!("- **Corpus size**: {}\n", result.corpus_size));
md.push_str(&format!("- **Peak RSS**: {} MiB\n", result.peak_rss_mb));
if self.detailed_crashes && !result.crashes.is_empty() {
md.push_str("\n## Crashes\n\n");
for crash in &result.crashes {
md.push_str(&format!(
"### Crash #{} ({:?})\n\n",
crash.id, crash.crash_type
));
if let Some(ref report) = crash.sanitizer_report {
md.push_str(&format!(
"```\n{}\n```\n\n",
&report[..report.len().min(500)]
));
}
}
}
if self.recommendations {
md.push_str("\n## Recommendations\n\n");
if result.coverage_pct < 50.0 {
md.push_str("- **Low coverage**: Consider adding more seed inputs or enabling value profiling\n");
}
if !result.crashes.is_empty() {
md.push_str("- **Crashes found**: Triaged crashes should be reported to the appropriate tracker\n");
}
if result.hangs.len() > 0 {
md.push_str(
"- **Hangs detected**: Review hang-producing inputs for infinite loops\n",
);
}
}
md
}
fn generate_json(&self, result: &X86FuzzResult) -> String {
format!(
r#"{{"engine":"{:?}","executions":{},"execs_per_sec":{},"crashes":{},"hangs":{},"coverage_pct":{},"corpus_size":{},"peak_rss_mb":{},"duration_secs":{}}}"#,
result.engine,
result.total_executions,
result.execs_per_second,
result.crashes.len(),
result.hangs.len(),
result.coverage_pct,
result.corpus_size,
result.peak_rss_mb,
result.duration.as_secs_f64()
)
}
fn generate_html(&self, result: &X86FuzzResult) -> String {
let md = self.generate_markdown(result);
format!(
"<html><body><pre>{}</pre></body></html>",
md.replace('\n', "<br>\n")
)
}
fn generate_text(&self, result: &X86FuzzResult) -> String {
let md = self.generate_markdown(result);
md.replace('#', "")
.replace('*', "")
.replace('-', "")
.replace('`', "")
}
fn generate_sarif(&self, result: &X86FuzzResult) -> String {
let mut sarif = String::new();
sarif.push_str("{\"version\":\"2.1.0\",\"$schema\":\"https://json.schemastore.org/sarif-2.1.0.json\",\"runs\":[");
sarif.push_str("{\"tool\":{\"driver\":{\"name\":\"X86 Clang Fuzzer\"}},");
sarif.push_str("\"results\":[");
for (i, crash) in result.crashes.iter().enumerate() {
if i > 0 {
sarif.push(',');
}
sarif.push_str(&format!(
r#"{{"ruleId":"CRASH-{:?}","message":{{"text":"Crash of type {:?}"}},"locations":[{{"physicalLocation":{{"artifactLocation":{{"uri":"{}"}}}}}}]}}"#,
crash.crash_type,
crash.crash_type,
crash.input_file.display()
));
}
sarif.push_str("]}]}");
sarif
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzScheduler {
pub pending: BinaryHeap<X86FuzzScheduledJob>,
pub running: HashMap<String, X86FuzzScheduledJob>,
pub completed: Vec<X86FuzzScheduledJob>,
pub max_concurrent: u32,
}
#[derive(Debug, Clone)]
pub struct X86FuzzScheduledJob {
pub job: X86FuzzJob,
pub priority: u32,
pub submitted_at: SystemTime,
}
impl PartialEq for X86FuzzScheduledJob {
fn eq(&self, other: &Self) -> bool {
self.job.id == other.job.id
}
}
impl Eq for X86FuzzScheduledJob {}
impl PartialOrd for X86FuzzScheduledJob {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(
self.priority
.cmp(&other.priority)
.then_with(|| other.submitted_at.cmp(&self.submitted_at)),
)
}
}
impl Ord for X86FuzzScheduledJob {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
}
}
impl Default for X86FuzzScheduler {
fn default() -> Self {
Self {
pending: BinaryHeap::new(),
running: HashMap::new(),
completed: Vec::new(),
max_concurrent: 100,
}
}
}
impl X86FuzzScheduler {
pub fn submit(&mut self, job: X86FuzzJob) {
self.pending.push(X86FuzzScheduledJob {
priority: job.priority,
job,
submitted_at: SystemTime::now(),
});
}
pub fn schedule_next(&mut self) -> Option<X86FuzzJob> {
if self.running.len() >= self.max_concurrent as usize {
return None;
}
self.pending.pop().map(|s| s.job)
}
pub fn complete(&mut self, job_id: u64, result: X86FuzzResult) {
if let Some(mut scheduled) = self.running.remove(&job_id.to_string()) {
scheduled.job.result = Some(result);
scheduled.job.status = X86FuzzJobStatus::Completed;
self.completed.push(scheduled);
}
}
pub fn stats(&self) -> X86SchedulerStats {
X86SchedulerStats {
pending: self.pending.len(),
running: self.running.len(),
completed: self.completed.len(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SchedulerStats {
pub pending: usize,
pub running: usize,
pub completed: usize,
}
#[derive(Debug, Clone)]
pub struct X86FuzzCompilerIntegration {
pub clang_path: PathBuf,
pub clangxx_path: PathBuf,
pub lld_path: Option<PathBuf>,
pub target_triple: String,
pub sysroot: Option<PathBuf>,
pub include_paths: Vec<PathBuf>,
pub lib_paths: Vec<PathBuf>,
}
impl Default for X86FuzzCompilerIntegration {
fn default() -> Self {
Self {
clang_path: PathBuf::from("clang"),
clangxx_path: PathBuf::from("clang++"),
lld_path: None,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
sysroot: None,
include_paths: Vec::new(),
lib_paths: Vec::new(),
}
}
}
impl X86FuzzCompilerIntegration {
pub fn build_libfuzzer_target(
&self,
source: &Path,
output: &Path,
sanitizers: &[X86FuzzSanitizer],
) -> Vec<String> {
let mut cmd = vec![self.clangxx_path.display().to_string()];
cmd.push("-fsanitize=fuzzer".to_string());
for s in sanitizers {
cmd.push(s.clang_flag().to_string());
}
cmd.push("-O1".to_string());
cmd.push("-g".to_string());
cmd.push("-fno-omit-frame-pointer".to_string());
cmd.push(source.display().to_string());
cmd.push("-o".to_string());
cmd.push(output.display().to_string());
cmd
}
pub fn build_afl_target(&self, source: &Path, output: &Path) -> Vec<String> {
let mut cmd = vec!["afl-clang-fast".to_string()];
cmd.push("-O2".to_string());
cmd.push("-g".to_string());
cmd.push(source.display().to_string());
cmd.push("-o".to_string());
cmd.push(output.display().to_string());
cmd
}
pub fn build_honggfuzz_target(&self, source: &Path, output: &Path) -> Vec<String> {
let mut cmd = vec!["hfuzz-clang++".to_string()];
cmd.push("-O1".to_string());
cmd.push("-g".to_string());
cmd.push("-fsanitize=address,undefined".to_string());
cmd.push(source.display().to_string());
cmd.push("-o".to_string());
cmd.push(output.display().to_string());
cmd
}
pub fn compiler_flags_string(&self, config: &X86FuzzConfig) -> String {
let mut flags = Vec::new();
flags.push("-O1".to_string());
flags.push("-gline-tables-only".to_string());
flags.push("-fno-omit-frame-pointer".to_string());
flags.push(format!("-target={}", self.target_triple));
for s in &config.sanitizers {
flags.push(s.clang_flag().to_string());
}
for cf in config.coverage.sancov_flags() {
flags.push(cf.to_string());
}
if !config.engine.compiler_flag().is_empty() {
flags.push(config.engine.compiler_flag().to_string());
}
match config.arch {
X86ArchVariant::X86_64 => flags.push("-m64".to_string()),
X86ArchVariant::X86_32 => flags.push("-m32".to_string()),
_ => {}
}
flags.extend(config.extra_clang_flags.clone());
flags.join(" ")
}
}
#[derive(Debug, Clone)]
pub struct X86FullGrammarGenerator {
pub categories: HashMap<String, Vec<X86GrammarRule>>,
pub depth: u32,
pub max_depth: u32,
pub max_output_len: usize,
pub language: X86FuzzLanguage,
pub seed: Option<u64>,
pub include_headers: bool,
}
impl Default for X86FullGrammarGenerator {
fn default() -> Self {
let mut gen = Self {
categories: HashMap::new(),
depth: 0,
max_depth: 15,
max_output_len: 50_000,
language: X86FuzzLanguage::C,
seed: None,
include_headers: true,
};
gen.init_c_grammar();
gen
}
}
impl X86FullGrammarGenerator {
pub fn init_c_grammar(&mut self) {
self.categories.insert(
"declaration".to_string(),
vec![X86GrammarRule {
name: "decl".to_string(),
productions: vec![
vec![X86GrammarSymbol::NonTerminal("var_decl".to_string())],
vec![X86GrammarSymbol::NonTerminal("func_def".to_string())],
vec![X86GrammarSymbol::NonTerminal("func_decl".to_string())],
vec![X86GrammarSymbol::NonTerminal("struct_def".to_string())],
vec![X86GrammarSymbol::NonTerminal("union_def".to_string())],
vec![X86GrammarSymbol::NonTerminal("enum_def".to_string())],
vec![X86GrammarSymbol::NonTerminal("typedef_decl".to_string())],
vec![X86GrammarSymbol::NonTerminal("extern_decl".to_string())],
],
weight: 1.0,
}],
);
self.categories.insert(
"type".to_string(),
vec![
X86GrammarRule {
name: "type_spec".to_string(),
productions: vec![
vec![X86GrammarSymbol::Terminal("int".to_string())],
vec![X86GrammarSymbol::Terminal("char".to_string())],
vec![X86GrammarSymbol::Terminal("short".to_string())],
vec![X86GrammarSymbol::Terminal("long".to_string())],
vec![X86GrammarSymbol::Terminal("long long".to_string())],
vec![X86GrammarSymbol::Terminal("float".to_string())],
vec![X86GrammarSymbol::Terminal("double".to_string())],
vec![X86GrammarSymbol::Terminal("void".to_string())],
vec![X86GrammarSymbol::Terminal("unsigned".to_string())],
vec![X86GrammarSymbol::Terminal("unsigned int".to_string())],
vec![X86GrammarSymbol::Terminal("unsigned long".to_string())],
vec![X86GrammarSymbol::Terminal("signed char".to_string())],
vec![X86GrammarSymbol::Terminal("unsigned char".to_string())],
vec![X86GrammarSymbol::Terminal("size_t".to_string())],
vec![X86GrammarSymbol::Terminal("intptr_t".to_string())],
vec![X86GrammarSymbol::Terminal("uint8_t".to_string())],
vec![X86GrammarSymbol::Terminal("uint16_t".to_string())],
vec![X86GrammarSymbol::Terminal("uint32_t".to_string())],
vec![X86GrammarSymbol::Terminal("uint64_t".to_string())],
vec![X86GrammarSymbol::NonTerminal("struct_type".to_string())],
vec![X86GrammarSymbol::NonTerminal("pointer_type".to_string())],
vec![X86GrammarSymbol::NonTerminal("array_type".to_string())],
vec![X86GrammarSymbol::NonTerminal("const_type".to_string())],
vec![X86GrammarSymbol::NonTerminal("volatile_type".to_string())],
],
weight: 1.0,
},
X86GrammarRule {
name: "pointer_type".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal("*".to_string()),
]],
weight: 0.5,
},
X86GrammarRule {
name: "array_type".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal("[".to_string()),
X86GrammarSymbol::IntConstant { min: 1, max: 256 },
X86GrammarSymbol::Terminal("]".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "const_type".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("const ".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
]],
weight: 0.4,
},
X86GrammarRule {
name: "volatile_type".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("volatile ".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
]],
weight: 0.2,
},
X86GrammarRule {
name: "struct_type".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("struct ".to_string()),
X86GrammarSymbol::Identifier,
]],
weight: 0.3,
},
],
);
self.categories.insert(
"expression".to_string(),
vec![
X86GrammarRule {
name: "expr".to_string(),
productions: vec![
vec![X86GrammarSymbol::IntConstant {
min: -2147483648,
max: 2147483647,
}],
vec![X86GrammarSymbol::Identifier],
vec![X86GrammarSymbol::NonTerminal("binary_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("unary_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("call_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("cast_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("ternary_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("sizeof_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("addr_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("deref_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("member_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("array_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("assign_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal("comma_expr".to_string())],
vec![X86GrammarSymbol::NonTerminal(
"compound_literal".to_string(),
)],
],
weight: 1.0,
},
X86GrammarRule {
name: "binary_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Choice(vec![
X86GrammarSymbol::Terminal(" + ".to_string()),
X86GrammarSymbol::Terminal(" - ".to_string()),
X86GrammarSymbol::Terminal(" * ".to_string()),
X86GrammarSymbol::Terminal(" / ".to_string()),
X86GrammarSymbol::Terminal(" % ".to_string()),
X86GrammarSymbol::Terminal(" << ".to_string()),
X86GrammarSymbol::Terminal(" >> ".to_string()),
X86GrammarSymbol::Terminal(" & ".to_string()),
X86GrammarSymbol::Terminal(" | ".to_string()),
X86GrammarSymbol::Terminal(" ^ ".to_string()),
X86GrammarSymbol::Terminal(" && ".to_string()),
X86GrammarSymbol::Terminal(" || ".to_string()),
X86GrammarSymbol::Terminal(" == ".to_string()),
X86GrammarSymbol::Terminal(" != ".to_string()),
X86GrammarSymbol::Terminal(" < ".to_string()),
X86GrammarSymbol::Terminal(" > ".to_string()),
X86GrammarSymbol::Terminal(" <= ".to_string()),
X86GrammarSymbol::Terminal(" >= ".to_string()),
]),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.8,
},
X86GrammarRule {
name: "unary_expr".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Terminal("-".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
vec![
X86GrammarSymbol::Terminal("!".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
vec![
X86GrammarSymbol::Terminal("~".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
vec![
X86GrammarSymbol::Terminal("++ ".to_string()),
X86GrammarSymbol::Identifier,
],
vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("++".to_string()),
],
vec![
X86GrammarSymbol::Terminal("-- ".to_string()),
X86GrammarSymbol::Identifier,
],
vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("--".to_string()),
],
],
weight: 0.5,
},
X86GrammarRule {
name: "call_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("arg_list".to_string()),
X86GrammarSymbol::Terminal(")".to_string()),
]],
weight: 0.6,
},
X86GrammarRule {
name: "cast_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(")".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "ternary_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(" ? ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(" : ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "sizeof_expr".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Terminal("sizeof(".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(")".to_string()),
],
vec![
X86GrammarSymbol::Terminal("sizeof ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
],
weight: 0.2,
},
X86GrammarRule {
name: "addr_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("&".to_string()),
X86GrammarSymbol::Identifier,
]],
weight: 0.3,
},
X86GrammarRule {
name: "deref_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("*".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "member_expr".to_string(),
productions: vec![
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(".".to_string()),
X86GrammarSymbol::Identifier,
],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("->".to_string()),
X86GrammarSymbol::Identifier,
],
],
weight: 0.2,
},
X86GrammarRule {
name: "array_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("[".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("]".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "assign_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Choice(vec![
X86GrammarSymbol::Terminal(" = ".to_string()),
X86GrammarSymbol::Terminal(" += ".to_string()),
X86GrammarSymbol::Terminal(" -= ".to_string()),
X86GrammarSymbol::Terminal(" *= ".to_string()),
X86GrammarSymbol::Terminal(" /= ".to_string()),
X86GrammarSymbol::Terminal(" %= ".to_string()),
X86GrammarSymbol::Terminal(" &= ".to_string()),
X86GrammarSymbol::Terminal(" |= ".to_string()),
X86GrammarSymbol::Terminal(" ^= ".to_string()),
X86GrammarSymbol::Terminal(" <<= ".to_string()),
X86GrammarSymbol::Terminal(" >>= ".to_string()),
]),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.4,
},
X86GrammarRule {
name: "comma_expr".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(", ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
]],
weight: 0.1,
},
X86GrammarRule {
name: "compound_literal".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal("){ ".to_string()),
X86GrammarSymbol::NonTerminal("init_list".to_string()),
X86GrammarSymbol::Terminal(" }".to_string()),
]],
weight: 0.1,
},
],
);
self.categories.insert(
"statement".to_string(),
vec![
X86GrammarRule {
name: "stmt".to_string(),
productions: vec![
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(";".to_string()),
],
vec![X86GrammarSymbol::NonTerminal("compound_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("if_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("while_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("for_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("do_while_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("switch_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("return_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("break_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("continue_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("goto_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("labeled_stmt".to_string())],
vec![X86GrammarSymbol::NonTerminal("var_decl".to_string())],
],
weight: 1.0,
},
X86GrammarRule {
name: "compound_stmt".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("{".to_string()),
X86GrammarSymbol::NonTerminal("stmt_list".to_string()),
X86GrammarSymbol::Terminal("}".to_string()),
]],
weight: 0.5,
},
X86GrammarRule {
name: "if_stmt".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Terminal("if (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
vec![
X86GrammarSymbol::Terminal("if (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
X86GrammarSymbol::Terminal(" else ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
],
weight: 0.6,
},
X86GrammarRule {
name: "while_stmt".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("while (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
]],
weight: 0.5,
},
X86GrammarRule {
name: "for_stmt".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Terminal("for (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("; ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("; ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
vec![
X86GrammarSymbol::Terminal("for (".to_string()),
X86GrammarSymbol::NonTerminal("var_decl".to_string()),
X86GrammarSymbol::Terminal("; ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal("; ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
],
weight: 0.4,
},
X86GrammarRule {
name: "do_while_stmt".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("do ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
X86GrammarSymbol::Terminal(" while (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(");".to_string()),
]],
weight: 0.2,
},
X86GrammarRule {
name: "switch_stmt".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("switch (".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(") { ".to_string()),
X86GrammarSymbol::NonTerminal("case_list".to_string()),
X86GrammarSymbol::Terminal("}".to_string()),
]],
weight: 0.2,
},
X86GrammarRule {
name: "return_stmt".to_string(),
productions: vec![
vec![X86GrammarSymbol::Terminal("return;".to_string())],
vec![
X86GrammarSymbol::Terminal("return ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(";".to_string()),
],
],
weight: 0.4,
},
X86GrammarRule {
name: "break_stmt".to_string(),
productions: vec![vec![X86GrammarSymbol::Terminal("break;".to_string())]],
weight: 0.1,
},
X86GrammarRule {
name: "continue_stmt".to_string(),
productions: vec![vec![X86GrammarSymbol::Terminal("continue;".to_string())]],
weight: 0.1,
},
X86GrammarRule {
name: "goto_stmt".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("goto ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(";".to_string()),
]],
weight: 0.05,
},
X86GrammarRule {
name: "labeled_stmt".to_string(),
productions: vec![
vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(": ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
vec![
X86GrammarSymbol::Terminal("case ".to_string()),
X86GrammarSymbol::IntConstant { min: 0, max: 100 },
X86GrammarSymbol::Terminal(": ".to_string()),
X86GrammarSymbol::NonTerminal("stmt".to_string()),
],
],
weight: 0.1,
},
],
);
self.categories.insert(
"declaration".to_string(),
vec![
X86GrammarRule {
name: "var_decl".to_string(),
productions: vec![
vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(";".to_string()),
],
vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(" = ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(";".to_string()),
],
],
weight: 0.7,
},
X86GrammarRule {
name: "func_def".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("param_list".to_string()),
X86GrammarSymbol::Terminal(") {".to_string()),
X86GrammarSymbol::NonTerminal("stmt_list".to_string()),
X86GrammarSymbol::Terminal("}".to_string()),
]],
weight: 0.5,
},
X86GrammarRule {
name: "func_decl".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal("(".to_string()),
X86GrammarSymbol::NonTerminal("param_list".to_string()),
X86GrammarSymbol::Terminal(");".to_string()),
]],
weight: 0.3,
},
X86GrammarRule {
name: "struct_def".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("struct ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(" { ".to_string()),
X86GrammarSymbol::Repeat(Box::new(X86GrammarSymbol::NonTerminal(
"field_decl".to_string(),
))),
X86GrammarSymbol::Terminal("};".to_string()),
]],
weight: 0.2,
},
X86GrammarRule {
name: "union_def".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("union ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(" { ".to_string()),
X86GrammarSymbol::Repeat(Box::new(X86GrammarSymbol::NonTerminal(
"field_decl".to_string(),
))),
X86GrammarSymbol::Terminal("};".to_string()),
]],
weight: 0.1,
},
X86GrammarRule {
name: "enum_def".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("enum ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(" { ".to_string()),
X86GrammarSymbol::NonTerminal("enum_list".to_string()),
X86GrammarSymbol::Terminal("};".to_string()),
]],
weight: 0.1,
},
X86GrammarRule {
name: "typedef_decl".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("typedef ".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(";".to_string()),
]],
weight: 0.1,
},
X86GrammarRule {
name: "extern_decl".to_string(),
productions: vec![vec![
X86GrammarSymbol::Terminal("extern ".to_string()),
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(";".to_string()),
]],
weight: 0.05,
},
X86GrammarRule {
name: "field_decl".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(";".to_string()),
]],
weight: 0.5,
},
],
);
self.categories.insert(
"misc".to_string(),
vec![
X86GrammarRule {
name: "stmt_list".to_string(),
productions: vec![vec![X86GrammarSymbol::Repeat(Box::new(
X86GrammarSymbol::NonTerminal("stmt".to_string()),
))]],
weight: 1.0,
},
X86GrammarRule {
name: "param_list".to_string(),
productions: vec![
vec![X86GrammarSymbol::Terminal("void".to_string())],
vec![X86GrammarSymbol::NonTerminal("param".to_string())],
vec![
X86GrammarSymbol::NonTerminal("param".to_string()),
X86GrammarSymbol::Terminal(", ".to_string()),
X86GrammarSymbol::NonTerminal("param".to_string()),
],
],
weight: 0.3,
},
X86GrammarRule {
name: "param".to_string(),
productions: vec![vec![
X86GrammarSymbol::NonTerminal("type_spec".to_string()),
X86GrammarSymbol::Terminal(" ".to_string()),
X86GrammarSymbol::Identifier,
]],
weight: 0.5,
},
X86GrammarRule {
name: "arg_list".to_string(),
productions: vec![
vec![],
vec![X86GrammarSymbol::NonTerminal("expr".to_string())],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(", ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
],
weight: 0.4,
},
X86GrammarRule {
name: "init_list".to_string(),
productions: vec![
vec![X86GrammarSymbol::NonTerminal("expr".to_string())],
vec![
X86GrammarSymbol::NonTerminal("expr".to_string()),
X86GrammarSymbol::Terminal(", ".to_string()),
X86GrammarSymbol::NonTerminal("expr".to_string()),
],
],
weight: 0.2,
},
X86GrammarRule {
name: "case_list".to_string(),
productions: vec![vec![X86GrammarSymbol::Repeat(Box::new(
X86GrammarSymbol::NonTerminal("labeled_stmt".to_string()),
))]],
weight: 0.1,
},
X86GrammarRule {
name: "enum_list".to_string(),
productions: vec![
vec![X86GrammarSymbol::Identifier],
vec![
X86GrammarSymbol::Identifier,
X86GrammarSymbol::Terminal(" = ".to_string()),
X86GrammarSymbol::IntConstant { min: 0, max: 10000 },
],
],
weight: 0.1,
},
],
);
}
pub fn generate_full_program(&self) -> String {
let mut output = String::new();
if self.include_headers {
output.push_str("#include <stdint.h>\n");
output.push_str("#include <stddef.h>\n");
output.push_str("#include <stdbool.h>\n");
output.push_str("#include <string.h>\n");
output.push_str("#include <stdio.h>\n");
output.push_str("#include <stdlib.h>\n\n");
}
let count = 1 + (self.depth % 3);
for i in 0..count {
self.expand_full("decl", 0, &mut output);
output.push('\n');
}
output
}
fn expand_full(&self, non_terminal: &str, depth: u32, output: &mut String) {
if depth > self.max_depth || output.len() > self.max_output_len {
return;
}
let mut matching_rules: Vec<&X86GrammarRule> = Vec::new();
for rules in self.categories.values() {
for rule in rules {
if rule.name == non_terminal {
matching_rules.push(rule);
}
}
}
if matching_rules.is_empty() {
output.push_str("0");
return;
}
let rule_idx = depth as usize % matching_rules.len();
let rule = matching_rules[rule_idx];
if rule.productions.is_empty() {
return;
}
let prod_idx = (depth as usize + output.len()) % rule.productions.len();
let production = &rule.productions[prod_idx];
for symbol in production {
match symbol {
X86GrammarSymbol::NonTerminal(name) => {
self.expand_full(name, depth + 1, output);
}
X86GrammarSymbol::Terminal(text) => {
output.push_str(text);
}
X86GrammarSymbol::IntConstant { min, max } => {
let range = (*max - *min) as u64;
let val = *min + ((depth as i64 + output.len() as i64) % (range as i64 + 1));
output.push_str(&val.to_string());
}
X86GrammarSymbol::Identifier => {
let chars = "abcdefghijklmnopqrstuvwxyz";
let idx = (depth as usize + output.len()) % 26;
output.push(chars.chars().nth(idx).unwrap_or('x'));
let num = (depth + output.len() as u32) % 100;
output.push_str(&num.to_string());
}
X86GrammarSymbol::Operator(op) => {
output.push(' ');
output.push_str(op);
output.push(' ');
}
X86GrammarSymbol::Choice(choices) => {
if !choices.is_empty() {
let idx = (depth as usize + output.len()) % choices.len();
match &choices[idx] {
X86GrammarSymbol::Terminal(t) => output.push_str(t),
X86GrammarSymbol::NonTerminal(name) => {
self.expand_full(name, depth + 1, output);
}
_ => output.push_str("0"),
}
}
}
X86GrammarSymbol::Optional(sym) => {
if depth % 2 == 0 {
match sym.as_ref() {
X86GrammarSymbol::Terminal(t) => output.push_str(t),
X86GrammarSymbol::NonTerminal(name) => {
self.expand_full(name, depth + 1, output);
}
_ => {}
}
}
}
X86GrammarSymbol::Repeat(sym) => {
let count = (depth as usize % 3) + 1;
for _ in 0..count {
match sym.as_ref() {
X86GrammarSymbol::NonTerminal(name) => {
self.expand_full(name, depth + 1, output);
output.push('\n');
}
X86GrammarSymbol::Terminal(t) => output.push_str(t),
_ => {}
}
}
}
}
}
}
pub fn generate_cpp_program(&self) -> String {
let mut output = String::new();
output.push_str("#include <iostream>\n");
output.push_str("#include <vector>\n");
output.push_str("#include <string>\n");
output.push_str("#include <map>\n");
output.push_str("#include <memory>\n");
output.push_str("#include <algorithm>\n\n");
output.push_str("class FuzzClass {\n");
output.push_str("public:\n");
output.push_str(" FuzzClass() : value_(0) {}\n");
output.push_str(" virtual ~FuzzClass() = default;\n");
output.push_str(" virtual int compute(int x) { return x + value_; }\n");
output.push_str("private:\n");
output.push_str(" int value_;\n");
output.push_str("};\n\n");
output.push_str("template<typename T>\n");
output.push_str("T fuzz_template(T a, T b) { return a < b ? b : a; }\n\n");
output.push_str("int main() {\n");
output.push_str(" auto lambda = [](int x) { return x * x; };\n");
output.push_str(" std::vector<int> vec = {1, 2, 3, 4, 5};\n");
output.push_str(" std::sort(vec.begin(), vec.end());\n");
output.push_str(" return lambda(vec[0]);\n");
output.push_str("}\n");
output
}
}
#[derive(Debug, Clone)]
pub struct X86CoverageMap {
pub edge_counts: Vec<u8>,
pub previous_map: Vec<u8>,
pub edges_discovered: AtomicU64,
pub new_edges: AtomicU64,
pub version: AtomicU64,
}
impl Default for X86CoverageMap {
fn default() -> Self {
Self {
edge_counts: vec![0u8; X86_SANCOV_MAP_SIZE],
previous_map: vec![0u8; X86_SANCOV_MAP_SIZE],
edges_discovered: AtomicU64::new(0),
new_edges: AtomicU64::new(0),
version: AtomicU64::new(0),
}
}
}
impl X86CoverageMap {
pub fn update(&mut self, new_data: &[u8]) -> bool {
let len = new_data.len().min(X86_SANCOV_MAP_SIZE);
let mut changed = false;
for i in 0..len {
let old = self.edge_counts[i];
let new = new_data[i];
if new > 0 && old == 0 {
self.edges_discovered.fetch_add(1, Ordering::Relaxed);
self.new_edges.fetch_add(1, Ordering::Relaxed);
changed = true;
}
self.edge_counts[i] = old.saturating_add(new);
}
if changed {
self.version.fetch_add(1, Ordering::Relaxed);
}
changed
}
pub fn coverage_pct(&self) -> f64 {
let discovered = self.edges_discovered.load(Ordering::Relaxed);
(discovered as f64 / X86_SANCOV_MAP_SIZE as f64) * 100.0
}
pub fn delta(&self) -> Vec<u16> {
let len = X86_SANCOV_MAP_SIZE;
let mut diff = Vec::with_capacity(len);
for i in 0..len {
let curr = self.edge_counts[i];
let prev = self.previous_map[i];
diff.push((curr as u16).saturating_sub(prev as u16));
}
diff
}
pub fn snapshot(&mut self) {
self.previous_map.copy_from_slice(&self.edge_counts);
self.new_edges.store(0, Ordering::Relaxed);
}
pub fn map_hash(&self) -> u64 {
let mut hash: u64 = 0x9e3779b97f4a7c15;
for chunk in self.edge_counts.chunks(64) {
for &byte in chunk {
if byte > 0 {
hash ^= byte as u64;
hash = hash.rotate_left(7);
}
}
}
hash
}
pub fn was_hit(&self, pc_index: usize) -> bool {
if pc_index < self.edge_counts.len() {
self.edge_counts[pc_index] > 0
} else {
false
}
}
pub fn top_edges(&self, n: usize) -> Vec<(usize, u8)> {
let mut indexed: Vec<(usize, u8)> = self
.edge_counts
.iter()
.enumerate()
.filter(|(_, &c)| c > 0)
.map(|(i, &c)| (i, c))
.collect();
indexed.sort_by(|a, b| b.1.cmp(&a.1));
indexed.truncate(n);
indexed
}
pub fn reset(&mut self) {
self.edge_counts.fill(0);
self.previous_map.fill(0);
self.edges_discovered.store(0, Ordering::Relaxed);
self.new_edges.store(0, Ordering::Relaxed);
self.version.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
pub struct X86DictionaryGenerator {
pub min_token_len: usize,
pub max_token_len: usize,
pub min_frequency: usize,
pub max_dictionary_size: usize,
pub entries: Vec<X86DictEntry>,
}
#[derive(Debug, Clone)]
pub struct X86DictEntry {
pub token: Vec<u8>,
pub frequency: usize,
pub source: X86DictSource,
pub level: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DictSource {
Manual,
AutoGenerated,
ExtractedFromCorpus,
ExtractedFromSource,
KeywordList,
}
impl Default for X86DictionaryGenerator {
fn default() -> Self {
Self {
min_token_len: 2,
max_token_len: 32,
min_frequency: 2,
max_dictionary_size: 4096,
entries: Vec::new(),
}
}
}
impl X86DictionaryGenerator {
pub fn extract_from_corpus(&mut self, corpus: &[Vec<u8>]) -> usize {
let mut token_freq: HashMap<Vec<u8>, usize> = HashMap::new();
for input in corpus {
for window in input.windows(self.min_token_len) {
let len = window.len().min(self.max_token_len);
let token = window[..len].to_vec();
*token_freq.entry(token).or_insert(0) += 1;
}
}
let mut new_entries: Vec<_> = token_freq
.into_iter()
.filter(|(_, freq)| *freq >= self.min_frequency)
.map(|(token, freq)| X86DictEntry {
token,
frequency: freq,
source: X86DictSource::ExtractedFromCorpus,
level: 1,
})
.collect();
new_entries.sort_by(|a, b| b.frequency.cmp(&a.frequency));
new_entries.truncate(self.max_dictionary_size);
let added = new_entries.len();
self.entries.extend(new_entries);
added
}
pub fn add_c_keywords(&mut self) {
let keywords = vec![
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"int",
"long",
"register",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"inline",
"restrict",
"_Bool",
"_Complex",
"_Imaginary",
"NULL",
"true",
"false",
"constexpr",
"noexcept",
"nullptr",
"template",
"typename",
"class",
"namespace",
"virtual",
"override",
"final",
"public",
"private",
"protected",
"new",
"delete",
"this",
"try",
"catch",
"throw",
"static_cast",
"dynamic_cast",
"const_cast",
"reinterpret_cast",
"sizeof",
"alignof",
"typeid",
"decltype",
"auto",
"thread_local",
"mutable",
"explicit",
"friend",
"using",
"operator",
];
for kw in keywords {
self.entries.push(X86DictEntry {
token: kw.as_bytes().to_vec(),
frequency: 1,
source: X86DictSource::KeywordList,
level: 0,
});
}
}
pub fn add_c_operators(&mut self) {
let ops = vec![
"++", "--", "->", "<<", ">>", "<=", ">=", "==", "!=", "&&", "||", "+=", "-=", "*=",
"/=", "%=", "&=", "|=", "^=", "<<=", ">>=", "::", ".*", "->*", "...",
];
for op in ops {
self.entries.push(X86DictEntry {
token: op.as_bytes().to_vec(),
frequency: 1,
source: X86DictSource::KeywordList,
level: 0,
});
}
}
pub fn to_afl_format(&self) -> String {
let mut out = String::new();
out.push_str("# AFL dictionary for X86 Clang fuzzing\n");
out.push_str("# Auto-generated by X86DictionaryGenerator\n\n");
for entry in &self.entries {
let escaped: String = String::from_utf8_lossy(&entry.token)
.replace("\\", "\\\\")
.replace("\"", "\\\"");
out.push_str(&format!("level_{}=\"{}\"\n", entry.level, escaped));
}
out
}
pub fn to_libfuzzer_format(&self) -> String {
let mut out = String::new();
out.push_str("# libFuzzer dictionary for X86 Clang fuzzing\n");
out.push_str("# Auto-generated by X86DictionaryGenerator\n\n");
for entry in &self.entries {
let escaped: String = String::from_utf8_lossy(&entry.token)
.replace("\\", "\\\\")
.replace("\"", "\\\"");
out.push_str(&format!("\"{}\"\n", escaped));
}
out
}
pub fn save(&self, path: &Path, format: X86DictFormat) -> io::Result<()> {
let content = match format {
X86DictFormat::AFL => self.to_afl_format(),
X86DictFormat::LibFuzzer => self.to_libfuzzer_format(),
};
std::fs::write(path, content)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DictFormat {
AFL,
LibFuzzer,
}
#[derive(Debug, Clone)]
pub struct X86CrashDedup {
pub known_signatures: HashSet<X86CrashSignature>,
pub strategy: X86DedupStrategy,
pub stack_depth: usize,
pub fuzzy_threshold: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86CrashSignature {
pub stack_hash: u64,
pub crash_type: X86CrashType,
pub fault_addr: Option<u64>,
pub top_function: Option<String>,
pub sanitizer_type: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DedupStrategy {
ExactHash,
HashAndType,
FullSignature,
FuzzyStack,
}
impl Default for X86CrashDedup {
fn default() -> Self {
Self {
known_signatures: HashSet::new(),
strategy: X86DedupStrategy::HashAndType,
stack_depth: 5,
fuzzy_threshold: 0.9,
}
}
}
impl X86CrashDedup {
pub fn is_duplicate(&self, crash: &X86CrashInfo) -> bool {
let sig = self.build_signature(crash);
self.known_signatures.contains(&sig)
}
pub fn register(&mut self, crash: &X86CrashInfo) -> bool {
let sig = self.build_signature(crash);
self.known_signatures.insert(sig)
}
fn build_signature(&self, crash: &X86CrashInfo) -> X86CrashSignature {
X86CrashSignature {
stack_hash: crash.stack_hash,
crash_type: crash.crash_type,
fault_addr: None,
top_function: None,
sanitizer_type: crash.sanitizer_report.as_ref().map(|r| {
if r.contains("AddressSanitizer") {
"ASan".to_string()
} else if r.contains("UndefinedBehavior") {
"UBSan".to_string()
} else if r.contains("MemorySanitizer") {
"MSan".to_string()
} else {
"Unknown".to_string()
}
}),
}
}
pub fn fuzzy_match(&self, a: &X86CrashInfo, b: &X86CrashInfo) -> f64 {
if a.crash_type != b.crash_type {
return 0.0;
}
let ha = a.stack_hash;
let hb = b.stack_hash;
let xor = ha ^ hb;
let matching_bits = 64 - xor.count_ones();
matching_bits as f64 / 64.0
}
pub fn stats(&self) -> X86DedupStats {
X86DedupStats {
total_signatures: self.known_signatures.len(),
strategy: self.strategy,
}
}
}
#[derive(Debug, Clone)]
pub struct X86DedupStats {
pub total_signatures: usize,
pub strategy: X86DedupStrategy,
}
#[derive(Debug, Clone)]
pub struct X86CorpusImport {
pub supported_formats: Vec<X86CorpusArchiveFormat>,
pub validate_on_import: bool,
pub max_import_size: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CorpusArchiveFormat {
Directory,
Tar,
TarGz,
Zip,
TextBase64,
}
impl Default for X86CorpusImport {
fn default() -> Self {
Self {
supported_formats: vec![
X86CorpusArchiveFormat::Directory,
X86CorpusArchiveFormat::TarGz,
X86CorpusArchiveFormat::Zip,
],
validate_on_import: true,
max_import_size: 1_073_741_824, }
}
}
impl X86CorpusImport {
pub fn import_directory(
&self,
dir: &Path,
manager: &mut X86CorpusManager,
) -> io::Result<usize> {
let mut count = 0;
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let metadata = std::fs::metadata(&path)?;
if metadata.len() > self.max_import_size {
continue;
}
let data = std::fs::read(&path)?;
if self.validate_on_import && data.is_empty() {
continue;
}
if manager.add(data, X86CorpusSource::User) {
count += 1;
}
}
}
}
Ok(count)
}
pub fn export_directory(&self, dir: &Path, manager: &X86CorpusManager) -> io::Result<usize> {
std::fs::create_dir_all(dir)?;
let mut count = 0;
for (i, elem) in manager.inputs.iter().enumerate() {
let filename = format!(
"corpus_{:08}_{}",
i,
hex::encode_hex(&elem.data[..elem.data.len().min(8)])
);
let path = dir.join(&filename);
std::fs::write(&path, &elem.data)?;
count += 1;
}
Ok(count)
}
pub fn export_manifest(&self, path: &Path, manager: &X86CorpusManager) -> io::Result<()> {
let stats = manager.stats();
let manifest = format!(
r#"{{"total":{},"total_bytes":{},"min_size":{},"max_size":{},"avg_size":{:.1},"sources":{}}}"#,
stats.total_elements,
stats.total_bytes,
stats.min_size,
stats.max_size,
stats.avg_size,
serde_json_like::sources_to_json(&stats.by_source)
);
std::fs::write(path, manifest)
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzTimeline {
pub events: VecDeque<X86FuzzTimelineEvent>,
pub max_events: usize,
}
#[derive(Debug, Clone)]
pub struct X86FuzzTimelineEvent {
pub timestamp: SystemTime,
pub kind: X86FuzzEventKind,
pub description: String,
pub executions: u64,
pub coverage_pct: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FuzzEventKind {
FuzzingStarted,
FuzzingStopped,
NewCoverage,
CrashFound,
HangDetected,
OOMDetected,
CorpusMinimized,
CorpusDistilled,
WorkerJoined,
WorkerLeft,
JobSubmitted,
JobCompleted,
PipelineStageComplete,
Error,
Warning,
Info,
}
impl Default for X86FuzzTimeline {
fn default() -> Self {
Self {
events: VecDeque::new(),
max_events: 10_000,
}
}
}
impl X86FuzzTimeline {
pub fn add_event(
&mut self,
kind: X86FuzzEventKind,
description: &str,
executions: u64,
coverage_pct: f64,
) {
if self.events.len() >= self.max_events {
self.events.pop_front();
}
self.events.push_back(X86FuzzTimelineEvent {
timestamp: SystemTime::now(),
kind,
description: description.to_string(),
executions,
coverage_pct,
});
}
pub fn events_of_kind(&self, kind: X86FuzzEventKind) -> Vec<&X86FuzzTimelineEvent> {
self.events.iter().filter(|e| e.kind == kind).collect()
}
pub fn crashes(&self) -> Vec<&X86FuzzTimelineEvent> {
self.events_of_kind(X86FuzzEventKind::CrashFound)
}
pub fn to_text(&self) -> String {
let mut out = String::new();
out.push_str("=== Fuzzing Timeline ===\n");
for event in &self.events {
let ts = event
.timestamp
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
out.push_str(&format!(
"[{}] {:?}: {} (execs: {}, cov: {:.1}%)\n",
ts, event.kind, event.description, event.executions, event.coverage_pct
));
}
out
}
pub fn clear(&mut self) {
self.events.clear();
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzAllocStats {
pub total_allocated: AtomicU64,
pub total_freed: AtomicU64,
pub peak_bytes: AtomicU64,
pub alloc_count: AtomicU64,
pub free_count: AtomicU64,
}
impl Default for X86FuzzAllocStats {
fn default() -> Self {
Self {
total_allocated: AtomicU64::new(0),
total_freed: AtomicU64::new(0),
peak_bytes: AtomicU64::new(0),
alloc_count: AtomicU64::new(0),
free_count: AtomicU64::new(0),
}
}
}
impl X86FuzzAllocStats {
pub fn record_alloc(&self, size: u64) {
self.total_allocated.fetch_add(size, Ordering::Relaxed);
self.alloc_count.fetch_add(1, Ordering::Relaxed);
let current = self.current_usage();
let mut peak = self.peak_bytes.load(Ordering::Relaxed);
while current > peak {
match self.peak_bytes.compare_exchange_weak(
peak,
current,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => peak = p,
}
}
}
pub fn record_free(&self, size: u64) {
self.total_freed.fetch_add(size, Ordering::Relaxed);
self.free_count.fetch_add(1, Ordering::Relaxed);
}
pub fn current_usage(&self) -> u64 {
self.total_allocated
.load(Ordering::Relaxed)
.saturating_sub(self.total_freed.load(Ordering::Relaxed))
}
pub fn summary(&self) -> String {
format!(
"Allocs: {} ({} total), Frees: {} ({} total), Current: {} bytes, Peak: {} bytes",
self.alloc_count.load(Ordering::Relaxed),
self.total_allocated.load(Ordering::Relaxed),
self.free_count.load(Ordering::Relaxed),
self.total_freed.load(Ordering::Relaxed),
self.current_usage(),
self.peak_bytes.load(Ordering::Relaxed),
)
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzValidator {
pub validate_c_syntax: bool,
pub validate_ir: bool,
pub validate_assembly: bool,
pub execute_safely: bool,
pub exec_timeout_secs: u64,
pub max_output_size: usize,
}
impl Default for X86FuzzValidator {
fn default() -> Self {
Self {
validate_c_syntax: true,
validate_ir: true,
validate_assembly: true,
execute_safely: false,
exec_timeout_secs: 5,
max_output_size: 10_485_760, }
}
}
impl X86FuzzValidator {
pub fn validate_c_source(&self, input: &[u8]) -> X86ValidationResult {
let text = String::from_utf8_lossy(input);
let mut result = X86ValidationResult::default();
let open_braces = text.matches('{').count();
let close_braces = text.matches('}').count();
let open_parens = text.matches('(').count();
let close_parens = text.matches(')').count();
if open_braces != close_braces {
result.warnings.push(format!(
"Mismatched braces: {} open, {} close",
open_braces, close_braces
));
}
if open_parens != close_parens {
result.warnings.push(format!(
"Mismatched parens: {} open, {} close",
open_parens, close_parens
));
}
let has_main = text.contains("main");
let has_return = text.contains("return");
let has_semicolons = text.matches(';').count() > 0;
result.is_valid = has_semicolons && open_braces == close_braces;
result.score = if has_main && has_return { 0.9 } else { 0.5 };
if open_parens == close_parens {
result.score += 0.1;
}
result
}
pub fn validate_assembly_text(&self, input: &[u8]) -> X86ValidationResult {
let text = String::from_utf8_lossy(input);
let mut result = X86ValidationResult::default();
let has_directives =
text.contains('.') || text.contains("section") || text.contains("global");
let has_instructions = text.contains("mov")
|| text.contains("add")
|| text.contains("ret")
|| text.contains("push")
|| text.contains("pop")
|| text.contains("call");
let has_labels = text.contains(":");
result.is_valid = has_instructions || has_directives;
result.score = if has_instructions && has_labels {
0.9
} else if has_instructions {
0.7
} else if has_directives {
0.3
} else {
0.0
};
result
}
pub fn validate_binary_input(&self, input: &[u8]) -> X86ValidationResult {
let mut result = X86ValidationResult::default();
result.is_valid = !input.is_empty();
result.score = if input.len() >= 1 && input.len() <= 15 {
0.8 } else if input.len() <= 1024 {
0.5
} else {
0.1
};
result
}
}
#[derive(Debug, Clone)]
pub struct X86ValidationResult {
pub is_valid: bool,
pub score: f64,
pub warnings: Vec<String>,
}
impl Default for X86ValidationResult {
fn default() -> Self {
Self {
is_valid: false,
score: 0.0,
warnings: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_x86_fuzzing_default() {
let fz = X86Fuzzing::default();
assert!(!fz.active);
assert_eq!(fz.config.engine, X86FuzzEngine::LibFuzzer);
assert_eq!(fz.config.jobs, X86_FUZZ_DEFAULT_JOBS);
}
#[test]
fn test_x86_fuzzing_compiler_config() {
let fz = X86Fuzzing::compiler_fuzzing();
assert!(fz.config.differential_testing);
assert_eq!(fz.config.coverage, X86FuzzCoverage::Full);
assert!(fz.config.sanitizers.contains(&X86FuzzSanitizer::Address));
}
#[test]
fn test_x86_fuzzing_afl_config() {
let fz = X86Fuzzing::afl_binary_fuzzing();
assert_eq!(fz.config.engine, X86FuzzEngine::AFLPlusPlus);
}
#[test]
fn test_x86_fuzzing_ossfuzz_config() {
let fz = X86Fuzzing::ossfuzz_pipeline();
assert_eq!(fz.config.engine, X86FuzzEngine::OSSFuzz);
assert!(fz.config.differential_testing);
}
#[test]
fn test_x86_fuzzing_build_clang_command() {
let fz = X86Fuzzing::default();
let cmd = fz.build_clang_command();
assert!(cmd.iter().any(|f| f.contains("fuzzer")));
}
#[test]
fn test_x86_fuzzing_summary() {
let mut fz = X86Fuzzing::default();
fz.stats.total_executions.store(1000, Ordering::Relaxed);
fz.stats.total_crashes.store(5, Ordering::Relaxed);
let summary = fz.summary();
assert!(summary.contains("1000"));
assert!(summary.contains("5"));
}
#[test]
fn test_x86_fuzzing_start_stop() {
let mut fz = X86Fuzzing::default();
let result = fz.start();
assert!(result.success);
fz.stop();
assert!(!fz.active);
}
#[test]
fn test_engine_names() {
assert_eq!(X86FuzzEngine::LibFuzzer.name(), "libFuzzer");
assert_eq!(X86FuzzEngine::AFL.name(), "AFL");
assert_eq!(X86FuzzEngine::Honggfuzz.name(), "Honggfuzz");
assert_eq!(X86FuzzEngine::AFLPlusPlus.name(), "AFL++");
assert_eq!(X86FuzzEngine::Custom.name(), "Custom");
assert_eq!(X86FuzzEngine::OSSFuzz.name(), "OSS-Fuzz");
}
#[test]
fn test_engine_compiler_flags() {
assert_eq!(
X86FuzzEngine::LibFuzzer.compiler_flag(),
"-fsanitize=fuzzer"
);
assert_eq!(X86FuzzEngine::AFL.compiler_flag(), "");
assert_eq!(X86FuzzEngine::OSSFuzz.compiler_flag(), "-fsanitize=fuzzer");
}
#[test]
fn test_engine_is_in_process() {
assert!(X86FuzzEngine::LibFuzzer.is_in_process());
assert!(!X86FuzzEngine::AFL.is_in_process());
assert!(!X86FuzzEngine::Honggfuzz.is_in_process());
}
#[test]
fn test_engine_supports_fork_server() {
assert!(X86FuzzEngine::AFL.supports_fork_server());
assert!(X86FuzzEngine::Honggfuzz.supports_fork_server());
assert!(!X86FuzzEngine::LibFuzzer.supports_fork_server());
}
#[test]
fn test_sanitizer_flags() {
assert_eq!(X86FuzzSanitizer::Address.clang_flag(), "-fsanitize=address");
assert_eq!(X86FuzzSanitizer::Memory.clang_flag(), "-fsanitize=memory");
assert_eq!(
X86FuzzSanitizer::Undefined.clang_flag(),
"-fsanitize=undefined"
);
}
#[test]
fn test_sanitizer_descriptions() {
assert!(X86FuzzSanitizer::Address.description().contains("buffer"));
assert!(X86FuzzSanitizer::Memory
.description()
.contains("uninitialized"));
}
#[test]
fn test_coverage_sancov_flags() {
assert!(X86FuzzCoverage::None.sancov_flags().is_empty());
assert!(X86FuzzCoverage::Edge
.sancov_flags()
.contains(&"trace-pc-guard"));
let full = X86FuzzCoverage::Full.sancov_flags();
assert!(full.iter().any(|f| f.contains("inline-8bit-counters")));
}
#[test]
fn test_libfuzzer_harness_generation() {
let driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let harness = driver.generate_libfuzzer_harness(&config);
assert!(harness.contains("LLVMFuzzerTestOneInput"));
assert!(harness.contains("LLVMFuzzerInitialize"));
}
#[test]
fn test_afl_harness_generation() {
let driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let harness = driver.generate_afl_harness(&config);
assert!(harness.contains("__AFL_LOOP"));
assert!(harness.contains("__AFL_INIT"));
}
#[test]
fn test_honggfuzz_harness_generation() {
let driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let harness = driver.generate_honggfuzz_harness(&config);
assert!(harness.contains("HF_ITER"));
}
#[test]
fn test_ossfuzz_build_script() {
let driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let script = driver.generate_ossfuzz_build_script(&config);
assert!(script.contains("oss-fuzz"));
assert!(script.contains("clang++"));
}
#[test]
fn test_ossfuzz_dockerfile() {
let driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let dockerfile = driver.generate_ossfuzz_dockerfile(&config);
assert!(dockerfile.contains("FROM"));
assert!(dockerfile.contains("base-builder"));
}
#[test]
fn test_driver_run_libfuzzer() {
let mut driver = X86FuzzDriver::default();
let config = X86FuzzConfig::default();
let mut stats = X86FuzzStats::default();
let result = driver.run(&config, &mut stats);
assert!(result.success);
}
#[test]
fn test_driver_run_afl() {
let mut config = X86FuzzConfig::default();
config.engine = X86FuzzEngine::AFLPlusPlus;
let mut driver = X86FuzzDriver::default();
let mut stats = X86FuzzStats::default();
let result = driver.run(&config, &mut stats);
assert!(result.success);
}
#[test]
fn test_afl_power_schedule_args() {
assert_eq!(X86AFLPowerSchedule::Explore.as_arg(), "explore");
assert_eq!(X86AFLPowerSchedule::Exploit.as_arg(), "exploit");
assert_eq!(X86AFLPowerSchedule::Fast.as_arg(), "fast");
}
#[test]
fn test_targets_enable_all() {
let mut t = X86FuzzTargets::default();
t.enable_all();
assert_eq!(t.enabled_count(), 7);
}
#[test]
fn test_targets_enable_compiler_only() {
let mut t = X86FuzzTargets::default();
t.enable_compiler_only();
assert!(t.enabled.compiler);
assert!(!t.enabled.linker);
}
#[test]
fn test_targets_enable_binary_only() {
let mut t = X86FuzzTargets::default();
t.enable_binary_only();
assert!(!t.enabled.compiler);
assert!(t.enabled.linker);
assert!(t.enabled.disassembler);
}
#[test]
fn test_compiler_fuzz_target_default() {
let t = X86CompilerFuzzTarget::default();
assert_eq!(t.language, X86FuzzLanguage::C);
assert!(t.opt_levels.len() >= 1);
}
#[test]
fn test_parser_fuzz_target_default() {
let t = X86ParserFuzzTarget::default();
assert!(t.all_diagnostics);
assert!(t.error_recovery);
}
#[test]
fn test_optimizer_fuzz_target_default() {
let t = X86OptimizerFuzzTarget::default();
assert!(t.passes.contains(&"instcombine".to_string()));
assert!(t.verify_after_pass);
}
#[test]
fn test_codegen_fuzz_target_default() {
let t = X86CodegenFuzzTarget::default();
assert!(t.check_assembly);
assert!(t.interpreter_verify);
}
#[test]
fn test_linker_fuzz_target_default() {
let t = X86LinkerFuzzTarget::default();
assert!(t.test_lto);
assert!(t.test_symbol_resolution);
}
#[test]
fn test_asm_fuzz_target_default() {
let t = X86AsmFuzzTarget::default();
assert!(t.fuzz_mnemonics);
assert!(t.roundtrip_verify);
}
#[test]
fn test_disasm_fuzz_target_default() {
let t = X86DisasmFuzzTarget::default();
assert_eq!(t.max_binary_len, 1024);
assert!(t.verify_roundtrip);
}
#[test]
fn test_targets_harness_generation() {
let t = X86FuzzTargets::default();
let h = t.generate_compiler_harness();
assert!(h.contains("LLVMFuzzerTestOneInput"));
let h = t.generate_parser_harness();
assert!(h.contains("LLVMFuzzerTestOneInput"));
let h = t.generate_optimizer_harness();
assert!(h.contains("LLVMFuzzerTestOneInput"));
}
#[test]
fn test_grammar_generator_c_grammar() {
let g = X86GrammarGenerator::c_grammar();
assert!(!g.rules.is_empty());
assert!(g.rules.iter().any(|r| r.name == "program"));
assert!(g.rules.iter().any(|r| r.name == "expr"));
}
#[test]
fn test_grammar_generator_generate() {
let g = X86GrammarGenerator::c_grammar();
let output = g.generate();
assert!(!output.is_empty());
}
#[test]
fn test_grammar_generator_max_depth() {
let mut g = X86GrammarGenerator::c_grammar();
g.max_depth = 2;
let output = g.generate();
assert!(!output.is_empty());
}
#[test]
fn test_mutation_generator_set_seeds() {
let mut mg = X86MutationGenerator::default();
mg.set_seeds(vec![b"int main() {}".to_vec()]);
assert_eq!(mg.seeds.len(), 1);
}
#[test]
fn test_mutation_generator_generate() {
let mg = X86MutationGenerator::default();
let seed = b"int main() { return 0; }";
let rng = X86FuzzRng::new(42);
let output = mg.generate(seed, &rng);
assert!(!output.is_empty());
}
#[test]
fn test_structured_generator_protobuf_harness() {
let sg = X86StructuredGenerator::default();
let harness = sg.generate_protobuf_harness();
assert!(harness.contains("DEFINE_PROTO_FUZZER"));
}
#[test]
fn test_coverage_guided_generator_energy() {
let cg = X86CoverageGuidedGenerator::default();
let map = vec![1u8; 100];
let energy = cg.compute_energy(&map);
assert!(energy >= 1.0);
}
#[test]
fn test_generators_set_active() {
let mut gens = X86FuzzGenerators::default();
gens.set_active(X86GeneratorKind::Mutation);
let rng = X86FuzzRng::new(0);
let output = gens.generate(&rng);
assert!(!output.is_empty());
}
#[test]
fn test_rng_new() {
let mut rng = X86FuzzRng::new(42);
assert!(rng.next_u64() > 0);
}
#[test]
fn test_rng_determinism() {
let mut rng1 = X86FuzzRng::new(42);
let mut rng2 = X86FuzzRng::new(42);
assert_eq!(rng1.next_u64(), rng2.next_u64());
}
#[test]
fn test_rng_next_usize_range() {
let mut rng = X86FuzzRng::new(1);
for _ in 0..100 {
let v = rng.next_usize(10);
assert!(v < 10);
}
}
#[test]
fn test_rng_next_i64_range() {
let mut rng = X86FuzzRng::new(1);
for _ in 0..100 {
let v = rng.next_i64_range(5, 15);
assert!(v >= 5 && v <= 15);
}
}
#[test]
fn test_rng_choose() {
let mut rng = X86FuzzRng::new(1);
let slice = &[1, 2, 3];
let chosen = rng.choose(slice);
assert!(chosen.is_some());
}
#[test]
fn test_rng_choose_empty() {
let mut rng = X86FuzzRng::new(1);
let empty: &[i32] = &[];
assert!(rng.choose(empty).is_none());
}
#[test]
fn test_crash_triage_deduplicate() {
let triage = X86CrashTriage::default();
let crashes = vec![
X86CrashInfo {
stack_hash: 100,
..Default::default()
},
X86CrashInfo {
stack_hash: 100,
..Default::default()
},
X86CrashInfo {
stack_hash: 200,
..Default::default()
},
];
let unique = triage.deduplicate(&crashes);
assert_eq!(unique.len(), 2);
}
#[test]
fn test_crash_triage_classify_heap_overflow() {
let triage = X86CrashTriage::default();
let ct = triage.classify("ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...");
assert_eq!(ct, X86CrashType::HeapBufferOverflow);
}
#[test]
fn test_crash_triage_classify_use_after_free() {
let triage = X86CrashTriage::default();
let ct = triage.classify("ERROR: AddressSanitizer: heap-use-after-free");
assert_eq!(ct, X86CrashType::UseAfterFree);
}
#[test]
fn test_crash_triage_classify_segfault() {
let triage = X86CrashTriage::default();
let ct = triage.classify("Segmentation fault (core dumped)");
assert_eq!(ct, X86CrashType::Segfault);
}
#[test]
fn test_crash_triage_classify_unknown() {
let triage = X86CrashTriage::default();
let ct = triage.classify("Something weird happened");
assert_eq!(ct, X86CrashType::Unknown);
}
#[test]
fn test_crash_triage_generate_report() {
let triage = X86CrashTriage::default();
let crash = X86CrashInfo {
id: 1,
crash_type: X86CrashType::HeapBufferOverflow,
sanitizer_report: Some("ERROR: ...".to_string()),
..Default::default()
};
let report = triage.generate_report(&crash);
assert!(report.contains("Crash Report"));
assert!(report.contains("HeapBufferOverflow"));
}
#[test]
fn test_crash_type_descriptions() {
assert!(X86CrashType::Segfault
.description()
.contains("Segmentation"));
assert!(X86CrashType::DoubleFree.description().contains("Double"));
assert!(X86CrashType::DataRace.description().contains("race"));
}
#[test]
fn test_hang_detection_default() {
let hd = X86HangDetection::default();
assert_eq!(hd.timeout_ms, X86_HANG_THRESHOLD_MS);
assert!(hd.detect_infinite_loops);
}
#[test]
fn test_memory_error_detection_env_vars() {
let med = X86MemoryErrorDetection::default();
let env = med.env_vars();
assert!(env.contains_key("ASAN_OPTIONS"));
assert!(env.contains_key("UBSAN_OPTIONS"));
}
#[test]
fn test_memory_error_detection_parse_output() {
let med = X86MemoryErrorDetection::default();
let errors = med.parse_sanitizer_output("heap-buffer-overflow");
assert!(!errors.is_empty());
}
#[test]
fn test_differential_testing_default() {
let dt = X86DifferentialTesting::default();
assert!(!dt.enabled);
assert_eq!(dt.reference_compiler, X86ReferenceCompiler::GCC);
}
#[test]
fn test_reference_compiler_names() {
assert_eq!(X86ReferenceCompiler::GCC.name(), "gcc");
assert_eq!(X86ReferenceCompiler::MSVC.command(), "cl.exe");
}
#[test]
fn test_oracle_default() {
let oracle = X86FuzzOracle::default();
assert!(oracle.roundtrip);
}
#[test]
fn test_analyzers_analyze() {
let analyzers = X86FuzzAnalyzers::default();
let results = vec![X86FuzzResult {
total_executions: 1000,
crashes: vec![X86CrashInfo::default()],
coverage_pct: 45.0,
..Default::default()
}];
let report = analyzers.analyze(&results);
assert_eq!(report.total_crashes, 1);
assert_eq!(report.total_executions, 1000);
}
#[test]
fn test_analyzers_differential_test() {
let analyzers = X86FuzzAnalyzers::default();
let result = analyzers.differential_test(
"int main() { return 0; }",
ClangOptLevel::O0,
ClangOptLevel::O2,
);
assert_eq!(result.opt_level_a, ClangOptLevel::O0);
}
#[test]
fn test_mutation_bit_flip_1() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8, 1u8, 2u8, 3u8];
let mut rng = X86FuzzRng::new(1);
engine.bit_flip_1(&mut buf, &mut rng);
let original = vec![0u8, 1u8, 2u8, 3u8];
assert!(buf != original || buf.len() == original.len());
}
#[test]
fn test_mutation_bit_flip_2() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let mut rng = X86FuzzRng::new(2);
engine.bit_flip_2(&mut buf, &mut rng);
assert_eq!(buf.len(), 10);
}
#[test]
fn test_mutation_bit_flip_4() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let mut rng = X86FuzzRng::new(3);
engine.bit_flip_4(&mut buf, &mut rng);
assert_eq!(buf.len(), 10);
}
#[test]
fn test_mutation_byte_flip() {
let engine = X86MutationEngine::default();
let mut buf = vec![0x00, 0xFF, 0xAA];
let original = buf.clone();
let mut rng = X86FuzzRng::new(4);
engine.bit_flip_byte(&mut buf, &mut rng);
assert_eq!(buf.len(), original.len());
}
#[test]
fn test_mutation_arith_add() {
let engine = X86MutationEngine::default();
let mut buf = vec![10u8; 5];
let original = buf.clone();
let mut rng = X86FuzzRng::new(5);
engine.arith_add(&mut buf, &mut rng);
assert_eq!(buf.len(), original.len());
}
#[test]
fn test_mutation_arith_sub() {
let engine = X86MutationEngine::default();
let mut buf = vec![10u8; 5];
let original = buf.clone();
let mut rng = X86FuzzRng::new(6);
engine.arith_sub(&mut buf, &mut rng);
assert_eq!(buf.len(), original.len());
}
#[test]
fn test_mutation_interesting_value() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let mut rng = X86FuzzRng::new(7);
engine.interesting_value(&mut buf, &mut rng);
assert_eq!(buf.len(), 10);
}
#[test]
fn test_mutation_dictionary_substitute_empty_dict() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let mut rng = X86FuzzRng::new(8);
engine.dictionary_substitute(&mut buf, &mut rng);
assert_eq!(buf.len(), 10);
}
#[test]
fn test_mutation_havoc() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 20];
let original = buf.clone();
let mut rng = X86FuzzRng::new(9);
engine.havoc(&mut buf, &mut rng);
assert_eq!(buf.len(), original.len());
}
#[test]
fn test_mutation_splice() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8, 1, 2, 3, 4, 5, 6, 7];
let mut rng = X86FuzzRng::new(10);
engine.splice(&mut buf, &mut rng);
assert!(!buf.is_empty());
}
#[test]
fn test_mutation_byte_delete() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let original_len = buf.len();
let mut rng = X86FuzzRng::new(11);
engine.byte_delete(&mut buf, &mut rng);
assert_eq!(buf.len(), original_len - 1);
}
#[test]
fn test_mutation_byte_insert() {
let engine = X86MutationEngine::default();
let mut buf = vec![0u8; 10];
let original_len = buf.len();
let mut rng = X86FuzzRng::new(12);
engine.byte_insert(&mut buf, &mut rng);
assert_eq!(buf.len(), original_len + 1);
}
#[test]
fn test_mutation_structured_insert() {
let engine = X86MutationEngine::default();
let mut buf = vec![65u8, 66, 67, 68, 69];
let mut rng = X86FuzzRng::new(13);
engine.structured_insert(&mut buf, &mut rng);
assert!(buf.len() >= 5);
}
#[test]
fn test_mutation_structured_delete() {
let engine = X86MutationEngine::default();
let mut buf = vec![65u8, 66, 67, 68, 69, 70];
let original_len = buf.len();
let mut rng = X86FuzzRng::new(14);
engine.structured_delete(&mut buf, &mut rng);
assert!(buf.len() <= original_len);
}
#[test]
fn test_mutation_structured_replace() {
let engine = X86MutationEngine::default();
let mut buf = vec![65u8, 66, 67, 68, 69, 70, 71, 72];
let mut rng = X86FuzzRng::new(15);
engine.structured_replace(&mut buf, &mut rng);
assert!(!buf.is_empty());
}
#[test]
fn test_mutation_empty_input() {
let engine = X86MutationEngine::default();
let mut rng = X86FuzzRng::new(0);
let output = engine.mutate_single(&[], &rng);
assert!(!output.is_empty());
}
#[test]
fn test_mutation_default_interesting_values() {
let values = X86MutationEngine::default_interesting_values();
assert!(!values.is_empty());
assert!(values.contains(&0x00));
assert!(values.contains(&0xFF));
}
#[test]
fn test_corpus_manager_default() {
let cm = X86CorpusManager::default();
assert_eq!(cm.inputs.len(), 0);
assert_eq!(cm.max_elements, 100_000);
}
#[test]
fn test_corpus_manager_add() {
let mut cm = X86CorpusManager::default();
assert!(cm.add(b"test input data".to_vec(), X86CorpusSource::User));
assert_eq!(cm.inputs.len(), 1);
}
#[test]
fn test_corpus_manager_add_min_size_filter() {
let mut cm = X86CorpusManager::default();
cm.min_input_size = 10;
assert!(!cm.add(b"short".to_vec(), X86CorpusSource::User));
assert_eq!(cm.inputs.len(), 0);
}
#[test]
fn test_corpus_manager_deduplicate() {
let mut cm = X86CorpusManager::default();
cm.add(b"same data".to_vec(), X86CorpusSource::User);
cm.add(b"same data".to_vec(), X86CorpusSource::User);
let removed = cm.deduplicate();
assert_eq!(removed, 1);
assert_eq!(cm.inputs.len(), 1);
}
#[test]
fn test_corpus_manager_minimize() {
let mut cm = X86CorpusManager::default();
cm.add(b"input one".to_vec(), X86CorpusSource::User);
cm.add(b"smaller but same coverage".to_vec(), X86CorpusSource::User);
let removed = cm.minimize();
assert!(removed >= 0);
}
#[test]
fn test_corpus_manager_distill() {
let mut cm = X86CorpusManager::default();
cm.add(b"unique input A".to_vec(), X86CorpusSource::User);
cm.add(b"unique input B".to_vec(), X86CorpusSource::User);
cm.add(b"unique input A duplicate".to_vec(), X86CorpusSource::User);
let removed = cm.distill();
assert!(removed >= 0);
}
#[test]
fn test_corpus_manager_merge() {
let mut cm1 = X86CorpusManager::default();
cm1.add(b"data1".to_vec(), X86CorpusSource::User);
let mut cm2 = X86CorpusManager::default();
cm2.add(b"data2".to_vec(), X86CorpusSource::User);
let added = cm1.merge(&cm2);
assert!(added > 0);
}
#[test]
fn test_corpus_manager_stats() {
let mut cm = X86CorpusManager::default();
cm.add(b"hello".to_vec(), X86CorpusSource::User);
cm.add(b"world!".to_vec(), X86CorpusSource::Generated);
let stats = cm.stats();
assert_eq!(stats.total_elements, 2);
assert!(stats.total_bytes > 0);
}
#[test]
fn test_corpus_manager_regression_test() {
let mut cm = X86CorpusManager::default();
cm.add(b"pass".to_vec(), X86CorpusSource::User);
cm.add(b"fail".to_vec(), X86CorpusSource::User);
let result = cm.regression_test(|data| data.len() > 3);
assert_eq!(result.total, 2);
assert_eq!(result.passed, 2);
}
#[test]
fn test_corpus_source_as_str() {
assert_eq!(X86CorpusSource::User.as_str(), "user");
assert_eq!(X86CorpusSource::Generated.as_str(), "generated");
assert_eq!(X86CorpusSource::CI.as_str(), "ci");
assert_eq!(X86CorpusSource::Merged.as_str(), "merged");
assert_eq!(X86CorpusSource::Minimized.as_str(), "minimized");
}
#[test]
fn test_server_default() {
let server = X86FuzzServer::default();
assert!(!server.running.load(Ordering::SeqCst));
assert_eq!(server.cluster.name, "x86-fuzz-cluster");
}
#[test]
fn test_server_start_stop() {
let mut server = X86FuzzServer::default();
server.start();
assert!(server.running.load(Ordering::SeqCst));
server.stop();
assert!(!server.running.load(Ordering::SeqCst));
}
#[test]
fn test_server_cluster_status() {
let server = X86FuzzServer::default();
let status = server.cluster_status();
assert_eq!(status.cluster_name, "x86-fuzz-cluster");
assert_eq!(status.total_workers, 0);
}
#[test]
fn test_server_dashboard_html() {
let server = X86FuzzServer::default();
let html = server.dashboard_html();
assert!(html.contains("X86 Clang Fuzzing"));
assert!(html.contains("<!DOCTYPE html>"));
}
#[test]
fn test_server_github_actions_workflow() {
let server = X86FuzzServer::default();
let wf = server.generate_github_actions_workflow();
assert!(wf.contains("Continuous Fuzzing"));
assert!(wf.contains("github-actions"));
}
#[test]
fn test_server_gitlab_ci_config() {
let server = X86FuzzServer::default();
let ci = server.generate_gitlab_ci_config();
assert!(ci.contains("fuzz:"));
assert!(ci.contains("artifacts"));
}
#[test]
fn test_ci_system_names() {
assert_eq!(X86CISystem::GitHubActions.name(), "GitHub Actions");
assert_eq!(X86CISystem::OSSFuzz.config_file(), "build.sh");
}
#[test]
fn test_crash_severity_ordering() {
assert!(X86CrashSeverity::Critical > X86CrashSeverity::High);
assert!(X86CrashSeverity::High > X86CrashSeverity::Medium);
assert!(X86CrashSeverity::Medium > X86CrashSeverity::Low);
assert!(X86CrashSeverity::Low > X86CrashSeverity::Info);
}
#[test]
fn test_pipeline_creation() {
let fz = X86Fuzzing::default();
let pipeline = X86FuzzPipeline::new("test-pipe", fz);
assert_eq!(pipeline.name, "test-pipe");
assert!(!pipeline.stages.is_empty());
}
#[test]
fn test_pipeline_run() {
let fz = X86Fuzzing::default();
let mut pipeline = X86FuzzPipeline::new("test", fz);
let results = pipeline.run();
assert!(!results.is_empty());
}
#[test]
fn test_instrument_clang_flags() {
let inst = X86FuzzInstrument::default();
let flags = inst.clang_flags();
assert!(flags.iter().any(|f| f.contains("trace-pc-guard")));
}
#[test]
fn test_instrument_fuzzer_initialize() {
let inst = X86FuzzInstrument::default();
let init = inst.fuzzer_initialize();
assert!(init.contains("LLVMFuzzerInitialize"));
}
#[test]
fn test_afl_mode_to_clang_flag() {
assert_eq!(
X86AFLMode::LLVM.to_clang_flag(),
"-fsanitize-coverage=trace-pc-guard"
);
assert_eq!(X86AFLMode::Classic.to_clang_flag(), "");
}
#[test]
fn test_report_markdown() {
let report_gen = X86FuzzReport::default();
let result = X86FuzzResult {
engine: X86FuzzEngine::LibFuzzer,
total_executions: 5000,
execs_per_second: 100.0,
crashes: vec![X86CrashInfo {
id: 1,
crash_type: X86CrashType::HeapBufferOverflow,
sanitizer_report: Some("ERROR: heap-buffer-overflow".to_string()),
..Default::default()
}],
coverage_pct: 62.3,
..Default::default()
};
let report = report_gen.generate(&result);
assert!(report.contains("5000"));
assert!(report.contains("HeapBufferOverflow"));
}
#[test]
fn test_report_json() {
let mut report_gen = X86FuzzReport::default();
report_gen.format = X86FuzzReportFormat::JSON;
let result = X86FuzzResult::default();
let report = report_gen.generate(&result);
assert!(report.contains('{'));
assert!(report.contains('}'));
}
#[test]
fn test_report_html() {
let mut report_gen = X86FuzzReport::default();
report_gen.format = X86FuzzReportFormat::HTML;
let result = X86FuzzResult::default();
let report = report_gen.generate(&result);
assert!(report.contains("<html>"));
}
#[test]
fn test_report_sarif() {
let mut report_gen = X86FuzzReport::default();
report_gen.format = X86FuzzReportFormat::SARIF;
let result = X86FuzzResult {
crashes: vec![X86CrashInfo {
id: 1,
crash_type: X86CrashType::Segfault,
..Default::default()
}],
..Default::default()
};
let report = report_gen.generate(&result);
assert!(report.contains("SARIF"));
assert!(report.contains("2.1.0"));
}
#[test]
fn test_scheduler_submit() {
let mut sch = X86FuzzScheduler::default();
let job = X86FuzzJob {
id: 1,
target: "parser".to_string(),
iterations: 1000,
priority: 5,
assigned_worker: None,
status: X86FuzzJobStatus::Pending,
result: None,
};
sch.submit(job);
assert_eq!(sch.pending.len(), 1);
}
#[test]
fn test_scheduler_schedule_next() {
let mut sch = X86FuzzScheduler::default();
let job = X86FuzzJob {
id: 1,
target: "parser".to_string(),
iterations: 1000,
priority: 5,
assigned_worker: None,
status: X86FuzzJobStatus::Pending,
result: None,
};
sch.submit(job);
let next = sch.schedule_next();
assert!(next.is_some());
}
#[test]
fn test_scheduler_complete() {
let mut sch = X86FuzzScheduler::default();
let job = X86FuzzJob {
id: 1,
target: "parser".to_string(),
iterations: 1000,
priority: 5,
assigned_worker: None,
status: X86FuzzJobStatus::Pending,
result: None,
};
sch.submit(job);
let _ = sch.schedule_next();
sch.complete(1, X86FuzzResult::default());
assert_eq!(sch.completed.len(), 0); }
#[test]
fn test_scheduler_stats() {
let sch = X86FuzzScheduler::default();
let stats = sch.stats();
assert_eq!(stats.pending, 0);
assert_eq!(stats.running, 0);
assert_eq!(stats.completed, 0);
}
#[test]
fn test_compiler_integration_default() {
let ci = X86FuzzCompilerIntegration::default();
assert_eq!(ci.clang_path, PathBuf::from("clang"));
assert_eq!(ci.target_triple, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_build_libfuzzer_target_command() {
let ci = X86FuzzCompilerIntegration::default();
let cmd = ci.build_libfuzzer_target(
Path::new("test.cc"),
Path::new("test_fuzzer"),
&[X86FuzzSanitizer::Address],
);
assert!(cmd.contains(&"-fsanitize=fuzzer".to_string()));
assert!(cmd.contains(&"test.cc".to_string()));
}
#[test]
fn test_build_afl_target_command() {
let ci = X86FuzzCompilerIntegration::default();
let cmd = ci.build_afl_target(Path::new("test.cc"), Path::new("test_afl"));
assert!(cmd.contains(&"afl-clang-fast".to_string()));
}
#[test]
fn test_build_honggfuzz_target_command() {
let ci = X86FuzzCompilerIntegration::default();
let cmd = ci.build_honggfuzz_target(Path::new("test.cc"), Path::new("test_hf"));
assert!(cmd.contains(&"hfuzz-clang++".to_string()));
}
#[test]
fn test_compiler_flags_string() {
let ci = X86FuzzCompilerIntegration::default();
let config = X86FuzzConfig::default();
let flags = ci.compiler_flags_string(&config);
assert!(flags.contains("-O1"));
assert!(flags.contains("fuzzer"));
}
#[test]
fn test_language_as_str() {
assert_eq!(X86FuzzLanguage::C.as_str(), "c");
assert_eq!(X86FuzzLanguage::Cpp.as_str(), "c++");
assert_eq!(X86FuzzLanguage::Both.as_str(), "c,c++");
}
#[test]
fn test_default_config_values() {
let cfg = X86FuzzConfig::default();
assert_eq!(cfg.max_input_len, X86_FUZZ_MAX_INPUT_LEN);
assert_eq!(cfg.timeout_ms, X86_FUZZ_DEFAULT_TIMEOUT_MS);
assert_eq!(cfg.rss_limit_mb, X86_FUZZ_DEFAULT_RSS_LIMIT_MB);
assert_eq!(cfg.jobs, X86_FUZZ_DEFAULT_JOBS);
}
#[test]
fn test_result_default() {
let result = X86FuzzResult::default();
assert!(result.success);
assert_eq!(result.total_executions, 0);
}
#[test]
fn test_hex_decode() {
let bytes = hex::decode("deadbeef").unwrap();
assert_eq!(bytes, vec![0xde, 0xad, 0xbe, 0xef]);
}
#[test]
fn test_hex_decode_empty() {
let bytes = hex::decode("").unwrap();
assert!(bytes.is_empty());
}
#[test]
fn test_hex_decode_odd_length() {
assert!(hex::decode("abc").is_err());
}
#[test]
fn test_full_pipeline_integration() {
let fz = X86Fuzzing::ossfuzz_pipeline();
let mut pipeline = X86FuzzPipeline::new("integration-test", fz);
let results = pipeline.run();
assert!(!results.is_empty());
for result in &results {
assert!(result.success);
}
}
#[test]
fn test_corpus_roundtrip() {
let mut cm = X86CorpusManager::default();
cm.add(b"seed input 1".to_vec(), X86CorpusSource::User);
cm.add(b"seed input 2".to_vec(), X86CorpusSource::Generated);
cm.deduplicate();
cm.minimize();
cm.distill();
let stats = cm.stats();
assert!(stats.total_elements > 0);
}
#[test]
fn test_mutation_engine_full_pipeline() {
let engine = X86MutationEngine::default();
let input = b"int main(int argc, char** argv) { return argc; }";
let mut rng = X86FuzzRng::new(0xCAFE);
for _ in 0..100 {
let _output = engine.mutate_single(input, &rng);
}
}
#[test]
fn test_fuzzing_targets_all_defaults() {
let targets = X86FuzzTargets::default();
assert_eq!(targets.enabled_count(), 6); assert!(targets.enabled.compiler);
assert!(!targets.enabled.codegen);
}
#[test]
fn test_afl_config_defaults() {
let afl = X86AFLConfig::default();
assert!(afl.master);
assert!(afl.comparison_coverage);
assert!(!afl.cmplog);
}
#[test]
fn test_honggfuzz_config_defaults() {
let hf = X86HonggfuzzConfig::default();
assert_eq!(hf.threads, 4);
assert!(hf.asan);
assert!(hf.ubsan);
}
#[test]
fn test_notification_thresholds() {
let thresholds = X86NotificationThresholds {
min_severity: X86CrashSeverity::High,
new_crash_threshold: 5,
coverage_drop_pct: 10.0,
stale_coverage_minutes: 120,
};
assert_eq!(thresholds.min_severity.as_str(), "high");
assert_eq!(thresholds.new_crash_threshold, 5);
}
#[test]
fn test_disasm_mode_values() {
let _m16 = X86DisasmMode::Mode16;
let _m32 = X86DisasmMode::Mode32;
let _m64 = X86DisasmMode::Mode64;
}
#[test]
fn test_asm_syntax_variants() {
let _intel = X86AsmSyntax::Intel;
let _att = X86AsmSyntax::ATT;
let _both = X86AsmSyntax::Both;
}
#[test]
fn test_struct_sizes_are_reasonable() {
use std::mem::size_of;
assert!(size_of::<X86FuzzConfig>() < 1024);
assert!(size_of::<X86FuzzResult>() < 4096);
assert!(size_of::<X86CrashInfo>() < 2048);
}
#[test]
fn test_sancov_map_size_constant() {
assert_eq!(X86_SANCOV_MAP_SIZE, 65536);
}
#[test]
fn test_full_grammar_generator_default() {
let gen = X86FullGrammarGenerator::default();
assert!(!gen.categories.is_empty());
assert_eq!(gen.language, X86FuzzLanguage::C);
}
#[test]
fn test_full_grammar_generate_program() {
let gen = X86FullGrammarGenerator::default();
let program = gen.generate_full_program();
assert!(!program.is_empty());
assert!(program.contains("#include"));
}
#[test]
fn test_full_grammar_generate_cpp() {
let gen = X86FullGrammarGenerator::default();
let program = gen.generate_cpp_program();
assert!(!program.is_empty());
assert!(program.contains("class"));
assert!(program.contains("template"));
assert!(program.contains("lambda"));
}
#[test]
fn test_full_grammar_max_depth() {
let mut gen = X86FullGrammarGenerator::default();
gen.max_depth = 3;
gen.max_output_len = 500;
let program = gen.generate_full_program();
assert!(!program.is_empty());
}
#[test]
fn test_full_grammar_different_outputs() {
let gen1 = X86FullGrammarGenerator::default();
let mut gen2 = X86FullGrammarGenerator::default();
gen2.seed = Some(42);
let p1 = gen1.generate_full_program();
let p2 = gen2.generate_full_program();
assert!(!p1.is_empty());
assert!(!p2.is_empty());
}
#[test]
fn test_full_grammar_categories_present() {
let gen = X86FullGrammarGenerator::default();
assert!(gen.categories.contains_key("type"));
assert!(gen.categories.contains_key("expression"));
assert!(gen.categories.contains_key("statement"));
assert!(gen.categories.contains_key("declaration"));
assert!(gen.categories.contains_key("misc"));
}
#[test]
fn test_full_grammar_empty_input_safety() {
let gen = X86FullGrammarGenerator::default();
let mut gen2 = gen.clone();
gen2.max_depth = 0;
let p = gen2.generate_full_program();
assert!(!p.is_empty() || p.chars().all(|c| c.is_ascii()));
}
#[test]
fn test_coverage_map_default() {
let map = X86CoverageMap::default();
assert_eq!(map.edge_counts.len(), X86_SANCOV_MAP_SIZE);
assert_eq!(map.edges_discovered.load(Ordering::Relaxed), 0);
}
#[test]
fn test_coverage_map_update_new_edges() {
let mut map = X86CoverageMap::default();
let data = vec![1u8; 100];
let changed = map.update(&data);
assert!(changed);
assert!(map.edges_discovered.load(Ordering::Relaxed) > 0);
}
#[test]
fn test_coverage_map_update_no_new_edges() {
let mut map = X86CoverageMap::default();
let data = vec![0u8; 100];
let changed = map.update(&data);
assert!(!changed);
assert_eq!(map.edges_discovered.load(Ordering::Relaxed), 0);
}
#[test]
fn test_coverage_map_pct() {
let mut map = X86CoverageMap::default();
let data = vec![1u8; 1000];
map.update(&data);
let pct = map.coverage_pct();
assert!(pct > 0.0);
assert!(pct <= 100.0);
}
#[test]
fn test_coverage_map_snapshot() {
let mut map = X86CoverageMap::default();
map.update(&[1u8; 64]);
map.snapshot();
let delta = map.delta();
assert_eq!(delta.iter().filter(|&&d| d > 0).count(), 0);
}
#[test]
fn test_coverage_map_hash_changes() {
let mut map = X86CoverageMap::default();
let h1 = map.map_hash();
map.update(&[1u8; 64]);
let h2 = map.map_hash();
assert_ne!(h1, h2);
}
#[test]
fn test_coverage_map_was_hit() {
let mut map = X86CoverageMap::default();
map.update(&[1u8; 64]);
assert!(map.was_hit(0));
assert!(!map.was_hit(100));
assert!(!map.was_hit(X86_SANCOV_MAP_SIZE + 1));
}
#[test]
fn test_coverage_map_top_edges() {
let mut map = X86CoverageMap::default();
let mut data = vec![0u8; 200];
data[10] = 5;
data[20] = 10;
data[30] = 3;
map.update(&data);
let top = map.top_edges(3);
assert_eq!(top.len(), 3);
assert_eq!(top[0].0, 20); assert_eq!(top[0].1, 10);
}
#[test]
fn test_coverage_map_reset() {
let mut map = X86CoverageMap::default();
map.update(&[1u8; 100]);
map.reset();
assert_eq!(map.edges_discovered.load(Ordering::Relaxed), 0);
assert_eq!(map.coverage_pct(), 0.0);
}
#[test]
fn test_dict_generator_default() {
let dg = X86DictionaryGenerator::default();
assert_eq!(dg.min_token_len, 2);
assert_eq!(dg.max_dictionary_size, 4096);
}
#[test]
fn test_dict_generator_add_c_keywords() {
let mut dg = X86DictionaryGenerator::default();
dg.add_c_keywords();
assert!(!dg.entries.is_empty());
assert!(dg.entries.iter().any(|e| e.token == b"if"));
assert!(dg.entries.iter().any(|e| e.token == b"return"));
}
#[test]
fn test_dict_generator_add_c_operators() {
let mut dg = X86DictionaryGenerator::default();
dg.add_c_operators();
assert!(dg.entries.iter().any(|e| e.token == b"++"));
assert!(dg.entries.iter().any(|e| e.token == b"->"));
}
#[test]
fn test_dict_generator_extract_from_corpus() {
let mut dg = X86DictionaryGenerator::default();
let corpus = vec![
b"int main() { return 0; }".to_vec(),
b"int foo() { return 1; }".to_vec(),
b"int bar() { return 2; }".to_vec(),
];
let added = dg.extract_from_corpus(&corpus);
assert!(added > 0);
}
#[test]
fn test_dict_generator_to_afl_format() {
let mut dg = X86DictionaryGenerator::default();
dg.add_c_keywords();
let afl = dg.to_afl_format();
assert!(afl.contains("AFL dictionary"));
assert!(afl.contains("level_"));
}
#[test]
fn test_dict_generator_to_libfuzzer_format() {
let mut dg = X86DictionaryGenerator::default();
dg.add_c_keywords();
let lf = dg.to_libfuzzer_format();
assert!(lf.contains("libFuzzer dictionary"));
}
#[test]
fn test_dict_format_variants() {
let _afl = X86DictFormat::AFL;
let _lf = X86DictFormat::LibFuzzer;
}
#[test]
fn test_crash_dedup_default() {
let dedup = X86CrashDedup::default();
assert_eq!(dedup.strategy, X86DedupStrategy::HashAndType);
assert_eq!(dedup.known_signatures.len(), 0);
}
#[test]
fn test_crash_dedup_register_and_check() {
let mut dedup = X86CrashDedup::default();
let crash = X86CrashInfo {
stack_hash: 0xDEAD,
crash_type: X86CrashType::HeapBufferOverflow,
sanitizer_report: Some("ERROR: AddressSanitizer: heap-buffer-overflow".to_string()),
..Default::default()
};
assert!(!dedup.is_duplicate(&crash));
assert!(dedup.register(&crash));
assert!(dedup.is_duplicate(&crash));
}
#[test]
fn test_crash_dedup_different_types() {
let mut dedup = X86CrashDedup::default();
let crash1 = X86CrashInfo {
stack_hash: 0x100,
crash_type: X86CrashType::Segfault,
..Default::default()
};
let crash2 = X86CrashInfo {
stack_hash: 0x100,
crash_type: X86CrashType::HeapBufferOverflow,
..Default::default()
};
dedup.register(&crash1);
assert!(!dedup.is_duplicate(&crash2)); }
#[test]
fn test_crash_dedup_fuzzy_match_same() {
let dedup = X86CrashDedup::default();
let a = X86CrashInfo {
stack_hash: 0x1234,
crash_type: X86CrashType::Segfault,
..Default::default()
};
let b = X86CrashInfo {
stack_hash: 0x1234,
crash_type: X86CrashType::Segfault,
..Default::default()
};
let sim = dedup.fuzzy_match(&a, &b);
assert!(sim > 0.9);
}
#[test]
fn test_crash_dedup_fuzzy_match_different() {
let dedup = X86CrashDedup::default();
let a = X86CrashInfo {
stack_hash: 0x0000,
crash_type: X86CrashType::Segfault,
..Default::default()
};
let b = X86CrashInfo {
stack_hash: 0xFFFF,
crash_type: X86CrashType::DoubleFree,
..Default::default()
};
let sim = dedup.fuzzy_match(&a, &b);
assert!(sim < 0.5);
}
#[test]
fn test_crash_dedup_stats() {
let mut dedup = X86CrashDedup::default();
let crash = X86CrashInfo {
stack_hash: 1,
crash_type: X86CrashType::Segfault,
..Default::default()
};
dedup.register(&crash);
let stats = dedup.stats();
assert_eq!(stats.total_signatures, 1);
}
#[test]
fn test_corpus_import_default() {
let ci = X86CorpusImport::default();
assert!(ci.validate_on_import);
assert!(!ci.supported_formats.is_empty());
}
#[test]
fn test_corpus_import_export_roundtrip() {
let ci = X86CorpusImport::default();
let mut cm = X86CorpusManager::default();
cm.add(b"test".to_vec(), X86CorpusSource::User);
cm.add(b"data".to_vec(), X86CorpusSource::User);
let _manifest_path = PathBuf::from("/tmp/test_manifest.json");
let stats = cm.stats();
assert_eq!(stats.total_elements, 2);
}
#[test]
fn test_corpus_archive_format_variants() {
let _dir = X86CorpusArchiveFormat::Directory;
let _tar = X86CorpusArchiveFormat::Tar;
let _targz = X86CorpusArchiveFormat::TarGz;
let _zip = X86CorpusArchiveFormat::Zip;
let _b64 = X86CorpusArchiveFormat::TextBase64;
}
#[test]
fn test_timeline_default() {
let tl = X86FuzzTimeline::default();
assert!(tl.events.is_empty());
assert_eq!(tl.max_events, 10_000);
}
#[test]
fn test_timeline_add_event() {
let mut tl = X86FuzzTimeline::default();
tl.add_event(X86FuzzEventKind::FuzzingStarted, "Started run 1", 0, 0.0);
assert_eq!(tl.events.len(), 1);
}
#[test]
fn test_timeline_events_of_kind() {
let mut tl = X86FuzzTimeline::default();
tl.add_event(X86FuzzEventKind::CrashFound, "Crash 1", 1000, 12.3);
tl.add_event(X86FuzzEventKind::NewCoverage, "New edge", 500, 5.0);
tl.add_event(X86FuzzEventKind::CrashFound, "Crash 2", 2000, 15.0);
let crashes = tl.events_of_kind(X86FuzzEventKind::CrashFound);
assert_eq!(crashes.len(), 2);
}
#[test]
fn test_timeline_crashes() {
let mut tl = X86FuzzTimeline::default();
tl.add_event(X86FuzzEventKind::CrashFound, "unique", 0, 0.0);
tl.add_event(X86FuzzEventKind::NewCoverage, "edge", 0, 0.0);
assert_eq!(tl.crashes().len(), 1);
}
#[test]
fn test_timeline_to_text() {
let mut tl = X86FuzzTimeline::default();
tl.add_event(X86FuzzEventKind::FuzzingStarted, "Test", 0, 0.0);
let text = tl.to_text();
assert!(text.contains("Fuzzing Timeline"));
assert!(text.contains("Test"));
}
#[test]
fn test_timeline_clear() {
let mut tl = X86FuzzTimeline::default();
tl.add_event(X86FuzzEventKind::Info, "test", 0, 0.0);
tl.clear();
assert!(tl.events.is_empty());
}
#[test]
fn test_timeline_max_events() {
let mut tl = X86FuzzTimeline::default();
tl.max_events = 5;
for i in 0..10 {
tl.add_event(X86FuzzEventKind::Info, &format!("Event {}", i), i, 0.0);
}
assert_eq!(tl.events.len(), 5);
}
#[test]
fn test_alloc_stats_default() {
let stats = X86FuzzAllocStats::default();
assert_eq!(stats.current_usage(), 0);
}
#[test]
fn test_alloc_stats_record_alloc_free() {
let stats = X86FuzzAllocStats::default();
stats.record_alloc(1024);
assert_eq!(stats.current_usage(), 1024);
stats.record_free(512);
assert_eq!(stats.current_usage(), 512);
}
#[test]
fn test_alloc_stats_peak_tracking() {
let stats = X86FuzzAllocStats::default();
stats.record_alloc(1000);
stats.record_alloc(2000);
stats.record_free(500);
stats.record_alloc(100);
stats.record_free(2500);
assert!(stats.peak_bytes.load(Ordering::Relaxed) > 0);
}
#[test]
fn test_alloc_stats_summary() {
let stats = X86FuzzAllocStats::default();
stats.record_alloc(100);
let summary = stats.summary();
assert!(summary.contains("Allocs"));
assert!(summary.contains("Peak"));
}
#[test]
fn test_validator_default() {
let v = X86FuzzValidator::default();
assert!(v.validate_c_syntax);
assert!(v.validate_ir);
assert!(!v.execute_safely);
}
#[test]
fn test_validator_validate_c_source_valid() {
let v = X86FuzzValidator::default();
let input = b"int main() { return 0; }";
let result = v.validate_c_source(input);
assert!(result.is_valid);
}
#[test]
fn test_validator_validate_c_source_invalid() {
let v = X86FuzzValidator::default();
let input = b"int main() { return 0;"; let result = v.validate_c_source(input);
assert!(!result.is_valid);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_validator_validate_assembly_valid() {
let v = X86FuzzValidator::default();
let input = b"mov eax, 1\nret";
let result = v.validate_assembly_text(input);
assert!(result.is_valid);
assert!(result.score > 0.5);
}
#[test]
fn test_validator_validate_assembly_directives() {
let v = X86FuzzValidator::default();
let input = b".section .text\n.global _start";
let result = v.validate_assembly_text(input);
assert!(result.is_valid);
}
#[test]
fn test_validator_validate_assembly_empty() {
let v = X86FuzzValidator::default();
let input = b"not assembly text at all";
let result = v.validate_assembly_text(input);
assert!(!result.is_valid);
}
#[test]
fn test_validator_validate_binary_input() {
let v = X86FuzzValidator::default();
let result = v.validate_binary_input(&[0x90, 0x90, 0xC3]);
assert!(result.is_valid);
assert!(result.score > 0.5);
}
#[test]
fn test_validator_validate_binary_input_empty() {
let v = X86FuzzValidator::default();
let result = v.validate_binary_input(&[]);
assert!(!result.is_valid);
}
#[test]
fn test_validation_result_default() {
let vr = X86ValidationResult::default();
assert!(!vr.is_valid);
assert_eq!(vr.score, 0.0);
assert!(vr.warnings.is_empty());
}
#[test]
fn test_dict_entry_creation() {
let entry = X86DictEntry {
token: b"test".to_vec(),
frequency: 5,
source: X86DictSource::Manual,
level: 0,
};
assert_eq!(entry.token.len(), 4);
assert_eq!(entry.frequency, 5);
}
#[test]
fn test_dict_source_variants() {
let _manual = X86DictSource::Manual;
let _auto = X86DictSource::AutoGenerated;
let _corpus = X86DictSource::ExtractedFromCorpus;
let _source = X86DictSource::ExtractedFromSource;
let _kw = X86DictSource::KeywordList;
}
#[test]
fn test_x86_fuzz_target_override_default() {
let ov = X86FuzzTargetOverride {
max_len: Some(500),
timeout_ms: Some(30000),
jobs: None,
extra_flags: vec!["-Wall".to_string()],
};
assert_eq!(ov.max_len, Some(500));
assert!(ov.jobs.is_none());
}
#[test]
fn test_x86_fuzz_target_mask_all_false() {
let mask = X86FuzzTargetMask {
compiler: false,
parser: false,
optimizer: false,
codegen: false,
linker: false,
assembler: false,
disassembler: false,
};
let t = X86FuzzTargets {
enabled: mask,
..Default::default()
};
assert_eq!(t.enabled_count(), 0);
}
#[test]
fn test_x86_worker_capabilities() {
let caps = X86WorkerCapabilities {
cpu_cores: 16,
ram_mb: 32768,
has_asan: true,
has_msan: false,
has_ubsan: true,
x86_features: vec!["avx2".to_string(), "bmi2".to_string()],
};
assert_eq!(caps.cpu_cores, 16);
assert!(caps.has_asan);
}
#[test]
fn test_x86_fuzz_job_default() {
let job = X86FuzzJob {
id: 42,
target: "optimizer".to_string(),
iterations: 50000,
priority: 3,
assigned_worker: Some("worker-1".to_string()),
status: X86FuzzJobStatus::Running,
result: None,
};
assert_eq!(job.id, 42);
assert_eq!(job.target, "optimizer");
}
#[test]
fn test_x86_fuzz_job_status_variants() {
let _p = X86FuzzJobStatus::Pending;
let _a = X86FuzzJobStatus::Assigned;
let _r = X86FuzzJobStatus::Running;
let _c = X86FuzzJobStatus::Completed;
let _f = X86FuzzJobStatus::Failed;
let _x = X86FuzzJobStatus::Cancelled;
}
#[test]
fn test_x86_fuzz_event_kind_variants() {
let _ = X86FuzzEventKind::FuzzingStarted;
let _ = X86FuzzEventKind::FuzzingStopped;
let _ = X86FuzzEventKind::NewCoverage;
let _ = X86FuzzEventKind::CrashFound;
let _ = X86FuzzEventKind::HangDetected;
let _ = X86FuzzEventKind::OOMDetected;
let _ = X86FuzzEventKind::CorpusMinimized;
let _ = X86FuzzEventKind::CorpusDistilled;
let _ = X86FuzzEventKind::WorkerJoined;
let _ = X86FuzzEventKind::WorkerLeft;
let _ = X86FuzzEventKind::JobSubmitted;
let _ = X86FuzzEventKind::JobCompleted;
let _ = X86FuzzEventKind::PipelineStageComplete;
let _ = X86FuzzEventKind::Error;
let _ = X86FuzzEventKind::Warning;
let _ = X86FuzzEventKind::Info;
}
#[test]
fn test_hex_encode() {
let encoded = hex::encode_hex(&[0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(encoded, "deadbeef");
}
#[test]
fn test_dedup_strategy_variants() {
let _exact = X86DedupStrategy::ExactHash;
let _ht = X86DedupStrategy::HashAndType;
let _full = X86DedupStrategy::FullSignature;
let _fuzzy = X86DedupStrategy::FuzzyStack;
}
#[test]
fn test_oracle_type_variants() {
let _none = X86OracleType::None;
let _ref = X86OracleType::ReferenceCompiler;
let _ir = X86OracleType::IRInterpreter;
let _rt = X86OracleType::Roundtrip;
let _diff = X86OracleType::DifferentialOptLevels;
let _tv = X86OracleType::TranslationValidation;
assert!(!X86OracleType::None.description().is_empty());
}
#[test]
fn test_e2e_compiler_fuzz_scenario() {
let mut fz = X86Fuzzing::compiler_fuzzing();
let full_gen = X86FullGrammarGenerator::default();
let program = full_gen.generate_full_program();
assert!(!program.is_empty());
fz.corpus
.add(program.into_bytes(), X86CorpusSource::Generated);
assert!(fz.corpus.inputs.len() > 0);
let result = fz.start();
assert!(result.success);
}
#[test]
fn test_e2e_mutation_pipeline() {
let mut engine = X86MutationEngine::default();
let initial = b"int main() { return 0; }";
let mut rng = X86FuzzRng::new(0xBEEF);
for _ in 0..50 {
let _ = engine.mutate_single(initial, &rng);
}
}
#[test]
fn test_e2e_corpus_lifecycle() {
let mut cm = X86CorpusManager::default();
for i in 0..20 {
cm.add(format!("seed_{}", i).into_bytes(), X86CorpusSource::User);
}
assert_eq!(cm.stats().total_elements, 20);
cm.deduplicate();
assert_eq!(cm.stats().total_elements, 20);
cm.minimize();
assert!(cm.stats().total_elements <= 20);
}
#[test]
fn test_e2e_server_workflow() {
let mut server = X86FuzzServer::default();
server.start();
assert!(server.running.load(Ordering::SeqCst));
let status = server.cluster_status();
assert!(status.is_running);
let html = server.dashboard_html();
assert!(html.contains("Running"));
server.stop();
assert!(!server.running.load(Ordering::SeqCst));
}
#[test]
fn test_e2e_crash_reporting_flow() {
let mut analyzers = X86FuzzAnalyzers::default();
let crash = X86CrashInfo {
id: 1,
crash_type: X86CrashType::HeapBufferOverflow,
sanitizer_report: Some("ERROR: heap-buffer-overflow".to_string()),
stack_hash: 0xCAFE,
..Default::default()
};
let dedup = analyzers.crash_triage.deduplicate(&[crash.clone(), crash]);
assert_eq!(dedup.len(), 1);
}
#[test]
fn test_all_fuzz_engine_variants_work() {
let configs = vec![
X86Fuzzing::compiler_fuzzing(),
X86Fuzzing::afl_binary_fuzzing(),
X86Fuzzing::honggfuzz_persistent(),
X86Fuzzing::ossfuzz_pipeline(),
];
for fz in &configs {
let summary = fz.summary();
assert!(!summary.is_empty());
}
}
#[test]
fn test_fuzz_instrument_afl_mode_neverzero() {
let mut inst = X86FuzzInstrument::default();
inst.afl_mode = Some(X86AFLMode::NeverZero);
let flags = inst.clang_flags();
assert!(flags.iter().any(|f| f.contains("no-prune")));
}
#[test]
fn test_coverage_map_version_tracking() {
let mut map = X86CoverageMap::default();
assert_eq!(map.version.load(Ordering::Relaxed), 0);
map.update(&[1u8; 64]);
assert!(map.version.load(Ordering::Relaxed) > 0);
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzIntegration {
pub fuzzing: X86Fuzzing,
pub compiler: X86FuzzCompilerIntegration,
pub instrument: X86FuzzInstrument,
pub reporter: X86FuzzReport,
pub timeline: X86FuzzTimeline,
pub alloc_stats: X86FuzzAllocStats,
pub validator: X86FuzzValidator,
}
impl Default for X86FuzzIntegration {
fn default() -> Self {
Self {
fuzzing: X86Fuzzing::default(),
compiler: X86FuzzCompilerIntegration::default(),
instrument: X86FuzzInstrument::default(),
reporter: X86FuzzReport::default(),
timeline: X86FuzzTimeline::default(),
alloc_stats: X86FuzzAllocStats::default(),
validator: X86FuzzValidator::default(),
}
}
}
impl X86FuzzIntegration {
pub fn for_compiler_fuzzing() -> Self {
Self {
fuzzing: X86Fuzzing::compiler_fuzzing(),
compiler: X86FuzzCompilerIntegration::default(),
instrument: X86FuzzInstrument {
trace_cmp: true,
trace_div: true,
trace_gep: true,
..Default::default()
},
reporter: X86FuzzReport::default(),
timeline: X86FuzzTimeline::default(),
alloc_stats: X86FuzzAllocStats::default(),
validator: X86FuzzValidator {
validate_c_syntax: true,
..Default::default()
},
}
}
pub fn for_binary_fuzzing() -> Self {
Self {
fuzzing: X86Fuzzing::afl_binary_fuzzing(),
compiler: X86FuzzCompilerIntegration::default(),
instrument: X86FuzzInstrument::default(),
reporter: X86FuzzReport::default(),
timeline: X86FuzzTimeline::default(),
alloc_stats: X86FuzzAllocStats::default(),
validator: X86FuzzValidator {
validate_assembly: true,
..Default::default()
},
}
}
pub fn for_ossfuzz() -> Self {
Self {
fuzzing: X86Fuzzing::ossfuzz_pipeline(),
compiler: X86FuzzCompilerIntegration::default(),
instrument: X86FuzzInstrument {
sancov: X86SanCovConfig {
trace_pc_guard: true,
inline_8bit_counters: true,
..Default::default()
},
inline_counters: true,
trace_cmp: true,
..Default::default()
},
reporter: X86FuzzReport {
format: X86FuzzReportFormat::SARIF,
..Default::default()
},
timeline: X86FuzzTimeline::default(),
alloc_stats: X86FuzzAllocStats::default(),
validator: X86FuzzValidator::default(),
}
}
pub fn build_flags(&self) -> String {
let mut all_flags = Vec::new();
all_flags.push(self.compiler.compiler_flags_string(&self.fuzzing.config));
let inst_flags = self.instrument.clang_flags();
all_flags.extend(inst_flags);
all_flags.extend(self.fuzzing.config.extra_clang_flags.clone());
all_flags.join(" ")
}
pub fn run_validated(&mut self) -> X86FuzzResult {
self.timeline.add_event(
X86FuzzEventKind::FuzzingStarted,
"Starting validated fuzz run",
0,
0.0,
);
let mut result = self.fuzzing.start();
if !result.success {
self.timeline.add_event(
X86FuzzEventKind::Error,
"Fuzz run failed",
result.total_executions,
result.coverage_pct,
);
} else {
self.timeline.add_event(
X86FuzzEventKind::FuzzingStopped,
"Fuzz run completed successfully",
result.total_executions,
result.coverage_pct,
);
}
let _report = self.reporter.generate(&result);
result.log.push(self.timeline.to_text());
result
}
pub fn run_continuous(
&mut self,
duration: Duration,
server: Option<&mut X86FuzzServer>,
) -> Vec<X86FuzzResult> {
let start = Instant::now();
let mut results = Vec::new();
self.timeline.add_event(
X86FuzzEventKind::FuzzingStarted,
&format!("Continuous fuzzing for {:?}", duration),
0,
0.0,
);
if let Some(srv) = server {
srv.start();
}
while start.elapsed() < duration {
let mut fz = self.fuzzing.clone();
let result = fz.start();
if !result.crashes.is_empty() {
for crash in &result.crashes {
self.timeline.add_event(
X86FuzzEventKind::CrashFound,
&format!("Crash {:?} found", crash.crash_type),
result.total_executions,
result.coverage_pct,
);
}
}
results.push(result);
thread::sleep(Duration::from_millis(100));
}
if let Some(srv) = server {
srv.stop();
}
results
}
}