#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
pub const X86_SANCODEGEN_ASAN_SHADOW_SCALE: u32 = 3;
pub const X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY: u32 = 1 << X86_SANCODEGEN_ASAN_SHADOW_SCALE;
pub const X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64: u64 = 0x7fff8000;
pub const X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64_ASLR: u64 = 0x7fff80000000;
pub const X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X32: u32 = 0x20000000;
pub const X86_SANCODEGEN_STACK_REDZONE_SIZE: u32 = 32;
pub const X86_SANCODEGEN_MIN_REDZONE_SIZE: u32 = 16;
pub const X86_SANCODEGEN_GLOBAL_REDZONE_SIZE: u32 = 32;
pub const X86_SANCODEGEN_MSAN_SHADOW_OFFSET_X64: u64 = 0x200000000000;
pub const X86_SANCODEGEN_MSAN_ORIGIN_OFFSET_X64: u64 = 0x100000000000;
pub const X86_SANCODEGEN_MSAN_SHADOW_SCALE: u32 = 1;
pub const X86_SANCODEGEN_TSAN_SHADOW_GRANULARITY: u32 = 1;
pub const X86_SANCODEGEN_UBSAN_MAX_SHIFT_WIDTH: u32 = 128;
pub const X86_SANCODEGEN_SAFESTACK_ALIGNMENT: u32 = 16;
pub const X86_SANCODEGEN_SCS_SHADOW_ENTRIES: u32 = 8;
#[derive(Debug, Clone)]
pub struct X86SanCodeGenConfig {
pub asan: bool,
pub msan: bool,
pub tsan: bool,
pub ubsan: bool,
pub lsan: bool,
pub safe_stack: bool,
pub shadow_call_stack: bool,
pub cfi: bool,
pub asan_shadow_offset: u64,
pub asan_redzone_size: u32,
pub halt_on_error: bool,
pub msan_track_origins: bool,
pub stack_trace_depth: u32,
pub detect_stack_uar: bool,
pub detect_container_overflow: bool,
pub ubsan_recover: bool,
pub minimal_runtime: bool,
pub is_64bit: bool,
}
impl Default for X86SanCodeGenConfig {
fn default() -> Self {
Self {
asan: false,
msan: false,
tsan: false,
ubsan: false,
lsan: false,
safe_stack: false,
shadow_call_stack: false,
cfi: false,
asan_shadow_offset: 0,
asan_redzone_size: X86_SANCODEGEN_STACK_REDZONE_SIZE,
halt_on_error: true,
msan_track_origins: false,
stack_trace_depth: 16,
detect_stack_uar: false,
detect_container_overflow: false,
ubsan_recover: true,
minimal_runtime: false,
is_64bit: true,
}
}
}
impl X86SanCodeGenConfig {
pub fn asan_x64() -> Self {
Self {
asan: true,
asan_shadow_offset: X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64,
is_64bit: true,
..Default::default()
}
}
pub fn msan_with_origins() -> Self {
Self {
msan: true,
msan_track_origins: true,
is_64bit: true,
..Default::default()
}
}
pub fn tsan() -> Self {
Self {
tsan: true,
halt_on_error: false,
is_64bit: true,
..Default::default()
}
}
pub fn ubsan() -> Self {
Self {
ubsan: true,
ubsan_recover: true,
halt_on_error: false,
is_64bit: true,
..Default::default()
}
}
pub fn full_sanitize() -> Self {
Self {
asan: true,
ubsan: true,
lsan: true,
safe_stack: true,
shadow_call_stack: true,
cfi: true,
is_64bit: true,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanInstrumentedAccess {
pub opcode: String,
pub is_store: bool,
pub size: u8,
pub alignment: u8,
pub is_stack: bool,
pub is_heap: bool,
pub is_global: bool,
pub address: u64,
pub source_loc: Option<X86SanSourceLocation>,
}
impl X86ASanInstrumentedAccess {
pub fn new(opcode: &str, is_store: bool, size: u8, alignment: u8, address: u64) -> Self {
Self {
opcode: opcode.to_string(),
is_store,
size,
alignment,
is_stack: false,
is_heap: false,
is_global: false,
address,
source_loc: None,
}
}
pub fn as_stack(mut self) -> Self {
self.is_stack = true;
self
}
pub fn as_heap(mut self) -> Self {
self.is_heap = true;
self
}
pub fn as_global(mut self) -> Self {
self.is_global = true;
self
}
pub fn with_source(mut self, file: &str, line: u32, col: u32) -> Self {
self.source_loc = Some(X86SanSourceLocation::new(file, line, col));
self
}
pub fn is_aligned(&self) -> bool {
self.alignment >= self.size || self.size <= self.alignment
}
pub fn compute_shadow_address_64(&self, shadow_offset: u64) -> u64 {
(self.address >> X86_SANCODEGEN_ASAN_SHADOW_SCALE) + shadow_offset
}
pub fn compute_shadow_address_32(&self, shadow_offset: u32) -> u32 {
((self.address as u32) >> X86_SANCODEGEN_ASAN_SHADOW_SCALE) + shadow_offset
}
}
#[derive(Debug, Clone)]
pub struct X86ASanStackVar {
pub name: String,
pub frame_offset: i32,
pub size: u32,
pub alignment: u32,
pub left_redzone: u32,
pub right_redzone: u32,
pub use_after_scope: bool,
pub use_after_return: bool,
pub left_shadow_byte: u8,
pub right_shadow_byte: u8,
pub mid_shadow_byte: u8,
pub life_start: u32,
pub life_end: u32,
}
impl X86ASanStackVar {
pub fn new(name: &str, frame_offset: i32, size: u32, alignment: u32) -> Self {
Self {
name: name.to_string(),
frame_offset,
size,
alignment,
left_redzone: X86_SANCODEGEN_STACK_REDZONE_SIZE,
right_redzone: X86_SANCODEGEN_STACK_REDZONE_SIZE,
use_after_scope: false,
use_after_return: false,
left_shadow_byte: X86_ASAN_SHADOW_STACK_LEFT,
right_shadow_byte: X86_ASAN_SHADOW_STACK_RIGHT,
mid_shadow_byte: X86_ASAN_SHADOW_STACK_MID,
life_start: 0,
life_end: u32::MAX,
}
}
pub fn total_size(&self) -> u32 {
self.left_redzone + self.size + self.right_redzone
}
pub fn var_start(&self) -> i32 {
self.frame_offset + self.left_redzone as i32
}
pub fn var_end(&self) -> i32 {
self.var_start() + self.size as i32
}
pub fn with_use_after_scope(mut self) -> Self {
self.use_after_scope = true;
self
}
pub fn with_use_after_return(mut self) -> Self {
self.use_after_return = true;
self
}
pub fn with_life_range(mut self, start: u32, end: u32) -> Self {
self.life_start = start;
self.life_end = end;
self
}
pub fn shadow_byte_for_partial(accessible_bytes: u8) -> u8 {
match accessible_bytes {
0 => X86_ASAN_SHADOW_ADDRESSABLE,
1 => X86_ASAN_SHADOW_PARTIAL1,
2 => X86_ASAN_SHADOW_PARTIAL2,
3 => X86_ASAN_SHADOW_PARTIAL3,
4 => X86_ASAN_SHADOW_PARTIAL4,
5 => X86_ASAN_SHADOW_PARTIAL5,
6 => X86_ASAN_SHADOW_PARTIAL6,
7 => X86_ASAN_SHADOW_PARTIAL7,
_ => X86_ASAN_SHADOW_ADDRESSABLE,
}
}
}
pub const X86_ASAN_SHADOW_ADDRESSABLE: u8 = 0x00;
pub const X86_ASAN_SHADOW_PARTIAL1: u8 = 0x01;
pub const X86_ASAN_SHADOW_PARTIAL2: u8 = 0x02;
pub const X86_ASAN_SHADOW_PARTIAL3: u8 = 0x03;
pub const X86_ASAN_SHADOW_PARTIAL4: u8 = 0x04;
pub const X86_ASAN_SHADOW_PARTIAL5: u8 = 0x05;
pub const X86_ASAN_SHADOW_PARTIAL6: u8 = 0x06;
pub const X86_ASAN_SHADOW_PARTIAL7: u8 = 0x07;
pub const X86_ASAN_SHADOW_HEAP_LEFT: u8 = 0xFA;
pub const X86_ASAN_SHADOW_HEAP_RIGHT: u8 = 0xFB;
pub const X86_ASAN_SHADOW_STACK_LEFT: u8 = 0xF1;
pub const X86_ASAN_SHADOW_STACK_MID: u8 = 0xF2;
pub const X86_ASAN_SHADOW_STACK_RIGHT: u8 = 0xF3;
pub const X86_ASAN_SHADOW_GLOBAL: u8 = 0xF9;
pub const X86_ASAN_SHADOW_FREED: u8 = 0xFD;
pub const X86_ASAN_SHADOW_UAR: u8 = 0xF8;
pub const X86_ASAN_SHADOW_SCOPE: u8 = 0xF5;
pub const X86_ASAN_SHADOW_INTRA_OBJECT: u8 = 0xF4;
pub const X86_ASAN_SHADOW_INVALID: u8 = 0xFF;
#[derive(Debug, Clone)]
pub struct X86ASanStackFrame {
pub variables: Vec<X86ASanStackVar>,
pub total_frame_size: u32,
pub uas_count: u32,
pub uar_count: u32,
pub uses_fake_stack: bool,
pub frame_descriptor_magic: u64,
pub frame_descriptor_offset: i32,
}
impl X86ASanStackFrame {
pub fn new() -> Self {
Self {
variables: Vec::new(),
total_frame_size: 0,
uas_count: 0,
uar_count: 0,
uses_fake_stack: false,
frame_descriptor_magic: 0x41B58AB3,
frame_descriptor_offset: 0,
}
}
pub fn add_variable(&mut self, var: X86ASanStackVar) {
if var.use_after_scope {
self.uas_count += 1;
}
if var.use_after_return {
self.uar_count += 1;
self.uses_fake_stack = true;
}
self.variables.push(var);
self.total_frame_size = self.variables.iter().map(|v| v.total_size()).sum();
}
pub fn compute_shadow_operations(&self) -> Vec<X86ASanShadowOp> {
let mut ops = Vec::new();
let mut offset: i32 = 0;
for var in &self.variables {
let left_start = offset;
let left_end = offset + var.left_redzone as i32;
if var.left_redzone > 0 {
ops.push(X86ASanShadowOp::poison(
left_start,
left_end,
var.left_shadow_byte,
));
}
offset = left_end;
let var_start = offset;
let var_end = offset + var.size as i32;
let aligned_size = var.size;
let shadow_bytes_needed = (aligned_size + X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY - 1)
/ X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY;
for i in 0..shadow_bytes_needed {
let addr_in_var = i * X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY;
let remaining =
if addr_in_var + X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY > aligned_size {
aligned_size - addr_in_var
} else {
X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY
};
let shadow_val = X86ASanStackVar::shadow_byte_for_partial(remaining as u8);
ops.push(X86ASanShadowOp::set_shadow(
var_start + i as i32 * X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY as i32,
shadow_val,
));
}
offset = var_end;
if var.right_redzone > 0 {
ops.push(X86ASanShadowOp::poison(
offset,
offset + var.right_redzone as i32,
var.right_shadow_byte,
));
}
offset += var.right_redzone as i32;
}
ops
}
pub fn unpoison_all(&self) -> Vec<X86ASanShadowOp> {
let mut ops = Vec::new();
let size = self.total_frame_size;
if size > 0 {
ops.push(X86ASanShadowOp::unpoison(0, size as i32));
}
ops
}
pub fn get_variable(&self, name: &str) -> Option<&X86ASanStackVar> {
self.variables.iter().find(|v| v.name == name)
}
pub fn variable_count(&self) -> usize {
self.variables.len()
}
}
impl Default for X86ASanStackFrame {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanShadowOp {
pub op_kind: X86ASanShadowOpKind,
pub start_offset: i32,
pub end_offset: i32,
pub shadow_value: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86ASanShadowOpKind {
Poison,
Unpoison,
SetShadow,
CheckAccess,
}
impl X86ASanShadowOp {
pub fn poison(start: i32, end: i32, value: u8) -> Self {
Self {
op_kind: X86ASanShadowOpKind::Poison,
start_offset: start,
end_offset: end,
shadow_value: value,
}
}
pub fn unpoison(start: i32, end: i32) -> Self {
Self {
op_kind: X86ASanShadowOpKind::Unpoison,
start_offset: start,
end_offset: end,
shadow_value: X86_ASAN_SHADOW_ADDRESSABLE,
}
}
pub fn set_shadow(offset: i32, value: u8) -> Self {
Self {
op_kind: X86ASanShadowOpKind::SetShadow,
start_offset: offset,
end_offset: offset + 1,
shadow_value: value,
}
}
pub fn check_access(offset: i32, size: u8) -> Self {
Self {
op_kind: X86ASanShadowOpKind::CheckAccess,
start_offset: offset,
end_offset: offset + size as i32,
shadow_value: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MSanShadowValue {
pub shadow: u64,
pub origin_id: u32,
}
impl X86MSanShadowValue {
pub const INITIALIZED: Self = Self {
shadow: 0,
origin_id: 0,
};
pub const UNINITIALIZED: Self = Self {
shadow: u64::MAX,
origin_id: 0,
};
pub fn new(shadow: u64, origin_id: u32) -> Self {
Self { shadow, origin_id }
}
pub fn is_initialized(&self) -> bool {
self.shadow == 0
}
pub fn is_uninitialized(&self) -> bool {
self.shadow != 0
}
pub fn propagate_binary(lhs: &Self, rhs: &Self) -> Self {
Self {
shadow: lhs.shadow | rhs.shadow,
origin_id: if lhs.shadow != 0 {
lhs.origin_id
} else {
rhs.origin_id
},
}
}
pub fn propagate_unary(val: &Self) -> Self {
val.clone()
}
pub fn truncate(&self, width: u32) -> Self {
let mask = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
Self {
shadow: self.shadow & mask,
origin_id: self.origin_id,
}
}
pub fn zext(&self, _from_width: u32) -> Self {
self.clone()
}
pub fn sext(&self, from_width: u32) -> Self {
if from_width == 0 || from_width >= 64 {
return self.clone();
}
let sign_bit = 1u64 << (from_width - 1);
let ext_mask = !((1u64 << from_width) - 1);
if self.shadow & sign_bit != 0 {
Self {
shadow: self.shadow | ext_mask,
origin_id: self.origin_id,
}
} else {
self.clone()
}
}
pub fn bitcast(&self) -> Self {
self.clone()
}
}
#[derive(Debug, Clone)]
pub struct X86MSanOrigin {
pub id: u32,
pub description: String,
pub stack_trace: Option<Vec<X86SanStackFrame>>,
pub kind: X86MSanOriginKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86MSanOriginKind {
HeapAllocation,
StackAllocation,
GlobalVariable,
FunctionParameter,
Instruction,
Deallocated,
Unknown,
}
impl fmt::Display for X86MSanOriginKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HeapAllocation => write!(f, "heap"),
Self::StackAllocation => write!(f, "stack"),
Self::GlobalVariable => write!(f, "global"),
Self::FunctionParameter => write!(f, "parameter"),
Self::Instruction => write!(f, "instruction"),
Self::Deallocated => write!(f, "deallocated"),
Self::Unknown => write!(f, "unknown"),
}
}
}
impl X86MSanOrigin {
pub fn new(id: u32, description: &str, kind: X86MSanOriginKind) -> Self {
Self {
id,
description: description.to_string(),
stack_trace: None,
kind,
}
}
pub fn with_stack_trace(mut self, frames: Vec<X86SanStackFrame>) -> Self {
self.stack_trace = Some(frames);
self
}
}
#[derive(Debug, Clone)]
pub struct X86MSanShadowPropagator {
pub propagation_rules: HashMap<String, X86MSanPropagationRule>,
pub track_origins: bool,
}
#[derive(Debug, Clone)]
pub enum X86MSanPropagationRule {
BinaryOr,
BinaryAnd,
Unary,
Select,
Comparison,
Phi,
Custom(String),
}
impl X86MSanShadowPropagator {
pub fn new(track_origins: bool) -> Self {
let mut rules = HashMap::new();
rules.insert("add".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("sub".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("mul".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("udiv".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("sdiv".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("urem".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("srem".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("and".to_string(), X86MSanPropagationRule::BinaryAnd);
rules.insert("or".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("xor".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("shl".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("lshr".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("ashr".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("select".to_string(), X86MSanPropagationRule::Select);
rules.insert("phi".to_string(), X86MSanPropagationRule::Phi);
rules.insert("icmp".to_string(), X86MSanPropagationRule::Comparison);
rules.insert("fcmp".to_string(), X86MSanPropagationRule::Comparison);
rules.insert("fadd".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("fsub".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("fmul".to_string(), X86MSanPropagationRule::BinaryOr);
rules.insert("fdiv".to_string(), X86MSanPropagationRule::BinaryOr);
Self {
propagation_rules: rules,
track_origins,
}
}
pub fn get_rule(&self, inst_name: &str) -> Option<&X86MSanPropagationRule> {
self.propagation_rules.get(inst_name)
}
pub fn propagate_binary(
&self,
rule: &X86MSanPropagationRule,
lhs: &X86MSanShadowValue,
rhs: &X86MSanShadowValue,
) -> X86MSanShadowValue {
match rule {
X86MSanPropagationRule::BinaryOr => X86MSanShadowValue::propagate_binary(lhs, rhs),
X86MSanPropagationRule::BinaryAnd => {
let shadow = lhs.shadow & rhs.shadow;
X86MSanShadowValue::new(shadow, lhs.origin_id)
}
_ => X86MSanShadowValue::propagate_binary(lhs, rhs),
}
}
pub fn register_rule(&mut self, inst_name: &str, rule: X86MSanPropagationRule) {
self.propagation_rules.insert(inst_name.to_string(), rule);
}
}
impl Default for X86MSanShadowPropagator {
fn default() -> Self {
Self::new(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TSanEventType {
Read,
Write,
MutexLock,
MutexUnlock,
MutexReadLock,
MutexReadUnlock,
ThreadCreate,
ThreadJoin,
AtomicLoadAcquire,
AtomicStoreRelease,
AtomicRMW,
AtomicFence,
SignalSend,
SignalWait,
}
impl fmt::Display for X86TSanEventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read => write!(f, "READ"),
Self::Write => write!(f, "WRITE"),
Self::MutexLock => write!(f, "MUTEX_LOCK"),
Self::MutexUnlock => write!(f, "MUTEX_UNLOCK"),
Self::MutexReadLock => write!(f, "MUTEX_RDLOCK"),
Self::MutexReadUnlock => write!(f, "MUTEX_RDUNLOCK"),
Self::ThreadCreate => write!(f, "THREAD_CREATE"),
Self::ThreadJoin => write!(f, "THREAD_JOIN"),
Self::AtomicLoadAcquire => write!(f, "ATOMIC_LOAD_ACQUIRE"),
Self::AtomicStoreRelease => write!(f, "ATOMIC_STORE_RELEASE"),
Self::AtomicRMW => write!(f, "ATOMIC_RMW"),
Self::AtomicFence => write!(f, "ATOMIC_FENCE"),
Self::SignalSend => write!(f, "SIGNAL_SEND"),
Self::SignalWait => write!(f, "SIGNAL_WAIT"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TSanEvent {
pub event_type: X86TSanEventType,
pub thread_id: u32,
pub clock: u64,
pub address: u64,
pub size: u8,
pub source_loc: Option<X86SanSourceLocation>,
pub mutex_id: u32,
}
impl X86TSanEvent {
pub fn new(event_type: X86TSanEventType, thread_id: u32, clock: u64) -> Self {
Self {
event_type,
thread_id,
clock,
address: 0,
size: 0,
source_loc: None,
mutex_id: 0,
}
}
pub fn at_address(mut self, addr: u64, size: u8) -> Self {
self.address = addr;
self.size = size;
self
}
pub fn with_source(mut self, file: &str, line: u32, col: u32) -> Self {
self.source_loc = Some(X86SanSourceLocation::new(file, line, col));
self
}
pub fn with_mutex(mut self, mutex_id: u32) -> Self {
self.mutex_id = mutex_id;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86TSanClockEntry {
pub thread_id: u32,
pub clock: u64,
}
#[derive(Debug, Clone)]
pub struct X86TSanVectorClock {
pub entries: Vec<X86TSanClockEntry>,
}
impl X86TSanVectorClock {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn get(&self, thread_id: u32) -> u64 {
for entry in &self.entries {
if entry.thread_id == thread_id {
return entry.clock;
}
}
0
}
pub fn set(&mut self, thread_id: u32, clock: u64) {
for entry in &mut self.entries {
if entry.thread_id == thread_id {
entry.clock = entry.clock.max(clock);
return;
}
}
self.entries.push(X86TSanClockEntry { thread_id, clock });
}
pub fn tick(&mut self, thread_id: u32) {
for entry in &mut self.entries {
if entry.thread_id == thread_id {
entry.clock += 1;
return;
}
}
self.entries.push(X86TSanClockEntry {
thread_id,
clock: 1,
});
}
pub fn happens_before(&self, other: &Self) -> bool {
if self.entries.is_empty() {
return true;
}
for entry in &self.entries {
let other_clock = other.get(entry.thread_id);
if entry.clock > other_clock {
return false;
}
}
true
}
pub fn concurrent_with(&self, other: &Self) -> bool {
!self.happens_before(other) && !other.happens_before(self)
}
pub fn join(&mut self, other: &Self) {
for entry in &other.entries {
self.set(entry.thread_id, entry.clock);
}
}
pub fn max_clock(&self) -> u64 {
self.entries.iter().map(|e| e.clock).max().unwrap_or(0)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for X86TSanVectorClock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86TSanShadowCell {
pub last_thread: u32,
pub last_clock: u64,
pub last_write_thread: u32,
pub write_clock: u64,
pub read_count: u32,
pub history: Vec<X86TSanShadowHistoryEntry>,
}
#[derive(Debug, Clone)]
pub struct X86TSanShadowHistoryEntry {
pub thread_id: u32,
pub clock: u64,
pub is_write: bool,
pub size: u8,
}
impl X86TSanShadowCell {
pub fn new() -> Self {
Self {
last_thread: 0,
last_clock: 0,
last_write_thread: 0,
write_clock: 0,
read_count: 0,
history: Vec::new(),
}
}
pub fn record_access(&mut self, thread_id: u32, clock: u64, is_write: bool, size: u8) -> bool {
let mut race_detected = false;
if is_write {
if self.last_write_thread != 0
&& self.last_write_thread != thread_id
&& self.write_clock >= clock
{
race_detected = true;
}
if self.last_thread != 0 && self.last_thread != thread_id && self.last_clock >= clock {
race_detected = true;
}
self.last_write_thread = thread_id;
self.write_clock = clock;
self.read_count = 0;
} else {
if self.last_write_thread != 0
&& self.last_write_thread != thread_id
&& self.write_clock >= clock
{
race_detected = true;
}
self.read_count += 1;
}
self.last_thread = thread_id;
self.last_clock = clock;
let history_limit = 8;
if self.history.len() >= history_limit {
self.history.remove(0);
}
self.history.push(X86TSanShadowHistoryEntry {
thread_id,
clock,
is_write,
size,
});
race_detected
}
pub fn reset(&mut self) {
self.last_thread = 0;
self.last_clock = 0;
self.last_write_thread = 0;
self.write_clock = 0;
self.read_count = 0;
self.history.clear();
}
}
impl Default for X86TSanShadowCell {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86TSanMutexState {
pub id: u32,
pub owner_thread: u32,
pub lock_count: u32,
pub is_read_lock: bool,
pub release_clock: X86TSanVectorClock,
pub is_destroyed: bool,
}
impl X86TSanMutexState {
pub fn new(id: u32) -> Self {
Self {
id,
owner_thread: 0,
lock_count: 0,
is_read_lock: false,
release_clock: X86TSanVectorClock::new(),
is_destroyed: false,
}
}
pub fn acquire(&mut self, thread_id: u32) {
self.owner_thread = thread_id;
self.lock_count += 1;
}
pub fn release(&mut self, thread_clock: &X86TSanVectorClock) -> X86TSanVectorClock {
let mut rel_clock = self.release_clock.clone();
rel_clock.join(thread_clock);
self.release_clock = rel_clock.clone();
self.owner_thread = 0;
self.lock_count = 0;
rel_clock
}
pub fn is_locked(&self) -> bool {
self.owner_thread != 0
}
pub fn destroy(&mut self) {
self.is_destroyed = true;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86UBSanErrorKind {
IntegerDivideByZero,
FloatDivideByZero,
ShiftExponentNegative,
ShiftExponentTooLarge,
SignedIntegerOverflow,
PointerOverflow,
NullPointerArithmetic,
VLABoundNotPositive,
OutOfBounds,
TypeMismatch,
AlignmentAssumption,
UnreachableCode,
MissingReturn,
NonNullViolation,
BuiltinUnreachable,
InvalidBool,
InvalidEnum,
ImplicitConversionTruncation,
ImplicitConversionSignChange,
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::FloatDivideByZero => write!(f, "float-divide-by-zero"),
Self::ShiftExponentNegative => write!(f, "shift-exponent-negative"),
Self::ShiftExponentTooLarge => write!(f, "shift-exponent-too-large"),
Self::SignedIntegerOverflow => write!(f, "signed-integer-overflow"),
Self::PointerOverflow => write!(f, "pointer-overflow"),
Self::NullPointerArithmetic => write!(f, "null-pointer-arithmetic"),
Self::VLABoundNotPositive => write!(f, "vla-bound-not-positive"),
Self::OutOfBounds => write!(f, "out-of-bounds"),
Self::TypeMismatch => write!(f, "type-mismatch"),
Self::AlignmentAssumption => write!(f, "alignment-assumption"),
Self::UnreachableCode => write!(f, "unreachable-code"),
Self::MissingReturn => write!(f, "missing-return"),
Self::NonNullViolation => write!(f, "nonnull-violation"),
Self::BuiltinUnreachable => write!(f, "builtin-unreachable"),
Self::InvalidBool => write!(f, "invalid-bool"),
Self::InvalidEnum => write!(f, "invalid-enum"),
Self::ImplicitConversionTruncation => write!(f, "implicit-conversion-truncation"),
Self::ImplicitConversionSignChange => write!(f, "implicit-conversion-sign-change"),
Self::NegativeArraySize => write!(f, "negative-array-size"),
Self::FunctionTypeMismatch => write!(f, "function-type-mismatch"),
Self::InvalidVptr => write!(f, "invalid-vptr"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86UBSanCheck {
pub kind: X86UBSanErrorKind,
pub source_loc: X86SanSourceLocation,
pub type_descr: Option<String>,
pub extra_data: Vec<u64>,
}
impl X86UBSanCheck {
pub fn new(kind: X86UBSanErrorKind, file: &str, line: u32, col: u32) -> Self {
Self {
kind,
source_loc: X86SanSourceLocation::new(file, line, col),
type_descr: None,
extra_data: Vec::new(),
}
}
pub fn with_type(mut self, descr: &str) -> Self {
self.type_descr = Some(descr.to_string());
self
}
pub fn with_data(mut self, data: Vec<u64>) -> Self {
self.extra_data = data;
self
}
}
#[derive(Debug, Clone)]
pub struct X86LSanAllocRecord {
pub ptr: u64,
pub size: u64,
pub alloc_stack: Vec<X86SanStackFrame>,
pub is_reachable: bool,
pub is_freed: bool,
pub alloc_id: u64,
pub is_cpp: bool,
}
impl X86LSanAllocRecord {
pub fn new(ptr: u64, size: u64, alloc_id: u64) -> Self {
Self {
ptr,
size,
alloc_stack: Vec::new(),
is_reachable: false,
is_freed: false,
alloc_id,
is_cpp: false,
}
}
pub fn with_stack(mut self, stack: Vec<X86SanStackFrame>) -> Self {
self.alloc_stack = stack;
self
}
pub fn mark_freed(&mut self) {
self.is_freed = true;
}
pub fn mark_reachable(&mut self) {
self.is_reachable = true;
}
}
#[derive(Debug, Clone)]
pub struct X86SafeStackConfig {
pub safe_stack_base: u64,
pub safe_stack_limit: u64,
pub unsafe_stack_base: u64,
pub unsafe_stack_limit: u64,
pub enabled: bool,
pub safe_stack_size: u64,
pub unsafe_stack_size: u64,
}
impl Default for X86SafeStackConfig {
fn default() -> Self {
Self {
safe_stack_base: 0,
safe_stack_limit: 0,
unsafe_stack_base: 0,
unsafe_stack_limit: 0,
enabled: false,
safe_stack_size: 1024 * 1024, unsafe_stack_size: 8 * 1024 * 1024, }
}
}
impl X86SafeStackConfig {
pub fn new(safe_stack_size: u64, unsafe_stack_size: u64) -> Self {
Self {
safe_stack_base: 0,
safe_stack_limit: safe_stack_size,
unsafe_stack_base: safe_stack_size,
unsafe_stack_limit: safe_stack_size + unsafe_stack_size,
enabled: true,
safe_stack_size,
unsafe_stack_size,
}
}
pub fn is_safe_object(&self, _alloca_name: &str, is_array: bool) -> bool {
!is_array
}
pub fn safe_stack_addr(&self, offset: i32) -> u64 {
self.safe_stack_base + offset as u64
}
pub fn unsafe_stack_addr(&self, offset: i32) -> u64 {
self.unsafe_stack_base + offset as u64
}
}
#[derive(Debug, Clone)]
pub struct X86ShadowCallStack {
pub entries: Vec<X86SCSEntry>,
pub depth: u32,
pub active: bool,
pub total_pushes: u64,
pub total_pops: u64,
pub mismatches: u64,
}
#[derive(Debug, Clone)]
pub struct X86SCSEntry {
pub return_address: u64,
pub frame_depth: u32,
pub valid: bool,
pub function: Option<String>,
}
impl X86ShadowCallStack {
pub fn new() -> Self {
Self {
entries: Vec::new(),
depth: 0,
active: false,
total_pushes: 0,
total_pops: 0,
mismatches: 0,
}
}
pub fn push(&mut self, return_address: u64, function: Option<&str>) {
self.entries.push(X86SCSEntry {
return_address,
frame_depth: self.depth,
valid: true,
function: function.map(String::from),
});
self.depth += 1;
self.total_pushes += 1;
}
pub fn pop(&mut self, expected_address: u64) -> bool {
self.total_pops += 1;
if let Some(entry) = self.entries.pop() {
self.depth = self.depth.saturating_sub(1);
if entry.return_address != expected_address {
self.mismatches += 1;
return false;
}
return true;
}
self.mismatches += 1;
false
}
pub fn peek(&self) -> Option<u64> {
self.entries.last().map(|e| e.return_address)
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn depth(&self) -> u32 {
self.depth
}
pub fn reset(&mut self) {
self.entries.clear();
self.depth = 0;
self.active = false;
}
}
impl Default for X86ShadowCallStack {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CFITypeCheck {
pub type_name: String,
pub type_hash: u64,
pub target_address: u64,
pub source_loc: Option<X86SanSourceLocation>,
pub is_vcall: bool,
pub is_icall: bool,
pub bitmask: Option<u64>,
}
impl X86CFITypeCheck {
pub fn new(type_name: &str, type_hash: u64, target_address: u64) -> Self {
Self {
type_name: type_name.to_string(),
type_hash,
target_address,
source_loc: None,
is_vcall: false,
is_icall: false,
bitmask: None,
}
}
pub fn as_vcall(mut self) -> Self {
self.is_vcall = true;
self
}
pub fn as_icall(mut self) -> Self {
self.is_icall = true;
self
}
pub fn with_bitmask(mut self, bitmask: u64) -> Self {
self.bitmask = Some(bitmask);
self
}
pub fn passes_bitmask(&self, type_id: u64) -> bool {
if let Some(mask) = self.bitmask {
(type_id & mask) == (self.type_hash & mask)
} else {
type_id == self.type_hash
}
}
}
#[derive(Debug, Clone)]
pub struct X86SanSourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
impl X86SanSourceLocation {
pub fn new(file: &str, line: u32, column: u32) -> Self {
Self {
file: file.to_string(),
line,
column,
}
}
pub fn format(&self) -> String {
format!("{}:{}:{}", self.file, self.line, self.column)
}
}
#[derive(Debug, Clone)]
pub struct X86SanStackFrame {
pub function: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
pub module: Option<String>,
pub module_offset: Option<u64>,
pub ip: u64,
}
impl X86SanStackFrame {
pub fn new(ip: u64) -> Self {
Self {
function: None,
file: None,
line: None,
column: None,
module: None,
module_offset: None,
ip,
}
}
pub fn with_function(ip: u64, func: &str) -> Self {
Self {
function: Some(func.to_string()),
ip,
..Self::new(ip)
}
}
pub fn format(&self) -> String {
if let Some(func) = &self.function {
if let (Some(file), Some(line)) = (&self.file, self.line) {
format!(" #{} 0x{:x} in {} {}:{}", 0, self.ip, func, file, line)
} else {
format!(" #{} 0x{:x} in {}", 0, self.ip, func)
}
} else {
format!(" #{} 0x{:x}", 0, self.ip)
}
}
}
#[derive(Debug, Clone)]
pub struct X86SanCodeGen {
pub config: X86SanCodeGenConfig,
pub asan_accesses: Vec<X86ASanInstrumentedAccess>,
pub asan_stack_frame: Option<X86ASanStackFrame>,
pub asan_frames: Vec<X86ASanStackFrame>,
pub msan_shadows: HashMap<String, X86MSanShadowValue>,
pub msan_origins: Vec<X86MSanOrigin>,
pub msan_propagator: X86MSanShadowPropagator,
pub tsan_events: Vec<X86TSanEvent>,
pub tsan_shadow: HashMap<u64, X86TSanShadowCell>,
pub tsan_mutexes: Vec<X86TSanMutexState>,
pub tsan_global_tick: u64,
pub ubsan_checks: Vec<X86UBSanCheck>,
pub lsan_allocations: Vec<X86LSanAllocRecord>,
pub lsan_next_alloc_id: u64,
pub safe_stack: X86SafeStackConfig,
pub shadow_call_stack: X86ShadowCallStack,
pub cfi_checks: Vec<X86CFITypeCheck>,
pub stats: X86SanCodeGenStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86SanCodeGenStats {
pub asan_instrumented: u64,
pub asan_stack_vars: u64,
pub msan_tracked: u64,
pub msan_origins: u64,
pub tsan_events: u64,
pub tsan_races: u64,
pub ubsan_checks: u64,
pub lsan_allocs: u64,
pub cfi_checks: u64,
pub scs_pushes: u64,
pub functions_processed: u64,
}
impl X86SanCodeGen {
pub fn new() -> Self {
let config = X86SanCodeGenConfig::default();
Self {
msan_propagator: X86MSanShadowPropagator::new(config.msan_track_origins),
config,
asan_accesses: Vec::new(),
asan_stack_frame: None,
asan_frames: Vec::new(),
msan_shadows: HashMap::new(),
msan_origins: Vec::new(),
tsan_events: Vec::new(),
tsan_shadow: HashMap::new(),
tsan_mutexes: Vec::new(),
tsan_global_tick: 0,
ubsan_checks: Vec::new(),
lsan_allocations: Vec::new(),
lsan_next_alloc_id: 1,
safe_stack: X86SafeStackConfig::default(),
shadow_call_stack: X86ShadowCallStack::new(),
cfi_checks: Vec::new(),
stats: X86SanCodeGenStats::default(),
}
}
pub fn with_config(config: X86SanCodeGenConfig) -> Self {
let msan_origins_enabled = config.msan_track_origins;
Self {
msan_propagator: X86MSanShadowPropagator::new(msan_origins_enabled),
config,
..Self::new()
}
}
pub fn asan_instrument_load(&mut self, addr: u64, size: u8, alignment: u8) {
if !self.config.asan {
return;
}
let access = X86ASanInstrumentedAccess::new("load", false, size, alignment, addr);
self.asan_accesses.push(access);
self.stats.asan_instrumented += 1;
}
pub fn asan_instrument_store(&mut self, addr: u64, size: u8, alignment: u8) {
if !self.config.asan {
return;
}
let access = X86ASanInstrumentedAccess::new("store", true, size, alignment, addr);
self.asan_accesses.push(access);
self.stats.asan_instrumented += 1;
}
pub fn asan_begin_stack_frame(&mut self) {
if !self.config.asan {
return;
}
self.asan_stack_frame = Some(X86ASanStackFrame::new());
}
pub fn asan_add_stack_variable(
&mut self,
name: &str,
frame_offset: i32,
size: u32,
alignment: u32,
) {
if !self.config.asan {
return;
}
let var = X86ASanStackVar::new(name, frame_offset, size, alignment);
if let Some(ref mut frame) = self.asan_stack_frame {
frame.add_variable(var);
self.stats.asan_stack_vars += 1;
}
}
pub fn asan_finish_stack_frame(&mut self) -> Vec<X86ASanShadowOp> {
if !self.config.asan {
return Vec::new();
}
if let Some(frame) = self.asan_stack_frame.take() {
let ops = frame.compute_shadow_operations();
self.asan_frames.push(frame);
ops
} else {
Vec::new()
}
}
pub fn asan_compute_shadow_64(&self, app_addr: u64) -> u64 {
let offset = if self.config.asan_shadow_offset != 0 {
self.config.asan_shadow_offset
} else {
X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64
};
(app_addr >> X86_SANCODEGEN_ASAN_SHADOW_SCALE) + offset
}
pub fn asan_compute_shadow_32(&self, app_addr: u32) -> u32 {
(app_addr >> X86_SANCODEGEN_ASAN_SHADOW_SCALE) + X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X32
}
pub fn msan_set_shadow(&mut self, name: &str, shadow: X86MSanShadowValue) {
if !self.config.msan {
return;
}
self.msan_shadows.insert(name.to_string(), shadow);
self.stats.msan_tracked += 1;
}
pub fn msan_get_shadow(&self, name: &str) -> X86MSanShadowValue {
if !self.config.msan {
return X86MSanShadowValue::INITIALIZED;
}
self.msan_shadows
.get(name)
.cloned()
.unwrap_or(X86MSanShadowValue::INITIALIZED)
}
pub fn msan_check_read(
&mut self,
addr: u64,
size: u64,
value_name: &str,
) -> Result<(), X86MSanErrorReport> {
if !self.config.msan {
return Ok(());
}
let shadow = self.msan_get_shadow(value_name);
if shadow.is_uninitialized() {
let origin = self.msan_origins.iter().find(|o| o.id == shadow.origin_id);
let report = X86MSanErrorReport {
address: addr,
size,
shadow,
origin_description: origin.map(|o| o.description.clone()),
origin_kind: origin.map(|o| o.kind.clone()),
};
return Err(report);
}
Ok(())
}
pub fn msan_allocate_origin(&mut self, description: &str, kind: X86MSanOriginKind) -> u32 {
if !self.config.msan || !self.config.msan_track_origins {
return 0;
}
let id = self.msan_origins.len() as u32 + 1;
self.msan_origins
.push(X86MSanOrigin::new(id, description, kind));
self.stats.msan_origins += 1;
id
}
pub fn msan_propagate_binary(
&self,
inst_name: &str,
lhs_name: &str,
rhs_name: &str,
) -> X86MSanShadowValue {
let lhs = self.msan_get_shadow(lhs_name);
let rhs = self.msan_get_shadow(rhs_name);
let rule = self
.msan_propagator
.get_rule(inst_name)
.unwrap_or(&X86MSanPropagationRule::BinaryOr);
self.msan_propagator.propagate_binary(rule, &lhs, &rhs)
}
pub fn tsan_record_access(
&mut self,
thread_id: u32,
addr: u64,
size: u8,
is_write: bool,
) -> bool {
if !self.config.tsan {
return false;
}
self.tsan_global_tick += 1;
let clock = self.tsan_global_tick;
let event = X86TSanEvent::new(
if is_write {
X86TSanEventType::Write
} else {
X86TSanEventType::Read
},
thread_id,
clock,
)
.at_address(addr, size);
self.tsan_events.push(event);
self.stats.tsan_events += 1;
let cell = self
.tsan_shadow
.entry(addr)
.or_insert_with(X86TSanShadowCell::new);
let race = cell.record_access(thread_id, clock, is_write, size);
if race {
self.stats.tsan_races += 1;
}
race
}
pub fn tsan_mutex_create(&mut self) -> u32 {
if !self.config.tsan {
return 0;
}
let id = self.tsan_mutexes.len() as u32 + 1;
self.tsan_mutexes.push(X86TSanMutexState::new(id));
id
}
pub fn tsan_mutex_lock(&mut self, mutex_id: u32, thread_id: u32) {
if !self.config.tsan {
return;
}
if let Some(mutex) = self.tsan_mutexes.get_mut(mutex_id as usize - 1) {
mutex.acquire(thread_id);
}
}
pub fn tsan_mutex_unlock(&mut self, mutex_id: u32, thread_id: u32) {
if !self.config.tsan {
return;
}
if let Some(mutex) = self.tsan_mutexes.get_mut(mutex_id as usize - 1) {
let thread_clock = X86TSanVectorClock::new(); mutex.release(&thread_clock);
}
}
pub fn tsan_tick(&mut self) -> u64 {
self.tsan_global_tick += 1;
self.tsan_global_tick
}
pub fn ubsan_check_overflow(&mut self, file: &str, line: u32, col: u32, type_descr: &str) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(
X86UBSanCheck::new(X86UBSanErrorKind::SignedIntegerOverflow, file, line, col)
.with_type(type_descr),
);
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_div_zero(&mut self, file: &str, line: u32, col: u32, is_float: bool) {
if !self.config.ubsan {
return;
}
let kind = if is_float {
X86UBSanErrorKind::FloatDivideByZero
} else {
X86UBSanErrorKind::IntegerDivideByZero
};
self.ubsan_checks
.push(X86UBSanCheck::new(kind, file, line, col));
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_shift(&mut self, file: &str, line: u32, col: u32, rhs_width: u32) {
if !self.config.ubsan {
return;
}
let kind = if rhs_width >= X86_SANCODEGEN_UBSAN_MAX_SHIFT_WIDTH {
X86UBSanErrorKind::ShiftExponentTooLarge
} else {
X86UBSanErrorKind::ShiftExponentNegative
};
self.ubsan_checks
.push(X86UBSanCheck::new(kind, file, line, col));
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_null(&mut self, file: &str, line: u32, col: u32) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(X86UBSanCheck::new(
X86UBSanErrorKind::NullPointerArithmetic,
file,
line,
col,
));
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_bounds(&mut self, file: &str, line: u32, col: u32, type_descr: &str) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(
X86UBSanCheck::new(X86UBSanErrorKind::OutOfBounds, file, line, col)
.with_type(type_descr),
);
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_alignment(&mut self, file: &str, line: u32, col: u32) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(X86UBSanCheck::new(
X86UBSanErrorKind::AlignmentAssumption,
file,
line,
col,
));
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_type_mismatch(&mut self, file: &str, line: u32, col: u32, type_descr: &str) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(
X86UBSanCheck::new(X86UBSanErrorKind::TypeMismatch, file, line, col)
.with_type(type_descr),
);
self.stats.ubsan_checks += 1;
}
pub fn ubsan_check_unreachable(&mut self, file: &str, line: u32, col: u32) {
if !self.config.ubsan {
return;
}
self.ubsan_checks.push(X86UBSanCheck::new(
X86UBSanErrorKind::UnreachableCode,
file,
line,
col,
));
self.stats.ubsan_checks += 1;
}
pub fn lsan_register_allocation(&mut self, ptr: u64, size: u64) -> u64 {
if !self.config.lsan {
return 0;
}
let id = self.lsan_next_alloc_id;
self.lsan_next_alloc_id += 1;
self.lsan_allocations
.push(X86LSanAllocRecord::new(ptr, size, id));
self.stats.lsan_allocs += 1;
id
}
pub fn lsan_unregister_allocation(&mut self, ptr: u64) {
if !self.config.lsan {
return;
}
for alloc in &mut self.lsan_allocations {
if alloc.ptr == ptr && !alloc.is_freed {
alloc.mark_freed();
return;
}
}
}
pub fn lsan_detect_leaks(&mut self, root_regions: &[(u64, u64)]) -> Vec<X86LSanLeakReport> {
if !self.config.lsan {
return Vec::new();
}
let mut leaks = Vec::new();
for alloc in &self.lsan_allocations {
if alloc.is_freed {
continue;
}
let mut reachable = false;
for &(root_start, root_end) in root_regions {
if alloc.ptr >= root_start && alloc.ptr < root_end {
reachable = true;
break;
}
}
if !reachable {
leaks.push(X86LSanLeakReport {
ptr: alloc.ptr,
size: alloc.size,
is_directly_lost: true,
alloc_id: alloc.alloc_id,
});
}
}
leaks
}
pub fn safe_stack_enable(&mut self, safe_size: u64, unsafe_size: u64) {
self.safe_stack = X86SafeStackConfig::new(safe_size, unsafe_size);
self.config.safe_stack = true;
}
pub fn safe_stack_is_safe_object(&self, name: &str, is_array: bool) -> bool {
self.safe_stack.is_safe_object(name, is_array)
}
pub fn scs_push(&mut self, return_address: u64, function: Option<&str>) {
self.shadow_call_stack.push(return_address, function);
self.stats.scs_pushes += 1;
}
pub fn scs_pop(&mut self, expected_address: u64) -> bool {
self.shadow_call_stack.pop(expected_address)
}
pub fn scs_enable(&mut self) {
self.shadow_call_stack.active = true;
self.config.shadow_call_stack = true;
}
pub fn cfi_add_icall_check(&mut self, type_name: &str, type_hash: u64, target_address: u64) {
if !self.config.cfi {
return;
}
self.cfi_checks
.push(X86CFITypeCheck::new(type_name, type_hash, target_address).as_icall());
self.stats.cfi_checks += 1;
}
pub fn cfi_add_vcall_check(&mut self, type_name: &str, type_hash: u64, target_address: u64) {
if !self.config.cfi {
return;
}
self.cfi_checks
.push(X86CFITypeCheck::new(type_name, type_hash, target_address).as_vcall());
self.stats.cfi_checks += 1;
}
pub fn begin_function(&mut self, _name: &str) {
self.stats.functions_processed += 1;
}
pub fn end_function(&mut self) {
self.msan_shadows.clear();
self.asan_stack_frame = None;
}
pub fn get_stats(&self) -> &X86SanCodeGenStats {
&self.stats
}
pub fn reset(&mut self) {
self.asan_accesses.clear();
self.asan_stack_frame = None;
self.asan_frames.clear();
self.msan_shadows.clear();
self.msan_origins.clear();
self.tsan_events.clear();
self.tsan_shadow.clear();
self.tsan_mutexes.clear();
self.tsan_global_tick = 0;
self.ubsan_checks.clear();
self.lsan_allocations.clear();
self.lsan_next_alloc_id = 1;
self.shadow_call_stack.reset();
self.cfi_checks.clear();
self.stats = X86SanCodeGenStats::default();
}
}
impl Default for X86SanCodeGen {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MSanErrorReport {
pub address: u64,
pub size: u64,
pub shadow: X86MSanShadowValue,
pub origin_description: Option<String>,
pub origin_kind: Option<X86MSanOriginKind>,
}
impl fmt::Display for X86MSanErrorReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MSan: use of uninitialized value at 0x{:x}, size={}",
self.address, self.size
)?;
if let Some(ref desc) = self.origin_description {
write!(f, ", origin: {}", desc)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86LSanLeakReport {
pub ptr: u64,
pub size: u64,
pub is_directly_lost: bool,
pub alloc_id: u64,
}
impl fmt::Display for X86LSanLeakReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"LSan: {} leak of {} bytes at 0x{:x} (alloc #{})",
if self.is_directly_lost {
"direct"
} else {
"indirect"
},
self.size,
self.ptr,
self.alloc_id,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_defaults() {
let cfg = X86SanCodeGenConfig::default();
assert!(!cfg.asan);
assert!(!cfg.msan);
assert!(!cfg.tsan);
assert!(!cfg.ubsan);
assert!(!cfg.lsan);
assert!(!cfg.safe_stack);
assert!(!cfg.shadow_call_stack);
assert!(!cfg.cfi);
assert!(cfg.is_64bit);
}
#[test]
fn test_config_asan_x64() {
let cfg = X86SanCodeGenConfig::asan_x64();
assert!(cfg.asan);
assert_eq!(
cfg.asan_shadow_offset,
X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64
);
assert!(cfg.is_64bit);
}
#[test]
fn test_config_msan_with_origins() {
let cfg = X86SanCodeGenConfig::msan_with_origins();
assert!(cfg.msan);
assert!(cfg.msan_track_origins);
}
#[test]
fn test_config_tsan() {
let cfg = X86SanCodeGenConfig::tsan();
assert!(cfg.tsan);
assert!(!cfg.halt_on_error);
}
#[test]
fn test_config_ubsan() {
let cfg = X86SanCodeGenConfig::ubsan();
assert!(cfg.ubsan);
assert!(cfg.ubsan_recover);
}
#[test]
fn test_config_full_sanitize() {
let cfg = X86SanCodeGenConfig::full_sanitize();
assert!(cfg.asan);
assert!(cfg.ubsan);
assert!(cfg.lsan);
assert!(cfg.safe_stack);
assert!(cfg.shadow_call_stack);
assert!(cfg.cfi);
}
#[test]
fn test_asan_access_creation() {
let access = X86ASanInstrumentedAccess::new("load", false, 8, 8, 0x1000);
assert!(!access.is_store);
assert_eq!(access.size, 8);
assert_eq!(access.alignment, 8);
assert_eq!(access.address, 0x1000);
}
#[test]
fn test_asan_access_with_flags() {
let access = X86ASanInstrumentedAccess::new("store", true, 4, 4, 0x2000)
.as_stack()
.with_source("test.c", 42, 10);
assert!(access.is_store);
assert!(access.is_stack);
assert!(access.source_loc.is_some());
assert_eq!(access.source_loc.as_ref().unwrap().line, 42);
}
#[test]
fn test_asan_access_aligned() {
let access = X86ASanInstrumentedAccess::new("load", false, 8, 8, 0x1000);
assert!(access.is_aligned());
let misaligned = X86ASanInstrumentedAccess::new("load", false, 8, 4, 0x1000);
assert!(!misaligned.is_aligned());
}
#[test]
fn test_asan_compute_shadow_64() {
let access = X86ASanInstrumentedAccess::new("load", false, 8, 8, 0x1000);
let shadow = access.compute_shadow_address_64(X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64);
let expected = (0x1000 >> 3) + X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64;
assert_eq!(shadow, expected);
}
#[test]
fn test_asan_compute_shadow_32() {
let access = X86ASanInstrumentedAccess::new("load", false, 4, 4, 0x1000);
let shadow = access.compute_shadow_address_32(X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X32);
let expected = (0x1000u32 >> 3) + X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X32;
assert_eq!(shadow, expected);
}
#[test]
fn test_stack_var_creation() {
let var = X86ASanStackVar::new("my_var", 16, 8, 8);
assert_eq!(var.name, "my_var");
assert_eq!(
var.total_size(),
X86_SANCODEGEN_STACK_REDZONE_SIZE + 8 + X86_SANCODEGEN_STACK_REDZONE_SIZE
);
}
#[test]
fn test_stack_var_with_options() {
let var = X86ASanStackVar::new("v", 0, 4, 4)
.with_use_after_scope()
.with_use_after_return()
.with_life_range(10, 50);
assert!(var.use_after_scope);
assert!(var.use_after_return);
assert_eq!(var.life_start, 10);
assert_eq!(var.life_end, 50);
}
#[test]
fn test_stack_var_shadow_byte_partial() {
assert_eq!(
X86ASanStackVar::shadow_byte_for_partial(0),
X86_ASAN_SHADOW_ADDRESSABLE
);
assert_eq!(
X86ASanStackVar::shadow_byte_for_partial(3),
X86_ASAN_SHADOW_PARTIAL3
);
assert_eq!(
X86ASanStackVar::shadow_byte_for_partial(7),
X86_ASAN_SHADOW_PARTIAL7
);
assert_eq!(
X86ASanStackVar::shadow_byte_for_partial(8),
X86_ASAN_SHADOW_ADDRESSABLE
);
}
#[test]
fn test_stack_frame_add_variable() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("a", 0, 4, 4));
frame.add_variable(X86ASanStackVar::new("b", 40, 8, 8));
assert_eq!(frame.variable_count(), 2);
}
#[test]
fn test_stack_frame_compute_shadow_ops() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("x", 0, 4, 4));
let ops = frame.compute_shadow_operations();
assert!(!ops.is_empty());
let has_poison = ops.iter().any(|o| o.op_kind == X86ASanShadowOpKind::Poison);
assert!(has_poison);
}
#[test]
fn test_stack_frame_unpoison_all() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("x", 0, 4, 4));
let ops = frame.unpoison_all();
assert!(!ops.is_empty());
assert_eq!(ops[0].op_kind, X86ASanShadowOpKind::Unpoison);
}
#[test]
fn test_msan_shadow_initialized() {
assert!(X86MSanShadowValue::INITIALIZED.is_initialized());
assert!(!X86MSanShadowValue::INITIALIZED.is_uninitialized());
}
#[test]
fn test_msan_shadow_uninitialized() {
assert!(!X86MSanShadowValue::UNINITIALIZED.is_initialized());
assert!(X86MSanShadowValue::UNINITIALIZED.is_uninitialized());
}
#[test]
fn test_msan_propagate_binary() {
let lhs = X86MSanShadowValue::new(0x0F, 1);
let rhs = X86MSanShadowValue::new(0xF0, 2);
let result = X86MSanShadowValue::propagate_binary(&lhs, &rhs);
assert_eq!(result.shadow, 0xFF);
assert_eq!(result.origin_id, 1); }
#[test]
fn test_msan_truncate() {
let val = X86MSanShadowValue::new(0xABCD, 3);
let truncated = val.truncate(8);
assert_eq!(truncated.shadow, 0xCD);
}
#[test]
fn test_msan_sext_with_sign() {
let val = X86MSanShadowValue::new(0x80, 5);
let extended = val.sext(8);
assert_eq!(extended.shadow, 0x80 | 0xFFFFFF00);
}
#[test]
fn test_msan_propagator_new() {
let prop = X86MSanShadowPropagator::new(true);
assert!(prop.track_origins);
assert!(prop.get_rule("add").is_some());
assert!(prop.get_rule("icmp").is_some());
assert!(prop.get_rule("nonexistent").is_none());
}
#[test]
fn test_msan_propagator_propagate() {
let prop = X86MSanShadowPropagator::new(false);
let lhs = X86MSanShadowValue::new(0x01, 1);
let rhs = X86MSanShadowValue::new(0x02, 2);
let rule = prop.get_rule("add").unwrap();
let result = prop.propagate_binary(rule, &lhs, &rhs);
assert_eq!(result.shadow, 0x03);
}
#[test]
fn test_tsan_vector_clock_new() {
let vc = X86TSanVectorClock::new();
assert!(vc.is_empty());
assert_eq!(vc.len(), 0);
}
#[test]
fn test_tsan_vector_clock_tick() {
let mut vc = X86TSanVectorClock::new();
vc.tick(1);
assert_eq!(vc.get(1), 1);
vc.tick(1);
assert_eq!(vc.get(1), 2);
}
#[test]
fn test_tsan_vector_clock_set() {
let mut vc = X86TSanVectorClock::new();
vc.set(1, 5);
vc.set(2, 10);
assert_eq!(vc.get(1), 5);
assert_eq!(vc.get(2), 10);
}
#[test]
fn test_tsan_vector_clock_happens_before() {
let mut vc1 = X86TSanVectorClock::new();
vc1.set(1, 5);
let mut vc2 = X86TSanVectorClock::new();
vc2.set(1, 10);
assert!(vc1.happens_before(&vc2));
assert!(!vc2.happens_before(&vc1));
}
#[test]
fn test_tsan_vector_clock_concurrent() {
let mut vc1 = X86TSanVectorClock::new();
vc1.set(1, 5);
let mut vc2 = X86TSanVectorClock::new();
vc2.set(2, 5);
assert!(vc1.concurrent_with(&vc2));
}
#[test]
fn test_tsan_vector_clock_join() {
let mut vc1 = X86TSanVectorClock::new();
vc1.set(1, 5);
let mut vc2 = X86TSanVectorClock::new();
vc2.set(2, 10);
vc1.join(&vc2);
assert_eq!(vc1.get(1), 5);
assert_eq!(vc1.get(2), 10);
}
#[test]
fn test_tsan_shadow_cell_new() {
let cell = X86TSanShadowCell::new();
assert_eq!(cell.last_thread, 0);
assert_eq!(cell.read_count, 0);
}
#[test]
fn test_tsan_shadow_cell_no_race_same_thread() {
let mut cell = X86TSanShadowCell::new();
assert!(!cell.record_access(1, 1, true, 4));
assert!(!cell.record_access(1, 2, false, 4));
}
#[test]
fn test_tsan_shadow_cell_write_write_race() {
let mut cell = X86TSanShadowCell::new();
cell.record_access(2, 5, true, 4);
let race = cell.record_access(1, 3, true, 4);
assert_eq!(cell.last_write_thread, 1);
assert_eq!(cell.write_clock, 3);
}
#[test]
fn test_tsan_shadow_cell_history_limit() {
let mut cell = X86TSanShadowCell::new();
for i in 0..12 {
cell.record_access(i, i as u64, i % 2 == 0, 4);
}
assert!(cell.history.len() <= 8);
}
#[test]
fn test_tsan_shadow_cell_reset() {
let mut cell = X86TSanShadowCell::new();
cell.record_access(1, 1, true, 4);
cell.reset();
assert_eq!(cell.last_thread, 0);
assert!(cell.history.is_empty());
}
#[test]
fn test_tsan_mutex_new() {
let m = X86TSanMutexState::new(1);
assert_eq!(m.id, 1);
assert!(!m.is_locked());
}
#[test]
fn test_tsan_mutex_acquire_release() {
let mut m = X86TSanMutexState::new(1);
m.acquire(5);
assert!(m.is_locked());
assert_eq!(m.owner_thread, 5);
m.release(&X86TSanVectorClock::new());
assert!(!m.is_locked());
}
#[test]
fn test_tsan_mutex_destroy() {
let mut m = X86TSanMutexState::new(1);
assert!(!m.is_destroyed);
m.destroy();
assert!(m.is_destroyed);
}
#[test]
fn test_tsan_event_creation() {
let evt = X86TSanEvent::new(X86TSanEventType::Write, 1, 100);
assert_eq!(evt.thread_id, 1);
assert_eq!(evt.clock, 100);
}
#[test]
fn test_tsan_event_with_address() {
let evt = X86TSanEvent::new(X86TSanEventType::Read, 2, 50).at_address(0xDEAD, 8);
assert_eq!(evt.address, 0xDEAD);
assert_eq!(evt.size, 8);
}
#[test]
fn test_tsan_event_type_display() {
assert_eq!(format!("{}", X86TSanEventType::Read), "READ");
assert_eq!(format!("{}", X86TSanEventType::MutexLock), "MUTEX_LOCK");
assert_eq!(format!("{}", X86TSanEventType::AtomicFence), "ATOMIC_FENCE");
}
#[test]
fn test_ubsan_error_kind_display() {
assert_eq!(
format!("{}", X86UBSanErrorKind::IntegerDivideByZero),
"division-by-zero"
);
assert_eq!(
format!("{}", X86UBSanErrorKind::SignedIntegerOverflow),
"signed-integer-overflow"
);
assert_eq!(
format!("{}", X86UBSanErrorKind::InvalidVptr),
"invalid-vptr"
);
}
#[test]
fn test_ubsan_check_creation() {
let check = X86UBSanCheck::new(X86UBSanErrorKind::OutOfBounds, "foo.c", 10, 5);
assert_eq!(check.source_loc.file, "foo.c");
assert_eq!(check.source_loc.line, 10);
}
#[test]
fn test_ubsan_check_with_type() {
let check = X86UBSanCheck::new(X86UBSanErrorKind::TypeMismatch, "t.c", 1, 1)
.with_type("struct Foo*");
assert_eq!(check.type_descr.unwrap(), "struct Foo*");
}
#[test]
fn test_lsan_alloc_record_new() {
let record = X86LSanAllocRecord::new(0x1000, 64, 1);
assert_eq!(record.ptr, 0x1000);
assert_eq!(record.size, 64);
assert!(!record.is_freed);
}
#[test]
fn test_lsan_alloc_record_mark_freed() {
let mut record = X86LSanAllocRecord::new(0x1000, 64, 1);
record.mark_freed();
assert!(record.is_freed);
}
#[test]
fn test_safestack_config_default() {
let cfg = X86SafeStackConfig::default();
assert!(!cfg.enabled);
assert_eq!(cfg.safe_stack_size, 1024 * 1024);
assert_eq!(cfg.unsafe_stack_size, 8 * 1024 * 1024);
}
#[test]
fn test_safestack_new() {
let cfg = X86SafeStackConfig::new(4096, 8192);
assert!(cfg.enabled);
assert_eq!(cfg.safe_stack_size, 4096);
assert_eq!(cfg.safe_stack_addr(0), 0);
}
#[test]
fn test_safestack_is_safe_object() {
let cfg = X86SafeStackConfig::new(4096, 8192);
assert!(cfg.is_safe_object("x", false)); assert!(!cfg.is_safe_object("arr", true)); }
#[test]
fn test_scs_new() {
let scs = X86ShadowCallStack::new();
assert!(!scs.active);
assert_eq!(scs.depth(), 0);
assert!(scs.is_empty());
}
#[test]
fn test_scs_push_pop() {
let mut scs = X86ShadowCallStack::new();
scs.push(0x12345678, Some("foo"));
assert_eq!(scs.depth(), 1);
assert_eq!(scs.peek(), Some(0x12345678));
assert!(scs.pop(0x12345678));
assert_eq!(scs.depth(), 0);
}
#[test]
fn test_scs_pop_mismatch() {
let mut scs = X86ShadowCallStack::new();
scs.push(0xAAAA, None);
assert!(!scs.pop(0xBBBB));
assert_eq!(scs.mismatches, 1);
}
#[test]
fn test_scs_reset() {
let mut scs = X86ShadowCallStack::new();
scs.push(0x1, None);
scs.push(0x2, None);
scs.reset();
assert_eq!(scs.depth(), 0);
assert!(scs.is_empty());
}
#[test]
fn test_cfi_type_check_new() {
let check = X86CFITypeCheck::new("MyClass", 0xABCD, 0x4000);
assert_eq!(check.type_name, "MyClass");
assert_eq!(check.type_hash, 0xABCD);
}
#[test]
fn test_cfi_type_check_flags() {
let check = X86CFITypeCheck::new("Base", 0x1, 0x5000)
.as_vcall()
.with_bitmask(0xFF);
assert!(check.is_vcall);
assert!(!check.is_icall);
assert_eq!(check.bitmask, Some(0xFF));
}
#[test]
fn test_cfi_passes_bitmask() {
let check = X86CFITypeCheck::new("T", 0x1234, 0).with_bitmask(0xFF00);
assert!(check.passes_bitmask(0x1234));
assert!(!check.passes_bitmask(0x5678));
}
#[test]
fn test_source_location() {
let loc = X86SanSourceLocation::new("main.c", 42, 7);
assert_eq!(loc.file, "main.c");
assert_eq!(loc.format(), "main.c:42:7");
}
#[test]
fn test_stack_frame_new() {
let sf = X86SanStackFrame::new(0x401000);
assert_eq!(sf.ip, 0x401000);
assert!(sf.function.is_none());
}
#[test]
fn test_stack_frame_with_function() {
let sf = X86SanStackFrame::with_function(0x401000, "main");
assert_eq!(sf.function.as_deref(), Some("main"));
}
#[test]
fn test_stack_frame_format() {
let mut sf = X86SanStackFrame::new(0x401000);
let formatted = sf.format();
assert!(formatted.contains("0x401000"));
sf.function = Some("main".to_string());
let formatted2 = sf.format();
assert!(formatted2.contains("main"));
}
#[test]
fn test_sancodegen_new() {
let scg = X86SanCodeGen::new();
assert!(!scg.config.asan);
assert_eq!(scg.asan_accesses.len(), 0);
assert!(scg.asan_stack_frame.is_none());
}
#[test]
fn test_sancodegen_with_config() {
let cfg = X86SanCodeGenConfig::asan_x64();
let scg = X86SanCodeGen::with_config(cfg);
assert!(scg.config.asan);
}
#[test]
fn test_asan_instrument_load() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
scg.asan_instrument_load(0x1000, 8, 8);
assert_eq!(scg.asan_accesses.len(), 1);
assert_eq!(scg.stats.asan_instrumented, 1);
}
#[test]
fn test_asan_instrument_store() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
scg.asan_instrument_store(0x2000, 4, 4);
assert_eq!(scg.asan_accesses.len(), 1);
}
#[test]
fn test_asan_stack_frame_lifecycle() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
scg.asan_begin_stack_frame();
assert!(scg.asan_stack_frame.is_some());
scg.asan_add_stack_variable("buf", 0, 64, 16);
scg.asan_add_stack_variable("len", 80, 4, 4);
assert_eq!(scg.stats.asan_stack_vars, 2);
let ops = scg.asan_finish_stack_frame();
assert!(scg.asan_stack_frame.is_none());
assert!(!ops.is_empty());
assert_eq!(scg.asan_frames.len(), 1);
}
#[test]
fn test_asan_disabled_does_nothing() {
let mut scg = X86SanCodeGen::new();
scg.asan_instrument_load(0x1000, 8, 8);
assert!(scg.asan_accesses.is_empty());
scg.asan_begin_stack_frame();
assert!(scg.asan_stack_frame.is_none());
}
#[test]
fn test_asan_compute_shadow_methods() {
let scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
let shadow = scg.asan_compute_shadow_64(0x8000);
assert_eq!(
shadow,
(0x8000 >> 3) + X86_SANCODEGEN_ASAN_SHADOW_OFFSET_X64
);
}
#[test]
fn test_msan_set_get_shadow() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::msan_with_origins());
let sv = X86MSanShadowValue::new(0xFF, 42);
scg.msan_set_shadow("v1", sv);
let retrieved = scg.msan_get_shadow("v1");
assert_eq!(retrieved.shadow, 0xFF);
assert_eq!(retrieved.origin_id, 42);
}
#[test]
fn test_msan_check_read_clean() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::msan_with_origins());
scg.msan_set_shadow("x", X86MSanShadowValue::INITIALIZED);
let result = scg.msan_check_read(0x1000, 8, "x");
assert!(result.is_ok());
}
#[test]
fn test_msan_allocate_origin() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::msan_with_origins());
let id = scg.msan_allocate_origin("heap alloc", X86MSanOriginKind::HeapAllocation);
assert_eq!(id, 1);
let id2 = scg.msan_allocate_origin("stack var", X86MSanOriginKind::StackAllocation);
assert_eq!(id2, 2);
assert_eq!(scg.msan_origins.len(), 2);
}
#[test]
fn test_msan_origin_no_tracking() {
let mut scg = X86SanCodeGen::new();
let id = scg.msan_allocate_origin("test", X86MSanOriginKind::Unknown);
assert_eq!(id, 0);
}
#[test]
fn test_msan_propagate_binary_call() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::msan_with_origins());
scg.msan_set_shadow("a", X86MSanShadowValue::new(0x0F, 1));
scg.msan_set_shadow("b", X86MSanShadowValue::new(0xF0, 2));
let result = scg.msan_propagate_binary("add", "a", "b");
assert_eq!(result.shadow, 0xFF);
assert_eq!(result.origin_id, 1);
}
#[test]
fn test_tsan_record_access() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::tsan());
let race = scg.tsan_record_access(1, 0x4000, 4, true);
assert!(!race); assert_eq!(scg.stats.tsan_events, 1);
}
#[test]
fn test_tsan_mutex_lifecycle() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::tsan());
let mtx = scg.tsan_mutex_create();
assert_eq!(mtx, 1);
scg.tsan_mutex_lock(mtx, 5);
scg.tsan_mutex_unlock(mtx, 5);
}
#[test]
fn test_ubsan_add_checks() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::ubsan());
scg.ubsan_check_overflow("test.c", 10, 1, "int");
scg.ubsan_check_div_zero("test.c", 11, 1, false);
scg.ubsan_check_shift("test.c", 12, 1, 64);
scg.ubsan_check_null("test.c", 13, 1);
assert_eq!(scg.ubsan_checks.len(), 4);
assert_eq!(scg.stats.ubsan_checks, 4);
}
#[test]
fn test_ubsan_disabled_noop() {
let mut scg = X86SanCodeGen::new();
scg.ubsan_check_overflow("t.c", 1, 1, "int");
assert!(scg.ubsan_checks.is_empty());
}
#[test]
fn test_lsan_register_unregister() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
let id = scg.lsan_register_allocation(0xA000, 128);
assert!(id > 0);
assert_eq!(scg.stats.lsan_allocs, 1);
scg.lsan_unregister_allocation(0xA000);
}
#[test]
fn test_lsan_detect_leaks() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
scg.lsan_register_allocation(0xDEAD, 64);
let roots = vec![(0x0, 0x1000)]; let leaks = scg.lsan_detect_leaks(&roots);
assert_eq!(leaks.len(), 1);
}
#[test]
fn test_lsan_detect_no_leaks_in_root() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
scg.lsan_register_allocation(0x500, 64);
let roots = vec![(0x0, 0x1000)]; let leaks = scg.lsan_detect_leaks(&roots);
assert!(leaks.is_empty());
}
#[test]
fn test_safe_stack_enable() {
let mut scg = X86SanCodeGen::new();
assert!(!scg.config.safe_stack);
scg.safe_stack_enable(4096, 8192);
assert!(scg.config.safe_stack);
assert!(scg.safe_stack.enabled);
}
#[test]
fn test_scs_lifecycle() {
let mut scg = X86SanCodeGen::new();
scg.scs_enable();
assert!(scg.config.shadow_call_stack);
assert!(scg.shadow_call_stack.active);
scg.scs_push(0x401234, Some("func"));
assert_eq!(scg.stats.scs_pushes, 1);
assert!(scg.scs_pop(0x401234));
}
#[test]
fn test_cfi_add_checks() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
scg.cfi_add_icall_check("void(int)", 0xABCD, 0x5000);
scg.cfi_add_vcall_check("Base", 0x1234, 0x6000);
assert_eq!(scg.cfi_checks.len(), 2);
assert_eq!(scg.stats.cfi_checks, 2);
assert!(scg.cfi_checks[0].is_icall);
assert!(scg.cfi_checks[1].is_vcall);
}
#[test]
fn test_begin_end_function() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::msan_with_origins());
scg.msan_set_shadow("tmp", X86MSanShadowValue::INITIALIZED);
scg.begin_function("my_func");
assert_eq!(scg.stats.functions_processed, 1);
scg.end_function();
assert!(scg.msan_shadows.is_empty());
}
#[test]
fn test_get_stats() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
scg.asan_instrument_load(0x100, 8, 8);
scg.asan_instrument_store(0x200, 4, 4);
let stats = scg.get_stats();
assert_eq!(stats.asan_instrumented, 2);
}
#[test]
fn test_reset() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
scg.asan_instrument_load(0x100, 8, 8);
scg.ubsan_check_null("f.c", 1, 1);
scg.lsan_register_allocation(0x500, 32);
scg.scs_push(0xDEAD, None);
scg.reset();
assert!(scg.asan_accesses.is_empty());
assert!(scg.ubsan_checks.is_empty());
assert!(scg.lsan_allocations.is_empty());
assert_eq!(scg.stats.asan_instrumented, 0);
assert!(scg.shadow_call_stack.is_empty());
}
#[test]
fn test_shadow_op_poison() {
let op = X86ASanShadowOp::poison(0, 32, X86_ASAN_SHADOW_STACK_LEFT);
assert_eq!(op.op_kind, X86ASanShadowOpKind::Poison);
assert_eq!(op.shadow_value, X86_ASAN_SHADOW_STACK_LEFT);
}
#[test]
fn test_shadow_op_unpoison() {
let op = X86ASanShadowOp::unpoison(0, 32);
assert_eq!(op.op_kind, X86ASanShadowOpKind::Unpoison);
assert_eq!(op.shadow_value, X86_ASAN_SHADOW_ADDRESSABLE);
}
#[test]
fn test_shadow_op_set_shadow() {
let op = X86ASanShadowOp::set_shadow(16, X86_ASAN_SHADOW_PARTIAL4);
assert_eq!(op.op_kind, X86ASanShadowOpKind::SetShadow);
assert_eq!(op.shadow_value, X86_ASAN_SHADOW_PARTIAL4);
}
#[test]
fn test_shadow_op_check_access() {
let op = X86ASanShadowOp::check_access(0, 8);
assert_eq!(op.op_kind, X86ASanShadowOpKind::CheckAccess);
}
#[test]
fn test_msan_error_report_display() {
let report = X86MSanErrorReport {
address: 0x1234,
size: 8,
shadow: X86MSanShadowValue::UNINITIALIZED,
origin_description: Some("heap allocation".to_string()),
origin_kind: Some(X86MSanOriginKind::HeapAllocation),
};
let s = format!("{}", report);
assert!(s.contains("0x1234"));
assert!(s.contains("uninitialized"));
assert!(s.contains("heap allocation"));
}
#[test]
fn test_lsan_leak_report_display() {
let report = X86LSanLeakReport {
ptr: 0xDEAD,
size: 128,
is_directly_lost: true,
alloc_id: 7,
};
let s = format!("{}", report);
assert!(s.contains("0xdead"));
assert!(s.contains("128 bytes"));
assert!(s.contains("alloc #7"));
}
#[test]
fn test_origin_kind_display() {
assert_eq!(format!("{}", X86MSanOriginKind::HeapAllocation), "heap");
assert_eq!(format!("{}", X86MSanOriginKind::StackAllocation), "stack");
assert_eq!(
format!("{}", X86MSanOriginKind::FunctionParameter),
"parameter"
);
}
#[test]
fn test_full_asan_workflow() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::asan_x64());
scg.begin_function("example");
scg.asan_begin_stack_frame();
scg.asan_add_stack_variable("buffer", 0, 256, 16);
scg.asan_add_stack_variable("index", 272, 4, 4);
scg.asan_finish_stack_frame();
scg.asan_instrument_load(0x4000, 8, 8);
scg.asan_instrument_store(0x4008, 4, 4);
scg.end_function();
assert_eq!(scg.stats.functions_processed, 1);
assert_eq!(scg.asan_frames.len(), 1);
assert_eq!(scg.stats.asan_stack_vars, 2);
assert_eq!(scg.stats.asan_instrumented, 2);
}
#[test]
fn test_full_ubsan_workflow() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::ubsan());
scg.begin_function("compute");
scg.ubsan_check_overflow("compute.c", 5, 10, "int");
scg.ubsan_check_div_zero("compute.c", 7, 12, false);
scg.ubsan_check_bounds("compute.c", 9, 5, "int*");
scg.ubsan_check_unreachable("compute.c", 15, 1);
scg.end_function();
assert_eq!(scg.stats.functions_processed, 1);
assert_eq!(scg.stats.ubsan_checks, 4);
}
#[test]
fn test_combined_sanitizer_workflow() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::full_sanitize());
scg.begin_function("multi_san");
scg.asan_instrument_load(0x1000, 8, 8);
scg.ubsan_check_null("multi.c", 3, 1);
scg.lsan_register_allocation(0x5000, 64);
scg.cfi_add_icall_check("fnptr", 0x42, 0x7000);
scg.scs_enable();
scg.scs_push(0x401000, Some("multi_san"));
scg.end_function();
assert_eq!(scg.stats.functions_processed, 1);
assert!(scg.stats.asan_instrumented > 0);
assert!(scg.stats.ubsan_checks > 0);
assert!(scg.stats.lsan_allocs > 0);
assert!(scg.stats.cfi_checks > 0);
assert!(scg.stats.scs_pushes > 0);
}
#[test]
fn test_tsan_detailed_workflow() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::tsan());
scg.tsan_record_access(1, 0x4000, 4, true);
scg.tsan_record_access(2, 0x4000, 4, false);
assert_eq!(scg.stats.tsan_events, 2);
let mtx = scg.tsan_mutex_create();
scg.tsan_mutex_lock(mtx, 3);
scg.tsan_mutex_unlock(mtx, 3);
assert_eq!(scg.tsan_mutexes.len(), 1);
}
#[test]
fn test_msan_origin_kinds_display() {
let kinds = vec![
X86MSanOriginKind::HeapAllocation,
X86MSanOriginKind::StackAllocation,
X86MSanOriginKind::GlobalVariable,
X86MSanOriginKind::FunctionParameter,
X86MSanOriginKind::Instruction,
X86MSanOriginKind::Deallocated,
X86MSanOriginKind::Unknown,
];
for k in kinds {
let s = format!("{}", k);
assert!(!s.is_empty());
}
}
#[test]
fn test_msan_propagator_register_custom() {
let mut prop = X86MSanShadowPropagator::new(false);
prop.register_rule(
"my_op",
X86MSanPropagationRule::Custom("my_rule".to_string()),
);
let rule = prop.get_rule("my_op").unwrap();
match rule {
X86MSanPropagationRule::Custom(name) => assert_eq!(name, "my_rule"),
_ => panic!("expected custom rule"),
}
}
#[test]
fn test_shadow_constants() {
assert_eq!(X86_SANCODEGEN_ASAN_SHADOW_SCALE, 3);
assert_eq!(X86_SANCODEGEN_ASAN_SHADOW_GRANULARITY, 8);
assert_eq!(X86_SANCODEGEN_STACK_REDZONE_SIZE, 32);
assert_eq!(X86_ASAN_SHADOW_ADDRESSABLE, 0);
assert_eq!(X86_ASAN_SHADOW_STACK_LEFT, 0xF1);
assert_eq!(X86_ASAN_SHADOW_FREED, 0xFD);
}
#[test]
fn test_msan_shadow_value_bitcast() {
let val = X86MSanShadowValue::new(0xABCD1234, 10);
let cast = val.bitcast();
assert_eq!(cast.shadow, val.shadow);
assert_eq!(cast.origin_id, val.origin_id);
}
#[test]
fn test_vector_clock_max_clock() {
let mut vc = X86TSanVectorClock::new();
vc.set(1, 5);
vc.set(2, 12);
vc.set(3, 7);
assert_eq!(vc.max_clock(), 12);
}
#[test]
fn test_vector_clock_default() {
let vc = X86TSanVectorClock::default();
assert!(vc.is_empty());
}
#[test]
fn test_tsan_global_tick() {
let mut scg = X86SanCodeGen::with_config(X86SanCodeGenConfig::tsan());
let t1 = scg.tsan_tick();
let t2 = scg.tsan_tick();
assert!(t2 > t1);
}
#[test]
fn test_ubsan_all_error_kinds_display() {
let kinds = [
X86UBSanErrorKind::IntegerDivideByZero,
X86UBSanErrorKind::FloatDivideByZero,
X86UBSanErrorKind::ShiftExponentNegative,
X86UBSanErrorKind::ShiftExponentTooLarge,
X86UBSanErrorKind::SignedIntegerOverflow,
X86UBSanErrorKind::PointerOverflow,
X86UBSanErrorKind::NullPointerArithmetic,
X86UBSanErrorKind::VLABoundNotPositive,
X86UBSanErrorKind::OutOfBounds,
X86UBSanErrorKind::TypeMismatch,
X86UBSanErrorKind::AlignmentAssumption,
X86UBSanErrorKind::UnreachableCode,
X86UBSanErrorKind::MissingReturn,
X86UBSanErrorKind::NonNullViolation,
X86UBSanErrorKind::BuiltinUnreachable,
X86UBSanErrorKind::InvalidBool,
X86UBSanErrorKind::InvalidEnum,
X86UBSanErrorKind::ImplicitConversionTruncation,
X86UBSanErrorKind::ImplicitConversionSignChange,
X86UBSanErrorKind::NegativeArraySize,
X86UBSanErrorKind::FunctionTypeMismatch,
X86UBSanErrorKind::InvalidVptr,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_msan_propagator_all_builtin_rules() {
let prop = X86MSanShadowPropagator::default();
assert!(prop.get_rule("add").is_some());
assert!(prop.get_rule("sub").is_some());
assert!(prop.get_rule("mul").is_some());
assert!(prop.get_rule("udiv").is_some());
assert!(prop.get_rule("sdiv").is_some());
assert!(prop.get_rule("urem").is_some());
assert!(prop.get_rule("srem").is_some());
assert!(prop.get_rule("and").is_some());
assert!(prop.get_rule("or").is_some());
assert!(prop.get_rule("xor").is_some());
assert!(prop.get_rule("shl").is_some());
assert!(prop.get_rule("lshr").is_some());
assert!(prop.get_rule("ashr").is_some());
assert!(prop.get_rule("select").is_some());
assert!(prop.get_rule("phi").is_some());
assert!(prop.get_rule("icmp").is_some());
assert!(prop.get_rule("fadd").is_some());
}
#[test]
fn test_msan_propagator_default_no_origins() {
let prop = X86MSanShadowPropagator::default();
assert!(!prop.track_origins);
}
}