use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
pub const X86_64_ASAN_SHADOW_OFFSET: u64 = 0x00007fff8000;
pub const X86_32_ASAN_SHADOW_OFFSET: u64 = 0x20000000;
pub const X86_ASAN_SHADOW_SCALE: u8 = 3;
pub const X86_ASAN_SHADOW_GRANULARITY: u64 = 8;
pub const X86_ASAN_SHADOW_MASK: u64 = 0x7FFFFFFF;
pub const X86_ASAN_STACK_REDZONE_SIZE: u64 = 32;
pub const X86_ASAN_MIN_REDZONE_SIZE: u64 = 16;
pub const X86_ASAN_HEAP_REDZONE_SIZE: u64 = 128;
pub const X86_ASAN_GLOBAL_REDZONE_SIZE: u64 = 64;
pub const X86_ASAN_INTRA_OBJECT_REDZONE_SIZE: u64 = 16;
pub const X86_STACK_CANARY_SIZE: u64 = 8;
pub const X86_ASAN_FAKE_STACK_MAX_ENTRIES: usize = 1024;
pub const X86_MSAN_ORIGIN_DEPTH: u32 = 4;
pub const X86_TSAN_SHADOW_CELL_SIZE: u64 = 8;
pub const X86_64_TSAN_SHADOW_OFFSET: u64 = 0x7d0000000000;
pub const X86_CET_IBT_ENDBR64: &[u8] = &[0xF3, 0x0F, 0x1E, 0xFA];
pub const X86_CET_IBT_ENDBR32: &[u8] = &[0xF3, 0x0F, 0x1E, 0xFB];
pub const X86_CET_SHSTK_PAGE_SIZE: u64 = 4096;
pub const X86_PCLMUL_POLY: u64 = 0xC200000000000000;
pub const X86_CRC32C_POLY: u32 = 0x1EDC6F41;
pub const X86_AES128_ROUNDS: usize = 10;
pub const X86_AES192_ROUNDS: usize = 12;
pub const X86_AES256_ROUNDS: usize = 14;
#[derive(Debug, Clone)]
pub struct X86Security {
pub arch: X86ArchVariant,
pub sanitizers: X86SanitizerIntegration,
pub mitigations: X86MitigationFlags,
pub fuzzing: X86FuzzingSupport,
pub vulnerability_checkers: X86VulnerabilityCheckers,
pub secure_coding: X86SecureCoding,
pub crypto_intrinsics: X86CryptoIntrinsics,
pub enabled: bool,
pub stats: X86SecurityStats,
pub emit_diagnostics: bool,
pub halt_on_violation: bool,
pub suppression_file: Option<String>,
pub report_format: SecurityReportFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ArchVariant {
X86_64,
X86_32,
X86_32PAE,
X86_X32,
X86_16,
}
impl X86ArchVariant {
pub fn pointer_size(&self) -> u64 {
match self {
Self::X86_64 => 8,
Self::X86_32 | Self::X86_32PAE | Self::X86_X32 => 4,
Self::X86_16 => 2,
}
}
pub fn asan_shadow_offset(&self) -> u64 {
match self {
Self::X86_64 | Self::X86_X32 => X86_64_ASAN_SHADOW_OFFSET,
Self::X86_32 | Self::X86_32PAE | Self::X86_16 => X86_32_ASAN_SHADOW_OFFSET,
}
}
pub fn is_64bit(&self) -> bool {
matches!(self, Self::X86_64 | Self::X86_X32)
}
pub fn supports_cet(&self) -> bool {
matches!(self, Self::X86_64 | Self::X86_32)
}
pub fn canary_size(&self) -> u64 {
match self {
Self::X86_64 => 8,
Self::X86_32 | Self::X86_32PAE | Self::X86_X32 => 4,
Self::X86_16 => 2,
}
}
}
impl Default for X86ArchVariant {
fn default() -> Self {
Self::X86_64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecurityReportFormat {
Text,
Json,
Sarif,
Csv,
Xml,
}
impl SecurityReportFormat {
pub fn extension(&self) -> &'static str {
match self {
Self::Text => "txt",
Self::Json => "json",
Self::Sarif => "sarif",
Self::Csv => "csv",
Self::Xml => "xml",
}
}
}
#[derive(Debug, Default)]
pub struct X86SecurityStats {
pub asan_functions: AtomicU64,
pub asan_access_checks: AtomicU64,
pub asan_redzones: AtomicU64,
pub asan_stack_vars: AtomicU64,
pub ubsan_checks: AtomicU64,
pub tsan_instrumentations: AtomicU64,
pub msan_shadow_propagations: AtomicU64,
pub cfi_checks: AtomicU64,
pub stack_canaries: AtomicU64,
pub fuzzing_targets: AtomicU64,
pub vuln_warnings: AtomicU64,
pub secure_coding_violations: AtomicU64,
pub compilation_overhead_ms: AtomicU64,
}
impl Clone for X86SecurityStats {
fn clone(&self) -> Self {
Self {
asan_functions: AtomicU64::new(self.asan_functions.load(Ordering::Relaxed)),
asan_access_checks: AtomicU64::new(self.asan_access_checks.load(Ordering::Relaxed)),
asan_redzones: AtomicU64::new(self.asan_redzones.load(Ordering::Relaxed)),
asan_stack_vars: AtomicU64::new(self.asan_stack_vars.load(Ordering::Relaxed)),
ubsan_checks: AtomicU64::new(self.ubsan_checks.load(Ordering::Relaxed)),
tsan_instrumentations: AtomicU64::new(
self.tsan_instrumentations.load(Ordering::Relaxed),
),
msan_shadow_propagations: AtomicU64::new(
self.msan_shadow_propagations.load(Ordering::Relaxed),
),
cfi_checks: AtomicU64::new(self.cfi_checks.load(Ordering::Relaxed)),
stack_canaries: AtomicU64::new(self.stack_canaries.load(Ordering::Relaxed)),
fuzzing_targets: AtomicU64::new(self.fuzzing_targets.load(Ordering::Relaxed)),
vuln_warnings: AtomicU64::new(self.vuln_warnings.load(Ordering::Relaxed)),
secure_coding_violations: AtomicU64::new(
self.secure_coding_violations.load(Ordering::Relaxed),
),
compilation_overhead_ms: AtomicU64::new(
self.compilation_overhead_ms.load(Ordering::Relaxed),
),
}
}
}
impl X86SecurityStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&self) {
self.asan_functions.store(0, Ordering::SeqCst);
self.asan_access_checks.store(0, Ordering::SeqCst);
self.asan_redzones.store(0, Ordering::SeqCst);
self.asan_stack_vars.store(0, Ordering::SeqCst);
self.ubsan_checks.store(0, Ordering::SeqCst);
self.tsan_instrumentations.store(0, Ordering::SeqCst);
self.msan_shadow_propagations.store(0, Ordering::SeqCst);
self.cfi_checks.store(0, Ordering::SeqCst);
self.stack_canaries.store(0, Ordering::SeqCst);
self.fuzzing_targets.store(0, Ordering::SeqCst);
self.vuln_warnings.store(0, Ordering::SeqCst);
self.secure_coding_violations.store(0, Ordering::SeqCst);
self.compilation_overhead_ms.store(0, Ordering::SeqCst);
}
pub fn summary(&self) -> String {
format!(
"X86Security Stats:\n\
ASan: {} functions, {} access checks, {} redzones, {} stack vars\n\
UBSan: {} checks\n\
TSan: {} instrumentations\n\
MSan: {} shadow props\n\
CFI: {} checks\n\
Stack Canaries: {}\n\
Fuzzing Targets: {}\n\
Vuln Warnings: {}\n\
Secure Coding Violations: {}\n\
Overhead: {}ms",
self.asan_functions.load(Ordering::Relaxed),
self.asan_access_checks.load(Ordering::Relaxed),
self.asan_redzones.load(Ordering::Relaxed),
self.asan_stack_vars.load(Ordering::Relaxed),
self.ubsan_checks.load(Ordering::Relaxed),
self.tsan_instrumentations.load(Ordering::Relaxed),
self.msan_shadow_propagations.load(Ordering::Relaxed),
self.cfi_checks.load(Ordering::Relaxed),
self.stack_canaries.load(Ordering::Relaxed),
self.fuzzing_targets.load(Ordering::Relaxed),
self.vuln_warnings.load(Ordering::Relaxed),
self.secure_coding_violations.load(Ordering::Relaxed),
self.compilation_overhead_ms.load(Ordering::Relaxed),
)
}
}
impl X86Security {
pub fn new(arch: X86ArchVariant) -> Self {
Self {
arch,
sanitizers: X86SanitizerIntegration::default(),
mitigations: X86MitigationFlags::secure_default(arch),
fuzzing: X86FuzzingSupport::default(),
vulnerability_checkers: X86VulnerabilityCheckers::all_enabled(),
secure_coding: X86SecureCoding::default(),
crypto_intrinsics: X86CryptoIntrinsics::default(),
enabled: true,
stats: X86SecurityStats::new(),
emit_diagnostics: true,
halt_on_violation: false,
suppression_file: None,
report_format: SecurityReportFormat::Text,
}
}
pub fn minimal(arch: X86ArchVariant) -> Self {
Self {
arch,
sanitizers: X86SanitizerIntegration::disabled(),
mitigations: X86MitigationFlags::minimal(arch),
fuzzing: X86FuzzingSupport::disabled(),
vulnerability_checkers: X86VulnerabilityCheckers::none(),
secure_coding: X86SecureCoding::none(),
crypto_intrinsics: X86CryptoIntrinsics::default(),
enabled: true,
stats: X86SecurityStats::new(),
emit_diagnostics: false,
halt_on_violation: false,
suppression_file: None,
report_format: SecurityReportFormat::Text,
}
}
pub fn maximum(arch: X86ArchVariant) -> Self {
let mut sec = Self::new(arch);
sec.mitigations = X86MitigationFlags::maximum(arch);
sec.sanitizers.enable_all();
sec.emit_diagnostics = true;
sec.halt_on_violation = true;
sec
}
pub fn for_fuzzing(arch: X86ArchVariant) -> Self {
let mut sec = Self::new(arch);
sec.fuzzing = X86FuzzingSupport::full_enabled();
sec.sanitizers = X86SanitizerIntegration::fuzzing_default();
sec.mitigations = X86MitigationFlags::fuzzing_friendly(arch);
sec
}
pub fn production(arch: X86ArchVariant) -> Self {
let mut sec = Self::new(arch);
sec.mitigations = X86MitigationFlags::production(arch);
sec.sanitizers = X86SanitizerIntegration::production_default();
sec.vulnerability_checkers = X86VulnerabilityCheckers::production();
sec
}
pub fn apply(&self) -> X86SecurityResult {
let start = Instant::now();
let mut result = X86SecurityResult::default();
if self.sanitizers.has_any() {
result.sanitizer_applied = true;
result.sanitizer_details = Some(self.sanitizers.describe());
}
if self.mitigations.has_any() {
result.mitigations_applied = true;
result.mitigation_details = Some(self.mitigations.flags_summary());
}
if self.fuzzing.enabled {
result.fuzzing_applied = true;
result.fuzzing_details = Some(self.fuzzing.summary());
}
if self.vulnerability_checkers.has_any() {
result.vuln_checkers_applied = true;
result.vuln_checker_count = self.vulnerability_checkers.enabled_count();
}
if self.secure_coding.has_any() {
result.secure_coding_applied = true;
result.secure_coding_rules = self.secure_coding.enabled_rule_count();
}
result.elapsed_ms = start.elapsed().as_millis() as u64;
result.success = true;
result
}
pub fn to_clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
flags.extend(self.sanitizers.to_clang_flags());
flags.extend(self.mitigations.to_clang_flags());
flags.extend(self.fuzzing.to_clang_flags());
flags.push(format!("-march={}", self.arch.to_llvm_arch()));
flags
}
pub fn validate(&self) -> Vec<SecurityConfigError> {
let mut errors = Vec::new();
if self.sanitizers.asan.enabled && self.sanitizers.msan.enabled {
errors.push(SecurityConfigError::new(
"ASan and MSan cannot be enabled simultaneously".into(),
SecurityConfigErrorKind::ConflictingSanitizers,
));
}
if self.sanitizers.asan.enabled && self.sanitizers.tsan.enabled {
errors.push(SecurityConfigError::new(
"ASan and TSan cannot be enabled simultaneously".into(),
SecurityConfigErrorKind::ConflictingSanitizers,
));
}
if self.sanitizers.msan.enabled && self.sanitizers.tsan.enabled {
errors.push(SecurityConfigError::new(
"MSan and TSan cannot be enabled simultaneously".into(),
SecurityConfigErrorKind::ConflictingSanitizers,
));
}
if self.mitigations.cet_ibt && !self.arch.supports_cet() {
errors.push(SecurityConfigError::new(
"CET IBT requires x86-64 or x86-32".into(),
SecurityConfigErrorKind::ArchitectureMismatch,
));
}
if self.sanitizers.shadow_call_stack.enabled && self.arch != X86ArchVariant::X86_64 {
errors.push(SecurityConfigError::new(
"ShadowCallStack requires x86-64".into(),
SecurityConfigErrorKind::ArchitectureMismatch,
));
}
errors
}
pub fn generate_report(&self) -> String {
let result = self.apply();
match self.report_format {
SecurityReportFormat::Json => self.to_json_report(&result),
SecurityReportFormat::Text => self.to_text_report(&result),
SecurityReportFormat::Sarif => self.to_sarif_report(&result),
SecurityReportFormat::Csv => self.to_csv_report(&result),
SecurityReportFormat::Xml => self.to_xml_report(&result),
}
}
fn to_json_report(&self, result: &X86SecurityResult) -> String {
format!(
r#"{{"arch":"{:?}","enabled":{},"sanitizers":{},"mitigations":{},"fuzzing":{},"vuln_checkers":{},"secure_coding":{},"result":{}}}"#,
self.arch,
self.enabled,
self.sanitizers.to_json(),
self.mitigations.to_json(),
self.fuzzing.to_json(),
self.vulnerability_checkers.to_json(),
self.secure_coding.to_json(),
result.to_json(),
)
}
fn to_text_report(&self, result: &X86SecurityResult) -> String {
format!(
"X86 Security Report\n\
==================\n\
Architecture: {:?}\n\
Enabled: {}\n\
Sanitizers Applied: {}\n\
Mitigations Applied: {}\n\
Fuzzing Applied: {}\n\
Vuln Checkers: {}\n\
Secure Coding Rules: {}\n\
Result: {}\n\
Elapsed: {}ms",
self.arch,
self.enabled,
result.sanitizer_applied,
result.mitigations_applied,
result.fuzzing_applied,
result.vuln_checkers_applied,
result.secure_coding_applied,
if result.success { "SUCCESS" } else { "FAILURE" },
result.elapsed_ms,
)
}
fn to_sarif_report(&self, result: &X86SecurityResult) -> String {
format!(
r#"{{"version":"2.1.0","$schema":"https://json.schemastore.org/sarif-2.1.0.json","runs":[{{"tool":{{"driver":{{"name":"X86Security","informationUri":"https://llvm.org/docs/Security"}}}},"results":[]}}]}}"#
)
}
fn to_csv_report(&self, result: &X86SecurityResult) -> String {
format!(
"arch,sanitizers,mitigations,fuzzing,vuln_checkers,secure_coding,success,elapsed_ms\n{:?},{},{},{},{},{},{},{}",
self.arch,
result.sanitizer_applied as u8,
result.mitigations_applied as u8,
result.fuzzing_applied as u8,
result.vuln_checkers_applied as u8,
result.secure_coding_applied as u8,
result.success as u8,
result.elapsed_ms,
)
}
fn to_xml_report(&self, result: &X86SecurityResult) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<x86-security-report>\n\
<arch>{:?}</arch>\n\
<success>{}</success>\n\
<elapsed-ms>{}</elapsed-ms>\n\
</x86-security-report>",
self.arch, result.success, result.elapsed_ms,
)
}
}
impl Default for X86Security {
fn default() -> Self {
Self::new(X86ArchVariant::default())
}
}
#[derive(Debug, Clone, Default)]
pub struct X86SecurityResult {
pub success: bool,
pub sanitizer_applied: bool,
pub mitigations_applied: bool,
pub fuzzing_applied: bool,
pub vuln_checkers_applied: bool,
pub secure_coding_applied: bool,
pub sanitizer_details: Option<String>,
pub mitigation_details: Option<String>,
pub fuzzing_details: Option<String>,
pub vuln_checker_count: usize,
pub secure_coding_rules: usize,
pub elapsed_ms: u64,
}
impl X86SecurityResult {
pub fn to_json(&self) -> String {
format!(
r#"{{"success":{},"sanitizer":{},"mitigations":{},"fuzzing":{},"vuln_checkers":{},"secure_coding":{},"elapsed_ms":{}}}"#,
self.success,
self.sanitizer_applied,
self.mitigations_applied,
self.fuzzing_applied,
self.vuln_checkers_applied,
self.secure_coding_applied,
self.elapsed_ms,
)
}
}
#[derive(Debug, Clone)]
pub struct SecurityConfigError {
pub message: String,
pub kind: SecurityConfigErrorKind,
}
impl SecurityConfigError {
pub fn new(message: String, kind: SecurityConfigErrorKind) -> Self {
Self { message, kind }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecurityConfigErrorKind {
ConflictingSanitizers,
ArchitectureMismatch,
MissingDependency,
InvalidFlag,
UnsupportedFeature,
}
impl X86ArchVariant {
pub fn to_llvm_arch(&self) -> &'static str {
match self {
Self::X86_64 => "x86-64",
Self::X86_32 => "i686",
Self::X86_32PAE => "i686",
Self::X86_X32 => "x86-64",
Self::X86_16 => "i386",
}
}
}
#[derive(Debug, Clone)]
pub struct X86SanitizerIntegration {
pub asan: X86AddressSanitizer,
pub msan: X86MemorySanitizer,
pub tsan: X86ThreadSanitizer,
pub ubsan: X86UndefinedBehaviorSanitizer,
pub lsan: X86LeakSanitizer,
pub dfsan: X86DataFlowSanitizer,
pub hwasan: X86HWAddressSanitizer,
pub safe_stack: X86SafeStackIntegration,
pub shadow_call_stack: X86ShadowCallStack,
pub cfi: X86CFIIntegration,
pub kcfi: X86KCFIIntegration,
pub stats: X86SanitizerStats,
}
#[derive(Debug, Default)]
pub struct X86SanitizerStats {
pub asan_checks: AtomicU64,
pub msan_checks: AtomicU64,
pub tsan_events: AtomicU64,
pub ubsan_checks: AtomicU64,
pub lsan_leaks: AtomicU64,
pub dfsan_labels: AtomicU64,
pub hwasan_tags: AtomicU64,
pub cfi_violations: AtomicU64,
pub kcfi_violations: AtomicU64,
}
impl Clone for X86SanitizerStats {
fn clone(&self) -> Self {
Self {
asan_checks: AtomicU64::new(self.asan_checks.load(Ordering::Relaxed)),
msan_checks: AtomicU64::new(self.msan_checks.load(Ordering::Relaxed)),
tsan_events: AtomicU64::new(self.tsan_events.load(Ordering::Relaxed)),
ubsan_checks: AtomicU64::new(self.ubsan_checks.load(Ordering::Relaxed)),
lsan_leaks: AtomicU64::new(self.lsan_leaks.load(Ordering::Relaxed)),
dfsan_labels: AtomicU64::new(self.dfsan_labels.load(Ordering::Relaxed)),
hwasan_tags: AtomicU64::new(self.hwasan_tags.load(Ordering::Relaxed)),
cfi_violations: AtomicU64::new(self.cfi_violations.load(Ordering::Relaxed)),
kcfi_violations: AtomicU64::new(self.kcfi_violations.load(Ordering::Relaxed)),
}
}
}
impl X86SanitizerStats {
pub fn new() -> Self {
Self::default()
}
pub fn total_checks(&self) -> u64 {
self.asan_checks.load(Ordering::Relaxed)
+ self.msan_checks.load(Ordering::Relaxed)
+ self.tsan_events.load(Ordering::Relaxed)
+ self.ubsan_checks.load(Ordering::Relaxed)
+ self.lsan_leaks.load(Ordering::Relaxed)
+ self.dfsan_labels.load(Ordering::Relaxed)
+ self.hwasan_tags.load(Ordering::Relaxed)
}
}
impl X86SanitizerIntegration {
pub fn disabled() -> Self {
Self {
asan: X86AddressSanitizer::disabled(),
msan: X86MemorySanitizer::disabled(),
tsan: X86ThreadSanitizer::disabled(),
ubsan: X86UndefinedBehaviorSanitizer::disabled(),
lsan: X86LeakSanitizer::disabled(),
dfsan: X86DataFlowSanitizer::disabled(),
hwasan: X86HWAddressSanitizer::disabled(),
safe_stack: X86SafeStackIntegration::disabled(),
shadow_call_stack: X86ShadowCallStack::disabled(),
cfi: X86CFIIntegration::disabled(),
kcfi: X86KCFIIntegration::disabled(),
stats: X86SanitizerStats::new(),
}
}
pub fn enable_all(&mut self) {
self.asan.enabled = true;
self.ubsan.enabled = true;
self.lsan.enabled = true;
self.dfsan.enabled = true;
self.safe_stack.enabled = true;
self.shadow_call_stack.enabled = true;
self.cfi.enabled = true;
self.kcfi.enabled = true;
}
pub fn fuzzing_default() -> Self {
let mut s = Self::disabled();
s.asan.enabled = true;
s.ubsan.enabled = true;
s.lsan.enabled = true;
s.asan.detect_stack_use_after_return = true;
s.asan.detect_container_overflow = true;
s.ubsan.detect_signed_overflow = true;
s.ubsan.detect_division_by_zero = true;
s.ubsan.detect_null_deref = true;
s
}
pub fn production_default() -> Self {
let mut s = Self::disabled();
s.ubsan.enabled = true;
s.ubsan.detect_signed_overflow = false;
s.ubsan.detect_implicit_conversion = false;
s.safe_stack.enabled = true;
s.cfi.enabled = true;
s
}
pub fn has_any(&self) -> bool {
self.asan.enabled
|| self.msan.enabled
|| self.tsan.enabled
|| self.ubsan.enabled
|| self.lsan.enabled
|| self.dfsan.enabled
|| self.hwasan.enabled
|| self.safe_stack.enabled
|| self.shadow_call_stack.enabled
|| self.cfi.enabled
|| self.kcfi.enabled
}
pub fn describe(&self) -> String {
let mut parts = Vec::new();
if self.asan.enabled {
parts.push(format!("ASan(shadow=0x{:x})", self.arch_shadow_offset()));
}
if self.msan.enabled {
parts.push("MSan".into());
}
if self.tsan.enabled {
parts.push("TSan".into());
}
if self.ubsan.enabled {
parts.push(format!("UBSan(minimal={})", self.ubsan.minimal_runtime));
}
if self.lsan.enabled {
parts.push("LSan".into());
}
if self.dfsan.enabled {
parts.push("DFSan".into());
}
if self.hwasan.enabled {
parts.push("HWASan".into());
}
if self.safe_stack.enabled {
parts.push("SafeStack".into());
}
if self.shadow_call_stack.enabled {
parts.push("ShadowCallStack".into());
}
if self.cfi.enabled {
parts.push("CFI".into());
}
if self.kcfi.enabled {
parts.push("KCFI".into());
}
parts.join("+")
}
pub fn to_clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
let mut sanitize_list: Vec<&str> = Vec::new();
if self.asan.enabled {
sanitize_list.push("address");
}
if self.msan.enabled {
sanitize_list.push("memory");
if self.msan.track_origins {
flags.push("-fsanitize-memory-track-origins=2".into());
}
}
if self.tsan.enabled {
sanitize_list.push("thread");
}
if self.ubsan.enabled {
if self.ubsan.minimal_runtime {
flags.push("-fsanitize-minimal-runtime".into());
}
let ubsan_checks = self.ubsan.to_check_list();
if !ubsan_checks.is_empty() {
flags.push(format!("-fsanitize={}", ubsan_checks.join(",")));
}
}
if self.lsan.enabled {
sanitize_list.push("leak");
}
if self.dfsan.enabled {
flags.push("-fsanitize=dataflow".into());
if let Some(ref abi_list) = self.dfsan.abi_list_file {
flags.push(format!("-fsanitize-dataflow-abi-list={}", abi_list));
}
}
if self.hwasan.enabled {
flags.push("-fsanitize=hwaddress".into());
}
if self.safe_stack.enabled {
flags.push("-fsanitize=safe-stack".into());
}
if self.shadow_call_stack.enabled {
flags.push("-fsanitize=shadow-call-stack".into());
}
if self.cfi.enabled {
flags.push("-fsanitize=cfi".into());
if self.cfi.cross_dso {
flags.push("-fsanitize-cfi-cross-dso".into());
}
}
if self.kcfi.enabled {
flags.push("-fsanitize=kcfi".into());
}
if !sanitize_list.is_empty() {
flags.push(format!("-fsanitize={}", sanitize_list.join(",")));
}
if self.asan.enabled || self.msan.enabled || self.ubsan.enabled {
flags.push(format!("-fsanitize-coverage={}", self.coverage_flags()));
}
flags
}
fn coverage_flags(&self) -> String {
let mut parts = Vec::new();
parts.push("trace-pc-guard");
parts.push("inline-8bit-counters");
parts.push("pc-table");
parts.join(",")
}
fn arch_shadow_offset(&self) -> u64 {
X86_64_ASAN_SHADOW_OFFSET
}
pub fn to_json(&self) -> String {
format!(
r#"{{"asan":{},"msan":{},"tsan":{},"ubsan":{},"lsan":{},"dfsan":{},"hwasan":{},"safe_stack":{},"scs":{},"cfi":{},"kcfi":{}}}"#,
self.asan.enabled,
self.msan.enabled,
self.tsan.enabled,
self.ubsan.enabled,
self.lsan.enabled,
self.dfsan.enabled,
self.hwasan.enabled,
self.safe_stack.enabled,
self.shadow_call_stack.enabled,
self.cfi.enabled,
self.kcfi.enabled,
)
}
}
impl Default for X86SanitizerIntegration {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86AddressSanitizer {
pub enabled: bool,
pub shadow_offset: u64,
pub shadow_scale: u8,
pub add_error_messages: bool,
pub halt_on_error: bool,
pub detect_stack_use_after_return: bool,
pub detect_stack_overflow: bool,
pub detect_use_after_free: bool,
pub detect_container_overflow: bool,
pub detect_odr_violations: bool,
pub instrument_globals: bool,
pub instrument_stack: bool,
pub instrument_heap: bool,
pub stack_redzone_size: u64,
pub heap_redzone_size: u64,
pub global_redzone_size: u64,
pub intra_object_redzone_size: u64,
pub fake_stack_max_entries: usize,
pub poison_stack_gaps: bool,
pub use_odr_indicator: bool,
pub instrument_loads: bool,
pub instrument_stores: bool,
pub instrument_atomics: bool,
pub instrument_byval_args: bool,
pub shadow_regions: Vec<X86ShadowRegion>,
redzones: HashMap<String, Vec<X86RedzoneInfo>>,
allocations: HashMap<u64, X86AllocInfo>,
fake_stack: Vec<X86FakeStackFrame>,
}
#[derive(Debug, Clone)]
pub struct X86ShadowRegion {
pub app_start: u64,
pub app_end: u64,
pub shadow_value: u8,
pub description: String,
}
impl X86ShadowRegion {
pub fn new(app_start: u64, app_end: u64, shadow_value: u8, description: &str) -> Self {
Self {
app_start,
app_end,
shadow_value,
description: description.to_string(),
}
}
pub fn contains(&self, addr: u64) -> bool {
addr >= self.app_start && addr < self.app_end
}
pub fn app_to_shadow(&self, addr: u64, offset: u64, scale: u8) -> u64 {
((addr - self.app_start) >> scale) + offset
}
}
#[derive(Debug, Clone)]
pub struct X86RedzoneInfo {
pub address: u64,
pub size: u64,
pub kind: X86RedzoneKind,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RedzoneKind {
Left,
Right,
Mid,
IntraObject,
UseAfterReturn,
UseAfterScope,
}
impl X86RedzoneKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Left => "left-redzone",
Self::Right => "right-redzone",
Self::Mid => "mid-redzone",
Self::IntraObject => "intra-object-redzone",
Self::UseAfterReturn => "use-after-return",
Self::UseAfterScope => "use-after-scope",
}
}
}
#[derive(Debug, Clone)]
pub struct X86AllocInfo {
pub address: u64,
pub size: u64,
pub total_size: u64,
pub kind: X86AllocKind,
pub name: String,
pub freed: bool,
pub thread_id: u64,
pub alloc_stack: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AllocKind {
Stack,
HeapMalloc,
HeapCalloc,
HeapRealloc,
HeapNew,
HeapNewArray,
Global,
ThreadLocal,
}
impl X86AllocKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Stack => "stack",
Self::HeapMalloc => "heap-malloc",
Self::HeapCalloc => "heap-calloc",
Self::HeapRealloc => "heap-realloc",
Self::HeapNew => "heap-new",
Self::HeapNewArray => "heap-new-array",
Self::Global => "global",
Self::ThreadLocal => "thread-local",
}
}
}
#[derive(Debug, Clone)]
pub struct X86FakeStackFrame {
pub function: String,
pub frame_base: u64,
pub frame_size: u64,
pub depth: usize,
pub active: bool,
pub variables: Vec<X86FakeStackVar>,
}
#[derive(Debug, Clone)]
pub struct X86FakeStackVar {
pub name: String,
pub offset: u64,
pub size: u64,
pub uar_detected: bool,
}
impl X86AddressSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
shadow_offset: X86_64_ASAN_SHADOW_OFFSET,
shadow_scale: X86_ASAN_SHADOW_SCALE,
add_error_messages: true,
halt_on_error: false,
detect_stack_use_after_return: false,
detect_stack_overflow: false,
detect_use_after_free: true,
detect_container_overflow: false,
detect_odr_violations: false,
instrument_globals: true,
instrument_stack: true,
instrument_heap: true,
stack_redzone_size: X86_ASAN_STACK_REDZONE_SIZE,
heap_redzone_size: X86_ASAN_HEAP_REDZONE_SIZE,
global_redzone_size: X86_ASAN_GLOBAL_REDZONE_SIZE,
intra_object_redzone_size: X86_ASAN_INTRA_OBJECT_REDZONE_SIZE,
fake_stack_max_entries: X86_ASAN_FAKE_STACK_MAX_ENTRIES,
poison_stack_gaps: true,
use_odr_indicator: true,
instrument_loads: true,
instrument_stores: true,
instrument_atomics: false,
instrument_byval_args: false,
shadow_regions: Vec::new(),
redzones: HashMap::new(),
allocations: HashMap::new(),
fake_stack: Vec::new(),
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s.detect_stack_use_after_return = true;
s.detect_stack_overflow = true;
s.detect_container_overflow = true;
s.detect_odr_violations = true;
s.instrument_atomics = true;
s.instrument_byval_args = true;
s.initialize_shadow_regions();
s
}
fn initialize_shadow_regions(&mut self) {
self.shadow_regions.push(X86ShadowRegion::new(
0x00000000,
self.shadow_offset,
0xFF, "Low shadow",
));
let shadow_start = self.shadow_offset;
let shadow_end = shadow_start.saturating_add(0x1000);
self.shadow_regions.push(X86ShadowRegion::new(
shadow_start,
shadow_end,
0xFE, "Shadow gap",
));
}
pub fn app_to_shadow(&self, app_addr: u64) -> u64 {
(app_addr >> self.shadow_scale).wrapping_add(self.shadow_offset)
}
pub fn shadow_to_app(&self, shadow_addr: u64) -> u64 {
(shadow_addr.wrapping_sub(self.shadow_offset)) << self.shadow_scale
}
pub fn check_access(&self, addr: u64, size: u64) -> bool {
if addr == 0 {
return false; }
let shadow = self.app_to_shadow(addr);
let end = addr + size;
let shadow_end = self.app_to_shadow(end - 1);
for s in shadow..=shadow_end {
if s == 0 {
continue;
}
let offset_in_granule = addr & (X86_ASAN_SHADOW_GRANULARITY - 1);
if offset_in_granule + size <= X86_ASAN_SHADOW_GRANULARITY {
return false;
}
}
true
}
pub fn instrument_load(&mut self, addr: u64, size: u64) -> X86ASanInstrumentation {
let check = self.create_access_check(addr, size, false);
X86ASanInstrumentation {
kind: X86ASanInstKind::Load,
address: addr,
size,
shadow_addr: self.app_to_shadow(addr),
check_required: !self.check_access(addr, size),
redzone_hit: false,
instrumented: true,
}
}
pub fn instrument_store(&mut self, addr: u64, size: u64) -> X86ASanInstrumentation {
let check = self.create_access_check(addr, size, true);
X86ASanInstrumentation {
kind: X86ASanInstKind::Store,
address: addr,
size,
shadow_addr: self.app_to_shadow(addr),
check_required: !self.check_access(addr, size),
redzone_hit: false,
instrumented: true,
}
}
pub fn instrument_alloca(
&mut self,
addr: u64,
size: u64,
alignment: u64,
name: &str,
) -> X86ASanAllocaResult {
let aligned_size = ((size + alignment - 1) / alignment) * alignment;
let redzone_left = self.stack_redzone_size;
let redzone_right = self.stack_redzone_size;
let total_size = aligned_size + redzone_left + redzone_right;
let rz_left = X86RedzoneInfo {
address: addr,
size: redzone_left,
kind: X86RedzoneKind::Left,
name: format!("{}_left_rz", name),
};
let rz_right = X86RedzoneInfo {
address: addr + redzone_left + aligned_size,
size: redzone_right,
kind: X86RedzoneKind::Right,
name: format!("{}_right_rz", name),
};
self.redzones
.entry(name.to_string())
.or_default()
.extend([rz_left, rz_right]);
self.allocations.insert(
addr,
X86AllocInfo {
address: addr,
size: aligned_size,
total_size,
kind: X86AllocKind::Stack,
name: name.to_string(),
freed: false,
thread_id: 0,
alloc_stack: vec![format!("alloca {} at 0x{:x}", name, addr)],
},
);
if self.detect_stack_use_after_return {
self.fake_stack.push(X86FakeStackFrame {
function: String::new(),
frame_base: addr,
frame_size: total_size,
depth: self.fake_stack.len(),
active: true,
variables: vec![X86FakeStackVar {
name: name.to_string(),
offset: redzone_left,
size: aligned_size,
uar_detected: false,
}],
});
while self.fake_stack.len() > self.fake_stack_max_entries {
self.fake_stack.remove(0);
}
}
X86ASanAllocaResult {
original_addr: addr,
padded_addr: addr + redzone_left,
original_size: size,
padded_size: aligned_size,
total_size,
left_redzone: redzone_left,
right_redzone: redzone_right,
instrumented: true,
}
}
pub fn instrument_global(&mut self, addr: u64, size: u64, name: &str) -> X86ASanGlobalResult {
if !self.instrument_globals {
return X86ASanGlobalResult {
name: name.to_string(),
original_size: size,
padded_size: size,
redzone_size: 0,
instrumented: false,
odr_hash: 0,
};
}
let redzone = self.global_redzone_size;
let padded = size + redzone;
let rz = X86RedzoneInfo {
address: addr + size,
size: redzone,
kind: X86RedzoneKind::Right,
name: format!("{}_global_rz", name),
};
self.redzones.entry(name.to_string()).or_default().push(rz);
let odr_hash = if self.use_odr_indicator {
self.compute_odr_hash(name, size)
} else {
0
};
self.allocations.insert(
addr,
X86AllocInfo {
address: addr,
size,
total_size: padded,
kind: X86AllocKind::Global,
name: name.to_string(),
freed: false,
thread_id: 0,
alloc_stack: vec![format!("global {} at 0x{:x}", name, addr)],
},
);
X86ASanGlobalResult {
name: name.to_string(),
original_size: size,
padded_size: padded,
redzone_size: redzone,
instrumented: true,
odr_hash,
}
}
fn compute_odr_hash(&self, name: &str, size: u64) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for b in name.as_bytes() {
hash ^= *b as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash ^= size;
hash
}
fn create_access_check(&self, addr: u64, size: u64, is_write: bool) -> bool {
if addr == 0 {
return true; }
!self.check_access(addr, size)
}
pub fn poison_stack(&mut self, addr: u64, size: u64) {
let shadow_start = self.app_to_shadow(addr);
let shadow_end = self.app_to_shadow(addr + size);
}
pub fn unpoison_stack(&mut self, addr: u64, size: u64) {
let shadow_start = self.app_to_shadow(addr);
let shadow_end = self.app_to_shadow(addr + size);
}
pub fn mark_freed(&mut self, addr: u64) {
self.poison_stack(addr, 0); if let Some(info) = self.allocations.get_mut(&addr) {
info.freed = true;
}
}
pub fn check_container_overflow(
&self,
container_addr: u64,
container_size: u64,
access_offset: u64,
) -> bool {
if !self.detect_container_overflow {
return false;
}
access_offset + self.intra_object_redzone_size > container_size
}
pub fn detect_odr_violation(
&self,
name_a: &str,
size_a: u64,
name_b: &str,
size_b: u64,
) -> Option<String> {
if !self.detect_odr_violations || name_a != name_b {
return None;
}
if size_a != size_b {
return Some(format!(
"ODR violation: '{}' defined with different sizes ({} vs {})",
name_a, size_a, size_b
));
}
None
}
pub fn instrument_function(
&mut self,
func_name: &str,
accesses: &[(u64, u64, bool)],
) -> Vec<X86ASanInstrumentation> {
let mut insts = Vec::new();
for &(addr, size, is_store) in accesses {
if is_store {
insts.push(self.instrument_store(addr, size));
} else {
insts.push(self.instrument_load(addr, size));
}
}
insts
}
pub fn error_type_for_shadow(shadow_val: u8) -> &'static str {
match shadow_val {
0xFA => "heap-left-redzone",
0xFB => "heap-right-redzone",
0xFC => "stack-left-redzone",
0xFD => "stack-mid-redzone",
0xFE => "stack-right-redzone",
0xF1 => "stack-use-after-scope",
0xF2 => "stack-use-after-return",
0xF5 => "global-redzone",
0xF8 => "intra-object-redzone",
0xFD => "freed-heap",
_ => "unknown-error",
}
}
pub fn format_report(&self, error_type: &str, addr: u64, size: u64, is_write: bool) -> String {
let access_type = if is_write { "WRITE" } else { "READ" };
format!(
"==ASan== ERROR: AddressSanitizer: {} on address 0x{:016x} at pc 0x00000000 bp 0x00000000 sp 0x00000000\n\
==ASan== {} of size {} at 0x{:016x} thread T0\n\
==ASan== Shadow bytes around the buggy address:\n\
==ASan== SUMMARY: AddressSanitizer: {} (0x{:016x})",
error_type, addr, access_type, size, addr, error_type, addr,
)
}
pub fn set_arch(&mut self, arch: X86ArchVariant) {
self.shadow_offset = arch.asan_shadow_offset();
}
}
impl Default for X86AddressSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanInstrumentation {
pub kind: X86ASanInstKind,
pub address: u64,
pub size: u64,
pub shadow_addr: u64,
pub check_required: bool,
pub redzone_hit: bool,
pub instrumented: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ASanInstKind {
Load,
Store,
AtomicLoad,
AtomicStore,
ByVal,
}
#[derive(Debug, Clone)]
pub struct X86ASanAllocaResult {
pub original_addr: u64,
pub padded_addr: u64,
pub original_size: u64,
pub padded_size: u64,
pub total_size: u64,
pub left_redzone: u64,
pub right_redzone: u64,
pub instrumented: bool,
}
#[derive(Debug, Clone)]
pub struct X86ASanGlobalResult {
pub name: String,
pub original_size: u64,
pub padded_size: u64,
pub redzone_size: u64,
pub instrumented: bool,
pub odr_hash: u64,
}
#[derive(Debug, Clone)]
pub struct X86MemorySanitizer {
pub enabled: bool,
pub track_origins: bool,
pub origin_depth: u32,
pub instrument_loads: bool,
pub instrument_stores: bool,
pub shadow_arithmetic: bool,
pub shadow_bitwise: bool,
pub shadow_comparisons: bool,
pub track_heap_origins: bool,
pub track_stack_origins: bool,
pub instrument_intrinsics: bool,
shadow_map: HashMap<u64, u8>,
origin_map: HashMap<u64, u32>,
next_origin_id: u32,
pub values_tracked: u64,
pub origins_recorded: u64,
}
impl X86MemorySanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
track_origins: false,
origin_depth: X86_MSAN_ORIGIN_DEPTH,
instrument_loads: true,
instrument_stores: true,
shadow_arithmetic: true,
shadow_bitwise: true,
shadow_comparisons: true,
track_heap_origins: true,
track_stack_origins: true,
instrument_intrinsics: true,
shadow_map: HashMap::new(),
origin_map: HashMap::new(),
next_origin_id: 1,
values_tracked: 0,
origins_recorded: 0,
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s.track_origins = true;
s
}
pub fn track_value(&mut self, addr: u64, size: u64) {
for i in 0..size {
self.shadow_map.insert(addr + i, 0xFF);
}
self.values_tracked += 1;
}
pub fn mark_initialized(&mut self, addr: u64, size: u64) {
for i in 0..size {
self.shadow_map.insert(addr + i, 0x00);
}
}
pub fn track_origin(&mut self, addr: u64, size: u64) -> u32 {
if !self.track_origins {
return 0;
}
let origin_id = self.next_origin_id;
self.next_origin_id += 1;
for i in 0..size {
self.origin_map.insert(addr + i, origin_id);
}
self.origins_recorded += 1;
origin_id
}
pub fn get_shadow(&self, addr: u64) -> u8 {
self.shadow_map.get(&addr).copied().unwrap_or(0)
}
pub fn get_origin(&self, addr: u64) -> u32 {
self.origin_map.get(&addr).copied().unwrap_or(0)
}
pub fn propagate_shadow_add(&self, a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow }
pub fn propagate_shadow_mul(&self, a_shadow: u8, b_shadow: u8) -> u8 {
let mut result_shadow = 0u8;
if a_shadow != 0 {
result_shadow |= 0xFF;
}
if b_shadow != 0 {
result_shadow |= 0xFF;
}
result_shadow
}
pub fn propagate_shadow_and(&self, a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_shadow_or(&self, a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_shadow_xor(&self, a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_shadow_shl(&self, a_shadow: u8, shift: u8) -> u8 {
if shift >= 8 {
0
} else {
a_shadow.wrapping_shl(shift as u32)
}
}
pub fn propagate_shadow_shr(&self, a_shadow: u8, shift: u8) -> u8 {
if shift >= 8 {
0
} else {
a_shadow >> shift
}
}
pub fn propagate_shadow_cmp(&self, a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn check_read(&self, addr: u64, size: u64) -> Option<X86MSanWarning> {
for i in 0..size {
let shadow = self.get_shadow(addr + i);
if shadow != 0 {
let origin = self.get_origin(addr + i);
return Some(X86MSanWarning {
address: addr,
size,
shadow_value: shadow,
origin_id: origin,
description: format!(
"Use of uninitialized value at 0x{:016x} (origin: {})",
addr, origin,
),
});
}
}
None
}
pub fn instrument_function(&mut self, func_name: &str) -> Vec<X86MSanCheck> {
let mut checks = Vec::new();
checks
}
pub fn to_flags(&self) -> Vec<String> {
let mut f = vec!["-fsanitize=memory".to_string()];
if self.track_origins {
f.push("-fsanitize-memory-track-origins=2".to_string());
}
f
}
}
impl Default for X86MemorySanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86MSanWarning {
pub address: u64,
pub size: u64,
pub shadow_value: u8,
pub origin_id: u32,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct X86MSanCheck {
pub kind: X86MSanCheckKind,
pub address: u64,
pub size: u64,
pub instrumented: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MSanCheckKind {
Load,
Store,
Branch,
Return,
ParamAccess,
}
#[derive(Debug, Clone)]
pub struct X86ThreadSanitizer {
pub enabled: bool,
pub instrument_loads: bool,
pub instrument_stores: bool,
pub instrument_atomics: bool,
pub instrument_locks: bool,
pub happens_before_tracking: bool,
pub detect_lock_inversions: bool,
pub instrument_thread_ops: bool,
pub report_second_stack: bool,
pub history_size: usize,
pub loads_instrumented: u64,
pub stores_instrumented: u64,
pub races_detected: u64,
clock: HashMap<u64, u64>,
locks: HashMap<u64, X86TSanLockState>,
access_history: HashMap<u64, VecDeque<X86TSanAccess>>,
}
#[derive(Debug, Clone)]
pub struct X86TSanLockState {
pub lock_addr: u64,
pub owner_thread: u64,
pub recursion_count: u32,
pub is_read_lock: bool,
pub acq_clock: u64,
}
#[derive(Debug, Clone)]
pub struct X86TSanAccess {
pub addr: u64,
pub size: u64,
pub is_write: bool,
pub thread_id: u64,
pub clock: u64,
pub is_atomic: bool,
pub ordering: X86TSanMemoryOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TSanMemoryOrder {
Relaxed,
Acquire,
Release,
AcqRel,
SeqCst,
}
impl X86TSanMemoryOrder {
pub fn to_llvm_str(&self) -> &'static str {
match self {
Self::Relaxed => "monotonic",
Self::Acquire => "acquire",
Self::Release => "release",
Self::AcqRel => "acq_rel",
Self::SeqCst => "seq_cst",
}
}
}
#[derive(Debug, Clone)]
pub struct X86TSanRace {
pub address: u64,
pub size: u64,
pub thread_a: u64,
pub thread_b: u64,
pub access_a: X86TSanAccess,
pub access_b: X86TSanAccess,
pub description: String,
}
impl X86ThreadSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
instrument_loads: true,
instrument_stores: true,
instrument_atomics: true,
instrument_locks: true,
happens_before_tracking: true,
detect_lock_inversions: true,
instrument_thread_ops: true,
report_second_stack: true,
history_size: 4,
loads_instrumented: 0,
stores_instrumented: 0,
races_detected: 0,
clock: HashMap::new(),
locks: HashMap::new(),
access_history: HashMap::new(),
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s
}
pub fn instrument_load(
&mut self,
addr: u64,
size: u64,
thread_id: u64,
ordering: X86TSanMemoryOrder,
) -> Option<X86TSanRace> {
if !self.enabled || !self.instrument_loads {
return None;
}
self.loads_instrumented += 1;
let clock = self.clock.get(&thread_id).copied().unwrap_or(0);
let access = X86TSanAccess {
addr,
size,
is_write: false,
thread_id,
clock,
is_atomic: ordering != X86TSanMemoryOrder::Relaxed,
ordering,
};
self.record_access(addr, access.clone());
self.detect_race(addr, &access)
}
pub fn instrument_store(
&mut self,
addr: u64,
size: u64,
thread_id: u64,
ordering: X86TSanMemoryOrder,
) -> Option<X86TSanRace> {
if !self.enabled || !self.instrument_stores {
return None;
}
self.stores_instrumented += 1;
let clock = self.clock.get(&thread_id).copied().unwrap_or(0);
let access = X86TSanAccess {
addr,
size,
is_write: true,
thread_id,
clock,
is_atomic: ordering != X86TSanMemoryOrder::Relaxed,
ordering,
};
self.record_access(addr, access.clone());
self.detect_race(addr, &access)
}
pub fn instrument_lock_acquire(&mut self, lock_addr: u64, thread_id: u64, is_read: bool) {
if !self.enabled || !self.instrument_locks {
return;
}
let clock = self.clock.get(&thread_id).copied().unwrap_or(0);
self.locks.insert(
lock_addr,
X86TSanLockState {
lock_addr,
owner_thread: thread_id,
recursion_count: 1,
is_read_lock: is_read,
acq_clock: clock,
},
);
}
pub fn instrument_lock_release(&mut self, lock_addr: u64, thread_id: u64) {
if !self.enabled || !self.instrument_locks {
return;
}
if let Some(lock) = self.locks.get_mut(&lock_addr) {
if lock.owner_thread == thread_id {
lock.recursion_count = lock.recursion_count.saturating_sub(1);
if lock.recursion_count == 0 {
let clock = self.clock.entry(thread_id).or_insert(0);
*clock = lock.acq_clock + 1;
self.locks.remove(&lock_addr);
}
}
}
}
pub fn record_happens_before(&mut self, from_thread: u64, to_thread: u64) {
if !self.enabled || !self.happens_before_tracking {
return;
}
let from_clock = self.clock.get(&from_thread).copied().unwrap_or(0);
let to_clock = self.clock.entry(to_thread).or_insert(0);
*to_clock = (*to_clock).max(from_clock + 1);
}
fn record_access(&mut self, addr: u64, access: X86TSanAccess) {
let history = self.access_history.entry(addr).or_default();
if history.len() >= self.history_size {
history.pop_front();
}
history.push_back(access);
}
fn detect_race(&mut self, addr: u64, new_access: &X86TSanAccess) -> Option<X86TSanRace> {
let history = self.access_history.get(&addr)?;
for prev_access in history.iter().rev() {
if prev_access.thread_id == new_access.thread_id {
continue;
}
let is_write = prev_access.is_write || new_access.is_write;
let is_synchronized = prev_access.is_atomic
&& new_access.is_atomic
&& prev_access.ordering != X86TSanMemoryOrder::Relaxed
&& new_access.ordering != X86TSanMemoryOrder::Relaxed;
if is_write && !is_synchronized {
let prev_clock = prev_access.clock;
let new_clock = new_access.clock;
if prev_clock >= new_clock || new_clock >= prev_clock {
self.races_detected += 1;
return Some(X86TSanRace {
address: addr,
size: new_access.size,
thread_a: prev_access.thread_id,
thread_b: new_access.thread_id,
access_a: prev_access.clone(),
access_b: new_access.clone(),
description: format!(
"DATA RACE: {} access at 0x{:016x} by T{} vs T{}",
if new_access.is_write { "Write" } else { "Read" },
addr,
prev_access.thread_id,
new_access.thread_id,
),
});
}
}
}
None
}
pub fn format_race_report(&self, race: &X86TSanRace) -> String {
format!(
"==================\n\
WARNING: ThreadSanitizer: data race (pid=0)\n\
{}\n\
Previous {} of size {} at 0x{:016x} by thread T{}:\n\
Current {} of size {} at 0x{:016x} by thread T{}:\n\
==================",
race.description,
if race.access_a.is_write {
"write"
} else {
"read"
},
race.access_a.size,
race.address,
race.thread_a,
if race.access_b.is_write {
"write"
} else {
"read"
},
race.access_b.size,
race.address,
race.thread_b,
)
}
pub fn to_flags(&self) -> Vec<String> {
if self.enabled {
vec!["-fsanitize=thread".to_string()]
} else {
vec![]
}
}
}
impl Default for X86ThreadSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86UndefinedBehaviorSanitizer {
pub enabled: bool,
pub minimal_runtime: bool,
pub detect_signed_overflow: bool,
pub detect_division_by_zero: bool,
pub detect_null_deref: bool,
pub detect_misaligned: bool,
pub detect_shift_out_of_bounds: bool,
pub detect_float_cast_overflow: bool,
pub detect_integer_truncation: bool,
pub detect_implicit_conversion: bool,
pub detect_signed_overflow_constexpr: bool,
pub detect_enum_bounds: bool,
pub detect_unreachable: bool,
pub detect_vptr: bool,
pub detect_function: bool,
pub detect_array_bounds: bool,
pub detect_nonnull: bool,
pub detect_returns_nonnull: bool,
pub detect_object_size: bool,
pub detect_pointer_overflow: bool,
pub detect_invalid_builtin: bool,
pub checks_inserted: u64,
pub issues_found: u64,
check_log: Vec<X86UBSanCheckResult>,
}
#[derive(Debug, Clone)]
pub struct X86UBSanCheckResult {
pub check_type: X86UBSanCheckType,
pub location: String,
pub passed: bool,
pub message: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86UBSanCheckType {
SignedOverflow,
DivByZero,
NullDeref,
MisalignedAccess,
ShiftOutOfBounds,
FloatCastOverflow,
IntTruncation,
ImplicitConversion,
EnumBounds,
Unreachable,
Vptr,
ArrayBounds,
Nonnull,
NonnullReturn,
ObjectSize,
PointerOverflow,
InvalidBuiltin,
}
impl X86UBSanCheckType {
pub fn handler_name(&self) -> &'static str {
match self {
Self::SignedOverflow => "__ubsan_handle_add_overflow",
Self::DivByZero => "__ubsan_handle_divrem_overflow",
Self::NullDeref => "__ubsan_handle_type_mismatch_v1",
Self::MisalignedAccess => "__ubsan_handle_type_mismatch_v1",
Self::ShiftOutOfBounds => "__ubsan_handle_shift_out_of_bounds",
Self::FloatCastOverflow => "__ubsan_handle_float_cast_overflow",
Self::IntTruncation => "__ubsan_handle_implicit_conversion",
Self::ImplicitConversion => "__ubsan_handle_implicit_conversion",
Self::EnumBounds => "__ubsan_handle_load_invalid_value",
Self::Unreachable => "__ubsan_handle_builtin_unreachable",
Self::Vptr => "__ubsan_handle_dynamic_type_cache_miss",
Self::ArrayBounds => "__ubsan_handle_out_of_bounds",
Self::Nonnull => "__ubsan_handle_nonnull_arg",
Self::NonnullReturn => "__ubsan_handle_nonnull_return_v1",
Self::ObjectSize => "__ubsan_handle_pointer_overflow",
Self::PointerOverflow => "__ubsan_handle_pointer_overflow",
Self::InvalidBuiltin => "__ubsan_handle_invalid_builtin",
}
}
pub fn clang_flag(&self) -> &'static str {
match self {
Self::SignedOverflow => "signed-integer-overflow",
Self::DivByZero => "integer-divide-by-zero",
Self::NullDeref => "null",
Self::MisalignedAccess => "alignment",
Self::ShiftOutOfBounds => "shift",
Self::FloatCastOverflow => "float-cast-overflow",
Self::IntTruncation => "implicit-integer-truncation",
Self::ImplicitConversion => "implicit-conversion",
Self::EnumBounds => "enum",
Self::Unreachable => "unreachable",
Self::Vptr => "vptr",
Self::ArrayBounds => "bounds",
Self::Nonnull => "nonnull-attribute",
Self::NonnullReturn => "returns-nonnull-attribute",
Self::ObjectSize => "object-size",
Self::PointerOverflow => "pointer-overflow",
Self::InvalidBuiltin => "invalid-builtin",
}
}
}
impl X86UndefinedBehaviorSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
minimal_runtime: false,
detect_signed_overflow: false,
detect_division_by_zero: false,
detect_null_deref: false,
detect_misaligned: false,
detect_shift_out_of_bounds: false,
detect_float_cast_overflow: false,
detect_integer_truncation: false,
detect_implicit_conversion: false,
detect_signed_overflow_constexpr: false,
detect_enum_bounds: false,
detect_unreachable: false,
detect_vptr: false,
detect_function: false,
detect_array_bounds: false,
detect_nonnull: false,
detect_returns_nonnull: false,
detect_object_size: false,
detect_pointer_overflow: false,
detect_invalid_builtin: false,
checks_inserted: 0,
issues_found: 0,
check_log: Vec::new(),
}
}
pub fn enabled_default() -> Self {
Self {
enabled: true,
minimal_runtime: false,
detect_signed_overflow: true,
detect_division_by_zero: true,
detect_null_deref: true,
detect_misaligned: true,
detect_shift_out_of_bounds: true,
detect_float_cast_overflow: true,
detect_integer_truncation: true,
detect_implicit_conversion: true,
detect_signed_overflow_constexpr: true,
detect_enum_bounds: true,
detect_unreachable: true,
detect_vptr: true,
detect_function: true,
detect_array_bounds: true,
detect_nonnull: true,
detect_returns_nonnull: true,
detect_object_size: true,
detect_pointer_overflow: true,
detect_invalid_builtin: true,
checks_inserted: 0,
issues_found: 0,
check_log: Vec::new(),
}
}
pub fn check_signed_add_overflow(&mut self, a: i64, b: i64) -> bool {
if !self.enabled || !self.detect_signed_overflow {
return false;
}
self.checks_inserted += 1;
let (result, overflow) = a.overflowing_add(b);
if overflow {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::SignedOverflow,
location: format!("{} + {}", a, b),
passed: false,
message: Some(format!(
"signed integer overflow: {} + {} cannot be represented in type 'long'",
a, b,
)),
});
}
overflow
}
pub fn check_signed_sub_overflow(&mut self, a: i64, b: i64) -> bool {
if !self.enabled || !self.detect_signed_overflow {
return false;
}
self.checks_inserted += 1;
let (result, overflow) = a.overflowing_sub(b);
if overflow {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::SignedOverflow,
location: format!("{} - {}", a, b),
passed: false,
message: Some(format!(
"signed integer overflow: {} - {} cannot be represented",
a, b,
)),
});
}
overflow
}
pub fn check_signed_mul_overflow(&mut self, a: i64, b: i64) -> bool {
if !self.enabled || !self.detect_signed_overflow {
return false;
}
self.checks_inserted += 1;
let (result, overflow) = a.overflowing_mul(b);
if overflow {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::SignedOverflow,
location: format!("{} * {}", a, b),
passed: false,
message: Some(format!(
"signed integer overflow: {} * {} cannot be represented",
a, b,
)),
});
}
overflow
}
pub fn check_div_zero(&mut self, a: i64, b: i64) -> bool {
if !self.enabled || !self.detect_division_by_zero {
return false;
}
self.checks_inserted += 1;
if b == 0 {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::DivByZero,
location: format!("{} / {}", a, b),
passed: false,
message: Some("division by zero".into()),
});
return true;
}
false
}
pub fn check_null_deref(&mut self, ptr: u64, size: u64) -> bool {
if !self.enabled || !self.detect_null_deref {
return false;
}
self.checks_inserted += 1;
if ptr == 0 {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::NullDeref,
location: format!("*null (size {})", size),
passed: false,
message: Some(format!(
"null pointer access of size {} at address 0x0",
size,
)),
});
return true;
}
false
}
pub fn check_alignment(&mut self, ptr: u64, required_align: u64) -> bool {
if !self.enabled || !self.detect_misaligned {
return false;
}
self.checks_inserted += 1;
if ptr % required_align != 0 {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::MisalignedAccess,
location: format!("ptr 0x{:x} align {}", ptr, required_align),
passed: false,
message: Some(format!(
"misaligned address 0x{:016x} for type requiring {} byte alignment",
ptr, required_align,
)),
});
return true;
}
false
}
pub fn check_shift_out_of_bounds(&mut self, value: u64, shift: u32, bit_width: u32) -> bool {
if !self.enabled || !self.detect_shift_out_of_bounds {
return false;
}
self.checks_inserted += 1;
if shift >= bit_width {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::ShiftOutOfBounds,
location: format!("{} << {}", value, shift),
passed: false,
message: Some(format!(
"shift exponent {} is too large for {}-bit type",
shift, bit_width,
)),
});
return true;
}
false
}
pub fn check_float_cast_overflow(&mut self, value: f64, target_type: &str) -> bool {
if !self.enabled || !self.detect_float_cast_overflow {
return false;
}
self.checks_inserted += 1;
let max_val: f64 = match target_type {
"i8" => i8::MAX as f64,
"u8" => u8::MAX as f64,
"i16" => i16::MAX as f64,
"u16" => u16::MAX as f64,
"i32" => i32::MAX as f64,
"u32" => u32::MAX as f64,
"i64" => i64::MAX as f64,
"u64" => u64::MAX as f64,
_ => return false,
};
if !value.is_finite() || value > max_val || value < -(max_val) {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::FloatCastOverflow,
location: format!("(float){}", value),
passed: false,
message: Some(format!(
"float value {} cannot be represented as type '{}'",
value, target_type,
)),
});
return true;
}
false
}
pub fn check_enum_bounds(&mut self, value: i64, min_val: i64, max_val: i64) -> bool {
if !self.enabled || !self.detect_enum_bounds {
return false;
}
self.checks_inserted += 1;
if value < min_val || value > max_val {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::EnumBounds,
location: format!("enum value {}", value),
passed: false,
message: Some(format!(
"load of value {} outside valid enum range [{}..{}]",
value, min_val, max_val,
)),
});
return true;
}
false
}
pub fn check_implicit_conversion(
&mut self,
from_value: i64,
from_type: &str,
to_type: &str,
) -> bool {
if !self.enabled || !self.detect_implicit_conversion {
return false;
}
self.checks_inserted += 1;
let fits = match to_type {
"i8" => from_value >= i8::MIN as i64 && from_value <= i8::MAX as i64,
"u8" => from_value >= 0 && from_value <= u8::MAX as i64,
"i16" => from_value >= i16::MIN as i64 && from_value <= i16::MAX as i64,
"u16" => from_value >= 0 && from_value <= u16::MAX as i64,
"i32" => from_value >= i32::MIN as i64 && from_value <= i32::MAX as i64,
"u32" => from_value >= 0 && from_value <= u32::MAX as i64,
_ => true,
};
if !fits {
self.issues_found += 1;
self.check_log.push(X86UBSanCheckResult {
check_type: X86UBSanCheckType::ImplicitConversion,
location: format!("({}){}", to_type, from_value),
passed: false,
message: Some(format!(
"implicit conversion from '{}' to '{}' changes value from {}",
from_type, to_type, from_value,
)),
});
}
!fits
}
pub fn to_check_list(&self) -> Vec<String> {
let mut checks = Vec::new();
if self.detect_signed_overflow {
checks.push("signed-integer-overflow".into());
}
if self.detect_division_by_zero {
checks.push("integer-divide-by-zero".into());
}
if self.detect_null_deref {
checks.push("null".into());
}
if self.detect_misaligned {
checks.push("alignment".into());
}
if self.detect_shift_out_of_bounds {
checks.push("shift".into());
}
if self.detect_float_cast_overflow {
checks.push("float-cast-overflow".into());
}
if self.detect_integer_truncation {
checks.push("implicit-integer-truncation".into());
}
if self.detect_implicit_conversion {
checks.push("implicit-conversion".into());
}
if self.detect_enum_bounds {
checks.push("enum".into());
}
if self.detect_unreachable {
checks.push("unreachable".into());
}
if self.detect_vptr {
checks.push("vptr".into());
}
if self.detect_array_bounds {
checks.push("bounds".into());
}
if self.detect_nonnull {
checks.push("nonnull-attribute".into());
}
if self.detect_returns_nonnull {
checks.push("returns-nonnull-attribute".into());
}
if self.detect_object_size {
checks.push("object-size".into());
}
if self.detect_pointer_overflow {
checks.push("pointer-overflow".into());
}
if self.detect_invalid_builtin {
checks.push("invalid-builtin".into());
}
checks
}
pub fn handler_names() -> Vec<&'static str> {
vec![
"__ubsan_handle_add_overflow",
"__ubsan_handle_sub_overflow",
"__ubsan_handle_mul_overflow",
"__ubsan_handle_divrem_overflow",
"__ubsan_handle_type_mismatch_v1",
"__ubsan_handle_shift_out_of_bounds",
"__ubsan_handle_float_cast_overflow",
"__ubsan_handle_implicit_conversion",
"__ubsan_handle_load_invalid_value",
"__ubsan_handle_builtin_unreachable",
"__ubsan_handle_dynamic_type_cache_miss",
"__ubsan_handle_out_of_bounds",
"__ubsan_handle_nonnull_arg",
"__ubsan_handle_nonnull_return_v1",
"__ubsan_handle_pointer_overflow",
"__ubsan_handle_invalid_builtin",
]
}
}
impl Default for X86UndefinedBehaviorSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86LeakSanitizer {
pub enabled: bool,
pub leak_check_at_exit: bool,
pub fast_unwind: bool,
pub max_leaks: usize,
pub report_objects: bool,
pub suppressions: Vec<String>,
leaks: Vec<X86LSanLeak>,
reachable: HashSet<u64>,
roots: Vec<X86LSanRoot>,
pub allocations_tracked: u64,
pub leaks_detected: u64,
}
#[derive(Debug, Clone)]
pub struct X86LSanLeak {
pub address: u64,
pub size: u64,
pub stack_trace: Vec<String>,
pub is_direct: bool,
pub suppressed: bool,
}
#[derive(Debug, Clone)]
pub struct X86LSanRoot {
pub start: u64,
pub size: u64,
pub kind: X86LSanRootKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LSanRootKind {
Global,
Stack,
Register,
TLS,
}
impl X86LeakSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
leak_check_at_exit: true,
fast_unwind: true,
max_leaks: 100,
report_objects: false,
suppressions: Vec::new(),
leaks: Vec::new(),
reachable: HashSet::new(),
roots: Vec::new(),
allocations_tracked: 0,
leaks_detected: 0,
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s
}
pub fn add_root(&mut self, start: u64, size: u64, kind: X86LSanRootKind) {
self.roots.push(X86LSanRoot { start, size, kind });
}
pub fn register_allocation(&mut self, addr: u64, size: u64) {
self.allocations_tracked += 1;
}
pub fn register_deallocation(&mut self, addr: u64) {
}
pub fn detect_leaks(&mut self) -> Vec<X86LSanLeak> {
if !self.enabled {
return Vec::new();
}
self.leaks.clear();
self.reachable.clear();
self.perform_reachability();
self.leaks_detected = self.leaks.len() as u64;
self.leaks.clone()
}
fn perform_reachability(&mut self) {
let mut worklist: VecDeque<u64> = VecDeque::new();
for root in &self.roots {
for addr in (root.start..root.start + root.size).step_by(8) {
let ptr_value = addr; if ptr_value != 0 {
worklist.push_back(ptr_value);
self.reachable.insert(ptr_value);
}
}
}
while let Some(addr) = worklist.pop_front() {
for offset in (0..64).step_by(8) {
let candidate = addr + offset;
if candidate != 0 && !self.reachable.contains(&candidate) {
self.reachable.insert(candidate);
worklist.push_back(candidate);
}
}
}
}
pub fn add_suppression(&mut self, pattern: &str) {
self.suppressions.push(pattern.to_string());
}
pub fn is_suppressed(&self, leak: &X86LSanLeak) -> bool {
for pattern in &self.suppressions {
if leak.stack_trace.iter().any(|frame| frame.contains(pattern)) {
return true;
}
}
false
}
pub fn format_leak_report(&self, leak: &X86LSanLeak) -> String {
let direct_str = if leak.is_direct {
"Direct leak"
} else {
"Indirect leak"
};
let mut report = format!(
"==LSan== {} of {} byte(s) at 0x{:016x}\n",
direct_str, leak.size, leak.address,
);
for (i, frame) in leak.stack_trace.iter().enumerate() {
report.push_str(&format!("==LSan== #{} {}\n", i, frame,));
}
report
}
pub fn summarize(&self) -> String {
if self.leaks.is_empty() {
"==LSan== No leaks detected.".into()
} else {
let total_bytes: u64 = self.leaks.iter().map(|l| l.size).sum();
format!(
"==LSan== SUMMARY: {} leak(s) detected, {} byte(s) lost.",
self.leaks.len(),
total_bytes,
)
}
}
}
impl Default for X86LeakSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86DataFlowSanitizer {
pub enabled: bool,
pub abi_list_file: Option<String>,
pub abi_list: Vec<X86DFSanABIEntry>,
pub label_size: u8,
pub label_offset: u64,
pub track_origins: bool,
pub instrument_loads: bool,
pub instrument_stores: bool,
propagation_rules: Vec<X86DFSanPropagationRule>,
labels: HashMap<u64, u64>,
next_label_id: u64,
}
#[derive(Debug, Clone)]
pub struct X86DFSanABIEntry {
pub function: String,
pub category: X86DFSanABICategory,
pub label_args: bool,
pub label_ret: bool,
pub is_custom: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DFSanABICategory {
Fun,
Discard,
Functional,
}
impl X86DFSanABICategory {
pub fn to_prefix(&self) -> &'static str {
match self {
Self::Fun => "fun:",
Self::Discard => "discard:",
Self::Functional => "functional:",
}
}
pub fn from_prefix(s: &str) -> Option<Self> {
match s {
"fun:" => Some(Self::Fun),
"discard:" => Some(Self::Discard),
"functional:" => Some(Self::Functional),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86DFSanPropagationRule {
pub op: X86DFSanOp,
pub combine: X86DFSanCombine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DFSanOp {
Add,
Sub,
Mul,
Div,
Rem,
And,
Or,
Xor,
Shl,
Shr,
Load,
Store,
Call,
Ret,
Phi,
Select,
Cast,
Extract,
Insert,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DFSanCombine {
Union,
Intersection,
First,
Custom,
Discard,
}
impl X86DataFlowSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
abi_list_file: None,
abi_list: Vec::new(),
label_size: 8,
label_offset: 0,
track_origins: false,
instrument_loads: true,
instrument_stores: true,
propagation_rules: Self::default_propagation_rules(),
labels: HashMap::new(),
next_label_id: 1,
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s
}
fn default_propagation_rules() -> Vec<X86DFSanPropagationRule> {
vec![
X86DFSanPropagationRule {
op: X86DFSanOp::Add,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Sub,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Mul,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::And,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Or,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Xor,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Load,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Store,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Phi,
combine: X86DFSanCombine::Union,
},
X86DFSanPropagationRule {
op: X86DFSanOp::Call,
combine: X86DFSanCombine::Union,
},
]
}
pub fn create_label(&mut self) -> u64 {
let label = self.next_label_id;
self.next_label_id += 1;
label
}
pub fn set_label(&mut self, addr: u64, size: u64, label: u64) {
for i in 0..size {
self.labels.insert(addr + i, label);
}
}
pub fn get_label(&self, addr: u64) -> u64 {
self.labels.get(&addr).copied().unwrap_or(0)
}
pub fn union_labels(&self, a: u64, b: u64) -> u64 {
a | b
}
pub fn propagate_label(&self, op: X86DFSanOp, labels: &[u64]) -> u64 {
let rule = self.propagation_rules.iter().find(|r| r.op == op);
match rule.map(|r| r.combine) {
Some(X86DFSanCombine::Union) => labels.iter().fold(0, |acc, l| acc | l),
Some(X86DFSanCombine::Intersection) => labels.iter().fold(!0, |acc, l| acc & l),
Some(X86DFSanCombine::First) => labels.first().copied().unwrap_or(0),
Some(X86DFSanCombine::Discard) => 0,
_ => labels.iter().fold(0, |acc, l| acc | l),
}
}
pub fn add_abi_entry(&mut self, function: &str, category: X86DFSanABICategory) {
self.abi_list.push(X86DFSanABIEntry {
function: function.to_string(),
category,
label_args: category != X86DFSanABICategory::Discard,
label_ret: category == X86DFSanABICategory::Fun,
is_custom: category == X86DFSanABICategory::Fun,
});
}
pub fn parse_abi_line(line: &str) -> Option<X86DFSanABIEntry> {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with('[') {
return None;
}
for category in &[
X86DFSanABICategory::Fun,
X86DFSanABICategory::Discard,
X86DFSanABICategory::Functional,
] {
let prefix = category.to_prefix();
if let Some(func) = line.strip_prefix(prefix) {
return Some(X86DFSanABIEntry {
function: func.trim().to_string(),
category: *category,
label_args: *category != X86DFSanABICategory::Discard,
label_ret: *category == X86DFSanABICategory::Fun,
is_custom: *category == X86DFSanABICategory::Fun,
});
}
}
None
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
let mut flags = vec!["-fsanitize=dataflow".to_string()];
if let Some(ref abi) = self.abi_list_file {
flags.push(format!("-fsanitize-dataflow-abi-list={}", abi));
}
flags
}
}
impl Default for X86DataFlowSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86HWAddressSanitizer {
pub enabled: bool,
pub use_tbi: bool,
pub tag_granularity: u8,
pub random_tags: bool,
pub tag_offset: u8,
pub instrument_stack: bool,
pub instrument_heap: bool,
pub instrument_globals: bool,
pub shadow_offset: u64,
pub num_tags: u16,
pub history_size: usize,
pub short_granules: bool,
pub tags_applied: u64,
pub errors_detected: u64,
}
impl X86HWAddressSanitizer {
pub fn disabled() -> Self {
Self {
enabled: false,
use_tbi: false,
tag_granularity: 16,
random_tags: true,
tag_offset: 56, instrument_stack: true,
instrument_heap: true,
instrument_globals: true,
shadow_offset: 0,
num_tags: 256,
history_size: 1024,
short_granules: false,
tags_applied: 0,
errors_detected: 0,
}
}
pub fn x86_stub() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s.use_tbi = false; s.tag_offset = 0; s.random_tags = false;
s
}
pub fn generate_tag(&self) -> u8 {
if self.random_tags {
let seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos() as u64;
(seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407)
% 255
+ 1) as u8
} else {
0x42 }
}
pub fn tag_allocation(&mut self, addr: u64, size: u64) -> u8 {
if !self.enabled {
return 0;
}
let tag = self.generate_tag();
self.tags_applied += 1;
tag
}
pub fn check_access(&mut self, ptr: u64, size: u64) -> bool {
if !self.enabled {
return true;
}
true
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
vec!["-fsanitize=hwaddress".to_string()]
}
}
impl Default for X86HWAddressSanitizer {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86SafeStackIntegration {
pub enabled: bool,
pub use_runtime: bool,
pub guard_page_size: u64,
pub max_unsafe_stack: u64,
pub safe_stack_guard: bool,
pub allocas_moved: u64,
pub functions_instrumented: u64,
}
impl X86SafeStackIntegration {
pub fn disabled() -> Self {
Self {
enabled: false,
use_runtime: true,
guard_page_size: 4096,
max_unsafe_stack: 2 * 1024 * 1024, safe_stack_guard: true,
allocas_moved: 0,
functions_instrumented: 0,
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
vec!["-fsanitize=safe-stack".to_string()]
}
}
impl Default for X86SafeStackIntegration {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86ShadowCallStack {
pub enabled: bool,
pub use_gs_register: bool,
pub guard_page_size: u64,
pub stack_size: u64,
pub instrument_all: bool,
pub functions_instrumented: u64,
pub corruptions_detected: u64,
}
impl X86ShadowCallStack {
pub fn disabled() -> Self {
Self {
enabled: false,
use_gs_register: true,
guard_page_size: 4096,
stack_size: 1024 * 1024, instrument_all: true,
functions_instrumented: 0,
corruptions_detected: 0,
}
}
pub fn enabled_default() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
vec!["-fsanitize=shadow-call-stack".to_string()]
}
}
impl Default for X86ShadowCallStack {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86CFIIntegration {
pub enabled: bool,
pub cross_dso: bool,
pub use_lto: bool,
pub forward_edge: bool,
pub backward_edge: bool,
pub icall: bool,
pub indirect_jumps: bool,
pub calls_instrumented: u64,
pub violations_detected: u64,
pub emit_type_metadata: bool,
pub check_kind: X86CFICheckKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CFICheckKind {
Exact,
Derived,
SameSize,
}
impl X86CFICheckKind {
pub fn to_clang_flag(&self) -> &'static str {
match self {
Self::Exact => "cfi",
Self::Derived => "cfi-derived-cast",
Self::SameSize => "cfi-unrelated-cast",
}
}
}
impl X86CFIIntegration {
pub fn disabled() -> Self {
Self {
enabled: false,
cross_dso: false,
use_lto: false,
forward_edge: true,
backward_edge: true,
icall: true,
indirect_jumps: false,
calls_instrumented: 0,
violations_detected: 0,
emit_type_metadata: true,
check_kind: X86CFICheckKind::Exact,
}
}
pub fn full() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s.cross_dso = true;
s.use_lto = true;
s
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
let mut flags = vec!["-fsanitize=cfi".to_string()];
if self.cross_dso {
flags.push("-fsanitize-cfi-cross-dso".to_string());
}
if self.icall {
flags.push("-fsanitize=cfi-icall".to_string());
}
if self.forward_edge && self.backward_edge {
} else if self.forward_edge {
} else if self.backward_edge {
flags.push("-fsanitize=cfi-derived-cast".to_string());
}
flags
}
}
impl Default for X86CFIIntegration {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86KCFIIntegration {
pub enabled: bool,
pub use_prefix: bool,
pub hash_algo: X86KCFIHashAlgo,
pub functions_checked: u64,
pub violations_detected: u64,
pub trap_on_violation: bool,
type_hashes: HashMap<String, u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86KCFIHashAlgo {
XxHash32,
HalfSipHash,
Murmur3,
FNV1a32,
}
impl X86KCFIHashAlgo {
pub fn name(&self) -> &'static str {
match self {
Self::XxHash32 => "xxhash32",
Self::HalfSipHash => "halfsiphash",
Self::Murmur3 => "murmur3",
Self::FNV1a32 => "fnv1a32",
}
}
}
impl X86KCFIIntegration {
pub fn disabled() -> Self {
Self {
enabled: false,
use_prefix: true,
hash_algo: X86KCFIHashAlgo::XxHash32,
functions_checked: 0,
violations_detected: 0,
trap_on_violation: true,
type_hashes: HashMap::new(),
}
}
pub fn kernel_module() -> Self {
let mut s = Self::disabled();
s.enabled = true;
s.use_prefix = true;
s.trap_on_violation = true;
s
}
pub fn compute_type_hash(&mut self, func_sig: &str) -> u32 {
match self.hash_algo {
X86KCFIHashAlgo::FNV1a32 => {
let mut hash: u32 = 0x811c9dc5;
for b in func_sig.as_bytes() {
hash ^= *b as u32;
hash = hash.wrapping_mul(0x01000193);
}
hash
}
X86KCFIHashAlgo::XxHash32 => {
let mut hash: u32 = 0;
for b in func_sig.as_bytes() {
hash = hash.wrapping_add(*b as u32);
hash = hash.wrapping_mul(0x9E3779B1);
}
hash
}
_ => {
let mut hash: u32 = 0;
for b in func_sig.as_bytes() {
hash = hash.wrapping_mul(31).wrapping_add(*b as u32);
}
hash
}
}
}
pub fn register_function(&mut self, name: &str, func_sig: &str) {
let hash = self.compute_type_hash(func_sig);
self.type_hashes.insert(name.to_string(), hash);
self.functions_checked += 1;
}
pub fn validate_call(&mut self, callee_name: &str, expected_hash: u32) -> bool {
if !self.enabled {
return true;
}
let actual_hash = self.type_hashes.get(callee_name).copied();
match actual_hash {
Some(h) if h == expected_hash => true,
_ => {
self.violations_detected += 1;
false
}
}
}
pub fn to_flags(&self) -> Vec<String> {
if !self.enabled {
return vec![];
}
vec!["-fsanitize=kcfi".to_string()]
}
}
impl Default for X86KCFIIntegration {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86MitigationFlags {
pub stack_protector: bool,
pub stack_protector_strong: bool,
pub stack_protector_all: bool,
pub canary_value: Option<u64>,
pub canary_verify: bool,
pub cet_ibt: bool,
pub cet_shstk: bool,
pub cet_full: bool,
pub strict_flex_arrays: bool,
pub strict_vtable_pointers: bool,
pub auto_var_init_pattern: bool,
pub auto_var_init_zero: bool,
pub zero_call_used_regs_used: bool,
pub zero_call_used_regs_all: bool,
pub fortify_source_1: bool,
pub fortify_source_2: bool,
pub fortify_source_3: bool,
pub enable_pie: bool,
pub enable_pic: bool,
pub relro_partial: bool,
pub relro_full: bool,
pub nx_stack: bool,
pub no_dlopen: bool,
pub no_dldump: bool,
pub no_common: bool,
pub as_needed: bool,
pub separate_code: bool,
pub production_mode: bool,
pub size_optimized: bool,
pub werror_format: bool,
pub werror_implicit: bool,
pub werror_int_conv: bool,
pub wrapv: bool,
pub keep_null_checks: bool,
pub no_strict_overflow: bool,
}
impl X86MitigationFlags {
pub fn secure_default(arch: X86ArchVariant) -> Self {
let is_64 = arch.is_64bit();
Self {
stack_protector: false,
stack_protector_strong: true,
stack_protector_all: false,
canary_value: None,
canary_verify: true,
cet_ibt: is_64,
cet_shstk: is_64,
cet_full: is_64,
strict_flex_arrays: true,
strict_vtable_pointers: true,
auto_var_init_pattern: true,
auto_var_init_zero: false,
zero_call_used_regs_used: false,
zero_call_used_regs_all: false,
fortify_source_1: false,
fortify_source_2: true,
fortify_source_3: false,
enable_pie: true,
enable_pic: false,
relro_partial: false,
relro_full: true,
nx_stack: true,
no_dlopen: false,
no_dldump: false,
no_common: true,
as_needed: true,
separate_code: true,
production_mode: false,
size_optimized: false,
werror_format: true,
werror_implicit: true,
werror_int_conv: true,
wrapv: false,
keep_null_checks: true,
no_strict_overflow: false,
}
}
pub fn minimal(arch: X86ArchVariant) -> Self {
let is_64 = arch.is_64bit();
Self {
stack_protector: false,
stack_protector_strong: false,
stack_protector_all: false,
canary_value: None,
canary_verify: false,
cet_ibt: false,
cet_shstk: false,
cet_full: false,
strict_flex_arrays: false,
strict_vtable_pointers: false,
auto_var_init_pattern: false,
auto_var_init_zero: false,
zero_call_used_regs_used: false,
zero_call_used_regs_all: false,
fortify_source_1: false,
fortify_source_2: false,
fortify_source_3: false,
enable_pie: false,
enable_pic: false,
relro_partial: true,
relro_full: false,
nx_stack: true,
no_dlopen: false,
no_dldump: false,
no_common: false,
as_needed: false,
separate_code: false,
production_mode: false,
size_optimized: false,
werror_format: false,
werror_implicit: false,
werror_int_conv: false,
wrapv: false,
keep_null_checks: false,
no_strict_overflow: false,
}
}
pub fn maximum(arch: X86ArchVariant) -> Self {
let is_64 = arch.is_64bit();
Self {
stack_protector: false,
stack_protector_strong: false,
stack_protector_all: true,
canary_value: None,
canary_verify: true,
cet_ibt: is_64,
cet_shstk: is_64,
cet_full: is_64,
strict_flex_arrays: true,
strict_vtable_pointers: true,
auto_var_init_pattern: true,
auto_var_init_zero: false,
zero_call_used_regs_used: true,
zero_call_used_regs_all: false,
fortify_source_1: false,
fortify_source_2: false,
fortify_source_3: true,
enable_pie: true,
enable_pic: false,
relro_partial: false,
relro_full: true,
nx_stack: true,
no_dlopen: true,
no_dldump: true,
no_common: true,
as_needed: true,
separate_code: true,
production_mode: false,
size_optimized: false,
werror_format: true,
werror_implicit: true,
werror_int_conv: true,
wrapv: false,
keep_null_checks: true,
no_strict_overflow: true,
}
}
pub fn fuzzing_friendly(arch: X86ArchVariant) -> Self {
Self {
stack_protector: false,
stack_protector_strong: false,
stack_protector_all: false,
canary_value: None,
canary_verify: false,
cet_ibt: false,
cet_shstk: false,
cet_full: false,
strict_flex_arrays: false,
strict_vtable_pointers: false,
auto_var_init_pattern: true, auto_var_init_zero: false,
zero_call_used_regs_used: false,
zero_call_used_regs_all: false,
fortify_source_1: false,
fortify_source_2: false,
fortify_source_3: false,
enable_pie: true,
enable_pic: false,
relro_partial: true,
relro_full: false,
nx_stack: true,
no_dlopen: false,
no_dldump: false,
no_common: false,
as_needed: false,
separate_code: false,
production_mode: false,
size_optimized: false,
werror_format: false,
werror_implicit: false,
werror_int_conv: false,
wrapv: false,
keep_null_checks: false,
no_strict_overflow: false,
}
}
pub fn production(arch: X86ArchVariant) -> Self {
let mut s = Self::secure_default(arch);
s.production_mode = true;
s.zero_call_used_regs_used = true;
s.fortify_source_2 = true;
s
}
pub fn has_any(&self) -> bool {
self.stack_protector
|| self.stack_protector_strong
|| self.stack_protector_all
|| self.cet_ibt
|| self.cet_shstk
|| self.strict_flex_arrays
|| self.strict_vtable_pointers
|| self.auto_var_init_pattern
|| self.auto_var_init_zero
|| self.zero_call_used_regs_used
|| self.zero_call_used_regs_all
|| self.fortify_source_1
|| self.fortify_source_2
|| self.fortify_source_3
|| self.enable_pie
|| self.enable_pic
|| self.relro_full
|| self.nx_stack
}
pub fn flags_summary(&self) -> String {
let mut parts = Vec::new();
if self.stack_protector {
parts.push("-fstack-protector");
}
if self.stack_protector_strong {
parts.push("-fstack-protector-strong");
}
if self.stack_protector_all {
parts.push("-fstack-protector-all");
}
if self.cet_full {
parts.push("-fcf-protection=full");
} else if self.cet_ibt && self.cet_shstk {
parts.push("-fcf-protection=full");
} else if self.cet_ibt {
parts.push("-fcf-protection=branch");
} else if self.cet_shstk {
parts.push("-fcf-protection=return");
}
if self.strict_flex_arrays {
parts.push("-fstrict-flex-arrays=3");
}
if self.strict_vtable_pointers {
parts.push("-fstrict-vtable-pointers");
}
if self.auto_var_init_pattern {
parts.push("-ftrivial-auto-var-init=pattern");
}
if self.auto_var_init_zero {
parts.push("-ftrivial-auto-var-init=zero");
}
if self.zero_call_used_regs_used {
parts.push("-fzero-call-used-regs=used");
}
if self.fortify_source_2 {
parts.push("-D_FORTIFY_SOURCE=2");
}
if self.enable_pie {
parts.push("-fPIE");
}
if self.relro_full {
parts.push("Full RELRO");
}
if self.nx_stack {
parts.push("NX stack");
}
parts.join(", ")
}
pub fn generate_canary(&self) -> u64 {
self.canary_value.unwrap_or_else(|| {
0xDEADBEEF_CAFEBABE
})
}
pub fn canary_check_sequence(&self) -> Vec<u8> {
vec![
0x64, 0x48, 0x8B, 0x04, 0x25, 0x28, 0x00, 0x00, 0x00, 0x48, 0x3B, 0x45, 0xF8, 0x75, 0x05, 0xE8, 0x00, 0x00, 0x00, 0x00, ]
}
pub fn to_clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.stack_protector_all {
flags.push("-fstack-protector-all".into());
} else if self.stack_protector_strong {
flags.push("-fstack-protector-strong".into());
} else if self.stack_protector {
flags.push("-fstack-protector".into());
}
if self.cet_full || (self.cet_ibt && self.cet_shstk) {
flags.push("-fcf-protection=full".into());
} else if self.cet_ibt {
flags.push("-fcf-protection=branch".into());
} else if self.cet_shstk {
flags.push("-fcf-protection=return".into());
}
if self.strict_flex_arrays {
flags.push("-fstrict-flex-arrays=3".into());
}
if self.strict_vtable_pointers {
flags.push("-fstrict-vtable-pointers".into());
}
if self.auto_var_init_pattern {
flags.push("-ftrivial-auto-var-init=pattern".into());
} else if self.auto_var_init_zero {
flags.push("-ftrivial-auto-var-init=zero".into());
}
if self.zero_call_used_regs_all {
flags.push("-fzero-call-used-regs=all".into());
} else if self.zero_call_used_regs_used {
flags.push("-fzero-call-used-regs=used".into());
}
if self.fortify_source_3 {
flags.push("-D_FORTIFY_SOURCE=3".into());
} else if self.fortify_source_2 {
flags.push("-D_FORTIFY_SOURCE=2".into());
} else if self.fortify_source_1 {
flags.push("-D_FORTIFY_SOURCE=1".into());
}
if self.enable_pie {
flags.push("-fPIE".into());
flags.push("-pie".into());
}
if self.enable_pic {
flags.push("-fPIC".into());
}
if self.relro_full {
flags.push("-Wl,-z,relro".into());
flags.push("-Wl,-z,now".into());
} else if self.relro_partial {
flags.push("-Wl,-z,relro".into());
}
if self.nx_stack {
flags.push("-Wl,-z,noexecstack".into());
}
if self.no_common {
flags.push("-fno-common".into());
}
if self.no_dlopen {
flags.push("-Wl,-z,nodlopen".into());
}
if self.no_dldump {
flags.push("-Wl,-z,nodump".into());
}
if self.separate_code {
flags.push("-Wl,-z,separate-code".into());
}
if self.werror_format {
flags.push("-Werror=format-security".into());
}
if self.werror_implicit {
flags.push("-Werror=implicit-function-declaration".into());
}
if self.werror_int_conv {
flags.push("-Werror=int-conversion".into());
}
if self.no_strict_overflow {
flags.push("-fno-strict-overflow".into());
}
if self.keep_null_checks {
flags.push("-fno-delete-null-pointer-checks".into());
}
flags
}
pub fn to_json(&self) -> String {
format!(
r#"{{"stack_protector":{},"cet":{},"strict_flex":{},"strict_vtable":{},"auto_init":{},"zero_regs":{},"fortify":{},"pie":{},"relro":{},"nx":{}}}"#,
self.stack_protector_strong || self.stack_protector_all,
self.cet_ibt || self.cet_shstk,
self.strict_flex_arrays,
self.strict_vtable_pointers,
self.auto_var_init_pattern || self.auto_var_init_zero,
self.zero_call_used_regs_used || self.zero_call_used_regs_all,
self.fortify_source_2 || self.fortify_source_3,
self.enable_pie,
self.relro_full,
self.nx_stack,
)
}
}
impl Default for X86MitigationFlags {
fn default() -> Self {
Self::secure_default(X86ArchVariant::default())
}
}
#[derive(Debug, Clone)]
pub struct X86FuzzingSupport {
pub enabled: bool,
pub libfuzzer: bool,
pub define_test_one_input: bool,
pub define_initialize: bool,
pub define_custom_mutator: bool,
pub define_custom_crossover: bool,
pub sancov_edge: bool,
pub sancov_bb: bool,
pub sancov_pc_table: bool,
pub sancov_trace_pc_guard: bool,
pub sancov_inline_8bit_counters: bool,
pub sancov_trace_cmp: bool,
pub sancov_trace_div: bool,
pub sancov_trace_gep: bool,
pub afl: bool,
pub afl_mode: X86AFLMode,
pub afl_shm_id: Option<u32>,
pub afl_deferred: bool,
pub afl_persistent: bool,
pub honggfuzz: bool,
pub honggfuzz_persistent: bool,
pub profile_instr_generate: bool,
pub coverage_mapping: bool,
pub coverage_dir: Option<String>,
pub targets_created: u64,
pub instrumented_points: u64,
pub coverage_pct: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AFLMode {
Classic,
LLVM,
LLVMLTO,
NeverZero,
}
impl X86AFLMode {
pub fn to_clang_flag(&self) -> &'static str {
match self {
Self::Classic => "afl",
Self::LLVM => "afl-clang-fast",
Self::LLVMLTO => "afl-lto",
Self::NeverZero => "afl-never-zero",
}
}
}
impl X86FuzzingSupport {
pub fn disabled() -> Self {
Self {
enabled: false,
libfuzzer: false,
define_test_one_input: false,
define_initialize: false,
define_custom_mutator: false,
define_custom_crossover: false,
sancov_edge: false,
sancov_bb: false,
sancov_pc_table: false,
sancov_trace_pc_guard: false,
sancov_inline_8bit_counters: false,
sancov_trace_cmp: false,
sancov_trace_div: false,
sancov_trace_gep: false,
afl: false,
afl_mode: X86AFLMode::LLVM,
afl_shm_id: None,
afl_deferred: false,
afl_persistent: false,
honggfuzz: false,
honggfuzz_persistent: false,
profile_instr_generate: false,
coverage_mapping: false,
coverage_dir: None,
targets_created: 0,
instrumented_points: 0,
coverage_pct: 0.0,
}
}
pub fn full_enabled() -> Self {
Self {
enabled: true,
libfuzzer: true,
define_test_one_input: true,
define_initialize: true,
define_custom_mutator: true,
define_custom_crossover: true,
sancov_edge: true,
sancov_bb: true,
sancov_pc_table: true,
sancov_trace_pc_guard: true,
sancov_inline_8bit_counters: true,
sancov_trace_cmp: true,
sancov_trace_div: false,
sancov_trace_gep: false,
afl: true,
afl_mode: X86AFLMode::LLVM,
afl_shm_id: None,
afl_deferred: true,
afl_persistent: true,
honggfuzz: true,
honggfuzz_persistent: true,
profile_instr_generate: true,
coverage_mapping: true,
coverage_dir: None,
targets_created: 0,
instrumented_points: 0,
coverage_pct: 0.0,
}
}
pub fn libfuzzer_only() -> Self {
Self {
enabled: true,
libfuzzer: true,
define_test_one_input: true,
define_initialize: false,
define_custom_mutator: false,
define_custom_crossover: false,
sancov_edge: true,
sancov_bb: false,
sancov_pc_table: false,
sancov_trace_pc_guard: true,
sancov_inline_8bit_counters: true,
sancov_trace_cmp: false,
sancov_trace_div: false,
sancov_trace_gep: false,
afl: false,
afl_mode: X86AFLMode::LLVM,
afl_shm_id: None,
afl_deferred: false,
afl_persistent: false,
honggfuzz: false,
honggfuzz_persistent: false,
profile_instr_generate: false,
coverage_mapping: false,
coverage_dir: None,
targets_created: 0,
instrumented_points: 0,
coverage_pct: 0.0,
}
}
pub fn test_one_input_declaration(&self) -> &'static str {
r#"int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
// Fuzz target implementation
return 0;
}"#
}
pub fn initialize_declaration(&self) -> &'static str {
r#"int LLVMFuzzerInitialize(int *argc, char ***argv) {
// Initialize the fuzz target
return 0;
}"#
}
pub fn custom_mutator_declaration(&self) -> &'static str {
r#"size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
size_t MaxSize, unsigned int Seed) {
// Custom mutation logic
return Size;
}"#
}
pub fn custom_crossover_declaration(&self) -> &'static str {
r#"size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
const uint8_t *Data2, size_t Size2,
uint8_t *Out, size_t MaxOutSize,
unsigned int Seed) {
// Custom crossover logic
return 0;
}"#
}
pub fn harness_signature(&self) -> String {
let mut sig = String::new();
if self.define_initialize {
sig.push_str("LLVMFuzzerInitialize|");
}
if self.define_test_one_input {
sig.push_str("LLVMFuzzerTestOneInput");
}
if self.define_custom_mutator {
sig.push_str("|LLVMFuzzerCustomMutator");
}
if self.define_custom_crossover {
sig.push_str("|LLVMFuzzerCustomCrossOver");
}
sig
}
pub fn sancov_flags(&self) -> String {
let mut parts = Vec::new();
if self.sancov_edge {
parts.push("edge");
}
if self.sancov_bb {
parts.push("bb");
}
if self.sancov_pc_table {
parts.push("pc-table");
}
if self.sancov_trace_pc_guard {
parts.push("trace-pc-guard");
}
if self.sancov_inline_8bit_counters {
parts.push("inline-8bit-counters");
}
if self.sancov_trace_cmp {
parts.push("trace-cmp");
}
if self.sancov_trace_div {
parts.push("trace-div");
}
if self.sancov_trace_gep {
parts.push("trace-gep");
}
parts.join(",")
}
pub fn summary(&self) -> String {
let mut parts = Vec::new();
if self.libfuzzer {
parts.push("libFuzzer".into());
}
if self.afl {
parts.push(format!("AFL({})", self.afl_mode.to_clang_flag()));
}
if self.honggfuzz {
parts.push("Honggfuzz".into());
}
if !self.sancov_flags().is_empty() {
parts.push(format!("SanCov({})", self.sancov_flags()));
}
if self.profile_instr_generate {
parts.push("PGO-Instr".into());
}
parts.join("+")
}
pub fn to_clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if !self.enabled {
return flags;
}
if self.libfuzzer {
flags.push("-fsanitize=fuzzer".into());
}
if self.afl {
flags.push(format!("-fsanitize-coverage={}", self.sancov_flags()));
}
if self.honggfuzz {
flags.push("-fsanitize-coverage=trace-pc-guard,inline-8bit-counters".into());
}
if self.sancov_trace_pc_guard || self.sancov_edge {
flags.push(format!("-fsanitize-coverage={}", self.sancov_flags()));
}
if self.profile_instr_generate {
flags.push("-fprofile-instr-generate".into());
}
if self.coverage_mapping {
flags.push("-fcoverage-mapping".into());
}
flags
}
pub fn to_json(&self) -> String {
format!(
r#"{{"enabled":{},"libfuzzer":{},"afl":{},"honggfuzz":{},"sancov":{},"profile":{}}}"#,
self.enabled,
self.libfuzzer,
self.afl,
self.honggfuzz,
!self.sancov_flags().is_empty(),
self.profile_instr_generate,
)
}
}
impl Default for X86FuzzingSupport {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone)]
pub struct X86VulnerabilityCheckers {
pub buffer_overflow: bool,
pub use_after_free: bool,
pub double_free: bool,
pub format_string: bool,
pub integer_overflow: bool,
pub race_condition: bool,
pub command_injection: bool,
pub path_traversal: bool,
pub sign_conversion: bool,
pub truncation: bool,
pub memory_leak: bool,
pub uninitialized_use: bool,
pub null_deref: bool,
pub out_of_bounds: bool,
pub warnings: Vec<X86VulnWarning>,
pub total_warnings: u64,
}
#[derive(Debug, Clone)]
pub struct X86VulnWarning {
pub vuln_type: X86VulnType,
pub location: String,
pub function: String,
pub cwe_id: Option<u32>,
pub severity: X86VulnSeverity,
pub message: String,
pub suggestion: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86VulnType {
BufferOverflow,
UseAfterFree,
DoubleFree,
FormatString,
IntegerOverflow,
RaceCondition,
CommandInjection,
PathTraversal,
SignConversion,
Truncation,
MemoryLeak,
UninitializedUse,
NullDeref,
OutOfBounds,
}
impl X86VulnType {
pub fn default_cwe(&self) -> u32 {
match self {
Self::BufferOverflow => 120,
Self::UseAfterFree => 416,
Self::DoubleFree => 415,
Self::FormatString => 134,
Self::IntegerOverflow => 190,
Self::RaceCondition => 367,
Self::CommandInjection => 77,
Self::PathTraversal => 22,
Self::SignConversion => 195,
Self::Truncation => 197,
Self::MemoryLeak => 401,
Self::UninitializedUse => 457,
Self::NullDeref => 476,
Self::OutOfBounds => 125,
}
}
pub fn description(&self) -> &'static str {
match self {
Self::BufferOverflow => "Buffer overflow vulnerability",
Self::UseAfterFree => "Use-after-free vulnerability",
Self::DoubleFree => "Double-free vulnerability",
Self::FormatString => "Format string vulnerability",
Self::IntegerOverflow => "Integer overflow in security-critical context",
Self::RaceCondition => "Race condition (TOCTOU)",
Self::CommandInjection => "Command injection vulnerability",
Self::PathTraversal => "Path traversal vulnerability",
Self::SignConversion => "Signedness conversion hazard",
Self::Truncation => "Truncation hazard",
Self::MemoryLeak => "Memory leak",
Self::UninitializedUse => "Use of uninitialized variable",
Self::NullDeref => "Null pointer dereference",
Self::OutOfBounds => "Out-of-bounds array access",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86VulnSeverity {
Info,
Low,
Medium,
High,
Critical,
}
impl X86VulnSeverity {
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 DangerousFunction {
pub name: &'static str,
pub vuln_type: X86VulnType,
pub severity: X86VulnSeverity,
pub safe_alternative: &'static str,
}
pub const DANGEROUS_FUNCTIONS: &[DangerousFunction] = &[
DangerousFunction {
name: "gets",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::Critical,
safe_alternative: "fgets",
},
DangerousFunction {
name: "strcpy",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::High,
safe_alternative: "strncpy",
},
DangerousFunction {
name: "strcat",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::High,
safe_alternative: "strncat",
},
DangerousFunction {
name: "sprintf",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::High,
safe_alternative: "snprintf",
},
DangerousFunction {
name: "scanf",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::High,
safe_alternative: "scanf with field width limits",
},
DangerousFunction {
name: "vsprintf",
vuln_type: X86VulnType::BufferOverflow,
severity: X86VulnSeverity::High,
safe_alternative: "vsnprintf",
},
DangerousFunction {
name: "printf",
vuln_type: X86VulnType::FormatString,
severity: X86VulnSeverity::High,
safe_alternative: "printf(\"%s\", user_input)",
},
DangerousFunction {
name: "fprintf",
vuln_type: X86VulnType::FormatString,
severity: X86VulnSeverity::High,
safe_alternative: "fprintf(fp, \"%s\", user_input)",
},
DangerousFunction {
name: "syslog",
vuln_type: X86VulnType::FormatString,
severity: X86VulnSeverity::Medium,
safe_alternative: "syslog(priority, \"%s\", user_input)",
},
DangerousFunction {
name: "system",
vuln_type: X86VulnType::CommandInjection,
severity: X86VulnSeverity::Critical,
safe_alternative: "execvp or fork+execve",
},
DangerousFunction {
name: "popen",
vuln_type: X86VulnType::CommandInjection,
severity: X86VulnSeverity::High,
safe_alternative: "fork+execve with pipe",
},
DangerousFunction {
name: "execl",
vuln_type: X86VulnType::CommandInjection,
severity: X86VulnSeverity::Medium,
safe_alternative: "execve with sanitized args",
},
DangerousFunction {
name: "execlp",
vuln_type: X86VulnType::CommandInjection,
severity: X86VulnSeverity::Medium,
safe_alternative: "execve with full path",
},
DangerousFunction {
name: "open",
vuln_type: X86VulnType::PathTraversal,
severity: X86VulnSeverity::Medium,
safe_alternative: "openat with AT_FDCWD after path canonicalization",
},
DangerousFunction {
name: "fopen",
vuln_type: X86VulnType::PathTraversal,
severity: X86VulnSeverity::Medium,
safe_alternative: "Validate path before fopen",
},
DangerousFunction {
name: "free",
vuln_type: X86VulnType::DoubleFree,
severity: X86VulnSeverity::Critical,
safe_alternative: "Set pointer to NULL after free",
},
DangerousFunction {
name: "malloc",
vuln_type: X86VulnType::IntegerOverflow,
severity: X86VulnSeverity::Medium,
safe_alternative: "calloc or check for overflow",
},
];
impl X86VulnerabilityCheckers {
pub fn all_enabled() -> Self {
Self {
buffer_overflow: true,
use_after_free: true,
double_free: true,
format_string: true,
integer_overflow: true,
race_condition: true,
command_injection: true,
path_traversal: true,
sign_conversion: true,
truncation: true,
memory_leak: true,
uninitialized_use: true,
null_deref: true,
out_of_bounds: true,
warnings: Vec::new(),
total_warnings: 0,
}
}
pub fn none() -> Self {
Self {
buffer_overflow: false,
use_after_free: false,
double_free: false,
format_string: false,
integer_overflow: false,
race_condition: false,
command_injection: false,
path_traversal: false,
sign_conversion: false,
truncation: false,
memory_leak: false,
uninitialized_use: false,
null_deref: false,
out_of_bounds: false,
warnings: Vec::new(),
total_warnings: 0,
}
}
pub fn production() -> Self {
Self {
buffer_overflow: true,
use_after_free: true,
double_free: true,
format_string: true,
integer_overflow: true,
race_condition: false,
command_injection: true,
path_traversal: false,
sign_conversion: true,
truncation: true,
memory_leak: false,
uninitialized_use: true,
null_deref: true,
out_of_bounds: true,
warnings: Vec::new(),
total_warnings: 0,
}
}
pub fn has_any(&self) -> bool {
self.buffer_overflow
|| self.use_after_free
|| self.double_free
|| self.format_string
|| self.integer_overflow
|| self.race_condition
|| self.command_injection
|| self.path_traversal
}
pub fn enabled_count(&self) -> usize {
let mut count = 0;
if self.buffer_overflow {
count += 1;
}
if self.use_after_free {
count += 1;
}
if self.double_free {
count += 1;
}
if self.format_string {
count += 1;
}
if self.integer_overflow {
count += 1;
}
if self.race_condition {
count += 1;
}
if self.command_injection {
count += 1;
}
if self.path_traversal {
count += 1;
}
if self.sign_conversion {
count += 1;
}
if self.truncation {
count += 1;
}
if self.memory_leak {
count += 1;
}
if self.uninitialized_use {
count += 1;
}
if self.null_deref {
count += 1;
}
if self.out_of_bounds {
count += 1;
}
count
}
pub fn check_function_call(
&mut self,
func_name: &str,
location: &str,
) -> Option<X86VulnWarning> {
if let Some(dangerous) = DANGEROUS_FUNCTIONS.iter().find(|df| df.name == func_name) {
if !self.checker_enabled(dangerous.vuln_type) {
return None;
}
let warning = X86VulnWarning {
vuln_type: dangerous.vuln_type,
location: location.to_string(),
function: func_name.to_string(),
cwe_id: Some(dangerous.vuln_type.default_cwe()),
severity: dangerous.severity,
message: format!(
"Call to '{}': {}. Use '{}' instead.",
func_name,
dangerous.vuln_type.description(),
dangerous.safe_alternative,
),
suggestion: Some(format!(
"Replace '{}' with '{}'",
func_name, dangerous.safe_alternative,
)),
};
self.warnings.push(warning.clone());
self.total_warnings += 1;
Some(warning)
} else {
None
}
}
pub fn check_buffer_overflow(
&mut self,
dest_size: u64,
copy_size: u64,
location: &str,
) -> Option<X86VulnWarning> {
if !self.buffer_overflow {
return None;
}
if copy_size > dest_size {
let warning = X86VulnWarning {
vuln_type: X86VulnType::BufferOverflow,
location: location.to_string(),
function: "memcpy".to_string(),
cwe_id: Some(120),
severity: X86VulnSeverity::High,
message: format!(
"Buffer overflow: destination size {} is smaller than copy size {}",
dest_size, copy_size,
),
suggestion: Some("Use memcpy_s or bounds-checking wrapper".into()),
};
self.warnings.push(warning.clone());
self.total_warnings += 1;
Some(warning)
} else {
None
}
}
pub fn check_integer_overflow(
&mut self,
a: i64,
b: i64,
op: &str,
location: &str,
) -> Option<X86VulnWarning> {
if !self.integer_overflow {
return None;
}
let overflow = match op {
"+" => a.overflowing_add(b).1,
"-" => a.overflowing_sub(b).1,
"*" => a.overflowing_mul(b).1,
_ => false,
};
if overflow {
let warning = X86VulnWarning {
vuln_type: X86VulnType::IntegerOverflow,
location: location.to_string(),
function: format!("{}{}{}", a, op, b),
cwe_id: Some(190),
severity: X86VulnSeverity::Medium,
message: format!("Integer overflow in expression: {} {} {}", a, op, b,),
suggestion: Some(
"Use checked arithmetic (__builtin_add_overflow) or saturating operations"
.into(),
),
};
self.warnings.push(warning.clone());
self.total_warnings += 1;
Some(warning)
} else {
None
}
}
pub fn check_use_after_free(
&mut self,
ptr_name: &str,
freed: bool,
location: &str,
) -> Option<X86VulnWarning> {
if !self.use_after_free || !freed {
return None;
}
let warning = X86VulnWarning {
vuln_type: X86VulnType::UseAfterFree,
location: location.to_string(),
function: ptr_name.to_string(),
cwe_id: Some(416),
severity: X86VulnSeverity::Critical,
message: format!(
"Use-after-free: pointer '{}' accessed after free()",
ptr_name
),
suggestion: Some("Set pointer to NULL after free() and check before use".into()),
};
self.warnings.push(warning.clone());
self.total_warnings += 1;
Some(warning)
}
pub fn check_double_free(
&mut self,
ptr_name: &str,
already_freed: bool,
location: &str,
) -> Option<X86VulnWarning> {
if !self.double_free || !already_freed {
return None;
}
let warning = X86VulnWarning {
vuln_type: X86VulnType::DoubleFree,
location: location.to_string(),
function: ptr_name.to_string(),
cwe_id: Some(415),
severity: X86VulnSeverity::Critical,
message: format!("Double-free: pointer '{}' freed more than once", ptr_name),
suggestion: Some("Set pointer to NULL after free()".into()),
};
self.warnings.push(warning.clone());
self.total_warnings += 1;
Some(warning)
}
fn checker_enabled(&self, vuln_type: X86VulnType) -> bool {
match vuln_type {
X86VulnType::BufferOverflow => self.buffer_overflow,
X86VulnType::UseAfterFree => self.use_after_free,
X86VulnType::DoubleFree => self.double_free,
X86VulnType::FormatString => self.format_string,
X86VulnType::IntegerOverflow => self.integer_overflow,
X86VulnType::RaceCondition => self.race_condition,
X86VulnType::CommandInjection => self.command_injection,
X86VulnType::PathTraversal => self.path_traversal,
X86VulnType::SignConversion => self.sign_conversion,
X86VulnType::Truncation => self.truncation,
X86VulnType::MemoryLeak => self.memory_leak,
X86VulnType::UninitializedUse => self.uninitialized_use,
X86VulnType::NullDeref => self.null_deref,
X86VulnType::OutOfBounds => self.out_of_bounds,
}
}
pub fn warnings_by_severity(&self) -> BTreeMap<X86VulnSeverity, Vec<&X86VulnWarning>> {
let mut map: BTreeMap<X86VulnSeverity, Vec<&X86VulnWarning>> = BTreeMap::new();
for w in &self.warnings {
map.entry(w.severity).or_default().push(w);
}
map
}
pub fn generate_csv_report(&self) -> String {
let mut report =
String::from("cwe_id,severity,type,location,function,message,suggestion\n");
for w in &self.warnings {
report.push_str(&format!(
"{},{},{},{},{},{},{}\n",
w.cwe_id.map(|c| c.to_string()).unwrap_or_default(),
w.severity.as_str(),
format!("{:?}", w.vuln_type),
w.location,
w.function,
w.message,
w.suggestion.as_deref().unwrap_or(""),
));
}
report
}
pub fn generate_sarif_report(&self) -> String {
let mut results_json = String::new();
for (i, w) in self.warnings.iter().enumerate() {
if i > 0 {
results_json.push(',');
}
results_json.push_str(&format!(
r#"{{"ruleId":"CWE-{}","level":"{}","message":{{"text":"{}"}},"locations":[{{"physicalLocation":{{"artifactLocation":{{"uri":"{}"}}}}}}]}}"#,
w.cwe_id.unwrap_or(0),
w.severity.as_str().to_lowercase(),
w.message,
w.location,
));
}
format!(
r#"{{"version":"2.1.0","$schema":"https://json.schemastore.org/sarif-2.1.0.json","runs":[{{"tool":{{"driver":{{"name":"X86VulnerabilityCheckers"}}}},"results":[{}]}}]}}"#,
results_json,
)
}
pub fn to_json(&self) -> String {
format!(
r#"{{"buffer_overflow":{},"use_after_free":{},"double_free":{},"format_string":{},"int_overflow":{},"race_condition":{},"cmd_injection":{},"path_traversal":{},"total_warnings":{}}}"#,
self.buffer_overflow,
self.use_after_free,
self.double_free,
self.format_string,
self.integer_overflow,
self.race_condition,
self.command_injection,
self.path_traversal,
self.total_warnings,
)
}
}
impl Default for X86VulnerabilityCheckers {
fn default() -> Self {
Self::all_enabled()
}
}
#[derive(Debug, Clone)]
pub struct X86SecureCoding {
pub cert_c: bool,
pub cert_cpp: bool,
pub misra_c: bool,
pub misra_cpp: bool,
pub autosar_cpp14: bool,
pub cwe_mapping: bool,
pub violations: Vec<X86CodingViolation>,
pub total_violations: u64,
}
#[derive(Debug, Clone)]
pub struct X86CodingViolation {
pub standard: X86CodingStandard,
pub rule_id: String,
pub rule_description: String,
pub location: String,
pub severity: X86VulnSeverity,
pub cwe: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CodingStandard {
CERT_C,
CERT_CPP,
MISRA_C_2012,
MISRA_CPP_2008,
AUTOSAR_CPP14,
}
impl X86CodingStandard {
pub fn name(&self) -> &'static str {
match self {
Self::CERT_C => "CERT C",
Self::CERT_CPP => "CERT C++",
Self::MISRA_C_2012 => "MISRA C:2012",
Self::MISRA_CPP_2008 => "MISRA C++:2008",
Self::AUTOSAR_CPP14 => "AUTOSAR C++14",
}
}
}
pub mod cert_c_rules {
pub const EXP33_C: &str = "EXP33-C: Do not read uninitialized memory";
pub const EXP34_C: &str = "EXP34-C: Do not dereference null pointers";
pub const INT30_C: &str = "INT30-C: Ensure unsigned int operations do not wrap";
pub const INT32_C: &str = "INT32-C: Ensure operations on signed ints do not overflow";
pub const STR31_C: &str = "STR31-C: Guarantee adequate space for character data";
pub const STR32_C: &str = "STR32-C: Do not pass non-null-terminated strings to library fns";
pub const MEM30_C: &str = "MEM30-C: Do not access freed memory";
pub const MEM31_C: &str = "MEM31-C: Free dynamically allocated memory when no longer needed";
pub const MEM34_C: &str = "MEM34-C: Only free memory allocated dynamically";
pub const FIO30_C: &str = "FIO30-C: Exclude user input from format strings";
pub const ENV33_C: &str = "ENV33-C: Do not call system()";
pub const ARR30_C: &str = "ARR30-C: Do not form or use out-of-bounds pointers or subscripts";
pub const MSC30_C: &str = "MSC30-C: Do not use the rand() function";
}
pub mod cert_cpp_rules {
pub const CTR50_CPP: &str = "CTR50-CPP: Guarantee container has elements before dereferencing";
pub const CTR51_CPP: &str = "CTR51-CPP: Use valid references/pointers/iterators";
pub const EXP50_CPP: &str = "EXP50-CPP: Do not depend on order of evaluation";
pub const EXP53_CPP: &str = "EXP53-CPP: Do not read uninitialized memory";
pub const MEM50_CPP: &str = "MEM50-CPP: Do not access freed memory";
pub const MEM51_CPP: &str = "MEM51-CPP: Properly deallocate dynamically allocated resources";
pub const OOP50_CPP: &str = "OOP50-CPP: Do not invoke virtual functions from constructors";
pub const STR50_CPP: &str = "STR50-CPP: Guarantee storage for strings has sufficient space";
pub const STR51_CPP: &str = "STR51-CPP: Do not attempt to create a std::string from null ptr";
}
pub mod misra_c_2012_rules {
pub const RULE_1_3: &str =
"Rule 1.3: No occurrence of undefined or critical unspecified behavior";
pub const RULE_8_1: &str = "Rule 8.1: Types shall be explicitly specified";
pub const RULE_8_4: &str =
"Rule 8.4: Compatible declarations for objects/functions with external linkage";
pub const RULE_10_1: &str =
"Rule 10.1: Operands shall not be of an inappropriate essential type";
pub const RULE_10_4: &str =
"Rule 10.4: Both operands of an operator shall have same essential type";
pub const RULE_11_3: &str =
"Rule 11.3: Cast between pointer to object and pointer to object with different alignment";
pub const RULE_12_1: &str = "Rule 12.1: Operator precedence rules for expressions";
pub const RULE_13_2: &str =
"Rule 13.2: Value of expression and its persistent side effects shall be the same";
pub const RULE_17_2: &str =
"Rule 17.2: Functions shall not call themselves directly or indirectly";
pub const RULE_17_7: &str = "Rule 17.7: Value returned by a function shall be used";
pub const RULE_18_1: &str =
"Rule 18.1: Pointer resulting from arithmetic on pointer operand shall address same array";
pub const RULE_20_1: &str = "Rule 20.1: #include directives shall only be preceded by preprocessor directives or comments";
pub const RULE_21_1: &str =
"Rule 21.1: #define and #undef shall not be used on a reserved identifier";
pub const RULE_21_6: &str =
"Rule 21.6: Standard library input/output functions shall not be used";
pub const RULE_21_7: &str =
"Rule 21.7: The atof, atoi, atol and atoll functions shall not be used";
pub const RULE_21_8: &str =
"Rule 21.8: Library functions abort, exit, getenv and system shall not be used";
pub const RULE_22_1: &str =
"Rule 22.1: All resources obtained from Standard Library shall be released";
pub const RULE_22_2: &str =
"Rule 22.2: Block of memory shall be freed using correct deallocation function";
pub const RULE_22_4: &str = "Rule 22.4: Only read from a file opened for reading";
pub const RULE_22_5: &str = "Rule 22.5: Pointer to FILE shall not be dereferenced";
}
impl X86SecureCoding {
pub fn new(
cert_c: bool,
cert_cpp: bool,
misra_c: bool,
misra_cpp: bool,
autosar_cpp14: bool,
cwe_mapping: bool,
) -> Self {
Self {
cert_c,
cert_cpp,
misra_c,
misra_cpp,
autosar_cpp14,
cwe_mapping,
violations: Vec::new(),
total_violations: 0,
}
}
pub fn none() -> Self {
Self::new(false, false, false, false, false, false)
}
pub fn all() -> Self {
Self::new(true, true, true, true, true, true)
}
pub fn has_any(&self) -> bool {
self.cert_c || self.cert_cpp || self.misra_c || self.misra_cpp || self.autosar_cpp14
}
pub fn enabled_rule_count(&self) -> usize {
let mut count = 0;
if self.cert_c {
count += 1;
}
if self.cert_cpp {
count += 1;
}
if self.misra_c {
count += 1;
}
if self.misra_cpp {
count += 1;
}
if self.autosar_cpp14 {
count += 1;
}
count
}
pub fn add_violation(
&mut self,
standard: X86CodingStandard,
rule_id: &str,
description: &str,
location: &str,
severity: X86VulnSeverity,
cwe: Option<u32>,
) {
self.violations.push(X86CodingViolation {
standard,
rule_id: rule_id.to_string(),
rule_description: description.to_string(),
location: location.to_string(),
severity,
cwe,
});
self.total_violations += 1;
}
pub fn cwe_to_cert_c(cwe_id: u32) -> Vec<&'static str> {
match cwe_id {
120 => vec![cert_c_rules::STR31_C, cert_c_rules::ARR30_C],
416 => vec![cert_c_rules::MEM30_C],
415 => vec![cert_c_rules::MEM34_C],
134 => vec![cert_c_rules::FIO30_C],
190 => vec![cert_c_rules::INT32_C],
77 => vec![cert_c_rules::ENV33_C],
476 => vec![cert_c_rules::EXP33_C],
457 => vec![cert_c_rules::EXP33_C],
_ => vec![],
}
}
pub fn cwe_to_cert_cpp(cwe_id: u32) -> Vec<&'static str> {
match cwe_id {
120 => vec![cert_cpp_rules::STR50_CPP],
416 => vec![cert_cpp_rules::MEM50_CPP],
415 => vec![cert_cpp_rules::MEM51_CPP],
476 => vec![cert_cpp_rules::EXP53_CPP],
457 => vec![cert_cpp_rules::EXP53_CPP],
_ => vec![],
}
}
pub fn get_rules(standard: X86CodingStandard) -> Vec<(&'static str, &'static str)> {
match standard {
X86CodingStandard::CERT_C => vec![
("EXP33-C", cert_c_rules::EXP33_C),
("EXP34-C", cert_c_rules::EXP34_C),
("INT30-C", cert_c_rules::INT30_C),
("INT32-C", cert_c_rules::INT32_C),
("STR31-C", cert_c_rules::STR31_C),
("STR32-C", cert_c_rules::STR32_C),
("MEM30-C", cert_c_rules::MEM30_C),
("MEM31-C", cert_c_rules::MEM31_C),
("MEM34-C", cert_c_rules::MEM34_C),
("FIO30-C", cert_c_rules::FIO30_C),
("ENV33-C", cert_c_rules::ENV33_C),
("ARR30-C", cert_c_rules::ARR30_C),
("MSC30-C", cert_c_rules::MSC30_C),
],
X86CodingStandard::CERT_CPP => vec![
("CTR50-CPP", cert_cpp_rules::CTR50_CPP),
("CTR51-CPP", cert_cpp_rules::CTR51_CPP),
("EXP50-CPP", cert_cpp_rules::EXP50_CPP),
("EXP53-CPP", cert_cpp_rules::EXP53_CPP),
("MEM50-CPP", cert_cpp_rules::MEM50_CPP),
("MEM51-CPP", cert_cpp_rules::MEM51_CPP),
("OOP50-CPP", cert_cpp_rules::OOP50_CPP),
("STR50-CPP", cert_cpp_rules::STR50_CPP),
("STR51-CPP", cert_cpp_rules::STR51_CPP),
],
X86CodingStandard::MISRA_C_2012 => vec![
("Rule 1.3", misra_c_2012_rules::RULE_1_3),
("Rule 8.1", misra_c_2012_rules::RULE_8_1),
("Rule 8.4", misra_c_2012_rules::RULE_8_4),
("Rule 10.1", misra_c_2012_rules::RULE_10_1),
("Rule 10.4", misra_c_2012_rules::RULE_10_4),
("Rule 11.3", misra_c_2012_rules::RULE_11_3),
("Rule 12.1", misra_c_2012_rules::RULE_12_1),
("Rule 13.2", misra_c_2012_rules::RULE_13_2),
("Rule 17.2", misra_c_2012_rules::RULE_17_2),
("Rule 17.7", misra_c_2012_rules::RULE_17_7),
("Rule 18.1", misra_c_2012_rules::RULE_18_1),
("Rule 20.1", misra_c_2012_rules::RULE_20_1),
("Rule 21.6", misra_c_2012_rules::RULE_21_6),
("Rule 21.8", misra_c_2012_rules::RULE_21_8),
("Rule 22.1", misra_c_2012_rules::RULE_22_1),
("Rule 22.2", misra_c_2012_rules::RULE_22_2),
],
_ => vec![],
}
}
pub fn to_json(&self) -> String {
format!(
r#"{{"cert_c":{},"cert_cpp":{},"misra_c":{},"misra_cpp":{},"autosar":{},"cwe":{},"violations":{}}}"#,
self.cert_c,
self.cert_cpp,
self.misra_c,
self.misra_cpp,
self.autosar_cpp14,
self.cwe_mapping,
self.total_violations,
)
}
}
impl Default for X86SecureCoding {
fn default() -> Self {
Self::new(true, true, false, false, false, true)
}
}
#[derive(Debug, Clone)]
pub struct X86CryptoIntrinsics {
pub aes_ni: bool,
pub sha_ni: bool,
pub clmul: bool,
pub rdrand: bool,
pub rdseed: bool,
pub crc32: bool,
aes_key_cache: HashMap<Vec<u8>, Vec<Vec<u8>>>,
}
pub mod aes_ni {
pub fn aesenc(state: &[u8; 16], round_key: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
for i in 0..16 {
result[i] = state[i] ^ round_key[i];
}
result
}
pub fn aesenclast(state: &[u8; 16], round_key: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
for i in 0..16 {
result[i] = state[i] ^ round_key[i];
}
result
}
pub fn aesdec(state: &[u8; 16], round_key: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
for i in 0..16 {
result[i] = state[i] ^ round_key[i];
}
result
}
pub fn aesdeclast(state: &[u8; 16], round_key: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
for i in 0..16 {
result[i] = state[i] ^ round_key[i];
}
result
}
pub fn aesimc(round_key: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
result.copy_from_slice(round_key);
result
}
pub fn aeskeygenassist(key: &[u8; 16], rcon: u8) -> [u8; 16] {
let mut result = [0u8; 16];
for i in 0..16 {
result[i] = key[i].wrapping_add(rcon);
}
result
}
pub fn aes128_encrypt(plaintext: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
let rounds = super::X86_AES128_ROUNDS;
let round_keys = generate_round_keys(key, rounds);
let mut state = *plaintext;
for i in 0..16 {
state[i] ^= round_keys[0][i];
}
for r in 1..rounds {
state = aesenc(&state, &round_keys[r]);
}
aesenclast(&state, &round_keys[rounds])
}
pub fn aes256_encrypt(plaintext: &[u8; 16], key: &[u8; 32]) -> [u8; 16] {
let rounds = super::X86_AES256_ROUNDS;
let round_keys = generate_round_keys_256(key);
let mut state = *plaintext;
for i in 0..16 {
state[i] ^= round_keys[0][i];
}
for r in 1..rounds {
state = aesenc(&state, &round_keys[r]);
}
aesenclast(&state, &round_keys[rounds])
}
fn generate_round_keys(key: &[u8; 16], num_rounds: usize) -> Vec<[u8; 16]> {
let mut rk = Vec::with_capacity(num_rounds + 1);
rk.push(*key);
for r in 0..num_rounds {
let prev = rk[r];
let next = aeskeygenassist(&prev, (r + 1) as u8);
rk.push(next);
}
rk
}
fn generate_round_keys_256(key: &[u8; 32]) -> Vec<[u8; 16]> {
let mut rk = Vec::new();
let k0: [u8; 16] = key[0..16].try_into().unwrap();
let k1: [u8; 16] = key[16..32].try_into().unwrap();
rk.push(k0);
rk.push(k1);
for _ in 2..super::X86_AES256_ROUNDS + 1 {
rk.push(rk[0]);
}
rk
}
pub const SBOX: [u8; 256] = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB,
0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4,
0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71,
0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6,
0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB,
0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45,
0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44,
0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A,
0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49,
0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25,
0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E,
0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1,
0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB,
0x16,
];
}
pub mod sha_ni {
pub type Sha1State = [u32; 5];
pub type Sha256State = [u32; 8];
pub fn sha1rnds4(state: &Sha1State, wk: &[u32; 4], rounds: u8) -> Sha1State {
let mut s = *state;
for _ in 0..4 {
let f = (s[1] & s[2]) | (!s[1] & s[3]);
let temp = s[0]
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(s[4])
.wrapping_add(wk[0])
.wrapping_add(0x5A827999); s[4] = s[3];
s[3] = s[2];
s[2] = s[1].rotate_left(30);
s[1] = s[0];
s[0] = temp;
}
s
}
pub fn sha1nexte(wk: &[u32; 4], msg: &[u32; 4]) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
result[i] = wk[i].rotate_left(1) ^ msg[i];
}
result
}
pub fn sha1msg1(w0: &[u32; 4], w1: &[u32; 4]) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
result[i] = w0[i] ^ w1[i];
}
result
}
pub fn sha1msg2(w0: &[u32; 4], w1: &[u32; 4]) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
result[i] = w0[i].rotate_left(1) ^ w1[i];
}
result
}
pub fn sha256rnds2(state: &Sha256State, wk: &[u32; 4]) -> Sha256State {
let mut s = *state;
for _ in 0..2 {
let s1 = s[4].rotate_right(6) ^ s[4].rotate_right(11) ^ s[4].rotate_right(25);
let ch = (s[4] & s[5]) ^ (!s[4] & s[6]);
let temp1 = s[7]
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(wk[0])
.wrapping_add(0x428A2F98);
let s0 = s[0].rotate_right(2) ^ s[0].rotate_right(13) ^ s[0].rotate_right(22);
let maj = (s[0] & s[1]) ^ (s[0] & s[2]) ^ (s[1] & s[2]);
let temp2 = s0.wrapping_add(maj);
s[7] = s[6];
s[6] = s[5];
s[5] = s[4];
s[4] = s[3].wrapping_add(temp1);
s[3] = s[2];
s[2] = s[1];
s[1] = s[0];
s[0] = temp1.wrapping_add(temp2);
}
s
}
pub fn sha256msg1(w0: &[u32; 4], w1: &[u32; 4]) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
let s0 = w0[i].rotate_right(7) ^ w0[i].rotate_right(18) ^ (w0[i] >> 3);
result[i] = w0[i].wrapping_add(s0).wrapping_add(w1[i]);
}
result
}
pub fn sha256msg2(w0: &[u32; 4], w1: &[u32; 4]) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
let s1 = w0[i].rotate_right(17) ^ w0[i].rotate_right(19) ^ (w0[i] >> 10);
result[i] = w0[i].wrapping_add(s1).wrapping_add(w1[i]);
}
result
}
pub const SHA256_INIT: Sha256State = [
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB,
0x5BE0CD19,
];
pub const SHA256_K: [u32; 64] = [
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4,
0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE,
0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F,
0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC,
0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B,
0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116,
0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7,
0xC67178F2,
];
}
pub mod clmul {
pub fn pclmulqdq(a: u64, b: u64) -> u128 {
let mut result: u128 = 0;
let mut a_ext: u128 = a as u128;
let mut b_ext: u128 = b as u128;
for i in 0..64 {
if (b_ext & 1) != 0 {
result ^= a_ext << i;
}
b_ext >>= 1;
}
result
}
pub fn pclmulqdq_imm(a: u64, b: u64, imm: u8) -> u128 {
let a_high = (imm & 0x01) != 0;
let b_high = (imm & 0x10) != 0;
let a_val = if a_high { a >> 32 } else { a & 0xFFFFFFFF };
let b_val = if b_high { b >> 32 } else { b & 0xFFFFFFFF };
pclmulqdq(a_val, b_val)
}
pub fn ghash_mul(h: u128, x: u128) -> u128 {
let h_lo = (h & 0xFFFFFFFFFFFFFFFF) as u64;
let h_hi = (h >> 64) as u64;
let x_lo = (x & 0xFFFFFFFFFFFFFFFF) as u64;
let x_hi = (x >> 64) as u64;
let m_ll = pclmulqdq(h_lo, x_lo);
let m_lh = pclmulqdq(h_lo, x_hi);
let m_hl = pclmulqdq(h_hi, x_lo);
let m_hh = pclmulqdq(h_hi, x_hi);
let mid = m_lh ^ m_hl;
let lo = m_ll ^ (mid << 64);
let hi = m_hh ^ (mid >> 64);
let r = super::X86_PCLMUL_POLY as u128;
let mut result = lo ^ hi;
for _ in 0..64 {
if (result >> 127) != 0 {
result = (result << 1) ^ r;
} else {
result <<= 1;
}
}
result
}
pub const GHASH_POLY: u128 = 0xE1000000000000000000000000000000;
}
pub mod rdrand {
use std::time::{SystemTime, UNIX_EPOCH};
pub fn rdrand64() -> Option<u64> {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let val = (nanos as u64)
.wrapping_mul(0x9E3779B97F4A7C15)
.rotate_left(17)
^ 0xBF58476D1CE4E5B9;
Some(val)
}
pub fn rdrand32() -> Option<u32> {
rdrand64().map(|v| v as u32)
}
pub fn rdrand16() -> Option<u16> {
rdrand64().map(|v| v as u16)
}
pub fn rdseed64() -> Option<u64> {
rdrand64().map(|v| v ^ 0xA5A5A5A5A5A5A5A5)
}
pub fn rdseed32() -> Option<u32> {
rdseed64().map(|v| v as u32)
}
pub fn rdrand_fill(buf: &mut [u8]) -> bool {
let mut i = 0;
while i + 8 <= buf.len() {
if let Some(val) = rdrand64() {
buf[i..i + 8].copy_from_slice(&val.to_le_bytes());
i += 8;
} else {
return false;
}
}
if i < buf.len() {
if let Some(val) = rdrand64() {
let bytes = val.to_le_bytes();
for j in 0..(buf.len() - i) {
buf[i + j] = bytes[j];
}
} else {
return false;
}
}
true
}
}
pub mod crc32_intrinsics {
pub const CRC32C_TABLE: [u32; 256] = {
let mut table = [0u32; 256];
let polynomial: u32 = 0x1EDC6F41;
let mut i = 0;
while i < 256 {
let mut crc = i as u32;
let mut j = 0;
while j < 8 {
if crc & 1 != 0 {
crc = polynomial ^ (crc >> 1);
} else {
crc >>= 1;
}
j += 1;
}
table[i] = crc;
i += 1;
}
table
};
pub fn crc32b(crc: u32, byte: u8) -> u32 {
let idx = ((crc as u8) ^ byte) as usize;
(crc >> 8) ^ CRC32C_TABLE[idx]
}
pub fn crc32w(crc: u32, word: u16) -> u32 {
let mut c = crc;
c = crc32b(c, word as u8);
c = crc32b(c, (word >> 8) as u8);
c
}
pub fn crc32l(crc: u32, dword: u32) -> u32 {
let mut c = crc;
c = crc32w(c, dword as u16);
c = crc32w(c, (dword >> 16) as u16);
c
}
pub fn crc32q(crc: u64, qword: u64) -> u64 {
let mut c = crc;
c = crc32l(c as u32, qword as u32) as u64;
c = crc32l(c as u32, (qword >> 32) as u32) as u64;
c
}
pub fn crc32c_slice(crc: u32, data: &[u8]) -> u32 {
let mut c = crc;
for &byte in data {
c = crc32b(c, byte);
}
c
}
pub fn crc32_ieee(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFFFFFF;
for &byte in data {
crc ^= byte as u32;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
!crc
}
}
impl X86CryptoIntrinsics {
pub fn default() -> Self {
Self {
aes_ni: true,
sha_ni: true,
clmul: true,
rdrand: true,
rdseed: true,
crc32: true,
aes_key_cache: HashMap::new(),
}
}
pub fn none() -> Self {
Self {
aes_ni: false,
sha_ni: false,
clmul: false,
rdrand: false,
rdseed: false,
crc32: false,
aes_key_cache: HashMap::new(),
}
}
pub fn to_target_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.aes_ni {
flags.push("-maes".into());
}
if self.sha_ni {
flags.push("-msha".into());
}
if self.clmul {
flags.push("-mpclmul".into());
}
if self.rdrand {
flags.push("-mrdrnd".into());
}
if self.rdseed {
flags.push("-mrdseed".into());
}
if self.crc32 {
flags.push("-mcrc32".into());
}
flags
}
pub fn detect_cpuid() -> Self {
let features = X86CryptoIntrinsics::default();
features
}
pub fn cache_aes_key(&mut self, key: &[u8], rounds: usize) {
let round_keys = vec![vec![0u8; 16]; rounds + 1];
self.aes_key_cache.insert(key.to_vec(), round_keys);
}
pub fn get_aes_key(&self, key: &[u8]) -> Option<&Vec<Vec<u8>>> {
self.aes_key_cache.get(key)
}
pub fn aes_ctr_encrypt(&self, key: &[u8; 16], nonce: &[u8; 12], plaintext: &[u8]) -> Vec<u8> {
if !self.aes_ni {
return plaintext.to_vec();
}
let mut ciphertext = Vec::with_capacity(plaintext.len());
let mut counter = 0u32;
let mut ctr_block = [0u8; 16];
ctr_block[..12].copy_from_slice(nonce);
for chunk in plaintext.chunks(16) {
counter += 1;
ctr_block[12..16].copy_from_slice(&counter.to_be_bytes());
let keystream = aes_ni::aes128_encrypt(&ctr_block, key);
for (i, &byte) in chunk.iter().enumerate() {
ciphertext.push(byte ^ keystream[i]);
}
}
ciphertext
}
pub fn aes_gcm_encrypt(
&self,
key: &[u8; 16],
nonce: &[u8; 12],
plaintext: &[u8],
aad: &[u8],
) -> (Vec<u8>, [u8; 16]) {
let ciphertext = self.aes_ctr_encrypt(key, nonce, plaintext);
let mut h = [0u8; 16];
h[..16].copy_from_slice(&aes_ni::aes128_encrypt(&[0u8; 16], key));
let tag = [0u8; 16]; (ciphertext, tag)
}
}
#[derive(Debug, Clone)]
pub struct X86SecurityBuilder {
arch: X86ArchVariant,
security: X86Security,
}
impl X86SecurityBuilder {
pub fn new(arch: X86ArchVariant) -> Self {
Self {
arch,
security: X86Security::new(arch),
}
}
pub fn with_asan(mut self, enabled: bool) -> Self {
self.security.sanitizers.asan.enabled = enabled;
if enabled {
self.security.sanitizers.asan = X86AddressSanitizer::enabled_default();
self.security.sanitizers.asan.set_arch(self.arch);
}
self
}
pub fn with_msan(mut self, enabled: bool) -> Self {
self.security.sanitizers.msan.enabled = enabled;
if enabled {
self.security.sanitizers.msan = X86MemorySanitizer::enabled_default();
}
self
}
pub fn with_tsan(mut self, enabled: bool) -> Self {
self.security.sanitizers.tsan.enabled = enabled;
if enabled {
self.security.sanitizers.tsan = X86ThreadSanitizer::enabled_default();
}
self
}
pub fn with_ubsan(mut self, enabled: bool) -> Self {
self.security.sanitizers.ubsan.enabled = enabled;
if enabled {
self.security.sanitizers.ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
}
self
}
pub fn with_lsan(mut self, enabled: bool) -> Self {
self.security.sanitizers.lsan.enabled = enabled;
if enabled {
self.security.sanitizers.lsan = X86LeakSanitizer::enabled_default();
}
self
}
pub fn with_dfsan(mut self, enabled: bool) -> Self {
self.security.sanitizers.dfsan.enabled = enabled;
if enabled {
self.security.sanitizers.dfsan = X86DataFlowSanitizer::enabled_default();
}
self
}
pub fn with_hwasan(mut self, enabled: bool) -> Self {
self.security.sanitizers.hwasan.enabled = enabled;
if enabled {
self.security.sanitizers.hwasan = X86HWAddressSanitizer::x86_stub();
}
self
}
pub fn with_safe_stack(mut self, enabled: bool) -> Self {
self.security.sanitizers.safe_stack.enabled = enabled;
self
}
pub fn with_shadow_call_stack(mut self, enabled: bool) -> Self {
self.security.sanitizers.shadow_call_stack.enabled = enabled;
self
}
pub fn with_cfi(mut self, enabled: bool) -> Self {
self.security.sanitizers.cfi.enabled = enabled;
if enabled {
self.security.sanitizers.cfi = X86CFIIntegration::full();
}
self
}
pub fn with_kcfi(mut self, enabled: bool) -> Self {
self.security.sanitizers.kcfi.enabled = enabled;
if enabled {
self.security.sanitizers.kcfi = X86KCFIIntegration::kernel_module();
}
self
}
pub fn with_stack_protector(mut self, level: &str) -> Self {
match level {
"strong" => {
self.security.mitigations.stack_protector_strong = true;
self.security.mitigations.stack_protector_all = false;
}
"all" => {
self.security.mitigations.stack_protector_all = true;
self.security.mitigations.stack_protector_strong = false;
}
_ => {
self.security.mitigations.stack_protector = true;
}
}
self
}
pub fn with_cet(mut self, level: &str) -> Self {
match level {
"full" => {
self.security.mitigations.cet_full = true;
self.security.mitigations.cet_ibt = true;
self.security.mitigations.cet_shstk = true;
}
"branch" => {
self.security.mitigations.cet_ibt = true;
}
"return" => {
self.security.mitigations.cet_shstk = true;
}
_ => {}
}
self
}
pub fn with_fuzzing(mut self, enabled: bool) -> Self {
self.security.fuzzing.enabled = enabled;
if enabled {
self.security.fuzzing = X86FuzzingSupport::libfuzzer_only();
}
self
}
pub fn with_report_format(mut self, format: SecurityReportFormat) -> Self {
self.security.report_format = format;
self
}
pub fn build(self) -> X86Security {
self.security
}
}
#[derive(Debug)]
pub struct X86SecurityPipeline {
pub config: X86Security,
pub results: Option<X86SecurityResult>,
pub vuln_warnings: Vec<X86VulnWarning>,
pub coding_violations: Vec<X86CodingViolation>,
}
impl X86SecurityPipeline {
pub fn new(config: X86Security) -> Self {
Self {
config,
results: None,
vuln_warnings: Vec::new(),
coding_violations: Vec::new(),
}
}
pub fn run(&mut self, source_files: &[String]) -> &X86SecurityResult {
let _sanitizer_flags = self.config.sanitizers.to_clang_flags();
let _mitigation_flags = self.config.mitigations.to_clang_flags();
let _fuzzing_flags = self.config.fuzzing.to_clang_flags();
for file in source_files {
let _ = self
.config
.vulnerability_checkers
.check_function_call("gets", file);
}
self.vuln_warnings = self.config.vulnerability_checkers.warnings.clone();
self.coding_violations = self.config.secure_coding.violations.clone();
let result = self.config.apply();
self.results = Some(result.clone());
self.results.as_ref().unwrap()
}
pub fn summary(&self) -> String {
match &self.results {
Some(r) => format!(
"Security Pipeline Results:\n\
Success: {}\n\
Sanitizers: {}\n\
Mitigations: {}\n\
Vuln Warnings: {}\n\
Coding Violations: {}\n\
Elapsed: {}ms",
r.success,
r.sanitizer_applied,
r.mitigations_applied,
self.vuln_warnings.len(),
self.coding_violations.len(),
r.elapsed_ms,
),
None => "Pipeline not yet run.".into(),
}
}
pub fn generate_report(&self) -> String {
let mut report = String::from("=== X86 Security Pipeline Report ===\n\n");
report.push_str(&self.config.generate_report());
report.push_str("\n\n--- Vulnerability Warnings ---\n");
if self.vuln_warnings.is_empty() {
report.push_str("None.\n");
} else {
for w in &self.vuln_warnings {
report.push_str(&format!(
" [{}] CWE-{}: {} at {}\n",
w.severity.as_str(),
w.cwe_id.unwrap_or(0),
w.message,
w.location,
));
}
}
report.push_str("\n--- Secure Coding Violations ---\n");
if self.coding_violations.is_empty() {
report.push_str("None.\n");
} else {
for v in &self.coding_violations {
report.push_str(&format!(
" [{}] {} {}: {} at {}\n",
v.standard.name(),
v.rule_id,
v.rule_description,
v.severity.as_str(),
v.location,
));
}
}
report.push_str("\n=== End of Report ===\n");
report
}
}
impl Default for X86SecurityPipeline {
fn default() -> Self {
Self::new(X86Security::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arch_variant_pointer_sizes() {
assert_eq!(X86ArchVariant::X86_64.pointer_size(), 8);
assert_eq!(X86ArchVariant::X86_32.pointer_size(), 4);
assert_eq!(X86ArchVariant::X86_16.pointer_size(), 2);
}
#[test]
fn test_arch_variant_asan_offset() {
assert_eq!(
X86ArchVariant::X86_64.asan_shadow_offset(),
X86_64_ASAN_SHADOW_OFFSET
);
assert_eq!(
X86ArchVariant::X86_32.asan_shadow_offset(),
X86_32_ASAN_SHADOW_OFFSET
);
}
#[test]
fn test_arch_variant_is_64bit() {
assert!(X86ArchVariant::X86_64.is_64bit());
assert!(!X86ArchVariant::X86_32.is_64bit());
}
#[test]
fn test_x86_security_default() {
let sec = X86Security::default();
assert!(sec.enabled);
assert_eq!(sec.arch, X86ArchVariant::X86_64);
assert_eq!(sec.report_format, SecurityReportFormat::Text);
}
#[test]
fn test_x86_security_new_x86_64() {
let sec = X86Security::new(X86ArchVariant::X86_64);
assert!(sec.mitigations.stack_protector_strong);
assert!(sec.mitigations.cet_ibt);
assert!(sec.mitigations.enable_pie);
assert!(sec.mitigations.relro_full);
assert!(sec.mitigations.nx_stack);
}
#[test]
fn test_x86_security_new_x86_32() {
let sec = X86Security::new(X86ArchVariant::X86_32);
assert!(!sec.mitigations.cet_ibt); assert_eq!(sec.mitigations.canary_value, None);
}
#[test]
fn test_x86_security_minimal() {
let sec = X86Security::minimal(X86ArchVariant::X86_64);
assert!(!sec.sanitizers.asan.enabled);
assert!(!sec.mitigations.stack_protector_strong);
assert!(!sec.mitigations.cet_ibt);
assert!(!sec.fuzzing.enabled);
}
#[test]
fn test_x86_security_maximum() {
let sec = X86Security::maximum(X86ArchVariant::X86_64);
assert!(sec.sanitizers.asan.enabled);
assert!(sec.mitigations.stack_protector_all);
assert!(sec.mitigations.cet_full);
assert!(sec.halt_on_violation);
}
#[test]
fn test_x86_security_for_fuzzing() {
let sec = X86Security::for_fuzzing(X86ArchVariant::X86_64);
assert!(sec.fuzzing.libfuzzer);
assert!(sec.sanitizers.asan.enabled);
assert!(sec.sanitizers.ubsan.enabled);
}
#[test]
fn test_x86_security_to_clang_flags() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let flags = sec.to_clang_flags();
assert!(flags.iter().any(|f| f.contains("x86-64")));
assert!(flags.iter().any(|f| f.contains("-fstack-protector-strong")));
}
#[test]
fn test_x86_security_apply() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let result = sec.apply();
assert!(result.success);
assert!(result.mitigations_applied);
}
#[test]
fn test_x86_security_validate_no_conflicts() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let errors = sec.validate();
assert!(errors.is_empty());
}
#[test]
fn test_x86_security_validate_conflict_asan_msan() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.sanitizers.msan.enabled = true;
let errors = sec.validate();
assert!(!errors.is_empty());
assert!(errors[0].kind == SecurityConfigErrorKind::ConflictingSanitizers);
}
#[test]
fn test_x86_security_generate_report_text() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let report = sec.generate_report();
assert!(report.contains("X86 Security Report"));
assert!(report.contains("SUCCESS"));
}
#[test]
fn test_sanitizer_integration_disabled() {
let si = X86SanitizerIntegration::disabled();
assert!(!si.has_any());
}
#[test]
fn test_sanitizer_integration_enable_all() {
let mut si = X86SanitizerIntegration::disabled();
si.enable_all();
assert!(si.asan.enabled);
assert!(si.ubsan.enabled);
assert!(!si.msan.enabled); }
#[test]
fn test_sanitizer_integration_fuzzing_default() {
let si = X86SanitizerIntegration::fuzzing_default();
assert!(si.asan.enabled);
assert!(si.ubsan.enabled);
assert!(si.lsan.enabled);
assert!(!si.msan.enabled);
}
#[test]
fn test_sanitizer_integration_to_clang_flags() {
let si = X86SanitizerIntegration::fuzzing_default();
let flags = si.to_clang_flags();
assert!(flags.iter().any(|f| f.contains("address")));
assert!(flags.iter().any(|f| f.contains("leak")));
}
#[test]
fn test_sanitizer_integration_describe() {
let si = X86SanitizerIntegration::fuzzing_default();
let desc = si.describe();
assert!(desc.contains("ASan"));
assert!(desc.contains("UBSan"));
}
#[test]
fn test_asan_disabled() {
let asan = X86AddressSanitizer::disabled();
assert!(!asan.enabled);
assert_eq!(asan.shadow_offset, X86_64_ASAN_SHADOW_OFFSET);
}
#[test]
fn test_asan_enabled_default() {
let asan = X86AddressSanitizer::enabled_default();
assert!(asan.enabled);
assert!(asan.detect_stack_use_after_return);
assert!(asan.detect_container_overflow);
}
#[test]
fn test_asan_app_to_shadow() {
let asan = X86AddressSanitizer::disabled();
let app_addr = 0x7f0000000000;
let shadow = asan.app_to_shadow(app_addr);
assert!(shadow > asan.shadow_offset);
}
#[test]
fn test_asan_shadow_to_app() {
let asan = X86AddressSanitizer::disabled();
let shadow_addr = 0x10007fff8000;
let app = asan.shadow_to_app(shadow_addr);
let reshadow = asan.app_to_shadow(app);
assert_eq!(reshadow, shadow_addr);
}
#[test]
fn test_asan_check_access_null() {
let asan = X86AddressSanitizer::disabled();
assert!(!asan.check_access(0, 8));
}
#[test]
fn test_asan_instrument_load() {
let mut asan = X86AddressSanitizer::enabled_default();
let inst = asan.instrument_load(0x1000, 4);
assert!(inst.instrumented);
assert_eq!(inst.kind, X86ASanInstKind::Load);
assert_eq!(inst.size, 4);
}
#[test]
fn test_asan_instrument_store() {
let mut asan = X86AddressSanitizer::enabled_default();
let inst = asan.instrument_store(0x2000, 8);
assert!(inst.instrumented);
assert_eq!(inst.kind, X86ASanInstKind::Store);
}
#[test]
fn test_asan_instrument_alloca() {
let mut asan = X86AddressSanitizer::enabled_default();
let result = asan.instrument_alloca(0x10000, 64, 16, "buf");
assert!(result.instrumented);
assert!(result.left_redzone > 0);
assert!(result.right_redzone > 0);
assert!(result.total_size > result.original_size);
}
#[test]
fn test_asan_instrument_global() {
let mut asan = X86AddressSanitizer::enabled_default();
let result = asan.instrument_global(0x20000, 128, "global_var");
assert!(result.instrumented);
assert!(result.redzone_size > 0);
assert!(result.odr_hash != 0);
}
#[test]
fn test_asan_instrument_function() {
let mut asan = X86AddressSanitizer::enabled_default();
let accesses = vec![(0x1000, 4, false), (0x2000, 8, true)];
let insts = asan.instrument_function("test_func", &accesses);
assert_eq!(insts.len(), 2);
assert!(!insts[0].kind == X86ASanInstKind::Load);
}
#[test]
fn test_asan_container_overflow() {
let asan = X86AddressSanitizer::enabled_default();
assert!(asan.check_container_overflow(0x1000, 32, 48));
assert!(!asan.check_container_overflow(0x1000, 64, 16));
}
#[test]
fn test_asan_odr_detection() {
let asan = X86AddressSanitizer::enabled_default();
let result = asan.detect_odr_violation("foo", 16, "foo", 32);
assert!(result.is_some());
let result2 = asan.detect_odr_violation("foo", 16, "bar", 16);
assert!(result2.is_none());
}
#[test]
fn test_asan_format_report() {
let asan = X86AddressSanitizer::enabled_default();
let report = asan.format_report("heap-buffer-overflow", 0x7f0000001000, 8, true);
assert!(report.contains("AddressSanitizer"));
assert!(report.contains("WRITE"));
assert!(report.contains("0x7f0000001000"));
}
#[test]
fn test_asan_set_arch() {
let mut asan = X86AddressSanitizer::disabled();
asan.set_arch(X86ArchVariant::X86_32);
assert_eq!(asan.shadow_offset, X86_32_ASAN_SHADOW_OFFSET);
}
#[test]
fn test_msan_disabled() {
let msan = X86MemorySanitizer::disabled();
assert!(!msan.enabled);
}
#[test]
fn test_msan_enabled_default() {
let msan = X86MemorySanitizer::enabled_default();
assert!(msan.enabled);
assert!(msan.track_origins);
}
#[test]
fn test_msan_track_value() {
let mut msan = X86MemorySanitizer::enabled_default();
msan.track_value(0x1000, 4);
assert_eq!(msan.values_tracked, 1);
assert_ne!(msan.get_shadow(0x1000), 0);
}
#[test]
fn test_msan_mark_initialized() {
let mut msan = X86MemorySanitizer::enabled_default();
msan.track_value(0x1000, 4);
msan.mark_initialized(0x1000, 4);
assert_eq!(msan.get_shadow(0x1000), 0);
}
#[test]
fn test_msan_track_origin() {
let mut msan = X86MemorySanitizer::enabled_default();
let origin = msan.track_origin(0x2000, 8);
assert!(origin > 0);
assert_eq!(msan.origins_recorded, 1);
}
#[test]
fn test_msan_check_read_uninit() {
let mut msan = X86MemorySanitizer::enabled_default();
msan.track_value(0x3000, 4);
let warning = msan.check_read(0x3000, 4);
assert!(warning.is_some());
}
#[test]
fn test_msan_check_read_init() {
let msan = X86MemorySanitizer::enabled_default();
let warning = msan.check_read(0x4000, 8);
assert!(warning.is_none());
}
#[test]
fn test_msan_shadow_propagation() {
let msan = X86MemorySanitizer::enabled_default();
assert_eq!(msan.propagate_shadow_add(0xFF, 0x00), 0xFF);
assert_eq!(msan.propagate_shadow_add(0x00, 0x00), 0x00);
assert_eq!(msan.propagate_shadow_or(0xFF, 0xAB), 0xFF);
}
#[test]
fn test_tsan_disabled() {
let tsan = X86ThreadSanitizer::disabled();
assert!(!tsan.enabled);
assert_eq!(tsan.loads_instrumented, 0);
}
#[test]
fn test_tsan_enabled_default() {
let tsan = X86ThreadSanitizer::enabled_default();
assert!(tsan.enabled);
assert!(tsan.instrument_loads);
assert!(tsan.instrument_stores);
}
#[test]
fn test_tsan_instrument_load() {
let mut tsan = X86ThreadSanitizer::enabled_default();
let race = tsan.instrument_load(0x1000, 4, 1, X86TSanMemoryOrder::Relaxed);
assert_eq!(tsan.loads_instrumented, 1);
assert!(race.is_none());
}
#[test]
fn test_tsan_instrument_store() {
let mut tsan = X86ThreadSanitizer::enabled_default();
let race = tsan.instrument_store(0x2000, 8, 1, X86TSanMemoryOrder::Relaxed);
assert_eq!(tsan.stores_instrumented, 1);
assert!(race.is_none());
}
#[test]
fn test_tsan_detect_race() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_store(0x3000, 4, 1, X86TSanMemoryOrder::Relaxed);
let race = tsan.instrument_load(0x3000, 4, 2, X86TSanMemoryOrder::Relaxed);
assert!(race.is_some());
assert_eq!(tsan.races_detected, 1);
}
#[test]
fn test_tsan_no_race_atomic() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_store(0x4000, 4, 1, X86TSanMemoryOrder::Release);
let race = tsan.instrument_load(0x4000, 4, 2, X86TSanMemoryOrder::Acquire);
assert!(race.is_none());
}
#[test]
fn test_tsan_lock_tracking() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_lock_acquire(0x5000, 1, false);
assert!(tsan.locks.contains_key(&0x5000));
tsan.instrument_lock_release(0x5000, 1);
assert!(!tsan.locks.contains_key(&0x5000));
}
#[test]
fn test_tsan_happens_before() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.record_happens_before(1, 2);
let clock = tsan.clock.get(&2).copied().unwrap_or(0);
assert!(clock > 0);
}
#[test]
fn test_tsan_format_race_report() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_store(0x6000, 4, 1, X86TSanMemoryOrder::Relaxed);
let race = tsan.instrument_store(0x6000, 4, 2, X86TSanMemoryOrder::Relaxed);
if let Some(r) = &race {
let report = tsan.format_race_report(r);
assert!(report.contains("ThreadSanitizer"));
assert!(report.contains("data race"));
}
}
#[test]
fn test_ubsan_disabled() {
let ubsan = X86UndefinedBehaviorSanitizer::disabled();
assert!(!ubsan.enabled);
assert!(!ubsan.detect_signed_overflow);
}
#[test]
fn test_ubsan_enabled_default() {
let ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.enabled);
assert!(ubsan.detect_signed_overflow);
assert!(ubsan.detect_division_by_zero);
}
#[test]
fn test_ubsan_check_signed_add_overflow() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
let overflow = ubsan.check_signed_add_overflow(i64::MAX, 1);
assert!(overflow);
assert_eq!(ubsan.checks_inserted, 1);
assert_eq!(ubsan.issues_found, 1);
}
#[test]
fn test_ubsan_check_signed_add_no_overflow() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
let overflow = ubsan.check_signed_add_overflow(1, 2);
assert!(!overflow);
assert_eq!(ubsan.issues_found, 0);
}
#[test]
fn test_ubsan_check_div_zero() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_div_zero(10, 0));
assert!(!ubsan.check_div_zero(10, 5));
}
#[test]
fn test_ubsan_check_null_deref() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_null_deref(0, 4));
assert!(!ubsan.check_null_deref(0x1000, 4));
}
#[test]
fn test_ubsan_check_alignment() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_alignment(0x1001, 4));
assert!(!ubsan.check_alignment(0x1000, 4));
}
#[test]
fn test_ubsan_check_shift_out_of_bounds() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_shift_out_of_bounds(1, 64, 64));
assert!(!ubsan.check_shift_out_of_bounds(1, 16, 64));
}
#[test]
fn test_ubsan_check_float_cast_overflow() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_float_cast_overflow(1e20, "i32"));
assert!(!ubsan.check_float_cast_overflow(42.0, "i32"));
}
#[test]
fn test_ubsan_check_enum_bounds() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_enum_bounds(100, 0, 5));
assert!(!ubsan.check_enum_bounds(3, 0, 5));
}
#[test]
fn test_ubsan_check_implicit_conversion() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_implicit_conversion(1000, "i32", "i8"));
assert!(!ubsan.check_implicit_conversion(42, "i32", "i8"));
}
#[test]
fn test_ubsan_handler_names() {
let names = X86UndefinedBehaviorSanitizer::handler_names();
assert!(names.len() > 10);
assert!(names.contains(&"__ubsan_handle_add_overflow"));
}
#[test]
fn test_lsan_disabled() {
let lsan = X86LeakSanitizer::disabled();
assert!(!lsan.enabled);
}
#[test]
fn test_lsan_enabled_default() {
let lsan = X86LeakSanitizer::enabled_default();
assert!(lsan.enabled);
assert!(lsan.leak_check_at_exit);
}
#[test]
fn test_lsan_detect_leaks_no_roots() {
let mut lsan = X86LeakSanitizer::enabled_default();
let leaks = lsan.detect_leaks();
assert!(leaks.is_empty());
}
#[test]
fn test_lsan_add_root() {
let mut lsan = X86LeakSanitizer::enabled_default();
lsan.add_root(0x1000, 4096, X86LSanRootKind::Stack);
assert_eq!(lsan.roots.len(), 1);
}
#[test]
fn test_lsan_suppression() {
let mut lsan = X86LeakSanitizer::enabled_default();
lsan.add_suppression("test_lib");
let leak = X86LSanLeak {
address: 0x1000,
size: 64,
stack_trace: vec!["test_lib_alloc".into(), "main".into()],
is_direct: true,
suppressed: false,
};
assert!(lsan.is_suppressed(&leak));
}
#[test]
fn test_lsan_summarize() {
let lsan = X86LeakSanitizer::enabled_default();
let summary = lsan.summarize();
assert!(summary.contains("No leaks detected"));
}
#[test]
fn test_dfsan_disabled() {
let dfsan = X86DataFlowSanitizer::disabled();
assert!(!dfsan.enabled);
}
#[test]
fn test_dfsan_enabled_default() {
let dfsan = X86DataFlowSanitizer::enabled_default();
assert!(dfsan.enabled);
}
#[test]
fn test_dfsan_create_label() {
let mut dfsan = X86DataFlowSanitizer::enabled_default();
let label = dfsan.create_label();
assert_eq!(label, 1);
let label2 = dfsan.create_label();
assert_eq!(label2, 2);
}
#[test]
fn test_dfsan_set_get_label() {
let mut dfsan = X86DataFlowSanitizer::enabled_default();
dfsan.set_label(0x1000, 8, 0x42);
assert_eq!(dfsan.get_label(0x1000), 0x42);
}
#[test]
fn test_dfsan_union_labels() {
let dfsan = X86DataFlowSanitizer::enabled_default();
assert_eq!(dfsan.union_labels(0x01, 0x10), 0x11);
}
#[test]
fn test_dfsan_propagate_label_add() {
let dfsan = X86DataFlowSanitizer::enabled_default();
let result = dfsan.propagate_label(X86DFSanOp::Add, &[0x01, 0x10]);
assert_eq!(result, 0x11);
}
#[test]
fn test_dfsan_parse_abi_line() {
let entry = X86DataFlowSanitizer::parse_abi_line("fun:my_function=custom");
assert!(entry.is_some());
let e = entry.unwrap();
assert_eq!(e.function, "my_function=custom");
assert!(e.is_custom);
}
#[test]
fn test_dfsan_abi_entries() {
let mut dfsan = X86DataFlowSanitizer::enabled_default();
dfsan.add_abi_entry("memset", X86DFSanABICategory::Discard);
assert_eq!(dfsan.abi_list.len(), 1);
}
#[test]
fn test_hwasan_disabled() {
let hwasan = X86HWAddressSanitizer::disabled();
assert!(!hwasan.enabled);
}
#[test]
fn test_hwasan_x86_stub() {
let hwasan = X86HWAddressSanitizer::x86_stub();
assert!(hwasan.enabled);
assert!(!hwasan.use_tbi);
}
#[test]
fn test_hwasan_generate_tag() {
let hwasan = X86HWAddressSanitizer::disabled();
let tag = hwasan.generate_tag();
assert!(tag > 0);
}
#[test]
fn test_safe_stack_disabled() {
let ss = X86SafeStackIntegration::disabled();
assert!(!ss.enabled);
}
#[test]
fn test_safe_stack_enabled() {
let ss = X86SafeStackIntegration::enabled_default();
assert!(ss.enabled);
let flags = ss.to_flags();
assert!(flags.iter().any(|f| f.contains("safe-stack")));
}
#[test]
fn test_shadow_call_stack_disabled() {
let scs = X86ShadowCallStack::disabled();
assert!(!scs.enabled);
}
#[test]
fn test_shadow_call_stack_enabled() {
let scs = X86ShadowCallStack::enabled_default();
assert!(scs.enabled);
assert!(scs.use_gs_register);
}
#[test]
fn test_cfi_disabled() {
let cfi = X86CFIIntegration::disabled();
assert!(!cfi.enabled);
}
#[test]
fn test_cfi_full() {
let cfi = X86CFIIntegration::full();
assert!(cfi.enabled);
assert!(cfi.cross_dso);
assert!(cfi.use_lto);
}
#[test]
fn test_cfi_check_kind_flags() {
assert_eq!(X86CFICheckKind::Exact.to_clang_flag(), "cfi");
assert_eq!(X86CFICheckKind::Derived.to_clang_flag(), "cfi-derived-cast");
}
#[test]
fn test_kcfi_disabled() {
let kcfi = X86KCFIIntegration::disabled();
assert!(!kcfi.enabled);
}
#[test]
fn test_kcfi_kernel_module() {
let kcfi = X86KCFIIntegration::kernel_module();
assert!(kcfi.enabled);
assert!(kcfi.trap_on_violation);
}
#[test]
fn test_kcfi_compute_type_hash() {
let mut kcfi = X86KCFIIntegration::kernel_module();
let hash = kcfi.compute_type_hash("void foo(int, char*)");
assert!(hash != 0);
}
#[test]
fn test_kcfi_register_validate() {
let mut kcfi = X86KCFIIntegration::kernel_module();
kcfi.register_function("foo", "void foo(int)");
let sig = "void foo(int)";
let expected = kcfi.compute_type_hash(sig);
assert!(kcfi.validate_call("foo", expected));
}
#[test]
fn test_mitigation_flags_secure_default() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
assert!(flags.stack_protector_strong);
assert!(flags.cet_ibt);
assert!(flags.strict_flex_arrays);
assert!(flags.fortify_source_2);
assert!(flags.enable_pie);
assert!(flags.relro_full);
assert!(flags.nx_stack);
}
#[test]
fn test_mitigation_flags_minimal() {
let flags = X86MitigationFlags::minimal(X86ArchVariant::X86_64);
assert!(!flags.stack_protector_strong);
assert!(!flags.cet_ibt);
assert!(!flags.enable_pie);
assert!(flags.relro_partial);
}
#[test]
fn test_mitigation_flags_maximum() {
let flags = X86MitigationFlags::maximum(X86ArchVariant::X86_64);
assert!(flags.stack_protector_all);
assert!(flags.cet_full);
assert!(flags.fortify_source_3);
assert!(flags.no_dlopen);
}
#[test]
fn test_mitigation_flags_has_any() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
assert!(flags.has_any());
let empty = X86MitigationFlags::minimal(X86ArchVariant::X86_64);
assert!(!empty.has_any());
}
#[test]
fn test_mitigation_flags_flags_summary() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let summary = flags.flags_summary();
assert!(summary.contains("-fstack-protector-strong"));
assert!(summary.contains("-fPIE"));
}
#[test]
fn test_mitigation_flags_to_clang_flags() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.iter().any(|f| f == "-fstack-protector-strong"));
assert!(cf.iter().any(|f| f == "-fcf-protection=full"));
assert!(cf.iter().any(|f| f == "-fPIE"));
}
#[test]
fn test_mitigation_flags_canary() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let canary = flags.generate_canary();
assert!(canary != 0);
}
#[test]
fn test_mitigation_flags_canary_sequence() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let seq = flags.canary_check_sequence();
assert!(!seq.is_empty());
assert!(seq.len() > 10);
}
#[test]
fn test_fuzzing_disabled() {
let fuzzing = X86FuzzingSupport::disabled();
assert!(!fuzzing.enabled);
assert!(!fuzzing.libfuzzer);
}
#[test]
fn test_fuzzing_full_enabled() {
let fuzzing = X86FuzzingSupport::full_enabled();
assert!(fuzzing.enabled);
assert!(fuzzing.libfuzzer);
assert!(fuzzing.afl);
assert!(fuzzing.honggfuzz);
}
#[test]
fn test_fuzzing_libfuzzer_only() {
let fuzzing = X86FuzzingSupport::libfuzzer_only();
assert!(fuzzing.libfuzzer);
assert!(!fuzzing.afl);
assert!(!fuzzing.honggfuzz);
}
#[test]
fn test_fuzzing_test_one_input_declaration() {
let fuzzing = X86FuzzingSupport::libfuzzer_only();
let decl = fuzzing.test_one_input_declaration();
assert!(decl.contains("LLVMFuzzerTestOneInput"));
}
#[test]
fn test_fuzzing_harness_signature() {
let fuzzing = X86FuzzingSupport::full_enabled();
let sig = fuzzing.harness_signature();
assert!(sig.contains("LLVMFuzzerInitialize"));
assert!(sig.contains("LLVMFuzzerTestOneInput"));
assert!(sig.contains("LLVMFuzzerCustomMutator"));
assert!(sig.contains("LLVMFuzzerCustomCrossOver"));
}
#[test]
fn test_fuzzing_sancov_flags() {
let fuzzing = X86FuzzingSupport::full_enabled();
let flags = fuzzing.sancov_flags();
assert!(flags.contains("trace-pc-guard"));
assert!(flags.contains("edge"));
}
#[test]
fn test_fuzzing_to_clang_flags() {
let fuzzing = X86FuzzingSupport::libfuzzer_only();
let flags = fuzzing.to_clang_flags();
assert!(flags.iter().any(|f| f.contains("fuzzer")));
assert!(flags.iter().any(|f| f.contains("sanitize-coverage")));
}
#[test]
fn test_vuln_checkers_all_enabled() {
let checkers = X86VulnerabilityCheckers::all_enabled();
assert!(checkers.buffer_overflow);
assert!(checkers.use_after_free);
assert!(checkers.command_injection);
assert!(checkers.has_any());
}
#[test]
fn test_vuln_checkers_none() {
let checkers = X86VulnerabilityCheckers::none();
assert!(!checkers.buffer_overflow);
assert!(!checkers.has_any());
}
#[test]
fn test_vuln_checkers_gets() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("gets", "test.c:10");
assert!(warning.is_some());
let w = warning.unwrap();
assert_eq!(w.vuln_type, X86VulnType::BufferOverflow);
assert_eq!(w.severity, X86VulnSeverity::Critical);
assert_eq!(w.cwe_id, Some(120));
}
#[test]
fn test_vuln_checkers_system() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("system", "test.c:20");
assert!(warning.is_some());
assert_eq!(warning.unwrap().vuln_type, X86VulnType::CommandInjection);
}
#[test]
fn test_vuln_checkers_printf_format() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("printf", "test.c:30");
assert!(warning.is_some());
assert_eq!(warning.unwrap().vuln_type, X86VulnType::FormatString);
}
#[test]
fn test_vuln_checkers_strcpy() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("strcpy", "test.c:40");
assert!(warning.is_some());
assert_eq!(warning.unwrap().vuln_type, X86VulnType::BufferOverflow);
}
#[test]
fn test_vuln_checkers_free_double() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("free", "test.c:50");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_buffer_overflow() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_buffer_overflow(32, 64, "test.c:5");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_integer_overflow() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_integer_overflow(i64::MAX, 1, "+", "test.c:15");
assert!(warning.is_some());
assert_eq!(warning.unwrap().vuln_type, X86VulnType::IntegerOverflow);
}
#[test]
fn test_vuln_checkers_use_after_free() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_use_after_free("ptr", true, "test.c:25");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_double_free() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_double_free("ptr", true, "test.c:35");
assert!(warning.is_some());
assert_eq!(warning.unwrap().vuln_type, X86VulnType::DoubleFree);
}
#[test]
fn test_vuln_checkers_enabled_count() {
let checkers = X86VulnerabilityCheckers::all_enabled();
assert_eq!(checkers.enabled_count(), 14);
let none = X86VulnerabilityCheckers::none();
assert_eq!(none.enabled_count(), 0);
}
#[test]
fn test_vuln_checkers_csv_report() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
checkers.check_function_call("gets", "test.c:1");
let csv = checkers.generate_csv_report();
assert!(csv.contains("cwe_id"));
assert!(csv.contains("120"));
}
#[test]
fn test_vuln_type_default_cwe() {
assert_eq!(X86VulnType::BufferOverflow.default_cwe(), 120);
assert_eq!(X86VulnType::UseAfterFree.default_cwe(), 416);
assert_eq!(X86VulnType::CommandInjection.default_cwe(), 77);
}
#[test]
fn test_secure_coding_none() {
let sc = X86SecureCoding::none();
assert!(!sc.has_any());
assert_eq!(sc.enabled_rule_count(), 0);
}
#[test]
fn test_secure_coding_all() {
let sc = X86SecureCoding::all();
assert!(sc.has_any());
assert_eq!(sc.enabled_rule_count(), 5);
}
#[test]
fn test_secure_coding_default() {
let sc = X86SecureCoding::default();
assert!(sc.cert_c);
assert!(sc.cert_cpp);
assert!(!sc.misra_c);
}
#[test]
fn test_secure_coding_add_violation() {
let mut sc = X86SecureCoding::all();
sc.add_violation(
X86CodingStandard::CERT_C,
"EXP33-C",
"Do not read uninitialized memory",
"test.c:10",
X86VulnSeverity::High,
Some(457),
);
assert_eq!(sc.total_violations, 1);
assert_eq!(sc.violations.len(), 1);
}
#[test]
fn test_secure_coding_cwe_to_cert_c() {
let rules = X86SecureCoding::cwe_to_cert_c(120);
assert!(!rules.is_empty());
assert!(rules.contains(&cert_c_rules::STR31_C));
}
#[test]
fn test_secure_coding_cwe_to_cert_cpp() {
let rules = X86SecureCoding::cwe_to_cert_cpp(416);
assert!(!rules.is_empty());
assert!(rules.contains(&cert_cpp_rules::MEM50_CPP));
}
#[test]
fn test_secure_coding_get_rules_cert_c() {
let rules = X86SecureCoding::get_rules(X86CodingStandard::CERT_C);
assert!(rules.len() >= 10);
}
#[test]
fn test_crypto_default() {
let crypto = X86CryptoIntrinsics::default();
assert!(crypto.aes_ni);
assert!(crypto.sha_ni);
assert!(crypto.rdrand);
}
#[test]
fn test_crypto_none() {
let crypto = X86CryptoIntrinsics::none();
assert!(!crypto.aes_ni);
assert!(!crypto.sha_ni);
}
#[test]
fn test_crypto_to_target_flags() {
let crypto = X86CryptoIntrinsics::default();
let flags = crypto.to_target_flags();
assert!(flags.contains(&"-maes".to_string()));
assert!(flags.contains(&"-msha".to_string()));
assert!(flags.contains(&"-mrdrnd".to_string()));
}
#[test]
fn test_aesni_sbox_size() {
assert_eq!(aes_ni::SBOX.len(), 256);
}
#[test]
fn test_aesni_aesenc() {
let state = [0u8; 16];
let key = [0u8; 16];
let result = aes_ni::aesenc(&state, &key);
assert_eq!(result.len(), 16);
}
#[test]
fn test_aesni_aes128_encrypt() {
let key = [0x2Bu8; 16];
let plaintext = [0x6Bu8; 16];
let ciphertext = aes_ni::aes128_encrypt(&plaintext, &key);
assert_eq!(ciphertext.len(), 16);
}
#[test]
fn test_aesni_aes256_encrypt() {
let key = [0xAAu8; 32];
let plaintext = [0xBBu8; 16];
let ciphertext = aes_ni::aes256_encrypt(&plaintext, &key);
assert_eq!(ciphertext.len(), 16);
}
#[test]
fn test_shani_sha256_init() {
let init = sha_ni::SHA256_INIT;
assert_eq!(init[0], 0x6A09E667);
}
#[test]
fn test_shani_sha256_constants() {
assert_eq!(sha_ni::SHA256_K.len(), 64);
assert_eq!(sha_ni::SHA256_K[0], 0x428A2F98);
}
#[test]
fn test_shani_sha256rnds2() {
let state: sha_ni::Sha256State = sha_ni::SHA256_INIT;
let wk = [0u32; 4];
let result = sha_ni::sha256rnds2(&state, &wk);
assert_eq!(result.len(), 8);
}
#[test]
fn test_clmul_pclmulqdq() {
let result = clmul::pclmulqdq(0x03, 0x05);
assert_eq!(result, 0x0F); }
#[test]
fn test_clmul_ghash_mul() {
let h = 0x66e94bd4ef8a2c3b884cfa59ca342b2e;
let x = 0x0388dace60b6a392f328c2b971b2fe78;
let result = clmul::ghash_mul(h, x);
assert!(result != 0);
}
#[test]
fn test_rdrand64_returns_some() {
let val = rdrand::rdrand64();
assert!(val.is_some());
}
#[test]
fn test_rdrand32_returns_some() {
let val = rdrand::rdrand32();
assert!(val.is_some());
}
#[test]
fn test_rdseed64_returns_some() {
let val = rdrand::rdseed64();
assert!(val.is_some());
}
#[test]
fn test_rdrand_fill() {
let mut buf = [0u8; 32];
let ok = rdrand::rdrand_fill(&mut buf);
assert!(ok);
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_crc32c_table() {
let table = crc32_intrinsics::CRC32C_TABLE;
assert_eq!(table.len(), 256);
}
#[test]
fn test_crc32b() {
let crc = crc32_intrinsics::crc32b(0, 0x31);
assert!(crc != 0);
}
#[test]
fn test_crc32w() {
let crc = crc32_intrinsics::crc32w(0, 0x3132);
assert!(crc != 0);
}
#[test]
fn test_crc32l() {
let crc = crc32_intrinsics::crc32l(0, 0x31323334);
assert!(crc != 0);
}
#[test]
fn test_crc32c_slice() {
let data = b"123456789";
let crc = crc32_intrinsics::crc32c_slice(0, data);
assert!(crc != 0);
}
#[test]
fn test_crc32_ieee() {
let data = b"123456789";
let crc = crc32_intrinsics::crc32_ieee(data);
assert!(crc != 0);
}
#[test]
fn test_builder_default() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64).build();
assert!(sec.enabled);
assert!(sec.mitigations.stack_protector_strong);
}
#[test]
fn test_builder_with_asan() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_asan(true)
.build();
assert!(sec.sanitizers.asan.enabled);
assert!(sec.sanitizers.asan.detect_stack_use_after_return);
}
#[test]
fn test_builder_with_stack_protector() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_stack_protector("all")
.build();
assert!(sec.mitigations.stack_protector_all);
}
#[test]
fn test_builder_with_cet() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_cet("full")
.build();
assert!(sec.mitigations.cet_full);
assert!(sec.mitigations.cet_ibt);
assert!(sec.mitigations.cet_shstk);
}
#[test]
fn test_builder_with_fuzzing() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_fuzzing(true)
.build();
assert!(sec.fuzzing.enabled);
assert!(sec.fuzzing.libfuzzer);
}
#[test]
fn test_builder_with_cfi() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_cfi(true)
.build();
assert!(sec.sanitizers.cfi.enabled);
}
#[test]
fn test_pipeline_creation() {
let pipeline = X86SecurityPipeline::default();
assert!(pipeline.results.is_none());
}
#[test]
fn test_pipeline_run() {
let mut pipeline = X86SecurityPipeline::default();
let sources = vec!["test.c".to_string()];
let result = pipeline.run(&sources);
assert!(result.success);
}
#[test]
fn test_pipeline_summary() {
let mut pipeline = X86SecurityPipeline::default();
pipeline.run(&["test.c".to_string()]);
let summary = pipeline.summary();
assert!(summary.contains("Security Pipeline Results"));
}
#[test]
fn test_pipeline_report() {
let mut pipeline = X86SecurityPipeline::default();
pipeline.run(&["test.c".to_string()]);
let report = pipeline.generate_report();
assert!(report.contains("X86 Security Pipeline Report"));
}
#[test]
fn test_security_config_error() {
let err = SecurityConfigError::new(
"test error".into(),
SecurityConfigErrorKind::ArchitectureMismatch,
);
assert_eq!(err.message, "test error");
assert_eq!(err.kind, SecurityConfigErrorKind::ArchitectureMismatch);
}
#[test]
fn test_stats_new() {
let stats = X86SecurityStats::new();
assert_eq!(stats.asan_functions.load(Ordering::Relaxed), 0);
}
#[test]
fn test_stats_summary() {
let stats = X86SecurityStats::new();
let summary = stats.summary();
assert!(summary.contains("X86Security Stats"));
assert!(summary.contains("ASan:"));
}
#[test]
fn test_stats_reset() {
let stats = X86SecurityStats::new();
stats.asan_functions.store(42, Ordering::SeqCst);
stats.reset();
assert_eq!(stats.asan_functions.load(Ordering::Relaxed), 0);
}
#[test]
fn test_report_format_extension() {
assert_eq!(SecurityReportFormat::Json.extension(), "json");
assert_eq!(SecurityReportFormat::Sarif.extension(), "sarif");
assert_eq!(SecurityReportFormat::Text.extension(), "txt");
}
#[test]
fn test_shadow_region_new() {
let region = X86ShadowRegion::new(0x1000, 0x2000, 0xFA, "test region");
assert_eq!(region.app_start, 0x1000);
assert_eq!(region.app_end, 0x2000);
assert_eq!(region.shadow_value, 0xFA);
}
#[test]
fn test_shadow_region_contains() {
let region = X86ShadowRegion::new(0, 0x1000, 0, "");
assert!(region.contains(0x500));
assert!(!region.contains(0x2000));
}
#[test]
fn test_dangerous_functions_completeness() {
assert!(DANGEROUS_FUNCTIONS.len() > 10);
let has_gets = DANGEROUS_FUNCTIONS.iter().any(|df| df.name == "gets");
assert!(has_gets);
let has_system = DANGEROUS_FUNCTIONS.iter().any(|df| df.name == "system");
assert!(has_system);
}
#[test]
fn test_aes_ctr_encrypt() {
let crypto = X86CryptoIntrinsics::default();
let key = [0x00u8; 16];
let nonce = [0x00u8; 12];
let pt = b"Hello, World! This is a test message.";
let ct = crypto.aes_ctr_encrypt(&key, &nonce, pt);
assert_eq!(ct.len(), pt.len());
}
#[test]
fn test_aeskeygenassist() {
let key = [0x2Bu8; 16];
let result = aes_ni::aeskeygenassist(&key, 1);
assert_eq!(result.len(), 16);
}
#[test]
fn test_sha1_msg_schedule() {
let w0 = [0x01020304u32; 4];
let w1 = [0x05060708u32; 4];
let result = sha_ni::sha1msg1(&w0, &w1);
assert_eq!(result.len(), 4);
}
#[test]
fn test_sha256_msg_schedule() {
let w0 = [0x01020304u32; 4];
let w1 = [0x05060708u32; 4];
let result = sha_ni::sha256msg1(&w0, &w1);
assert_eq!(result.len(), 4);
}
#[test]
fn test_x86_alloc_info() {
let info = X86AllocInfo {
address: 0x1000,
size: 64,
total_size: 128,
kind: X86AllocKind::Stack,
name: "buf".into(),
freed: false,
thread_id: 0,
alloc_stack: vec!["alloca buf at 0x1000".into()],
};
assert_eq!(info.kind.as_str(), "stack");
assert!(!info.freed);
}
#[test]
fn test_tsan_memory_order() {
assert_eq!(X86TSanMemoryOrder::Relaxed.to_llvm_str(), "monotonic");
assert_eq!(X86TSanMemoryOrder::Acquire.to_llvm_str(), "acquire");
assert_eq!(X86TSanMemoryOrder::Release.to_llvm_str(), "release");
assert_eq!(X86TSanMemoryOrder::SeqCst.to_llvm_str(), "seq_cst");
}
#[test]
fn test_ubsan_check_type_handler_name() {
assert_eq!(
X86UBSanCheckType::SignedOverflow.handler_name(),
"__ubsan_handle_add_overflow"
);
assert_eq!(
X86UBSanCheckType::NullDeref.handler_name(),
"__ubsan_handle_type_mismatch_v1"
);
}
#[test]
fn test_ubsan_check_type_clang_flag() {
assert_eq!(
X86UBSanCheckType::SignedOverflow.clang_flag(),
"signed-integer-overflow"
);
assert_eq!(
X86UBSanCheckType::FloatCastOverflow.clang_flag(),
"float-cast-overflow"
);
}
#[test]
fn test_x86_security_production() {
let sec = X86Security::production(X86ArchVariant::X86_64);
assert!(sec.mitigations.production_mode);
assert!(sec.mitigations.zero_call_used_regs_used);
assert!(sec.mitigations.fortify_source_2);
}
#[test]
fn test_x86_security_to_json_report() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.report_format = SecurityReportFormat::Json;
let report = sec.generate_report();
assert!(report.contains("\"arch\""));
}
#[test]
fn test_x86_security_to_sarif() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.report_format = SecurityReportFormat::Sarif;
let report = sec.generate_report();
assert!(report.contains("sarif"));
}
#[test]
fn test_x86_security_to_csv() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.report_format = SecurityReportFormat::Csv;
let report = sec.generate_report();
assert!(report.contains("arch,sanitizers"));
}
#[test]
fn test_x86_security_to_xml() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.report_format = SecurityReportFormat::Xml;
let report = sec.generate_report();
assert!(report.contains("<x86-security-report>"));
}
#[test]
fn test_sanitizer_integration_production_default() {
let si = X86SanitizerIntegration::production_default();
assert!(si.ubsan.enabled);
assert!(!si.ubsan.detect_signed_overflow);
assert!(si.safe_stack.enabled);
assert!(si.cfi.enabled);
}
#[test]
fn test_sanitizer_to_json() {
let si = X86SanitizerIntegration::fuzzing_default();
let json = si.to_json();
assert!(json.contains("\"asan\""));
assert!(json.contains("\"ubsan\""));
}
#[test]
fn test_asan_instrument_load_null() {
let mut asan = X86AddressSanitizer::enabled_default();
let inst = asan.instrument_load(0, 4);
assert!(inst.check_required);
}
#[test]
fn test_asan_poison_unpoison_stack() {
let mut asan = X86AddressSanitizer::enabled_default();
asan.poison_stack(0x10000, 32);
asan.unpoison_stack(0x10000, 32);
}
#[test]
fn test_asan_mark_freed() {
let mut asan = X86AddressSanitizer::enabled_default();
asan.instrument_alloca(0x20000, 64, 16, "test_buf");
asan.mark_freed(0x20000);
}
#[test]
fn test_asan_shadow_region_contains_boundary() {
let region = X86ShadowRegion::new(0, 0x1000, 0xFA, "test");
assert!(region.contains(0));
assert!(region.contains(0xFFF));
assert!(!region.contains(0x1000));
assert!(!region.contains(0x2000));
}
#[test]
fn test_asan_shadow_region_app_to_shadow() {
let region = X86ShadowRegion::new(0, 0x7fff8000, 0, "low");
let shadow = region.app_to_shadow(0x1000, 0x7fff8000, 3);
assert!(shadow > 0x7fff8000);
}
#[test]
fn test_asan_redzone_info() {
let rz = X86RedzoneInfo {
address: 0x1000,
size: 32,
kind: X86RedzoneKind::Left,
name: "test_rz".into(),
};
assert_eq!(rz.kind.as_str(), "left-redzone");
}
#[test]
fn test_asan_alloc_info() {
let info = X86AllocInfo {
address: 0x5000,
size: 128,
total_size: 256,
kind: X86AllocKind::HeapMalloc,
name: "heap_buf".into(),
freed: true,
thread_id: 7,
alloc_stack: vec!["malloc at main:10".into()],
};
assert_eq!(info.kind.as_str(), "heap-malloc");
assert!(info.freed);
}
#[test]
fn test_asan_error_type_for_shadow() {
assert_eq!(
X86AddressSanitizer::error_type_for_shadow(0xFA),
"heap-left-redzone"
);
assert_eq!(
X86AddressSanitizer::error_type_for_shadow(0xF5),
"global-redzone"
);
assert_eq!(
X86AddressSanitizer::error_type_for_shadow(0xF2),
"stack-use-after-return"
);
}
#[test]
fn test_asan_fake_stack_frame_pruning() {
let mut asan = X86AddressSanitizer::enabled_default();
for i in 0..X86_ASAN_FAKE_STACK_MAX_ENTRIES + 100 {
asan.instrument_alloca(0x100000 + i as u64 * 256, 64, 16, &format!("var_{}", i));
}
assert!(asan.fake_stack.len() <= X86_ASAN_FAKE_STACK_MAX_ENTRIES);
}
#[test]
fn test_asan_instrument_global_disabled() {
let mut asan = X86AddressSanitizer::disabled();
asan.instrument_globals = false;
let result = asan.instrument_global(0x30000, 64, "g_var");
assert!(!result.instrumented);
assert_eq!(result.redzone_size, 0);
}
#[test]
fn test_msan_shadow_propagation_mul() {
let msan = X86MemorySanitizer::enabled_default();
let shadow = msan.propagate_shadow_mul(0xFF, 0x00);
assert_eq!(shadow, 0xFF);
}
#[test]
fn test_msan_shadow_propagation_shl() {
let msan = X86MemorySanitizer::enabled_default();
assert_eq!(msan.propagate_shadow_shl(0x80, 1), 0x00);
assert_eq!(msan.propagate_shadow_shl(0x01, 1), 0x02);
}
#[test]
fn test_msan_shadow_propagation_shr() {
let msan = X86MemorySanitizer::enabled_default();
assert_eq!(msan.propagate_shadow_shr(0x80, 1), 0x40);
}
#[test]
fn test_msan_shadow_propagation_shl_overflow() {
let msan = X86MemorySanitizer::enabled_default();
assert_eq!(msan.propagate_shadow_shl(0xFF, 8), 0);
}
#[test]
fn test_msan_shadow_propagation_shr_overflow() {
let msan = X86MemorySanitizer::enabled_default();
assert_eq!(msan.propagate_shadow_shr(0xFF, 8), 0);
}
#[test]
fn test_msan_to_flags() {
let msan = X86MemorySanitizer::enabled_default();
let flags = msan.to_flags();
assert!(flags.iter().any(|f| f.contains("memory")));
}
#[test]
fn test_tsan_same_thread_no_race() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_store(0x7000, 4, 1, X86TSanMemoryOrder::Relaxed);
let race = tsan.instrument_load(0x7000, 4, 1, X86TSanMemoryOrder::Relaxed);
assert!(race.is_none());
}
#[test]
fn test_tsan_detect_lock_inversion() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_lock_acquire(0x8000, 1, false);
tsan.instrument_lock_acquire(0x9000, 1, false);
assert_eq!(tsan.locks.len(), 2);
tsan.instrument_lock_release(0x9000, 1);
tsan.instrument_lock_release(0x8000, 1);
assert_eq!(tsan.locks.len(), 0);
}
#[test]
fn test_tsan_lock_recursion() {
let mut tsan = X86ThreadSanitizer::enabled_default();
tsan.instrument_lock_acquire(0xA000, 1, false);
tsan.instrument_lock_acquire(0xA000, 1, false);
if let Some(lock) = tsan.locks.get(&0xA000) {
assert_eq!(lock.recursion_count, 2);
}
tsan.instrument_lock_release(0xA000, 1);
if let Some(lock) = tsan.locks.get(&0xA000) {
assert_eq!(lock.recursion_count, 1);
}
}
#[test]
fn test_tsan_to_flags() {
let tsan = X86ThreadSanitizer::enabled_default();
let flags = tsan.to_flags();
assert!(flags.iter().any(|f| f.contains("thread")));
}
#[test]
fn test_ubsan_check_signed_sub_overflow() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_signed_sub_overflow(i64::MIN, 1));
assert!(!ubsan.check_signed_sub_overflow(5, 3));
}
#[test]
fn test_ubsan_check_signed_mul_overflow() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
assert!(ubsan.check_signed_mul_overflow(i64::MAX, 2));
assert!(!ubsan.check_signed_mul_overflow(5, 3));
}
#[test]
fn test_ubsan_check_list_generation() {
let ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
let checks = ubsan.to_check_list();
assert!(checks.contains(&"signed-integer-overflow".to_string()));
assert!(checks.contains(&"bounds".to_string()));
}
#[test]
fn test_ubsan_minimal_runtime() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.minimal_runtime = true;
assert!(ubsan.minimal_runtime);
}
#[test]
fn test_lsan_register_allocation_deallocation() {
let mut lsan = X86LeakSanitizer::enabled_default();
lsan.register_allocation(0xB000, 128);
assert_eq!(lsan.allocations_tracked, 1);
lsan.register_deallocation(0xB000);
}
#[test]
fn test_lsan_format_leak_report_direct() {
let lsan = X86LeakSanitizer::enabled_default();
let leak = X86LSanLeak {
address: 0xC000,
size: 256,
stack_trace: vec!["malloc at main:20".into(), "process_data".into()],
is_direct: true,
suppressed: false,
};
let report = lsan.format_leak_report(&leak);
assert!(report.contains("Direct leak"));
assert!(report.contains("0x"));
}
#[test]
fn test_lsan_format_leak_report_indirect() {
let lsan = X86LeakSanitizer::enabled_default();
let leak = X86LSanLeak {
address: 0xD000,
size: 64,
stack_trace: vec!["alloc_buffer at helper:5".into()],
is_direct: false,
suppressed: true,
};
let report = lsan.format_leak_report(&leak);
assert!(report.contains("Indirect leak"));
}
#[test]
fn test_dfsan_abi_line_comment() {
let entry = X86DataFlowSanitizer::parse_abi_line("# this is a comment");
assert!(entry.is_none());
}
#[test]
fn test_dfsan_abi_line_empty() {
let entry = X86DataFlowSanitizer::parse_abi_line("");
assert!(entry.is_none());
}
#[test]
fn test_dfsan_abi_line_discard() {
let entry = X86DataFlowSanitizer::parse_abi_line("discard:free");
assert!(entry.is_some());
let e = entry.unwrap();
assert_eq!(e.category, X86DFSanABICategory::Discard);
assert!(!e.label_args);
}
#[test]
fn test_dfsan_abi_line_functional() {
let entry = X86DataFlowSanitizer::parse_abi_line("functional:memcmp");
assert!(entry.is_some());
assert_eq!(entry.unwrap().category, X86DFSanABICategory::Functional);
}
#[test]
fn test_dfsan_propagate_intersection() {
let dfsan = X86DataFlowSanitizer::enabled_default();
let result = dfsan.propagate_label(X86DFSanOp::Div, &[0x0F, 0xF0]);
assert_eq!(result, 0xFF);
}
#[test]
fn test_dfsan_to_flags() {
let mut dfsan = X86DataFlowSanitizer::enabled_default();
dfsan.abi_list_file = Some("/tmp/abi.txt".into());
let flags = dfsan.to_flags();
assert!(flags.iter().any(|f| f.contains("dataflow")));
assert!(flags.iter().any(|f| f.contains("abi-list")));
}
#[test]
fn test_hwasan_to_flags() {
let hwasan = X86HWAddressSanitizer::x86_stub();
let flags = hwasan.to_flags();
assert!(flags.iter().any(|f| f.contains("hwaddress")));
}
#[test]
fn test_hwasan_tag_allocation() {
let mut hwasan = X86HWAddressSanitizer::x86_stub();
let tag = hwasan.tag_allocation(0xE000, 64);
assert_eq!(tag, 0x42);
assert_eq!(hwasan.tags_applied, 1);
}
#[test]
fn test_hwasan_check_access() {
let mut hwasan = X86HWAddressSanitizer::x86_stub();
assert!(hwasan.check_access(0xE000, 16));
}
#[test]
fn test_safe_stack_to_flags() {
let ss = X86SafeStackIntegration::disabled();
let flags = ss.to_flags();
assert!(flags.is_empty());
}
#[test]
fn test_shadow_call_stack_to_flags() {
let scs = X86ShadowCallStack::disabled();
let flags = scs.to_flags();
assert!(flags.is_empty());
}
#[test]
fn test_cfi_to_flags_full() {
let cfi = X86CFIIntegration::full();
let flags = cfi.to_flags();
assert!(flags.iter().any(|f| f.contains("cfi")));
assert!(flags.iter().any(|f| f.contains("cross-dso")));
}
#[test]
fn test_cfi_to_flags_disabled() {
let cfi = X86CFIIntegration::disabled();
let flags = cfi.to_flags();
assert!(flags.is_empty());
}
#[test]
fn test_kcfi_to_flags() {
let kcfi = X86KCFIIntegration::kernel_module();
let flags = kcfi.to_flags();
assert!(flags.iter().any(|f| f.contains("kcfi")));
}
#[test]
fn test_kcfi_hash_fnv1a() {
let mut kcfi = X86KCFIIntegration::kernel_module();
kcfi.hash_algo = X86KCFIHashAlgo::FNV1a32;
let hash = kcfi.compute_type_hash("void bar(void)");
assert!(hash != 0);
}
#[test]
fn test_kcfi_hash_xxhash32() {
let mut kcfi = X86KCFIIntegration::kernel_module();
kcfi.hash_algo = X86KCFIHashAlgo::XxHash32;
let hash = kcfi.compute_type_hash("int handler(char*)");
assert!(hash != 0);
}
#[test]
fn test_kcfi_validate_call_fail() {
let mut kcfi = X86KCFIIntegration::kernel_module();
kcfi.register_function("foo", "void foo(int)");
assert!(!kcfi.validate_call("foo", 0xDEADBEEF));
assert_eq!(kcfi.violations_detected, 1);
}
#[test]
fn test_mitigation_flags_fuzzing_friendly() {
let flags = X86MitigationFlags::fuzzing_friendly(X86ArchVariant::X86_64);
assert!(!flags.stack_protector_strong);
assert!(!flags.cet_ibt);
assert!(!flags.fortify_source_2);
}
#[test]
fn test_mitigation_flags_production() {
let flags = X86MitigationFlags::production(X86ArchVariant::X86_64);
assert!(flags.production_mode);
assert!(flags.zero_call_used_regs_used);
}
#[test]
fn test_mitigation_flags_canary_custom() {
let mut flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
flags.canary_value = Some(0xCAFED00DDEADBEEF);
assert_eq!(flags.generate_canary(), 0xCAFED00DDEADBEEF);
}
#[test]
fn test_mitigation_flags_no_strict_overflow() {
let mut flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
flags.no_strict_overflow = true;
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-fno-strict-overflow".to_string()));
}
#[test]
fn test_mitigation_flags_keep_null_checks() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-fno-delete-null-pointer-checks".to_string()));
}
#[test]
fn test_mitigation_flags_zero_call_used_regs_all() {
let mut flags = X86MitigationFlags::maximum(X86ArchVariant::X86_64);
flags.zero_call_used_regs_all = true;
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-fzero-call-used-regs=all".to_string()));
}
#[test]
fn test_mitigation_flags_fortify_source_1() {
let mut flags = X86MitigationFlags::minimal(X86ArchVariant::X86_64);
flags.fortify_source_1 = true;
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-D_FORTIFY_SOURCE=1".to_string()));
}
#[test]
fn test_mitigation_flags_fortify_source_3() {
let flags = X86MitigationFlags::maximum(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-D_FORTIFY_SOURCE=3".to_string()));
}
#[test]
fn test_mitigation_flags_pie_pic() {
let mut flags = X86MitigationFlags::minimal(X86ArchVariant::X86_64);
flags.enable_pic = true;
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-fPIC".to_string()));
}
#[test]
fn test_mitigation_flags_separate_code() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-Wl,-z,separate-code".to_string()));
}
#[test]
fn test_mitigation_flags_no_common() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-fno-common".to_string()));
}
#[test]
fn test_mitigation_flags_no_dlopen_dldump() {
let flags = X86MitigationFlags::maximum(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-Wl,-z,nodlopen".to_string()));
assert!(cf.contains(&"-Wl,-z,nodump".to_string()));
}
#[test]
fn test_mitigation_flags_werror() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let cf = flags.to_clang_flags();
assert!(cf.contains(&"-Werror=format-security".to_string()));
assert!(cf.contains(&"-Werror=implicit-function-declaration".to_string()));
}
#[test]
fn test_fuzzing_custom_mutator_declaration() {
let fuzzing = X86FuzzingSupport::full_enabled();
let decl = fuzzing.custom_mutator_declaration();
assert!(decl.contains("LLVMFuzzerCustomMutator"));
}
#[test]
fn test_fuzzing_custom_crossover_declaration() {
let fuzzing = X86FuzzingSupport::full_enabled();
let decl = fuzzing.custom_crossover_declaration();
assert!(decl.contains("LLVMFuzzerCustomCrossOver"));
}
#[test]
fn test_fuzzing_initialize_declaration() {
let fuzzing = X86FuzzingSupport::full_enabled();
let decl = fuzzing.initialize_declaration();
assert!(decl.contains("LLVMFuzzerInitialize"));
}
#[test]
fn test_fuzzing_summary() {
let fuzzing = X86FuzzingSupport::full_enabled();
let summary = fuzzing.summary();
assert!(summary.contains("libFuzzer"));
assert!(summary.contains("AFL"));
assert!(summary.contains("Honggfuzz"));
}
#[test]
fn test_fuzzing_afl_mode_to_clang_flag() {
assert_eq!(X86AFLMode::Classic.to_clang_flag(), "afl");
assert_eq!(X86AFLMode::LLVM.to_clang_flag(), "afl-clang-fast");
assert_eq!(X86AFLMode::LLVMLTO.to_clang_flag(), "afl-lto");
assert_eq!(X86AFLMode::NeverZero.to_clang_flag(), "afl-never-zero");
}
#[test]
fn test_vuln_checkers_strcat() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("strcat", "vuln.c:99");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_sprintf() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("sprintf", "vuln.c:100");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_scanf() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("scanf", "vuln.c:101");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_fprintf() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("fprintf", "vuln.c:102");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_syslog() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("syslog", "vuln.c:103");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_popen() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("popen", "vuln.c:104");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_execl_execlp() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
assert!(checkers
.check_function_call("execl", "vuln.c:105")
.is_some());
assert!(checkers
.check_function_call("execlp", "vuln.c:106")
.is_some());
}
#[test]
fn test_vuln_checkers_open_fopen() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
assert!(checkers.check_function_call("open", "vuln.c:107").is_some());
assert!(checkers
.check_function_call("fopen", "vuln.c:108")
.is_some());
}
#[test]
fn test_vuln_checkers_malloc() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
let warning = checkers.check_function_call("malloc", "vuln.c:109");
assert!(warning.is_some());
}
#[test]
fn test_vuln_checkers_vsprintf() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
assert!(checkers
.check_function_call("vsprintf", "vuln.c:110")
.is_some());
}
#[test]
fn test_vuln_checkers_production() {
let checkers = X86VulnerabilityCheckers::production();
assert!(checkers.buffer_overflow);
assert!(checkers.use_after_free);
assert!(!checkers.race_condition);
assert!(!checkers.path_traversal);
}
#[test]
fn test_vuln_checkers_warnings_by_severity() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
checkers.check_function_call("gets", "test.c:1");
checkers.check_function_call("system", "test.c:2");
checkers.check_function_call("printf", "test.c:3");
let by_severity = checkers.warnings_by_severity();
assert!(by_severity.contains_key(&X86VulnSeverity::Critical));
assert!(by_severity.contains_key(&X86VulnSeverity::High));
}
#[test]
fn test_vuln_checkers_sarif_report() {
let mut checkers = X86VulnerabilityCheckers::all_enabled();
checkers.check_function_call("gets", "test.c:1");
let sarif = checkers.generate_sarif_report();
assert!(sarif.contains("sarif"));
assert!(sarif.contains("CWE-120"));
}
#[test]
fn test_dangerous_functions_safe_alternatives() {
for df in DANGEROUS_FUNCTIONS {
assert!(!df.safe_alternative.is_empty());
assert!(!df.name.is_empty());
assert_ne!(df.vuln_type.default_cwe(), 0);
}
}
#[test]
fn test_secure_coding_misra_rules() {
let rules = X86SecureCoding::get_rules(X86CodingStandard::MISRA_C_2012);
assert!(!rules.is_empty());
assert!(rules.iter().any(|(id, _)| *id == "Rule 1.3"));
}
#[test]
fn test_secure_coding_cwe_empty_mapping() {
let rules = X86SecureCoding::cwe_to_cert_c(99999);
assert!(rules.is_empty());
}
#[test]
fn test_secure_coding_cpp_rules_count() {
let rules = X86SecureCoding::get_rules(X86CodingStandard::CERT_CPP);
assert_eq!(rules.len(), 9);
}
#[test]
fn test_secure_coding_misra_rule_count() {
let rules = X86SecureCoding::get_rules(X86CodingStandard::MISRA_C_2012);
assert_eq!(rules.len(), 16);
}
#[test]
fn test_secure_coding_standard_name() {
assert_eq!(X86CodingStandard::CERT_C.name(), "CERT C");
assert_eq!(X86CodingStandard::AUTOSAR_CPP14.name(), "AUTOSAR C++14");
}
#[test]
fn test_severity_ordering() {
assert!(X86VulnSeverity::Critical > X86VulnSeverity::High);
assert!(X86VulnSeverity::High > X86VulnSeverity::Medium);
assert!(X86VulnSeverity::Medium > X86VulnSeverity::Low);
assert!(X86VulnSeverity::Low > X86VulnSeverity::Info);
}
#[test]
fn test_aes_ni_aesdec() {
let state = [0xFFu8; 16];
let key = [0xAAu8; 16];
let result = aes_ni::aesdec(&state, &key);
assert_eq!(result.len(), 16);
}
#[test]
fn test_aes_ni_aesdeclast() {
let state = [0xFFu8; 16];
let key = [0xAAu8; 16];
let result = aes_ni::aesdeclast(&state, &key);
assert_eq!(result.len(), 16);
}
#[test]
fn test_aes_ni_aesimc() {
let key = [0x2Bu8; 16];
let result = aes_ni::aesimc(&key);
assert_eq!(result.len(), 16);
}
#[test]
fn test_shani_sha1rnds4() {
let state: sha_ni::Sha1State = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
let wk = [0u32; 4];
let result = sha_ni::sha1rnds4(&state, &wk, 0);
assert_eq!(result.len(), 5);
}
#[test]
fn test_shani_sha1nexte() {
let wk = [0x01020304u32; 4];
let msg = [0x05060708u32; 4];
let result = sha_ni::sha1nexte(&wk, &msg);
assert_eq!(result.len(), 4);
}
#[test]
fn test_shani_sha256msg2() {
let w0 = [0xAABBCCDDu32; 4];
let w1 = [0x11223344u32; 4];
let result = sha_ni::sha256msg2(&w0, &w1);
assert_eq!(result.len(), 4);
}
#[test]
fn test_aes_gcm_encrypt() {
let crypto = X86CryptoIntrinsics::default();
let key = [0x01u8; 16];
let nonce = [0x02u8; 12];
let pt = b"test message";
let aad = b"auth data";
let (ct, tag) = crypto.aes_gcm_encrypt(&key, &nonce, pt, aad);
assert_eq!(ct.len(), pt.len());
assert_eq!(tag.len(), 16);
}
#[test]
fn test_crc32c_slice_empty() {
let crc = crc32_intrinsics::crc32c_slice(0, b"");
assert_eq!(crc, 0);
}
#[test]
fn test_crc32c_slice_known() {
let data = b"123456789";
let crc = crc32_intrinsics::crc32c_slice(0, data);
assert!(crc != 0);
}
#[test]
fn test_rdrand16() {
let val = rdrand::rdrand16();
assert!(val.is_some());
}
#[test]
fn test_rdseed32() {
let val = rdrand::rdseed32();
assert!(val.is_some());
}
#[test]
fn test_builder_chain_all() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_asan(true)
.with_ubsan(true)
.with_lsan(true)
.with_cfi(true)
.with_safe_stack(true)
.with_stack_protector("all")
.with_cet("full")
.build();
assert!(sec.sanitizers.asan.enabled);
assert!(sec.sanitizers.ubsan.enabled);
assert!(sec.sanitizers.lsan.enabled);
assert!(sec.sanitizers.cfi.enabled);
assert!(sec.mitigations.stack_protector_all);
assert!(sec.mitigations.cet_full);
}
#[test]
fn test_builder_with_report_format() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_report_format(SecurityReportFormat::Json)
.build();
assert_eq!(sec.report_format, SecurityReportFormat::Json);
}
#[test]
fn test_builder_with_msan() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_msan(true)
.build();
assert!(sec.sanitizers.msan.enabled);
assert!(sec.sanitizers.msan.track_origins);
}
#[test]
fn test_builder_with_tsan() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_tsan(true)
.build();
assert!(sec.sanitizers.tsan.enabled);
}
#[test]
fn test_builder_with_dfsan() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_dfsan(true)
.build();
assert!(sec.sanitizers.dfsan.enabled);
}
#[test]
fn test_builder_with_hwasan() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_hwasan(true)
.build();
assert!(sec.sanitizers.hwasan.enabled);
assert!(!sec.sanitizers.hwasan.use_tbi);
}
#[test]
fn test_builder_with_shadow_call_stack() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_shadow_call_stack(true)
.build();
assert!(sec.sanitizers.shadow_call_stack.enabled);
}
#[test]
fn test_builder_with_kcfi() {
let sec = X86SecurityBuilder::new(X86ArchVariant::X86_64)
.with_kcfi(true)
.build();
assert!(sec.sanitizers.kcfi.enabled);
}
#[test]
fn test_pipeline_with_fuzzing_config() {
let config = X86Security::for_fuzzing(X86ArchVariant::X86_64);
let mut pipeline = X86SecurityPipeline::new(config);
let sources = vec!["fuzz_target.c".to_string(), "harness.c".to_string()];
let result = pipeline.run(&sources);
assert!(result.success);
}
#[test]
fn test_pipeline_production_config() {
let config = X86Security::production(X86ArchVariant::X86_64);
let mut pipeline = X86SecurityPipeline::new(config);
pipeline.run(&["main.c".to_string()]);
let report = pipeline.generate_report();
assert!(report.contains("X86 Security Pipeline Report"));
}
#[test]
fn test_ubsan_check_signed_overflow_constexpr() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_signed_overflow_constexpr = true;
assert!(ubsan.detect_signed_overflow_constexpr);
}
#[test]
fn test_ubsan_check_function_detect() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_function = true;
assert!(ubsan.detect_function);
}
#[test]
fn test_ubsan_check_nonnull_attr() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_nonnull = true;
assert!(ubsan.detect_nonnull);
}
#[test]
fn test_ubsan_check_object_size() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_object_size = true;
assert!(ubsan.detect_object_size);
}
#[test]
fn test_ubsan_check_pointer_overflow_detect() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_pointer_overflow = true;
assert!(ubsan.detect_pointer_overflow);
}
#[test]
fn test_ubsan_check_invalid_builtin() {
let mut ubsan = X86UndefinedBehaviorSanitizer::enabled_default();
ubsan.detect_invalid_builtin = true;
assert!(ubsan.detect_invalid_builtin);
}
#[test]
fn test_crypto_detect_cpuid() {
let crypto = X86CryptoIntrinsics::detect_cpuid();
assert!(crypto.aes_ni);
assert!(crypto.sha_ni);
}
#[test]
fn test_crypto_cache_aes_key() {
let mut crypto = X86CryptoIntrinsics::default();
let key = vec![0x2Bu8; 16];
crypto.cache_aes_key(&key, X86_AES128_ROUNDS);
let cached = crypto.get_aes_key(&key);
assert!(cached.is_some());
}
#[test]
fn test_asan_instrument_atomics_disabled_default() {
let asan = X86AddressSanitizer::disabled();
assert!(!asan.instrument_atomics);
}
#[test]
fn test_asan_instrument_byval_disabled_default() {
let asan = X86AddressSanitizer::disabled();
assert!(!asan.instrument_byval_args);
}
#[test]
fn test_sanitizer_stats_total_checks() {
let stats = X86SanitizerStats::new();
assert_eq!(stats.total_checks(), 0);
stats.asan_checks.store(10, Ordering::Relaxed);
stats.ubsan_checks.store(5, Ordering::Relaxed);
assert_eq!(stats.total_checks(), 15);
}
#[test]
fn test_x86_arch_variant_to_llvm_arch() {
assert_eq!(X86ArchVariant::X86_64.to_llvm_arch(), "x86-64");
assert_eq!(X86ArchVariant::X86_32.to_llvm_arch(), "i686");
assert_eq!(X86ArchVariant::X86_X32.to_llvm_arch(), "x86-64");
}
#[test]
fn test_x86_arch_variant_supports_cet() {
assert!(X86ArchVariant::X86_64.supports_cet());
assert!(X86ArchVariant::X86_32.supports_cet());
}
#[test]
fn test_x86_arch_variant_canary_size() {
assert_eq!(X86ArchVariant::X86_64.canary_size(), 8);
assert_eq!(X86ArchVariant::X86_32.canary_size(), 4);
assert_eq!(X86ArchVariant::X86_16.canary_size(), 2);
}
#[test]
fn test_ubsan_handler_names_count() {
let names = X86UndefinedBehaviorSanitizer::handler_names();
assert_eq!(names.len(), 16);
}
#[test]
fn test_vuln_severity_display() {
assert_eq!(X86VulnSeverity::Critical.as_str(), "CRITICAL");
assert_eq!(X86VulnSeverity::Info.as_str(), "INFO");
assert_eq!(X86VulnSeverity::Medium.as_str(), "MEDIUM");
}
#[test]
fn test_vuln_type_description() {
assert!(X86VulnType::BufferOverflow
.description()
.contains("Buffer overflow"));
assert!(X86VulnType::UseAfterFree
.description()
.contains("Use-after-free"));
assert!(X86VulnType::CommandInjection
.description()
.contains("Command injection"));
}
#[test]
fn test_clmul_ghash_poly_constant() {
assert_eq!(clmul::GHASH_POLY, 0xE1000000000000000000000000000000);
}
#[test]
fn test_crc32_ieee_known() {
let data = b"123456789";
let crc = crc32_intrinsics::crc32_ieee(data);
assert_eq!(crc, 0xCBF43926);
}
#[test]
fn test_x86_security_validate_cet_arch() {
let mut sec = X86Security::new(X86ArchVariant::X86_16);
sec.mitigations.cet_ibt = true;
let errors = sec.validate();
assert!(errors
.iter()
.any(|e| e.kind == SecurityConfigErrorKind::ArchitectureMismatch));
}
#[test]
fn test_x86_security_validate_scs_arch() {
let mut sec = X86Security::new(X86ArchVariant::X86_32);
sec.sanitizers.shadow_call_stack.enabled = true;
let errors = sec.validate();
assert!(errors
.iter()
.any(|e| e.kind == SecurityConfigErrorKind::ArchitectureMismatch));
}
#[test]
fn test_x86_security_result_to_json() {
let mut result = X86SecurityResult::default();
result.success = true;
result.sanitizer_applied = true;
let json = result.to_json();
assert!(json.contains("\"success\":true"));
assert!(json.contains("\"sanitizer\":true"));
}
#[test]
fn test_aes_ni_aesenclast() {
let state = [0x32u8; 16];
let key = [0x2Bu8; 16];
let result = aes_ni::aesenclast(&state, &key);
assert_eq!(result.len(), 16);
}
#[test]
fn test_asan_check_access_zero_size() {
let asan = X86AddressSanitizer::enabled_default();
assert!(!asan.check_access(0, 0));
}
#[test]
fn test_tsan_memory_order_to_llvm() {
assert_eq!(X86TSanMemoryOrder::AcqRel.to_llvm_str(), "acq_rel");
}
#[test]
fn test_msan_check_kind_values() {
let check = X86MSanCheck {
kind: X86MSanCheckKind::Branch,
address: 0x1000,
size: 4,
instrumented: true,
};
assert!(check.instrumented);
}
#[test]
fn test_lsan_root_kinds() {
let root = X86LSanRoot {
start: 0x1000,
size: 4096,
kind: X86LSanRootKind::TLS,
};
assert_eq!(root.kind, X86LSanRootKind::TLS);
}
#[test]
fn test_alloc_kind_str() {
assert_eq!(X86AllocKind::HeapCalloc.as_str(), "heap-calloc");
assert_eq!(X86AllocKind::HeapNew.as_str(), "heap-new");
assert_eq!(X86AllocKind::ThreadLocal.as_str(), "thread-local");
}
#[test]
fn test_redzone_kind_str() {
assert_eq!(X86RedzoneKind::Mid.as_str(), "mid-redzone");
assert_eq!(X86RedzoneKind::UseAfterScope.as_str(), "use-after-scope");
}
#[test]
fn test_cfi_check_kind_values() {
assert_eq!(
X86CFICheckKind::SameSize.to_clang_flag(),
"cfi-unrelated-cast"
);
}
#[test]
fn test_kcfi_hash_algo_names() {
assert_eq!(X86KCFIHashAlgo::Murmur3.name(), "murmur3");
assert_eq!(X86KCFIHashAlgo::HalfSipHash.name(), "halfsiphash");
}
#[test]
fn test_dfsan_combined_union() {
let dfsan = X86DataFlowSanitizer::enabled_default();
let result = dfsan.propagate_label(X86DFSanOp::Phi, &[0x0F, 0xF0, 0x00]);
assert_eq!(result, 0xFF);
}
#[test]
fn test_fuzzing_to_json() {
let fuzzing = X86FuzzingSupport::libfuzzer_only();
let json = fuzzing.to_json();
assert!(json.contains("\"libfuzzer\":true"));
}
#[test]
fn test_mitigation_flags_to_json() {
let flags = X86MitigationFlags::secure_default(X86ArchVariant::X86_64);
let json = flags.to_json();
assert!(json.contains("\"stack_protector\""));
assert!(json.contains("\"nx\""));
}
#[test]
fn test_secure_coding_to_json() {
let sc = X86SecureCoding::default();
let json = sc.to_json();
assert!(json.contains("\"cert_c\""));
}
}
pub struct X86SecurityDriver {
pub security: X86Security,
pub source_files: Vec<String>,
pub output_file: Option<String>,
pub verbose: bool,
}
impl X86SecurityDriver {
pub fn new(security: X86Security) -> Self {
Self {
security,
source_files: Vec::new(),
output_file: None,
verbose: false,
}
}
pub fn add_source(&mut self, file: &str) {
self.source_files.push(file.to_string());
}
pub fn output(&mut self, path: &str) {
self.output_file = Some(path.to_string());
}
pub fn compile(&self) -> X86SecurityCompilationResult {
let start = Instant::now();
let config_errors = self.security.validate();
if !config_errors.is_empty() {
return X86SecurityCompilationResult {
success: false,
files_compiled: 0,
files_failed: self.source_files.len(),
errors: config_errors.iter().map(|e| e.message.clone()).collect(),
warnings: Vec::new(),
compile_time_ms: start.elapsed().as_millis() as u64,
flags_used: Vec::new(),
};
}
let mut flags = Vec::new();
flags.extend(self.security.sanitizers.to_clang_flags());
flags.extend(self.security.mitigations.to_clang_flags());
flags.extend(self.security.fuzzing.to_clang_flags());
flags.extend(self.security.crypto_intrinsics.to_target_flags());
let mut vuln_warnings = Vec::new();
for file in &self.source_files {
if self.security.vulnerability_checkers.has_any() {
for df in DANGEROUS_FUNCTIONS {
let _ = self
.security
.vulnerability_checkers
.clone()
.check_function_call(df.name, file);
}
}
}
X86SecurityCompilationResult {
success: true,
files_compiled: self.source_files.len(),
files_failed: 0,
errors: Vec::new(),
warnings: vuln_warnings,
compile_time_ms: start.elapsed().as_millis() as u64,
flags_used: flags,
}
}
pub fn to_command_line(&self) -> String {
let mut cmd = String::from("clang");
let flags = self.security.to_clang_flags();
for flag in &flags {
cmd.push(' ');
cmd.push_str(flag);
}
for file in &self.source_files {
cmd.push(' ');
cmd.push_str(file);
}
if let Some(ref out) = self.output_file {
cmd.push_str(&format!(" -o {}", out));
}
cmd
}
}
#[derive(Debug, Clone)]
pub struct X86SecurityCompilationResult {
pub success: bool,
pub files_compiled: usize,
pub files_failed: usize,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub compile_time_ms: u64,
pub flags_used: Vec<String>,
}
impl X86SecurityCompilationResult {
pub fn is_clean(&self) -> bool {
self.success && self.errors.is_empty() && self.warnings.is_empty()
}
pub fn summary(&self) -> String {
format!(
"Security Compilation: {} ({} compiled, {} failed, {} errors, {} warnings, {}ms)",
if self.success { "SUCCESS" } else { "FAILED" },
self.files_compiled,
self.files_failed,
self.errors.len(),
self.warnings.len(),
self.compile_time_ms,
)
}
}
#[cfg(test)]
mod driver_tests {
use super::*;
#[test]
fn test_driver_creation() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let driver = X86SecurityDriver::new(sec);
assert!(driver.source_files.is_empty());
}
#[test]
fn test_driver_add_source() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut driver = X86SecurityDriver::new(sec);
driver.add_source("main.c");
driver.add_source("util.c");
assert_eq!(driver.source_files.len(), 2);
}
#[test]
fn test_driver_output() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut driver = X86SecurityDriver::new(sec);
driver.output("a.out");
assert_eq!(driver.output_file.as_deref(), Some("a.out"));
}
#[test]
fn test_driver_compile_basic() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut driver = X86SecurityDriver::new(sec);
driver.add_source("hello.c");
let result = driver.compile();
assert!(result.success);
assert!(result.errors.is_empty());
assert!(!result.flags_used.is_empty());
}
#[test]
fn test_driver_compile_with_invalid_config() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.sanitizers.msan.enabled = true; sec.sanitizers.asan.enabled = true;
let mut driver = X86SecurityDriver::new(sec);
driver.add_source("test.c");
let result = driver.compile();
assert!(!result.success);
assert!(!result.errors.is_empty());
}
#[test]
fn test_driver_to_command_line() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut driver = X86SecurityDriver::new(sec);
driver.add_source("main.c");
driver.output("secure_app");
let cmd = driver.to_command_line();
assert!(cmd.starts_with("clang"));
assert!(cmd.contains("main.c"));
assert!(cmd.contains("-o secure_app"));
assert!(cmd.contains("-fstack-protector-strong"));
}
#[test]
fn test_compilation_result_is_clean() {
let result = X86SecurityCompilationResult {
success: true,
files_compiled: 1,
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 100,
flags_used: vec!["-O2".into()],
};
assert!(result.is_clean());
}
#[test]
fn test_compilation_result_with_warnings() {
let result = X86SecurityCompilationResult {
success: true,
files_compiled: 1,
files_failed: 0,
errors: Vec::new(),
warnings: vec!["unused variable".into()],
compile_time_ms: 50,
flags_used: vec![],
};
assert!(!result.is_clean());
}
#[test]
fn test_compilation_result_summary() {
let result = X86SecurityCompilationResult {
success: true,
files_compiled: 5,
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 250,
flags_used: vec![],
};
let summary = result.summary();
assert!(summary.contains("SUCCESS"));
assert!(summary.contains("5 compiled"));
assert!(summary.contains("250ms"));
}
}
#[derive(Debug, Clone)]
pub struct X86SecureAnnotationEngine {
pub function_exclusions: HashMap<String, X86FunctionSanitizerExclusion>,
pub global_exclusions: X86FunctionSanitizerExclusion,
pub respect_annotations: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86FunctionSanitizerExclusion {
pub no_asan: bool,
pub no_msan: bool,
pub no_tsan: bool,
pub no_ubsan: bool,
pub no_lsan: bool,
pub no_stack_protector: bool,
pub no_cfi: bool,
pub no_safe_stack: bool,
}
impl X86FunctionSanitizerExclusion {
pub fn all_excluded(&self) -> bool {
self.no_asan
&& self.no_msan
&& self.no_tsan
&& self.no_ubsan
&& self.no_lsan
&& self.no_stack_protector
&& self.no_cfi
&& self.no_safe_stack
}
pub fn from_sanitizer_list(list: &str) -> Self {
let mut exclusion = Self::default();
for name in list.split(',') {
match name.trim() {
"address" => exclusion.no_asan = true,
"memory" => exclusion.no_msan = true,
"thread" => exclusion.no_tsan = true,
"undefined" => exclusion.no_ubsan = true,
"leak" => exclusion.no_lsan = true,
_ => {}
}
}
exclusion
}
}
impl X86SecureAnnotationEngine {
pub fn new() -> Self {
Self {
function_exclusions: HashMap::new(),
global_exclusions: X86FunctionSanitizerExclusion::default(),
respect_annotations: true,
}
}
pub fn exclude_function(&mut self, func_name: &str, exclusion: X86FunctionSanitizerExclusion) {
self.function_exclusions
.insert(func_name.to_string(), exclusion);
}
pub fn should_skip_asan(&self, func_name: &str) -> bool {
if !self.respect_annotations {
return false;
}
self.function_exclusions
.get(func_name)
.map(|e| e.no_asan)
.unwrap_or(self.global_exclusions.no_asan)
}
pub fn should_skip_ubsan(&self, func_name: &str) -> bool {
if !self.respect_annotations {
return false;
}
self.function_exclusions
.get(func_name)
.map(|e| e.no_ubsan)
.unwrap_or(self.global_exclusions.no_ubsan)
}
pub fn should_skip_tsan(&self, func_name: &str) -> bool {
if !self.respect_annotations {
return false;
}
self.function_exclusions
.get(func_name)
.map(|e| e.no_tsan)
.unwrap_or(self.global_exclusions.no_tsan)
}
pub fn should_skip_cfi(&self, func_name: &str) -> bool {
if !self.respect_annotations {
return false;
}
self.function_exclusions
.get(func_name)
.map(|e| e.no_cfi)
.unwrap_or(self.global_exclusions.no_cfi)
}
pub fn should_skip_safe_stack(&self, func_name: &str) -> bool {
if !self.respect_annotations {
return false;
}
self.function_exclusions
.get(func_name)
.map(|e| e.no_safe_stack)
.unwrap_or(self.global_exclusions.no_safe_stack)
}
}
impl Default for X86SecureAnnotationEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod annotation_tests {
use super::*;
#[test]
fn test_exclusion_default() {
let exclusion = X86FunctionSanitizerExclusion::default();
assert!(!exclusion.no_asan);
assert!(!exclusion.all_excluded());
}
#[test]
fn test_exclusion_all_excluded() {
let exclusion = X86FunctionSanitizerExclusion {
no_asan: true,
no_msan: true,
no_tsan: true,
no_ubsan: true,
no_lsan: true,
no_stack_protector: true,
no_cfi: true,
no_safe_stack: true,
};
assert!(exclusion.all_excluded());
}
#[test]
fn test_exclusion_from_list() {
let exclusion =
X86FunctionSanitizerExclusion::from_sanitizer_list("address,thread,undefined");
assert!(exclusion.no_asan);
assert!(exclusion.no_tsan);
assert!(exclusion.no_ubsan);
assert!(!exclusion.no_msan);
}
#[test]
fn test_exclusion_from_single() {
let exclusion = X86FunctionSanitizerExclusion::from_sanitizer_list("memory");
assert!(exclusion.no_msan);
assert!(!exclusion.no_asan);
}
#[test]
fn test_annotation_engine_new() {
let engine = X86SecureAnnotationEngine::new();
assert!(engine.respect_annotations);
}
#[test]
fn test_annotation_engine_exclude_function() {
let mut engine = X86SecureAnnotationEngine::new();
let exclusion = X86FunctionSanitizerExclusion::from_sanitizer_list("address");
engine.exclude_function("sensitive_func", exclusion);
assert!(engine.should_skip_asan("sensitive_func"));
assert!(!engine.should_skip_ubsan("sensitive_func"));
}
#[test]
fn test_annotation_engine_respect_disabled() {
let mut engine = X86SecureAnnotationEngine::new();
engine.respect_annotations = false;
let exclusion = X86FunctionSanitizerExclusion::from_sanitizer_list("address");
engine.exclude_function("func", exclusion);
assert!(!engine.should_skip_asan("func"));
}
#[test]
fn test_annotation_engine_global_exclusion() {
let mut engine = X86SecureAnnotationEngine::new();
engine.global_exclusions.no_tsan = true;
assert!(engine.should_skip_tsan("any_func"));
}
#[test]
fn test_annotation_engine_cfi_skip() {
let mut engine = X86SecureAnnotationEngine::new();
let mut exclusion = X86FunctionSanitizerExclusion::default();
exclusion.no_cfi = true;
engine.exclude_function("indirect_call_target", exclusion);
assert!(engine.should_skip_cfi("indirect_call_target"));
assert!(!engine.should_skip_cfi("other_func"));
}
#[test]
fn test_annotation_engine_safe_stack_skip() {
let mut engine = X86SecureAnnotationEngine::new();
let mut exclusion = X86FunctionSanitizerExclusion::default();
exclusion.no_safe_stack = true;
engine.exclude_function("leaf_func", exclusion);
assert!(engine.should_skip_safe_stack("leaf_func"));
}
}
#[derive(Debug)]
pub struct X86SecurityReportGenerator {
pub config: X86Security,
pub include_asan_details: bool,
pub include_ubsan_details: bool,
pub include_vuln_checker_details: bool,
pub include_coding_standard_details: bool,
pub include_crypto_details: bool,
}
impl X86SecurityReportGenerator {
pub fn new(config: X86Security) -> Self {
Self {
config,
include_asan_details: true,
include_ubsan_details: true,
include_vuln_checker_details: true,
include_coding_standard_details: true,
include_crypto_details: true,
}
}
pub fn generate_markdown(&self) -> String {
let mut md = String::new();
md.push_str("# X86 Security Configuration Report\n\n");
md.push_str(&format!(
"## Architecture\n\n- **Variant:** `{:?}`\n",
self.config.arch
));
md.push_str(&format!(
"- **Pointer Size:** {} bytes\n",
self.config.arch.pointer_size()
));
md.push_str(&format!(
"- **ASan Shadow Offset:** `0x{:x}`\n\n",
self.config.arch.asan_shadow_offset()
));
md.push_str("## Sanitizers\n\n");
md.push_str("| Sanitizer | Enabled | Details |\n");
md.push_str("|-----------|---------|----------|\n");
let si = &self.config.sanitizers;
md.push_str(&format!(
"| ASan | {} | shadow=0x{:x} |\n",
si.asan.enabled, si.asan.shadow_offset
));
md.push_str(&format!(
"| MSan | {} | origins={} |\n",
si.msan.enabled, si.msan.track_origins
));
md.push_str(&format!("| TSan | {} | |\n", si.tsan.enabled));
md.push_str(&format!(
"| UBSan | {} | checks={} |\n",
si.ubsan.enabled,
si.ubsan.to_check_list().len()
));
md.push_str(&format!("| LSan | {} | |\n", si.lsan.enabled));
md.push_str(&format!("| DFSan | {} | |\n", si.dfsan.enabled));
md.push_str(&format!("| HWASan | {} | x86-stub |\n", si.hwasan.enabled));
md.push_str(&format!("| SafeStack | {} | |\n", si.safe_stack.enabled));
md.push_str(&format!(
"| ShadowCallStack | {} | |\n",
si.shadow_call_stack.enabled
));
md.push_str(&format!(
"| CFI | {} | cross-dso={} |\n",
si.cfi.enabled, si.cfi.cross_dso
));
md.push_str(&format!("| KCFI | {} | |\n\n", si.kcfi.enabled));
md.push_str("## Mitigations\n\n");
let mf = &self.config.mitigations;
md.push_str(&format!(
"- **Stack Protector:** {}\n",
if mf.stack_protector_all {
"all"
} else if mf.stack_protector_strong {
"strong"
} else if mf.stack_protector {
"basic"
} else {
"disabled"
}
));
md.push_str(&format!(
"- **CET IBT:** {} | **CET SHSTK:** {} | **CET Full:** {}\n",
mf.cet_ibt, mf.cet_shstk, mf.cet_full
));
md.push_str(&format!(
"- **PIE:** {} | **Full RELRO:** {} | **NX Stack:** {}\n",
mf.enable_pie, mf.relro_full, mf.nx_stack
));
md.push_str(&format!(
"- **FORTIFY_SOURCE:** {}\n",
if mf.fortify_source_3 {
"3"
} else if mf.fortify_source_2 {
"2"
} else if mf.fortify_source_1 {
"1"
} else {
"disabled"
}
));
md.push_str("\n## Fuzzing Support\n\n");
let fz = &self.config.fuzzing;
md.push_str(&format!("- **Enabled:** {}\n", fz.enabled));
md.push_str(&format!("- **libFuzzer:** {}\n", fz.libfuzzer));
md.push_str(&format!("- **AFL:** {}\n", fz.afl));
md.push_str(&format!("- **Honggfuzz:** {}\n", fz.honggfuzz));
md.push_str(&format!("- **SanCov:** {}\n", fz.sancov_flags()));
md.push_str("\n## Cryptographic Intrinsics\n\n");
let ci = &self.config.crypto_intrinsics;
md.push_str(&format!("- **AES-NI:** {}\n", ci.aes_ni));
md.push_str(&format!("- **SHA-NI:** {}\n", ci.sha_ni));
md.push_str(&format!("- **CLMUL:** {}\n", ci.clmul));
md.push_str(&format!(
"- **RDRAND:** {} | **RDSEED:** {}\n",
ci.rdrand, ci.rdseed
));
md.push_str(&format!("- **CRC32:** {}\n", ci.crc32));
md.push_str("\n## Secure Coding Standards\n\n");
let sc = &self.config.secure_coding;
md.push_str(&format!(
"- **CERT C:** {} | **CERT C++:** {}\n",
sc.cert_c, sc.cert_cpp
));
md.push_str(&format!(
"- **MISRA C:** {} | **MISRA C++:** {}\n",
sc.misra_c, sc.misra_cpp
));
md.push_str(&format!(
"- **AUTOSAR C++14:** {} | **CWE Mapping:** {}\n",
sc.autosar_cpp14, sc.cwe_mapping
));
md.push_str("\n---\n*Report generated by X86SecurityReportGenerator*\n");
md
}
pub fn generate_json(&self) -> String {
self.config.generate_report()
}
pub fn generate_kv(&self) -> String {
let mut kv = String::new();
kv.push_str(&format!("arch: {:?}\n", self.config.arch));
kv.push_str(&format!("enabled: {}\n", self.config.enabled));
kv.push_str(&format!("asan: {}\n", self.config.sanitizers.asan.enabled));
kv.push_str(&format!(
"ubsan: {}\n",
self.config.sanitizers.ubsan.enabled
));
kv.push_str(&format!(
"stack_protector: {}\n",
self.config.mitigations.stack_protector_strong
));
kv.push_str(&format!("pie: {}\n", self.config.mitigations.enable_pie));
kv.push_str(&format!("relro: {}\n", self.config.mitigations.relro_full));
kv.push_str(&format!("nx: {}\n", self.config.mitigations.nx_stack));
kv
}
}
#[cfg(test)]
mod report_tests {
use super::*;
#[test]
fn test_report_markdown() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let gen = X86SecurityReportGenerator::new(sec);
let md = gen.generate_markdown();
assert!(md.contains("# X86 Security Configuration Report"));
assert!(md.contains("## Architecture"));
assert!(md.contains("## Sanitizers"));
assert!(md.contains("## Mitigations"));
assert!(md.contains("## Fuzzing Support"));
assert!(md.contains("## Cryptographic Intrinsics"));
assert!(md.contains("## Secure Coding Standards"));
}
#[test]
fn test_report_markdown_fuzzing_config() {
let sec = X86Security::for_fuzzing(X86ArchVariant::X86_64);
let gen = X86SecurityReportGenerator::new(sec);
let md = gen.generate_markdown();
assert!(md.contains("true")); }
#[test]
fn test_report_kv() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let gen = X86SecurityReportGenerator::new(sec);
let kv = gen.generate_kv();
assert!(kv.contains("arch:"));
assert!(kv.contains("enabled:"));
assert!(kv.contains("pie:"));
}
#[test]
fn test_report_json() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let gen = X86SecurityReportGenerator::new(sec);
let json = gen.generate_json();
assert!(json.contains("{"));
}
}
#[derive(Debug)]
pub struct X86SecurityToolchain {
pub global_config: X86Security,
pub file_overrides: HashMap<String, X86Security>,
pub annotations: X86SecureAnnotationEngine,
pub report_generator: X86SecurityReportGenerator,
pub log: Vec<String>,
pub pipeline_results: Vec<X86SecurityCompilationResult>,
}
impl X86SecurityToolchain {
pub fn new(global_config: X86Security) -> Self {
let report_generator = X86SecurityReportGenerator::new(global_config.clone());
Self {
global_config: global_config.clone(),
file_overrides: HashMap::new(),
annotations: X86SecureAnnotationEngine::new(),
report_generator,
log: Vec::new(),
pipeline_results: Vec::new(),
}
}
pub fn override_file(&mut self, file: &str, config: X86Security) {
self.file_overrides.insert(file.to_string(), config);
}
pub fn config_for_file(&self, file: &str) -> &X86Security {
self.file_overrides.get(file).unwrap_or(&self.global_config)
}
pub fn build(&mut self, files: &[String]) -> X86SecurityBuildResult {
self.log.clear();
self.pipeline_results.clear();
let start = Instant::now();
let mut total_files = 0;
let mut failed_files = 0;
let mut all_flags: HashSet<String> = HashSet::new();
let mut all_warnings: Vec<String> = Vec::new();
self.log
.push(format!("Starting security build for {} files", files.len()));
for file in files {
let config = self.config_for_file(file);
let driver = X86SecurityDriver {
security: config.clone(),
source_files: vec![file.clone()],
output_file: None,
verbose: false,
};
let result = driver.compile();
total_files += result.files_compiled;
failed_files += result.files_failed;
for flag in &result.flags_used {
all_flags.insert(flag.clone());
}
all_warnings.extend(result.warnings.clone());
self.pipeline_results.push(result);
self.log.push(format!(
" {} -> {}",
file,
if result.success { "OK" } else { "FAIL" }
));
}
let elapsed = start.elapsed().as_millis() as u64;
self.log.push(format!(
"Build complete: {} files, {} failed, {}ms",
total_files, failed_files, elapsed
));
X86SecurityBuildResult {
success: failed_files == 0,
total_files,
failed_files,
elapsed_ms: elapsed,
flags_used: all_flags.into_iter().collect(),
warnings: all_warnings,
log: self.log.clone(),
}
}
pub fn audit_report(&self) -> String {
let mut report = String::new();
report.push_str("╔══════════════════════════════════════╗\n");
report.push_str("║ X86 Security Toolchain Audit ║\n");
report.push_str("╠══════════════════════════════════════╣\n");
report.push_str(&format!(
"║ Architecture: {:?} ║\n",
self.global_config.arch
));
report.push_str(&format!(
"║ ASan: {:5} MSan: {:5} ║\n",
self.global_config.sanitizers.asan.enabled, self.global_config.sanitizers.msan.enabled
));
report.push_str(&format!(
"║ TSan: {:5} UBSan: {:4} ║\n",
self.global_config.sanitizers.tsan.enabled, self.global_config.sanitizers.ubsan.enabled
));
report.push_str(&format!(
"║ CFI: {:5} KCFI: {:4} ║\n",
self.global_config.sanitizers.cfi.enabled, self.global_config.sanitizers.kcfi.enabled
));
report.push_str(&format!(
"║ Stack Protector: {:12} ║\n",
if self.global_config.mitigations.stack_protector_all {
"all"
} else if self.global_config.mitigations.stack_protector_strong {
"strong"
} else if self.global_config.mitigations.stack_protector {
"basic"
} else {
"none"
}
));
report.push_str(&format!(
"║ PIE: {:5} RELRO: {:4} ║\n",
self.global_config.mitigations.enable_pie,
if self.global_config.mitigations.relro_full {
"full"
} else if self.global_config.mitigations.relro_partial {
"partial"
} else {
"none"
}
));
report.push_str(&format!(
"║ NX: {:6} CET: {:5} ║\n",
self.global_config.mitigations.nx_stack, self.global_config.mitigations.cet_full
));
report.push_str("╚══════════════════════════════════════╝\n");
report.push_str(&self.report_generator.generate_markdown());
report
}
pub fn print_audit(&self) {
println!("{}", self.audit_report());
}
}
#[derive(Debug, Clone)]
pub struct X86SecurityBuildResult {
pub success: bool,
pub total_files: usize,
pub failed_files: usize,
pub elapsed_ms: u64,
pub flags_used: Vec<String>,
pub warnings: Vec<String>,
pub log: Vec<String>,
}
impl X86SecurityBuildResult {
pub fn empty() -> Self {
Self {
success: true,
total_files: 0,
failed_files: 0,
elapsed_ms: 0,
flags_used: Vec::new(),
warnings: Vec::new(),
log: Vec::new(),
}
}
pub fn is_clean(&self) -> bool {
self.success && self.failed_files == 0 && self.warnings.is_empty()
}
}
#[cfg(test)]
mod toolchain_tests {
use super::*;
#[test]
fn test_toolchain_new() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let tc = X86SecurityToolchain::new(sec);
assert!(tc.file_overrides.is_empty());
assert!(tc.log.is_empty());
assert!(tc.pipeline_results.is_empty());
}
#[test]
fn test_toolchain_override_file() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut tc = X86SecurityToolchain::new(sec);
let mut override_cfg = X86Security::new(X86ArchVariant::X86_64);
override_cfg.sanitizers.asan.enabled = false;
tc.override_file("legacy.c", override_cfg);
let cfg = tc.config_for_file("legacy.c");
assert!(!cfg.sanitizers.asan.enabled);
}
#[test]
fn test_toolchain_config_for_file_default() {
let mut sec = X86Security::new(X86ArchVariant::X86_64);
sec.sanitizers.ubsan.enabled = true;
let tc = X86SecurityToolchain::new(sec);
let cfg = tc.config_for_file("other.c");
assert!(cfg.sanitizers.ubsan.enabled);
}
#[test]
fn test_toolchain_build() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut tc = X86SecurityToolchain::new(sec);
let files = vec!["main.c".to_string(), "helper.c".to_string()];
let result = tc.build(&files);
assert!(result.success);
assert_eq!(result.total_files, 2);
assert!(!result.flags_used.is_empty());
}
#[test]
fn test_toolchain_build_with_override() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let mut tc = X86SecurityToolchain::new(sec);
let mut buggy_cfg = X86Security::new(X86ArchVariant::X86_64);
buggy_cfg.sanitizers.asan.enabled = true;
buggy_cfg.sanitizers.msan.enabled = true; tc.override_file("buggy.c", buggy_cfg);
let files = vec!["main.c".to_string(), "buggy.c".to_string()];
let result = tc.build(&files);
assert!(!result.success);
}
#[test]
fn test_toolchain_audit_report() {
let sec = X86Security::new(X86ArchVariant::X86_64);
let tc = X86SecurityToolchain::new(sec);
let report = tc.audit_report();
assert!(report.contains("X86 Security Toolchain Audit"));
assert!(report.contains("Architecture"));
}
#[test]
fn test_build_result_empty() {
let result = X86SecurityBuildResult::empty();
assert!(result.is_clean());
assert_eq!(result.total_files, 0);
}
#[test]
fn test_build_result_with_warnings() {
let mut result = X86SecurityBuildResult::empty();
result.warnings.push("test warning".into());
assert!(!result.is_clean());
}
}