#![allow(non_upper_case_globals, dead_code)]
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[derive(Debug, Clone)]
pub struct X86SanCommonFlags {
pub halt_on_error: bool,
pub verbosity: u32,
pub suppress_equal_pcs: bool,
pub stack_trace_depth: u32,
pub symbolize: bool,
pub external_symbolizer_path: Option<String>,
pub log_path: Option<String>,
pub strip_path_prefix: Option<String>,
pub fast_unwind: bool,
pub detect_container_overflow: bool,
pub print_cmdline: bool,
pub coverage: bool,
pub coverage_dir: Option<String>,
pub malloc_context_size: u32,
pub handle_segv: bool,
pub handle_sigbus: bool,
pub handle_sigfpe: bool,
pub handle_sigill: bool,
pub handle_abort: bool,
pub handle_sigabrt: bool,
}
impl Default for X86SanCommonFlags {
fn default() -> Self {
Self {
halt_on_error: false,
verbosity: 0,
suppress_equal_pcs: true,
stack_trace_depth: 64,
symbolize: true,
external_symbolizer_path: None,
log_path: None,
strip_path_prefix: None,
fast_unwind: true,
detect_container_overflow: true,
print_cmdline: false,
coverage: false,
coverage_dir: None,
malloc_context_size: 30,
handle_segv: true,
handle_sigbus: true,
handle_sigfpe: true,
handle_sigill: false,
handle_abort: false,
handle_sigabrt: false,
}
}
}
#[derive(Debug)]
pub struct X86SanAllocator {
pub allocations: HashMap<usize, X86AllocMeta>,
pub quarantine: VecDeque<X86AllocMeta>,
pub quarantine_max_size: usize,
pub total_allocated: AtomicU64,
pub total_freed: AtomicU64,
pub current_bytes: AtomicU64,
next_alloc_id: AtomicU64,
}
#[derive(Debug, Clone)]
pub struct X86AllocMeta {
pub id: u64,
pub ptr: usize,
pub total_size: usize,
pub requested_size: usize,
pub is_freed: bool,
pub alloc_stack: X86StackTrace,
pub free_stack: Option<X86StackTrace>,
pub thread_id: u64,
}
impl Default for X86SanAllocator {
fn default() -> Self {
Self {
allocations: HashMap::new(),
quarantine: VecDeque::new(),
quarantine_max_size: 256 * 1024 * 1024, total_allocated: AtomicU64::new(0),
total_freed: AtomicU64::new(0),
current_bytes: AtomicU64::new(0),
next_alloc_id: AtomicU64::new(1),
}
}
}
impl X86SanAllocator {
pub fn new() -> Self {
Self::default()
}
pub fn alloc(&mut self, size: usize, align: usize) -> *mut u8 {
let redzone_size = 32; let total = size + 2 * redzone_size;
let layout =
std::alloc::Layout::from_size_align(total, align.max(16)).expect("san alloc layout");
let raw = unsafe { std::alloc::alloc(layout) };
if raw.is_null() {
return std::ptr::null_mut();
}
let user_ptr = unsafe { raw.add(redzone_size) };
let id = self.next_alloc_id.fetch_add(1, Ordering::SeqCst);
let meta = X86AllocMeta {
id,
ptr: user_ptr as usize,
total_size: total,
requested_size: size,
is_freed: false,
alloc_stack: X86StackTrace::capture(),
free_stack: None,
thread_id: 0,
};
self.allocations.insert(user_ptr as usize, meta);
self.total_allocated
.fetch_add(size as u64, Ordering::SeqCst);
self.current_bytes.fetch_add(size as u64, Ordering::SeqCst);
user_ptr
}
pub fn free(&mut self, ptr: *mut u8) {
if ptr.is_null() {
return;
}
if let Some(meta) = self.allocations.get_mut(&(ptr as usize)) {
if meta.is_freed {
self.report_double_free(ptr);
return;
}
meta.is_freed = true;
meta.free_stack = Some(X86StackTrace::capture());
self.total_freed
.fetch_add(meta.requested_size as u64, Ordering::SeqCst);
self.current_bytes
.fetch_sub(meta.requested_size as u64, Ordering::SeqCst);
self.quarantine.push_back(meta.clone());
self.drain_quarantine();
} else {
self.report_bad_free(ptr);
}
}
fn drain_quarantine(&mut self) {
while self.quarantine_size() > self.quarantine_max_size {
if let Some(meta) = self.quarantine.pop_front() {
let raw = unsafe { (meta.ptr as *mut u8).sub(32) };
let layout = std::alloc::Layout::from_size_align(meta.total_size, 16)
.expect("san dealloc layout");
unsafe {
std::alloc::dealloc(raw, layout);
}
self.allocations.remove(&meta.ptr);
}
}
}
fn quarantine_size(&self) -> usize {
self.quarantine.iter().map(|m| m.total_size).sum()
}
fn report_double_free(&self, _ptr: *mut u8) {
eprintln!("==ERROR: AddressSanitizer: attempting double-free");
}
fn report_bad_free(&self, _ptr: *mut u8) {
eprintln!("==ERROR: AddressSanitizer: attempting free on address not malloc-ed");
}
}
#[derive(Debug, Clone, Default)]
pub struct X86StackTrace {
pub frames: Vec<usize>,
pub size: u32,
}
impl X86StackTrace {
pub fn capture() -> Self {
Self {
frames: Vec::new(),
size: 0,
}
}
pub fn capture_with_depth(depth: u32) -> Self {
let mut st = Self::capture();
if st.size > depth {
st.frames.truncate(depth as usize);
st.size = depth;
}
st
}
pub fn symbolize(&self) -> Vec<String> {
self.frames
.iter()
.map(|_f| "<unknown>".to_string())
.collect()
}
}
pub const ASAN_SHADOW_SCALE: usize = 3;
pub const ASAN_SHADOW_GRANULARITY: usize = 8;
pub const ASAN_SHADOW_OFFSET_64: u64 = 0x00007fff8000;
pub const ASAN_SHADOW_MASK: u64 = 0x00007fffffffffff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86AsanShadow(pub u8);
impl X86AsanShadow {
pub const ADDRESSABLE: Self = Self(0x00);
pub const fn partially_addressable(k: u8) -> Self {
Self(k)
}
pub const HEAP_LEFT_RZ: Self = Self(0xfa);
pub const HEAP_RIGHT_RZ: Self = Self(0xfb);
pub const HEAP_FREED: Self = Self(0xfd);
pub const STACK_LEFT_RZ: Self = Self(0xf1);
pub const STACK_MID_RZ: Self = Self(0xf2);
pub const STACK_RIGHT_RZ: Self = Self(0xf3);
pub const STACK_FREED: Self = Self(0xf5);
pub const STACK_USE_AFTER_RETURN: Self = Self(0xf8);
pub const GLOBAL_RZ: Self = Self(0xf9);
pub const HIGH_SHADOW: Self = Self(0xff);
pub fn is_addressable(&self) -> bool {
self.0 == 0
}
pub fn is_poisoned(&self) -> bool {
!self.is_addressable()
}
pub fn description(&self) -> &'static str {
match self.0 {
0x00 => "addressable",
0xfa => "heap left redzone",
0xfb => "heap right redzone",
0xfd => "freed heap",
0xf1 => "stack left redzone",
0xf2 => "stack mid redzone",
0xf3 => "stack right redzone",
0xf5 => "freed stack",
0xf8 => "stack use-after-return",
0xf9 => "global redzone",
0xff => "inaccessible",
n if n < 8 => "partially addressable",
_ => "unknown poison",
}
}
}
impl fmt::Display for X86AsanShadow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
#[derive(Debug)]
pub struct X86AsanShadowMemory {
pub shadow: Vec<u8>,
pub shadow_base: u64,
}
impl Default for X86AsanShadowMemory {
fn default() -> Self {
Self {
shadow: vec![0u8; 1 << 29], shadow_base: ASAN_SHADOW_OFFSET_64,
}
}
}
impl X86AsanShadowMemory {
pub fn new() -> Self {
Self::default()
}
pub fn shadow_addr(addr: usize) -> u64 {
((addr as u64) >> ASAN_SHADOW_SCALE).wrapping_add(ASAN_SHADOW_OFFSET_64)
}
pub fn get(&self, addr: usize) -> X86AsanShadow {
let saddr = Self::shadow_addr(addr);
let idx = (saddr.wrapping_sub(self.shadow_base)) as usize;
if idx < self.shadow.len() {
X86AsanShadow(self.shadow[idx])
} else {
X86AsanShadow::HIGH_SHADOW
}
}
pub fn set_range(&mut self, addr: usize, size: usize, value: X86AsanShadow) {
for i in 0..size {
let a = addr + i;
let saddr = Self::shadow_addr(a);
let idx = (saddr.wrapping_sub(self.shadow_base)) as usize;
if idx < self.shadow.len() {
self.shadow[idx] = value.0;
}
}
}
pub fn poison(&mut self, addr: usize, size: usize, value: X86AsanShadow) {
self.set_range(addr, size, value);
}
pub fn unpoison(&mut self, addr: usize, size: usize) {
self.set_range(addr, size, X86AsanShadow::ADDRESSABLE);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86AsanErrorKind {
HeapBufferOverflow,
HeapBufferUnderflow,
StackBufferOverflow,
StackBufferUnderflow,
GlobalBufferOverflow,
UseAfterFree,
UseAfterReturn,
UseAfterScope,
DoubleFree,
InvalidFree,
MismatchedFree,
InitOrderFiasco,
MemoryLeak,
ContainerOverflow,
ShadowGap,
ODRViolation,
NullDereference,
WildFree,
AllocDeallocMismatch,
BadParamsToAlloc,
DynamicStackAlloc,
}
impl fmt::Display for X86AsanErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HeapBufferOverflow => write!(f, "heap-buffer-overflow"),
Self::HeapBufferUnderflow => write!(f, "heap-buffer-underflow"),
Self::StackBufferOverflow => write!(f, "stack-buffer-overflow"),
Self::StackBufferUnderflow => write!(f, "stack-buffer-underflow"),
Self::GlobalBufferOverflow => write!(f, "global-buffer-overflow"),
Self::UseAfterFree => write!(f, "heap-use-after-free"),
Self::UseAfterReturn => write!(f, "stack-use-after-return"),
Self::UseAfterScope => write!(f, "stack-use-after-scope"),
Self::DoubleFree => write!(f, "attempting double-free"),
Self::InvalidFree => write!(f, "attempting free on invalid pointer"),
Self::MismatchedFree => write!(f, "alloc-dealloc-mismatch"),
Self::InitOrderFiasco => write!(f, "init-order-fiasco"),
Self::MemoryLeak => write!(f, "memory-leak"),
Self::ContainerOverflow => write!(f, "container-overflow"),
Self::ShadowGap => write!(f, "shadow-gap-access"),
Self::ODRViolation => write!(f, "odr-violation"),
Self::NullDereference => write!(f, "null-dereference"),
Self::WildFree => write!(f, "wild-free"),
Self::AllocDeallocMismatch => write!(f, "alloc-dealloc-mismatch"),
Self::BadParamsToAlloc => write!(f, "bad-params-to-alloc"),
Self::DynamicStackAlloc => write!(f, "dynamic-stack-buffer-overflow"),
}
}
}
#[derive(Debug)]
pub struct X86AsanRuntime {
pub shadow: X86AsanShadowMemory,
pub allocator: X86SanAllocator,
pub flags: X86SanCommonFlags,
pub activated: AtomicBool,
pub error_reports: Vec<X86AsanReport>,
pub suppressed: HashSet<u64>,
}
#[derive(Debug, Clone)]
pub struct X86AsanReport {
pub error_kind: X86AsanErrorKind,
pub address: usize,
pub size: usize,
pub access_type: X86AccessType,
pub stack: X86StackTrace,
pub alloc_stack: Option<X86StackTrace>,
pub free_stack: Option<X86StackTrace>,
pub thread_id: u64,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AccessType {
Read,
Write,
Free,
}
impl Default for X86AsanRuntime {
fn default() -> Self {
Self {
shadow: X86AsanShadowMemory::new(),
allocator: X86SanAllocator::new(),
flags: X86SanCommonFlags::default(),
activated: AtomicBool::new(true),
error_reports: Vec::new(),
suppressed: HashSet::new(),
}
}
}
impl X86AsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn activate(&self) {
self.activated.store(true, Ordering::SeqCst);
}
pub fn deactivate(&self) {
self.activated.store(false, Ordering::SeqCst);
}
pub fn check_access(&mut self, addr: usize, size: usize, access: X86AccessType) -> bool {
if !self.activated.load(Ordering::SeqCst) {
return true;
}
if addr == 0 {
self.report_error(X86AsanErrorKind::NullDereference, addr, size, access);
return false;
}
for i in 0..size {
let shadow_val = self.shadow.get(addr + i);
if shadow_val.is_poisoned() {
let kind = self.classify_access(addr + i, shadow_val);
self.report_error(kind, addr + i, size, access);
return false;
}
}
true
}
fn classify_access(&self, addr: usize, shadow: X86AsanShadow) -> X86AsanErrorKind {
match shadow.0 {
0xfa => X86AsanErrorKind::HeapBufferUnderflow,
0xfb => X86AsanErrorKind::HeapBufferOverflow,
0xfd => X86AsanErrorKind::UseAfterFree,
0xf1 | 0xf2 | 0xf3 => X86AsanErrorKind::StackBufferOverflow,
0xf5 => X86AsanErrorKind::UseAfterReturn,
0xf9 => X86AsanErrorKind::GlobalBufferOverflow,
_ => X86AsanErrorKind::ShadowGap,
}
}
fn report_error(
&mut self,
kind: X86AsanErrorKind,
addr: usize,
size: usize,
access: X86AccessType,
) {
let stack = X86StackTrace::capture_with_depth(self.flags.stack_trace_depth);
let hash = self.hash_stack(&stack);
if self.flags.suppress_equal_pcs && !self.suppressed.insert(hash) {
return;
}
let report = X86AsanReport {
error_kind: kind,
address: addr,
size,
access_type: access,
stack,
alloc_stack: None,
free_stack: None,
thread_id: 0,
description: format!("{} of size {} at address {:#x}", kind, size, addr),
};
if self.flags.verbosity >= 1 {
eprintln!(
"=={}== ERROR: AddressSanitizer: {}",
self.flags.verbosity, report.description
);
}
self.error_reports.push(report);
if self.flags.halt_on_error {
std::process::abort();
}
}
fn hash_stack(&self, stack: &X86StackTrace) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for frame in &stack.frames {
frame.hash(&mut hasher);
}
hasher.finish()
}
pub fn malloc(&mut self, size: usize) -> *mut u8 {
let ptr = self.allocator.alloc(size, 8);
if !ptr.is_null() {
let raw = unsafe { ptr.sub(32) };
self.shadow
.poison(raw as usize, 32, X86AsanShadow::HEAP_LEFT_RZ);
self.shadow
.poison(ptr as usize + size, 32, X86AsanShadow::HEAP_RIGHT_RZ);
}
ptr
}
pub fn free(&mut self, ptr: *mut u8) {
if ptr.is_null() {
return;
}
self.shadow.unpoison(ptr as usize, 32);
self.allocator.free(ptr);
self.shadow
.poison(ptr as usize, 32, X86AsanShadow::HEAP_FREED);
}
pub fn memcpy(&mut self, dst: *mut u8, src: *const u8, n: usize) {
self.check_access(dst as usize, n, X86AccessType::Write);
self.check_access(src as usize, n, X86AccessType::Read);
if !dst.is_null() && !src.is_null() && n > 0 {
unsafe {
std::ptr::copy_nonoverlapping(src, dst, n);
}
}
}
pub fn memset(&mut self, dst: *mut u8, val: i32, n: usize) {
self.check_access(dst as usize, n, X86AccessType::Write);
if !dst.is_null() && n > 0 {
unsafe {
std::ptr::write_bytes(dst, val as u8, n);
}
}
}
pub fn realloc(&mut self, old_ptr: *mut u8, new_size: usize) -> *mut u8 {
if old_ptr.is_null() {
return self.malloc(new_size);
}
if new_size == 0 {
self.free(old_ptr);
return std::ptr::null_mut();
}
let new_ptr = self.malloc(new_size);
if !new_ptr.is_null() {
if let Some(meta) = self.allocator.allocations.get(&(old_ptr as usize)) {
let copy_size = meta.requested_size.min(new_size);
self.memcpy(new_ptr, old_ptr, copy_size);
}
self.free(old_ptr);
}
new_ptr
}
pub fn stats(&self) -> X86AsanStats {
X86AsanStats {
total_allocated: self.allocator.total_allocated.load(Ordering::SeqCst),
total_freed: self.allocator.total_freed.load(Ordering::SeqCst),
current_bytes: self.allocator.current_bytes.load(Ordering::SeqCst),
error_count: self.error_reports.len() as u64,
reports_by_kind: self.error_reports.iter().fold(HashMap::new(), |mut m, r| {
*m.entry(r.error_kind).or_insert(0) += 1;
m
}),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86AsanStats {
pub total_allocated: u64,
pub total_freed: u64,
pub current_bytes: u64,
pub error_count: u64,
pub reports_by_kind: HashMap<X86AsanErrorKind, u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86UbsanErrorKind {
IntegerDivideByZero,
FloatDivideByZero,
ShiftExponentNegative,
ShiftExponentTooLarge,
SignedIntegerOverflow,
PointerOverflow,
NullPointerArithmetic,
VLABoundNotPositive,
OutOfBounds,
TypeMismatch,
AlignmentAssumption,
UnreachableCode,
MissingReturn,
NonNullViolation,
BuiltinUnreachable,
InvalidBool,
InvalidEnum,
ImplicitConversionTruncation,
ImplicitConversionSignChange,
ImplicitSignedIntegerTruncation,
NegativeArraySize,
FunctionTypeMismatch,
InvalidVptr,
}
impl fmt::Display for X86UbsanErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IntegerDivideByZero => write!(f, "division by zero"),
Self::SignedIntegerOverflow => write!(f, "signed integer overflow"),
Self::ShiftExponentNegative => write!(f, "shift exponent negative"),
Self::ShiftExponentTooLarge => write!(f, "shift exponent too large"),
Self::TypeMismatch => write!(f, "type mismatch"),
Self::OutOfBounds => write!(f, "out-of-bounds access"),
Self::NullPointerArithmetic => write!(f, "null pointer arithmetic"),
Self::UnreachableCode => write!(f, "unreachable code executed"),
Self::MissingReturn => write!(f, "missing return value"),
Self::AlignmentAssumption => write!(f, "alignment assumption violated"),
Self::InvalidBool => write!(f, "load of value not valid for bool"),
Self::InvalidEnum => write!(f, "load of value not valid for enum"),
Self::ImplicitConversionTruncation => write!(f, "implicit conversion truncation"),
Self::ImplicitConversionSignChange => write!(f, "implicit conversion sign change"),
Self::BuiltinUnreachable => write!(f, "__builtin_unreachable reached"),
Self::NegativeArraySize => write!(f, "negative variable length array size"),
Self::InvalidVptr => write!(f, "invalid vptr"),
_ => write!(f, "undefined behavior"),
}
}
}
#[derive(Debug)]
pub struct X86UbsanRuntime {
pub flags: X86SanCommonFlags,
pub error_reports: Vec<X86UbsanReport>,
}
#[derive(Debug, Clone)]
pub struct X86UbsanReport {
pub error_kind: X86UbsanErrorKind,
pub source_location: Option<X86SourceLocation>,
pub stack: X86StackTrace,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct X86SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
impl Default for X86UbsanRuntime {
fn default() -> Self {
Self {
flags: X86SanCommonFlags::default(),
error_reports: Vec::new(),
}
}
}
impl X86UbsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn handle_divrem_overflow(&mut self) {
self.report(
X86UbsanErrorKind::IntegerDivideByZero,
"integer division by zero",
);
}
pub fn handle_signed_overflow(&mut self) {
self.report(
X86UbsanErrorKind::SignedIntegerOverflow,
"signed integer overflow",
);
}
pub fn handle_shift_out_of_bounds(&mut self, exponent: i64, bit_width: u32) {
if exponent < 0 {
self.report(
X86UbsanErrorKind::ShiftExponentNegative,
&format!("shift exponent {} is negative", exponent),
);
} else {
self.report(
X86UbsanErrorKind::ShiftExponentTooLarge,
&format!(
"shift exponent {} too large for {}-bit type",
exponent, bit_width
),
);
}
}
pub fn handle_out_of_bounds(&mut self, index: i64, bound: u64) {
self.report(
X86UbsanErrorKind::OutOfBounds,
&format!("index {} out of bounds for size {}", index, bound),
);
}
pub fn handle_type_mismatch(&mut self) {
self.report(X86UbsanErrorKind::TypeMismatch, "type mismatch");
}
pub fn handle_alignment_assumption(&mut self, addr: usize, alignment: usize) {
self.report(
X86UbsanErrorKind::AlignmentAssumption,
&format!("address {:#x} is not aligned to {}", addr, alignment),
);
}
pub fn handle_builtin_unreachable(&mut self) {
self.report(
X86UbsanErrorKind::BuiltinUnreachable,
"executed __builtin_unreachable()",
);
}
pub fn handle_missing_return(&mut self) {
self.report(
X86UbsanErrorKind::MissingReturn,
"execution reached end of value-returning function",
);
}
pub fn handle_nonnull_arg(&mut self) {
self.report(
X86UbsanErrorKind::NonNullViolation,
"null pointer passed to nonnull argument",
);
}
pub fn handle_load_invalid_value(&mut self, bit_width: u32) {
self.report(
X86UbsanErrorKind::InvalidBool,
&format!(
"load of value {} which is not valid for a {}-bit type",
bit_width, bit_width
),
);
}
pub fn handle_implicit_conversion(&mut self, from_bits: u32, to_bits: u32) {
self.report(
X86UbsanErrorKind::ImplicitConversionTruncation,
&format!(
"implicit conversion from {} bits to {} bits",
from_bits, to_bits
),
);
}
pub fn handle_vla_bound_not_positive(&mut self, bound: i64) {
self.report(
X86UbsanErrorKind::VLABoundNotPositive,
&format!("variable length array bound {} is not positive", bound),
);
}
fn report(&mut self, kind: X86UbsanErrorKind, desc: &str) {
let stack = X86StackTrace::capture_with_depth(self.flags.stack_trace_depth);
let report = X86UbsanReport {
error_kind: kind,
source_location: None,
stack,
description: desc.to_string(),
};
if self.flags.verbosity >= 1 {
eprintln!(
"=={}== ERROR: UndefinedBehaviorSanitizer: {}",
self.flags.verbosity, kind
);
eprintln!(" {}", desc);
}
self.error_reports.push(report);
if self.flags.halt_on_error {
std::process::abort();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TsanEventType {
Read,
Write,
MutexLock,
MutexUnlock,
MutexReadLock,
MutexReadUnlock,
ThreadCreate,
ThreadJoin,
AtomicLoad,
AtomicStore,
AtomicRMW,
AtomicFence,
SignalSend,
SignalWait,
}
#[derive(Debug, Clone)]
pub struct X86TsanEvent {
pub event_type: X86TsanEventType,
pub thread_id: u64,
pub clock: u64,
pub address: usize,
pub size: usize,
pub stack: X86StackTrace,
pub mutex_id: Option<u64>,
}
#[derive(Debug, Clone, Default)]
pub struct X86TsanShadowCell {
pub last_thread: u64,
pub last_clock: u64,
pub last_write: bool,
pub write_clock: u64,
pub read_count: u32,
}
#[derive(Debug)]
pub struct X86TsanRuntime {
pub shadow: Vec<X86TsanShadowCell>,
pub thread_clocks: HashMap<u64, u64>,
pub mutexes: HashMap<u64, X86TsanMutex>,
pub global_clock: AtomicU64,
pub events: Vec<X86TsanEvent>,
pub races: Vec<X86TsanRaceReport>,
pub flags: X86SanCommonFlags,
pub active: AtomicBool,
}
#[derive(Debug, Clone)]
pub struct X86TsanMutex {
pub id: u64,
pub owner_thread: Option<u64>,
pub lock_count: u32,
pub creation_stack: X86StackTrace,
pub is_read_lock: bool,
}
#[derive(Debug, Clone)]
pub struct X86TsanRaceReport {
pub description: String,
pub address: usize,
pub size: usize,
pub thread1: u64,
pub thread2: u64,
pub stack1: X86StackTrace,
pub stack2: X86StackTrace,
pub is_write_write: bool,
}
impl Default for X86TsanRuntime {
fn default() -> Self {
Self {
shadow: vec![X86TsanShadowCell::default(); 1 << 20],
thread_clocks: HashMap::new(),
mutexes: HashMap::new(),
global_clock: AtomicU64::new(1),
events: Vec::new(),
races: Vec::new(),
flags: X86SanCommonFlags::default(),
active: AtomicBool::new(true),
}
}
}
impl X86TsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn activate(&self) {
self.active.store(true, Ordering::SeqCst);
}
pub fn deactivate(&self) {
self.active.store(false, Ordering::SeqCst);
}
fn tick(&self) -> u64 {
self.global_clock.fetch_add(1, Ordering::SeqCst)
}
fn thread_clock(&mut self, tid: u64) -> u64 {
let clock = self.tick();
*self.thread_clocks.entry(tid).or_insert(clock)
}
pub fn record_access(
&mut self,
tid: u64,
addr: usize,
size: usize,
is_write: bool,
) -> Option<X86TsanRaceReport> {
if !self.active.load(Ordering::SeqCst) {
return None;
}
let clock = self.tick();
self.thread_clocks.insert(tid, clock);
let shadow_idx = (addr >> 3) & ((1 << 20) - 1);
let cell_state = {
let cell = &self.shadow[shadow_idx];
(
cell.last_thread,
cell.last_write,
cell.last_clock,
cell.write_clock,
)
};
let (cell_last_thread, cell_last_write, cell_last_clock, cell_write_clock) = cell_state;
let mut race = None;
if is_write {
if cell_last_thread != 0 && cell_last_thread != tid {
if cell_last_write && cell_last_clock > clock {
race = Some(self.report_race(addr, size, cell_last_thread, tid, true));
} else if !cell_last_write && cell_last_clock > clock {
race = Some(self.report_race(addr, size, cell_last_thread, tid, false));
}
}
} else {
if cell_last_write && cell_last_thread != tid && cell_write_clock > clock {
race = Some(self.report_race(addr, size, cell_last_thread, tid, false));
}
}
{
let cell = &mut self.shadow[shadow_idx];
if is_write {
cell.last_thread = tid;
cell.write_clock = clock;
cell.last_write = true;
cell.read_count = 0;
} else {
if !cell.last_write && cell.read_count == 0 {
cell.last_thread = tid;
}
cell.read_count += 1;
}
cell.last_clock = clock;
}
self.events.push(X86TsanEvent {
event_type: if is_write {
X86TsanEventType::Write
} else {
X86TsanEventType::Read
},
thread_id: tid,
clock,
address: addr,
size,
stack: X86StackTrace::capture(),
mutex_id: None,
});
race
}
fn report_race(
&mut self,
addr: usize,
size: usize,
t1: u64,
t2: u64,
ww: bool,
) -> X86TsanRaceReport {
let report = X86TsanRaceReport {
description: format!(
"data race at {:#x} ({} bytes) between thread {} and thread {}",
addr, size, t1, t2
),
address: addr,
size,
thread1: t1,
thread2: t2,
stack1: X86StackTrace::capture(),
stack2: X86StackTrace::capture(),
is_write_write: ww,
};
self.races.push(report.clone());
report
}
pub fn mutex_lock(&mut self, tid: u64, mutex_id: u64) {
let clock = self.tick();
let mutex = self
.mutexes
.entry(mutex_id)
.or_insert_with(|| X86TsanMutex {
id: mutex_id,
owner_thread: None,
lock_count: 0,
creation_stack: X86StackTrace::capture(),
is_read_lock: false,
});
mutex.owner_thread = Some(tid);
mutex.lock_count += 1;
self.events.push(X86TsanEvent {
event_type: X86TsanEventType::MutexLock,
thread_id: tid,
clock,
address: 0,
size: 0,
stack: X86StackTrace::capture(),
mutex_id: Some(mutex_id),
});
}
pub fn mutex_unlock(&mut self, tid: u64, mutex_id: u64) {
let clock = self.tick();
if let Some(mutex) = self.mutexes.get_mut(&mutex_id) {
mutex.lock_count = mutex.lock_count.saturating_sub(1);
if mutex.lock_count == 0 {
mutex.owner_thread = None;
}
}
self.events.push(X86TsanEvent {
event_type: X86TsanEventType::MutexUnlock,
thread_id: tid,
clock,
address: 0,
size: 0,
stack: X86StackTrace::capture(),
mutex_id: Some(mutex_id),
});
}
pub fn atomic_store(&mut self, tid: u64, addr: usize) {
let clock = self.tick();
self.events.push(X86TsanEvent {
event_type: X86TsanEventType::AtomicStore,
thread_id: tid,
clock,
address: addr,
size: 0,
stack: X86StackTrace::capture(),
mutex_id: None,
});
}
pub fn atomic_load(&mut self, tid: u64, addr: usize) {
let clock = self.tick();
self.events.push(X86TsanEvent {
event_type: X86TsanEventType::AtomicLoad,
thread_id: tid,
clock,
address: addr,
size: 0,
stack: X86StackTrace::capture(),
mutex_id: None,
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86MsanShadow(pub u8);
impl X86MsanShadow {
pub const INIT: Self = Self(0x00);
pub const UNINIT: Self = Self(0xff);
pub fn is_initialized(&self) -> bool {
self.0 == 0
}
}
#[derive(Debug, Clone)]
pub struct X86MsanOrigin {
pub id: u32,
pub stack: X86StackTrace,
}
#[derive(Debug)]
pub struct X86MsanRuntime {
pub shadow: Vec<u8>,
pub origins: Vec<u32>,
pub flags: X86SanCommonFlags,
pub error_reports: Vec<X86MsanReport>,
}
#[derive(Debug, Clone)]
pub struct X86MsanReport {
pub description: String,
pub address: usize,
pub size: usize,
pub origin: Option<X86MsanOrigin>,
pub stack: X86StackTrace,
}
impl Default for X86MsanRuntime {
fn default() -> Self {
Self {
shadow: vec![0xffu8; 1 << 24], origins: vec![0u32; 1 << 24],
flags: X86SanCommonFlags::default(),
error_reports: Vec::new(),
}
}
}
impl X86MsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn set_shadow(&mut self, addr: usize, size: usize, value: X86MsanShadow) {
for i in 0..size {
let idx = (addr + i) & ((1 << 24) - 1);
self.shadow[idx] = value.0;
}
}
pub fn get_shadow(&self, addr: usize) -> X86MsanShadow {
let idx = addr & ((1 << 24) - 1);
X86MsanShadow(self.shadow[idx])
}
pub fn set_origin(&mut self, addr: usize, size: usize, origin_id: u32) {
for i in 0..size {
let idx = (addr + i) & ((1 << 24) - 1);
self.origins[idx] = origin_id;
}
}
pub fn propagate_binary(&self, a: X86MsanShadow, b: X86MsanShadow) -> X86MsanShadow {
X86MsanShadow(a.0 | b.0) }
pub fn check_read(&mut self, addr: usize, size: usize) -> bool {
for i in 0..size {
if self.get_shadow(addr + i).0 != 0 {
let report = X86MsanReport {
description: format!("use of uninitialized value at {:#x}", addr + i),
address: addr + i,
size,
origin: Some(X86MsanOrigin {
id: self.origins[(addr + i) & ((1 << 24) - 1)],
stack: X86StackTrace::capture(),
}),
stack: X86StackTrace::capture(),
};
self.error_reports.push(report);
return false;
}
}
true
}
}
#[derive(Debug)]
pub struct X86LsanRuntime {
pub allocations: HashMap<usize, X86LsanAllocRecord>,
pub root_set: HashSet<usize>,
pub flags: X86SanCommonFlags,
pub active: bool,
}
#[derive(Debug, Clone)]
pub struct X86LsanAllocRecord {
pub ptr: usize,
pub size: usize,
pub stack: X86StackTrace,
pub is_reachable: bool,
}
#[derive(Debug, Clone)]
pub struct X86LsanLeakReport {
pub ptr: usize,
pub size: usize,
pub stack: X86StackTrace,
pub is_directly_lost: bool,
pub is_indirectly_lost: bool,
}
impl Default for X86LsanRuntime {
fn default() -> Self {
Self {
allocations: HashMap::new(),
root_set: HashSet::new(),
flags: X86SanCommonFlags::default(),
active: true,
}
}
}
impl X86LsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn register_allocation(&mut self, ptr: usize, size: usize) {
self.allocations.insert(
ptr,
X86LsanAllocRecord {
ptr,
size,
stack: X86StackTrace::capture(),
is_reachable: false,
},
);
}
pub fn unregister_allocation(&mut self, ptr: usize) {
self.allocations.remove(&ptr);
}
pub fn add_root(&mut self, ptr: usize) {
self.root_set.insert(ptr);
}
pub fn clear_roots(&mut self) {
self.root_set.clear();
}
pub fn detect_leaks(&mut self) -> Vec<X86LsanLeakReport> {
if !self.active {
return Vec::new();
}
for alloc in self.allocations.values_mut() {
alloc.is_reachable = false;
}
let mut worklist: VecDeque<usize> = VecDeque::new();
for root in &self.root_set {
worklist.push_back(*root);
}
while let Some(ptr) = worklist.pop_front() {
if let Some(alloc) = self.allocations.get_mut(&ptr) {
if !alloc.is_reachable {
alloc.is_reachable = true;
}
}
}
let mut leaks = Vec::new();
for alloc in self.allocations.values() {
if !alloc.is_reachable {
leaks.push(X86LsanLeakReport {
ptr: alloc.ptr,
size: alloc.size,
stack: alloc.stack.clone(),
is_directly_lost: true,
is_indirectly_lost: false,
});
}
}
leaks.sort_by(|a, b| b.size.cmp(&a.size));
leaks
}
pub fn print_summary(&self, leaks: &[X86LsanLeakReport]) {
if leaks.is_empty() {
return;
}
let total_bytes: usize = leaks.iter().map(|l| l.size).sum();
eprintln!(
"==ERROR: LeakSanitizer: detected memory leaks\n\
Direct leak of {} bytes in {} objects",
total_bytes,
leaks.len()
);
for leak in leaks.iter().take(10) {
eprintln!(" {:#x} — {} bytes", leak.ptr, leak.size);
}
if leaks.len() > 10 {
eprintln!(" ... and {} more leaks", leaks.len() - 10);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X86DfsanLabel(pub u32);
impl X86DfsanLabel {
pub const ZERO: Self = Self(0);
pub const fn new(id: u32) -> Self {
Self(id)
}
pub fn union_label(a: Self, b: Self) -> Self {
Self(a.0 | b.0)
}
}
#[derive(Debug)]
pub struct X86DfsanRuntime {
pub labels: Vec<u32>,
pub propagation_table: Vec<X86DfsanPropagationFn>,
pub flags: X86SanCommonFlags,
}
pub type X86DfsanPropagationFn =
fn(&mut X86DfsanRuntime, dst: usize, src1: usize, src2: usize, size: usize) -> X86DfsanLabel;
impl Default for X86DfsanRuntime {
fn default() -> Self {
Self {
labels: vec![0u32; 1 << 24],
propagation_table: Vec::new(),
flags: X86SanCommonFlags::default(),
}
}
}
impl X86DfsanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn set_label(&mut self, addr: usize, size: usize, label: X86DfsanLabel) {
for i in 0..size {
let idx = (addr + i) & ((1 << 24) - 1);
self.labels[idx] = label.0;
}
}
pub fn get_label(&self, addr: usize) -> X86DfsanLabel {
let idx = addr & ((1 << 24) - 1);
X86DfsanLabel(self.labels[idx])
}
pub fn get_label_range(&self, addr: usize, size: usize) -> X86DfsanLabel {
let mut combined = 0u32;
for i in 0..size {
let idx = (addr + i) & ((1 << 24) - 1);
combined |= self.labels[idx];
}
X86DfsanLabel(combined)
}
pub fn propagate_binary(
&mut self,
dst: usize,
src1: usize,
src2: usize,
size: usize,
) -> X86DfsanLabel {
let l1 = self.get_label_range(src1, size);
let l2 = self.get_label_range(src2, size);
let result = X86DfsanLabel::union_label(l1, l2);
self.set_label(dst, size, result);
result
}
pub fn taint_arg(&mut self, addr: usize, size: usize, label_id: u32) {
self.set_label(addr, size, X86DfsanLabel(label_id));
}
}
#[derive(Debug)]
pub struct X86HwasanRuntime {
pub flags: X86SanCommonFlags,
pub tag_memory: HashMap<usize, u8>,
pub active: bool,
pub shadow: Vec<u8>,
}
impl Default for X86HwasanRuntime {
fn default() -> Self {
Self {
flags: X86SanCommonFlags::default(),
tag_memory: HashMap::new(),
active: false,
shadow: Vec::new(),
}
}
}
impl X86HwasanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn generate_tag() -> u8 {
((std::process::id() as u8).wrapping_mul(17) & 0x0f) | 0x01
}
pub fn tag_allocation(&mut self, ptr: usize, size: usize) -> u8 {
let tag = Self::generate_tag();
self.tag_memory.insert(ptr, tag);
for i in 0..size {
let idx = (ptr + i) & ((1 << 20) - 1);
if idx < self.shadow.len() {
self.shadow[idx] = tag;
}
}
tag
}
pub fn check_tag(&self, ptr: usize) -> bool {
if !self.active {
return true;
}
let idx = ptr & ((1 << 20) - 1);
if idx < self.shadow.len() {
let expected_tag = self.shadow[idx];
return self.tag_memory.values().any(|&t| t == expected_tag);
}
true
}
}
#[derive(Debug)]
pub struct X86SanRuntime {
pub asan: X86AsanRuntime,
pub ubsan: X86UbsanRuntime,
pub tsan: X86TsanRuntime,
pub msan: X86MsanRuntime,
pub lsan: X86LsanRuntime,
pub dfsan: X86DfsanRuntime,
pub hwasan: X86HwasanRuntime,
pub flags: X86SanCommonFlags,
pub active_sanitizers: X86ActiveSanitizers,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86ActiveSanitizers: u32 {
const ASAN = 1 << 0;
const UBSAN = 1 << 1;
const TSAN = 1 << 2;
const MSAN = 1 << 3;
const LSAN = 1 << 4;
const DFSAN = 1 << 5;
const HWASAN = 1 << 6;
const ALL = Self::ASAN.bits() | Self::UBSAN.bits() | Self::TSAN.bits()
| Self::MSAN.bits() | Self::LSAN.bits() | Self::DFSAN.bits()
| Self::HWASAN.bits();
}
}
impl Default for X86SanRuntime {
fn default() -> Self {
Self {
asan: X86AsanRuntime::new(),
ubsan: X86UbsanRuntime::new(),
tsan: X86TsanRuntime::new(),
msan: X86MsanRuntime::new(),
lsan: X86LsanRuntime::new(),
dfsan: X86DfsanRuntime::new(),
hwasan: X86HwasanRuntime::new(),
flags: X86SanCommonFlags::default(),
active_sanitizers: X86ActiveSanitizers::ALL,
}
}
}
impl X86SanRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn apply_flags(&mut self, flags: X86SanCommonFlags) {
self.flags = flags.clone();
self.asan.flags = flags.clone();
self.ubsan.flags = flags.clone();
self.tsan.flags = flags.clone();
self.msan.flags = flags;
}
pub fn activate_all(&mut self) {
self.active_sanitizers = X86ActiveSanitizers::ALL;
self.asan.activate();
self.tsan.activate();
}
pub fn deactivate_all(&mut self) {
self.active_sanitizers = X86ActiveSanitizers::empty();
self.asan.deactivate();
self.tsan.deactivate();
}
pub fn sanitizer_malloc(&mut self, size: usize) -> *mut u8 {
let ptr = self.asan.malloc(size);
if self.active_sanitizers.contains(X86ActiveSanitizers::LSAN) {
self.lsan.register_allocation(ptr as usize, size);
}
ptr
}
pub fn sanitizer_free(&mut self, ptr: *mut u8) {
self.asan.free(ptr);
if self.active_sanitizers.contains(X86ActiveSanitizers::LSAN) {
self.lsan.unregister_allocation(ptr as usize);
}
}
pub fn sanitizer_memcpy(&mut self, dst: *mut u8, src: *const u8, n: usize) {
self.asan.memcpy(dst, src, n);
}
pub fn sanitizer_memset(&mut self, dst: *mut u8, val: i32, n: usize) {
self.asan.memset(dst, val, n);
}
pub fn sanitizer_realloc(&mut self, old_ptr: *mut u8, new_size: usize) -> *mut u8 {
if self.active_sanitizers.contains(X86ActiveSanitizers::LSAN) {
self.lsan.unregister_allocation(old_ptr as usize);
}
let new_ptr = self.asan.realloc(old_ptr, new_size);
if self.active_sanitizers.contains(X86ActiveSanitizers::LSAN) {
self.lsan.register_allocation(new_ptr as usize, new_size);
}
new_ptr
}
pub fn run_leak_check(&mut self) -> Vec<X86LsanLeakReport> {
if !self.active_sanitizers.contains(X86ActiveSanitizers::LSAN) {
return Vec::new();
}
let roots: Vec<usize> = self
.asan
.allocator
.allocations
.iter()
.filter(|(_, m)| !m.is_freed)
.map(|(ptr, _)| *ptr)
.collect();
for root in &roots {
self.lsan.add_root(*root);
}
let leaks = self.lsan.detect_leaks();
self.lsan.print_summary(&leaks);
leaks
}
pub fn stats(&self) -> X86SanStats {
let asan_stats = self.asan.stats();
X86SanStats {
asan_errors: asan_stats.error_count,
asan_current_bytes: asan_stats.current_bytes,
asan_total_allocated: asan_stats.total_allocated,
ubsan_errors: self.ubsan.error_reports.len() as u64,
tsan_races: self.tsan.races.len() as u64,
msan_errors: self.msan.error_reports.len() as u64,
lsan_active: self.lsan.active,
dfsan_active: self.dfsan.propagation_table.len() > 0,
active_sanitizers: self.active_sanitizers,
}
}
}
#[derive(Debug, Clone)]
pub struct X86SanStats {
pub asan_errors: u64,
pub asan_current_bytes: u64,
pub asan_total_allocated: u64,
pub ubsan_errors: u64,
pub tsan_races: u64,
pub msan_errors: u64,
pub lsan_active: bool,
pub dfsan_active: bool,
pub active_sanitizers: X86ActiveSanitizers,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_san_common_flags_default() {
let flags = X86SanCommonFlags::default();
assert!(!flags.halt_on_error);
assert_eq!(flags.stack_trace_depth, 64);
assert!(flags.suppress_equal_pcs);
}
#[test]
fn test_asan_shadow_addressable() {
assert!(X86AsanShadow::ADDRESSABLE.is_addressable());
assert!(!X86AsanShadow::ADDRESSABLE.is_poisoned());
}
#[test]
fn test_asan_shadow_poisoned() {
assert!(X86AsanShadow::HEAP_LEFT_RZ.is_poisoned());
assert!(X86AsanShadow::HEAP_FREED.is_poisoned());
}
#[test]
fn test_asan_shadow_partial() {
let s = X86AsanShadow::partially_addressable(3);
assert!(!s.is_addressable());
assert_eq!(s.0, 3);
}
#[test]
fn test_asan_shadow_addr() {
let addr: usize = 0x7fff0000;
let saddr = X86AsanShadowMemory::shadow_addr(addr);
assert!(saddr > 0);
}
#[test]
fn test_asan_shadow_get_set() {
let mut shadow = X86AsanShadowMemory::new();
shadow.poison(0x1000, 16, X86AsanShadow::HEAP_LEFT_RZ);
assert_eq!(shadow.get(0x1000).0, X86AsanShadow::HEAP_LEFT_RZ.0);
shadow.unpoison(0x1000, 16);
assert_eq!(shadow.get(0x1000).0, X86AsanShadow::ADDRESSABLE.0);
}
#[test]
fn test_asan_runtime_activate_deactivate() {
let mut asan = X86AsanRuntime::new();
asan.deactivate();
assert!(!asan.activated.load(Ordering::SeqCst));
asan.activate();
assert!(asan.activated.load(Ordering::SeqCst));
}
#[test]
fn test_asan_malloc_free() {
let mut asan = X86AsanRuntime::new();
let ptr = asan.malloc(64);
assert!(!ptr.is_null());
asan.free(ptr);
}
#[test]
fn test_asan_check_access_null() {
let mut asan = X86AsanRuntime::new();
assert!(!asan.check_access(0, 4, X86AccessType::Read));
assert_eq!(asan.error_reports.len(), 1);
}
#[test]
fn test_asan_check_access_ok() {
let mut asan = X86AsanRuntime::new();
let ptr = asan.malloc(64);
assert!(asan.check_access(ptr as usize, 4, X86AccessType::Read));
asan.free(ptr);
}
#[test]
fn test_asan_report_error_kind() {
let reports = vec![
(X86AsanErrorKind::HeapBufferOverflow, "heap-buffer-overflow"),
(X86AsanErrorKind::UseAfterFree, "heap-use-after-free"),
(X86AsanErrorKind::DoubleFree, "attempting double-free"),
];
for (kind, s) in reports {
assert_eq!(format!("{}", kind), s);
}
}
#[test]
fn test_asan_stats() {
let mut asan = X86AsanRuntime::new();
let ptr = asan.malloc(32);
let stats = asan.stats();
assert!(stats.total_allocated >= 32);
asan.free(ptr);
}
#[test]
fn test_ubsan_divrem_overflow() {
let mut ubsan = X86UbsanRuntime::new();
ubsan.handle_divrem_overflow();
assert_eq!(ubsan.error_reports.len(), 1);
}
#[test]
fn test_ubsan_shift_exponent_negative() {
let mut ubsan = X86UbsanRuntime::new();
ubsan.handle_shift_out_of_bounds(-1, 32);
assert_eq!(ubsan.error_reports.len(), 1);
}
#[test]
fn test_ubsan_shift_exponent_large() {
let mut ubsan = X86UbsanRuntime::new();
ubsan.handle_shift_out_of_bounds(64, 32);
assert_eq!(ubsan.error_reports.len(), 1);
}
#[test]
fn test_ubsan_out_of_bounds() {
let mut ubsan = X86UbsanRuntime::new();
ubsan.handle_out_of_bounds(10, 5);
assert_eq!(ubsan.error_reports.len(), 1);
}
#[test]
fn test_ubsan_unreachable() {
let mut ubsan = X86UbsanRuntime::new();
ubsan.handle_builtin_unreachable();
assert_eq!(ubsan.error_reports.len(), 1);
}
#[test]
fn test_ubsan_error_kinds_display() {
let kinds = [
X86UbsanErrorKind::IntegerDivideByZero,
X86UbsanErrorKind::SignedIntegerOverflow,
X86UbsanErrorKind::UnreachableCode,
X86UbsanErrorKind::InvalidBool,
];
for kind in kinds {
assert!(!format!("{}", kind).is_empty());
}
}
#[test]
fn test_tsan_runtime_new() {
let tsan = X86TsanRuntime::new();
assert!(tsan.active.load(Ordering::SeqCst));
}
#[test]
fn test_tsan_record_access_no_race() {
let mut tsan = X86TsanRuntime::new();
let race = tsan.record_access(1, 0x1000, 8, false);
assert!(race.is_none());
let race = tsan.record_access(1, 0x1000, 8, false);
assert!(race.is_none());
}
#[test]
fn test_tsan_mutex_lock_unlock() {
let mut tsan = X86TsanRuntime::new();
tsan.mutex_lock(1, 100);
assert_eq!(tsan.mutexes.get(&100).unwrap().lock_count, 1);
tsan.mutex_unlock(1, 100);
assert_eq!(tsan.mutexes.get(&100).unwrap().lock_count, 0);
}
#[test]
fn test_tsan_atomic_ops() {
let mut tsan = X86TsanRuntime::new();
tsan.atomic_store(1, 0x2000);
tsan.atomic_load(1, 0x2000);
assert!(tsan.events.len() >= 2);
}
#[test]
fn test_msan_shadow_init() {
assert!(X86MsanShadow::INIT.is_initialized());
assert!(!X86MsanShadow::UNINIT.is_initialized());
}
#[test]
fn test_msan_set_get_shadow() {
let mut msan = X86MsanRuntime::new();
msan.set_shadow(0x100, 10, X86MsanShadow::INIT);
assert_eq!(msan.get_shadow(0x100).0, 0);
msan.set_shadow(0x100, 10, X86MsanShadow::UNINIT);
assert_eq!(msan.get_shadow(0x100).0, 0xff);
}
#[test]
fn test_msan_propagate_binary() {
let msan = X86MsanRuntime::new();
let r = msan.propagate_binary(X86MsanShadow::INIT, X86MsanShadow::UNINIT);
assert!(!r.is_initialized());
let r2 = msan.propagate_binary(X86MsanShadow::INIT, X86MsanShadow::INIT);
assert!(r2.is_initialized());
}
#[test]
fn test_lsan_register_unregister() {
let mut lsan = X86LsanRuntime::new();
lsan.register_allocation(0x5000, 128);
assert!(lsan.allocations.contains_key(&0x5000));
lsan.unregister_allocation(0x5000);
assert!(!lsan.allocations.contains_key(&0x5000));
}
#[test]
fn test_lsan_detect_no_leaks() {
let mut lsan = X86LsanRuntime::new();
lsan.register_allocation(0x6000, 64);
lsan.add_root(0x6000);
let leaks = lsan.detect_leaks();
assert!(leaks.is_empty());
}
#[test]
fn test_lsan_detect_leaks() {
let mut lsan = X86LsanRuntime::new();
lsan.register_allocation(0x7000, 128);
let leaks = lsan.detect_leaks();
assert_eq!(leaks.len(), 1);
assert_eq!(leaks[0].size, 128);
}
#[test]
fn test_dfsan_label_union() {
let a = X86DfsanLabel(0x01);
let b = X86DfsanLabel(0x02);
let c = X86DfsanLabel::union_label(a, b);
assert_eq!(c.0, 0x03);
}
#[test]
fn test_dfsan_set_get_label() {
let mut dfsan = X86DfsanRuntime::new();
dfsan.set_label(0x100, 8, X86DfsanLabel(0xaa));
assert_eq!(dfsan.get_label(0x100).0, 0xaa);
assert_eq!(dfsan.get_label_range(0x100, 8).0, 0xaa);
}
#[test]
fn test_dfsan_propagate_binary() {
let mut dfsan = X86DfsanRuntime::new();
dfsan.set_label(0x100, 4, X86DfsanLabel(0x01));
dfsan.set_label(0x200, 4, X86DfsanLabel(0x02));
let result = dfsan.propagate_binary(0x300, 0x100, 0x200, 4);
assert_eq!(result.0, 0x03);
assert_eq!(dfsan.get_label(0x300).0, 0x03);
}
#[test]
fn test_hwasan_generate_tag() {
let tag = X86HwasanRuntime::generate_tag();
assert!(tag > 0 && tag <= 15);
}
#[test]
fn test_hwasan_default_inactive() {
let hwasan = X86HwasanRuntime::new();
assert!(!hwasan.active);
}
#[test]
fn test_san_runtime_new() {
let san = X86SanRuntime::new();
assert!(san.active_sanitizers.contains(X86ActiveSanitizers::ASAN));
assert!(san.active_sanitizers.contains(X86ActiveSanitizers::TSAN));
}
#[test]
fn test_san_runtime_malloc() {
let mut san = X86SanRuntime::new();
let ptr = san.sanitizer_malloc(48);
assert!(!ptr.is_null());
san.sanitizer_free(ptr);
}
#[test]
fn test_san_runtime_deactivate_all() {
let mut san = X86SanRuntime::new();
san.deactivate_all();
assert!(san.active_sanitizers.is_empty());
let ptr = san.sanitizer_malloc(16);
assert!(!ptr.is_null());
san.sanitizer_free(ptr);
}
#[test]
fn test_san_runtime_stats() {
let san = X86SanRuntime::new();
let stats = san.stats();
assert_eq!(stats.asan_errors, 0);
assert_eq!(stats.ubsan_errors, 0);
}
#[test]
fn test_san_allocator_quarantine() {
let mut allocator = X86SanAllocator::new();
let ptr = allocator.alloc(64, 8);
assert!(!ptr.is_null());
allocator.free(ptr);
assert!(!allocator.quarantine.is_empty());
}
#[test]
fn test_asan_error_kind_eq() {
assert_eq!(
X86AsanErrorKind::HeapBufferOverflow,
X86AsanErrorKind::HeapBufferOverflow
);
assert_ne!(
X86AsanErrorKind::HeapBufferOverflow,
X86AsanErrorKind::UseAfterFree
);
}
#[test]
fn test_tsan_event_types() {
assert_ne!(X86TsanEventType::Read, X86TsanEventType::Write);
assert_ne!(X86TsanEventType::MutexLock, X86TsanEventType::MutexUnlock);
}
#[test]
fn test_active_sanitizers_bitflags() {
let mut flags = X86ActiveSanitizers::empty();
flags |= X86ActiveSanitizers::ASAN;
assert!(flags.contains(X86ActiveSanitizers::ASAN));
assert!(!flags.contains(X86ActiveSanitizers::TSAN));
flags |= X86ActiveSanitizers::TSAN;
assert!(flags.contains(X86ActiveSanitizers::TSAN));
}
#[test]
fn test_stack_trace_capture() {
let st = X86StackTrace::capture();
assert!(st.size <= 0); }
#[test]
fn test_access_type_enum() {
assert_ne!(X86AccessType::Read, X86AccessType::Write);
assert_ne!(X86AccessType::Read, X86AccessType::Free);
}
}