#![allow(non_upper_case_globals, dead_code)]
use crate::hwasan::*;
use crate::sanitize::*;
use crate::x86::*;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
pub const X86_ASAN_SHADOW_SCALE: u8 = 3;
pub const X86_ASAN_SHADOW_GRANULARITY: u64 = 1u64 << X86_ASAN_SHADOW_SCALE;
pub const X86_ASAN_SHADOW_MASK: u64 = X86_ASAN_SHADOW_GRANULARITY - 1;
pub const X86_ASAN_SHADOW_OFFSET_64_DEFAULT: u64 = 0x7fff8000;
pub const X86_ASAN_SHADOW_OFFSET_64_ASLR: u64 = 0x10007fff8000;
pub const X86_ASAN_SHADOW_OFFSET_32: u64 = 0x20000000;
pub const X86_ASAN_MAX_SHADOW_SIZE_64: u64 = 0x200000000000;
pub const X86_ASAN_STACK_REDZONE_SIZE: u64 = 32;
pub const X86_ASAN_GLOBAL_REDZONE_SIZE: u64 = 32;
pub const X86_ASAN_HEAP_REDZONE_SIZE: u64 = 32;
pub const X86_ASAN_MIN_REDZONE_SIZE: u64 = 16;
pub const X86_ASAN_MAX_STACK_REDZONE_SIZE: u64 = 256;
pub const X86_ASAN_QUARANTINE_MAX_SIZE: u64 = 256 * 1024 * 1024;
pub const X86_ASAN_THREAD_QUARANTINE_SIZE: u64 = 1024 * 1024;
pub const X86_ASAN_MAX_PRIMARY_ALLOC_SIZE: u64 = 256 * 1024;
pub const X86_ASAN_FAKE_STACK_MAX_ENTRIES: usize = 256;
pub const X86_ASAN_FAKE_STACK_MIN_SIZE: u64 = 1024 * 1024;
pub const X86_ASAN_MAX_STACK_DEPTH: usize = 256;
pub const X86_ASAN_SUPPRESSION_HASH_BITS: u32 = 16;
pub const X86_ASAN_MAX_REPORTS: u64 = 5000;
pub const X86_ASAN_REPORT_DEDUP_WINDOW: u64 = 60;
pub const X86_HWASAN_GRANULE_SIZE: u64 = 16;
pub const X86_HWASAN_TAG_BITS: u8 = 8;
pub const X86_HWASAN_TAG_MASK: u64 = (1u64 << X86_HWASAN_TAG_BITS) - 1;
pub const X86_HWASAN_TAG_SHIFT: u8 = 56;
pub const X86_HWASAN_MAX_TAG: u8 = 254;
pub const X86_HWASAN_UNTAGGED: u8 = 0;
pub const X86_HWASAN_ADDR_MASK: u64 = (1u64 << X86_HWASAN_TAG_SHIFT) - 1;
pub const X86_HWASAN_SHORT_GRANULE_MASK: u8 = 0xF0;
pub const X86_HWASAN_KERNEL_SHADOW_OFFSET: u64 = 0xffff_ff00_0000_0000;
pub const X86_HWASAN_KERNEL_SHADOW_SCALE: u8 = 4;
pub mod x86_asan_shadow_byte {
pub const ADDRESSABLE: i8 = 0;
pub const PARTIAL1: i8 = 1;
pub const PARTIAL2: i8 = 2;
pub const PARTIAL3: i8 = 3;
pub const PARTIAL4: i8 = 4;
pub const PARTIAL5: i8 = 5;
pub const PARTIAL6: i8 = 6;
pub const PARTIAL7: i8 = 7;
pub const HEAP_LEFT_REDZONE: i8 = -1;
pub const HEAP_RIGHT_REDZONE: i8 = -2;
pub const STACK_LEFT_REDZONE: i8 = -3;
pub const STACK_MID_REDZONE: i8 = -4;
pub const STACK_RIGHT_REDZONE: i8 = -5;
pub const STACK_UAR_REDZONE: i8 = -6;
pub const GLOBAL_REDZONE: i8 = -7;
pub const INTRA_OBJECT_REDZONE: i8 = -8;
pub const FREED: i8 = -9;
pub const STACK_USE_AFTER_SCOPE: i8 = -10;
pub const ODR_VIOLATION: i8 = -11;
pub const INVALID: i8 = -12;
pub const ALLOCATOR_METADATA: i8 = -13;
pub const SHADOW_GAP: i8 = -14;
pub const HIGH_SHADOW_GAP: i8 = -15;
pub const USER_POISON: i8 = -16;
pub const QUARANTINE: i8 = -17;
pub fn is_addressable(v: i8) -> bool {
v >= ADDRESSABLE && v <= PARTIAL7
}
pub fn is_poisoned(v: i8) -> bool {
v < ADDRESSABLE
}
pub fn is_freed(v: i8) -> bool {
v == FREED
}
pub fn is_redzone(v: i8) -> bool {
v == HEAP_LEFT_REDZONE
|| v == HEAP_RIGHT_REDZONE
|| v == STACK_LEFT_REDZONE
|| v == STACK_MID_REDZONE
|| v == STACK_RIGHT_REDZONE
|| v == STACK_UAR_REDZONE
|| v == GLOBAL_REDZONE
}
pub fn addr_bytes(v: i8) -> u8 {
if v >= 1 && v <= 7 {
v as u8
} else if v == ADDRESSABLE {
8
} else {
0
}
}
pub fn describe(v: i8) -> &'static str {
match v {
ADDRESSABLE => "addressable",
PARTIAL1 => "partially addressable (1 byte)",
PARTIAL2 => "partially addressable (2 bytes)",
PARTIAL3 => "partially addressable (3 bytes)",
PARTIAL4 => "partially addressable (4 bytes)",
PARTIAL5 => "partially addressable (5 bytes)",
PARTIAL6 => "partially addressable (6 bytes)",
PARTIAL7 => "partially addressable (7 bytes)",
HEAP_LEFT_REDZONE => "heap left redzone",
HEAP_RIGHT_REDZONE => "heap right redzone",
STACK_LEFT_REDZONE => "stack left redzone",
STACK_MID_REDZONE => "stack mid redzone",
STACK_RIGHT_REDZONE => "stack right redzone",
STACK_UAR_REDZONE => "stack use-after-return",
GLOBAL_REDZONE => "global redzone",
INTRA_OBJECT_REDZONE => "container overflow redzone",
FREED => "freed (heap-use-after-free)",
STACK_USE_AFTER_SCOPE => "stack-use-after-scope",
ODR_VIOLATION => "odr-violation indicator",
INVALID => "invalid address",
ALLOCATOR_METADATA => "allocator metadata",
SHADOW_GAP => "shadow gap",
HIGH_SHADOW_GAP => "high shadow gap",
USER_POISON => "user-poisoned",
QUARANTINE => "quarantined (use-after-free)",
_ => "unknown poison",
}
}
pub fn error_category(v: i8) -> &'static str {
match v {
HEAP_LEFT_REDZONE | HEAP_RIGHT_REDZONE => "heap-buffer-overflow",
STACK_LEFT_REDZONE | STACK_MID_REDZONE | STACK_RIGHT_REDZONE => {
"stack-buffer-overflow"
}
STACK_UAR_REDZONE => "stack-use-after-return",
GLOBAL_REDZONE => "global-buffer-overflow",
INTRA_OBJECT_REDZONE => "container-overflow",
FREED | QUARANTINE => "heap-use-after-free",
STACK_USE_AFTER_SCOPE => "stack-use-after-scope",
ODR_VIOLATION => "odr-violation",
USER_POISON => "use-after-poison",
INVALID => "wild-pointer-access",
SHADOW_GAP | HIGH_SHADOW_GAP => "shadow-gap-access",
_ => "unknown-crash",
}
}
}
use x86_asan_shadow_byte::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86AsanTargetArch {
X8664,
X8664WithAslr,
I386,
X32,
}
impl X86AsanTargetArch {
pub fn from_pointer_width(width: u8) -> Self {
match width {
64 => X86AsanTargetArch::X8664,
32 => X86AsanTargetArch::I386,
_ => X86AsanTargetArch::X8664,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanShadowMap {
pub shadow_offset: u64,
pub arch: X86AsanTargetArch,
pub shadow_allocated: bool,
pub shadow_base: u64,
pub shadow_size: u64,
pub app_region_start: u64,
pub app_region_end: u64,
shadow_buffer: Vec<u8>,
}
impl X86ASanShadowMap {
pub fn new(arch: X86AsanTargetArch) -> Self {
let shadow_offset = match arch {
X86AsanTargetArch::X8664 => X86_ASAN_SHADOW_OFFSET_64_DEFAULT,
X86AsanTargetArch::X8664WithAslr => X86_ASAN_SHADOW_OFFSET_64_ASLR,
X86AsanTargetArch::I386 => X86_ASAN_SHADOW_OFFSET_32,
X86AsanTargetArch::X32 => X86_ASAN_SHADOW_OFFSET_32,
};
let (app_start, app_end, shadow_size) = match arch {
X86AsanTargetArch::X8664 | X86AsanTargetArch::X8664WithAslr => {
(0u64, 0x0000_7fff_ffff_ffffu64, X86_ASAN_MAX_SHADOW_SIZE_64)
}
X86AsanTargetArch::I386 | X86AsanTargetArch::X32 => {
(0u64, 0xffff_ffffu64, 0x2000_0000u64) }
};
Self {
shadow_offset,
arch,
shadow_allocated: false,
shadow_base: 0,
shadow_size,
app_region_start: app_start,
app_region_end: app_end,
shadow_buffer: Vec::new(),
}
}
pub fn x86_64() -> Self {
Self::new(X86AsanTargetArch::X8664)
}
pub fn i386() -> Self {
Self::new(X86AsanTargetArch::I386)
}
pub fn x86_64_aslr() -> Self {
Self::new(X86AsanTargetArch::X8664WithAslr)
}
pub fn allocate_shadow(&mut self, max_app_address: u64) -> u64 {
let shadow_end = self.app_to_shadow(max_app_address);
let required = shadow_end + 1;
let actual = required.min(self.shadow_size) as usize;
self.shadow_buffer = vec![ADDRESSABLE as u8; actual];
self.shadow_base = self.shadow_offset; self.shadow_allocated = true;
self.shadow_base
}
pub fn deallocate_shadow(&mut self) {
self.shadow_buffer.clear();
self.shadow_allocated = false;
}
#[inline]
pub fn app_to_shadow(&self, addr: u64) -> u64 {
(addr >> X86_ASAN_SHADOW_SCALE) + self.shadow_offset
}
#[inline]
pub fn shadow_to_app(&self, shadow_addr: u64) -> u64 {
(shadow_addr - self.shadow_offset) << X86_ASAN_SHADOW_SCALE
}
#[inline]
pub fn get_shadow(&self, addr: u64) -> i8 {
let saddr = self.app_to_shadow(addr);
let idx = (saddr - self.shadow_offset) as usize;
if idx < self.shadow_buffer.len() {
self.shadow_buffer[idx] as i8
} else {
HIGH_SHADOW_GAP
}
}
#[inline]
pub fn set_shadow(&mut self, addr: u64, value: i8) {
let saddr = self.app_to_shadow(addr);
let idx = (saddr - self.shadow_offset) as usize;
if idx < self.shadow_buffer.len() {
self.shadow_buffer[idx] = value as u8;
}
}
pub fn poison_range(&mut self, start: u64, size: u64, value: i8) {
let shadow_start = self.app_to_shadow(start);
let shadow_end = self.app_to_shadow(start + size - 1);
for sa in shadow_start..=shadow_end {
let idx = (sa - self.shadow_offset) as usize;
if idx < self.shadow_buffer.len() {
self.shadow_buffer[idx] = value as u8;
}
}
}
pub fn unpoison_range(&mut self, start: u64, size: u64) {
self.poison_range(start, size, ADDRESSABLE);
}
pub fn set_partial_range(&mut self, start: u64, size: u64) {
let end = start + size;
let shadow_start = self.app_to_shadow(start);
let shadow_end = self.app_to_shadow(end.wrapping_sub(1));
for sa in shadow_start..=shadow_end {
let ga_start = self.shadow_to_app(sa);
let ga_end = ga_start + X86_ASAN_SHADOW_GRANULARITY;
let first_addr = start.max(ga_start);
let last_addr = (end - 1).min(ga_end - 1);
if first_addr <= last_addr {
let addr_bytes = (last_addr - first_addr + 1) as i8;
let offset = (first_addr - ga_start) as i8;
let value = if offset == 0 {
addr_bytes
} else if offset + addr_bytes >= X86_ASAN_SHADOW_GRANULARITY as i8 {
PARTIAL7 } else {
offset + addr_bytes
};
let idx = (sa - self.shadow_offset) as usize;
if idx < self.shadow_buffer.len() {
self.shadow_buffer[idx] = value as u8;
}
}
}
}
pub fn check_access(
&self,
addr: u64,
size: u64,
is_write: bool,
) -> Result<(), String> {
let shadow = self.get_shadow(addr);
if shadow == ADDRESSABLE {
return Ok(());
}
if shadow >= 1 && shadow <= 7 {
let offset_in_granule = addr & X86_ASAN_SHADOW_MASK;
if offset_in_granule + size <= shadow as u64 {
return Ok(());
}
}
Err(describe(shadow).to_string())
}
#[inline]
pub fn check_8byte_load(&self, addr: u64) -> Result<(), String> {
if addr & X86_ASAN_SHADOW_MASK == 0 {
let shadow = self.get_shadow(addr);
if shadow == ADDRESSABLE {
return Ok(());
}
if shadow == PARTIAL7 {
let next_shadow = self.get_shadow(addr + 7);
if next_shadow >= 1 {
return Ok(());
}
}
Err(describe(shadow).to_string())
} else {
self.check_access(addr, 8, false)
}
}
#[inline]
pub fn check_8byte_store(&self, addr: u64) -> Result<(), String> {
self.check_8byte_load(addr)
}
#[inline]
pub fn check_16byte_load(&self, addr: u64) -> Result<(), String> {
self.check_8byte_load(addr)?;
self.check_8byte_load(addr + 8)
}
#[inline]
pub fn check_16byte_store(&self, addr: u64) -> Result<(), String> {
self.check_16byte_load(addr)
}
pub fn bulk_poison(&mut self, start: u64, size: u64, value: i8) {
if size == 0 {
return;
}
self.poison_range(start, size, value);
}
pub fn bulk_unpoison(&mut self, start: u64, size: u64) {
self.bulk_poison(start, size, ADDRESSABLE);
}
pub fn shadow_region_size(&self, app_size: u64) -> u64 {
(app_size + X86_ASAN_SHADOW_GRANULARITY - 1) >> X86_ASAN_SHADOW_SCALE
}
pub fn granule_count(&self, start: u64, size: u64) -> u64 {
let shadow_start = self.app_to_shadow(start);
let shadow_end = self.app_to_shadow(start + size);
shadow_end - shadow_start
}
pub fn is_shadow_gap(&self, addr: u64) -> bool {
let shadow = self.get_shadow(addr);
shadow == SHADOW_GAP || shadow == HIGH_SHADOW_GAP || shadow == INVALID
}
pub fn find_poisoned_regions(&self) -> Vec<(u64, u64, i8)> {
let mut regions = Vec::new();
let mut i = 0usize;
while i < self.shadow_buffer.len() {
let val = self.shadow_buffer[i] as i8;
if val < 0 {
let start = i;
while i < self.shadow_buffer.len() && self.shadow_buffer[i] as i8 == val
{
i += 1;
}
let app_start = self.shadow_to_app(self.shadow_offset + start as u64);
let app_end = self.shadow_to_app(self.shadow_offset + i as u64);
regions.push((app_start, app_end - app_start, val));
} else {
i += 1;
}
}
regions
}
pub fn reset(&mut self) {
for b in self.shadow_buffer.iter_mut() {
*b = ADDRESSABLE as u8;
}
}
}
impl Default for X86ASanShadowMap {
fn default() -> Self {
Self::x86_64()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanStackVar {
pub name: String,
pub frame_offset: i64,
pub size: u64,
pub alignment: u64,
pub left_redzone_size: u64,
pub right_redzone_size: u64,
pub use_after_scope: bool,
pub use_after_return: bool,
pub left_shadow: i8,
pub right_shadow: i8,
pub mid_shadow: i8,
}
impl X86ASanStackVar {
pub fn new(name: &str, frame_offset: i64, size: u64, alignment: u64) -> Self {
let rz = X86_ASAN_STACK_REDZONE_SIZE.max(alignment).min(X86_ASAN_MAX_STACK_REDZONE_SIZE);
Self {
name: name.to_string(),
frame_offset,
size,
alignment,
left_redzone_size: rz,
right_redzone_size: rz,
use_after_scope: false,
use_after_return: false,
left_shadow: STACK_LEFT_REDZONE,
right_shadow: STACK_RIGHT_REDZONE,
mid_shadow: STACK_MID_REDZONE,
}
}
pub fn total_size(&self) -> u64 {
self.left_redzone_size + self.size + self.right_redzone_size
}
pub fn var_start(&self) -> i64 {
self.frame_offset + self.left_redzone_size as i64
}
pub fn var_end(&self) -> i64 {
self.var_start() + self.size as i64
}
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
}
}
#[derive(Debug, Clone)]
pub struct X86ASanStackFrame {
pub variables: Vec<X86ASanStackVar>,
pub total_frame_size: u64,
pub uas_count: usize,
pub uar_count: usize,
pub uses_fake_stack: bool,
pub frame_descriptor_magic: u64,
pub frame_descriptor_offset: i64,
}
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: -8,
}
}
pub fn add_variable(&mut self, mut 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;
}
let aligned_offset = self
.total_frame_size
.max(var.left_redzone_size)
.next_multiple_of(var.alignment);
var.frame_offset = -(aligned_offset as i64);
self.total_frame_size = aligned_offset + var.total_size();
self.variables.push(var);
}
pub fn compute_shadow_operations(
&self,
frame_base: u64,
) -> (Vec<(u64, u64, i8)>, Vec<(u64, u64)>) {
let mut poison = Vec::new();
let mut unpoison = Vec::new();
let frame_low = frame_base - self.total_frame_size;
for var in &self.variables {
let var_base = (frame_base as i64 + var.frame_offset) as u64;
let lrz_start = (frame_base as i64 + var.frame_offset - var.left_redzone_size as i64)
as u64;
if lrz_start >= frame_low {
poison.push((lrz_start, var.left_redzone_size, var.left_shadow));
}
unpoison.push((var_base, var.size));
let rrz_start = var_base + var.size;
poison.push((rrz_start, var.right_redzone_size, var.right_shadow));
}
(poison, unpoison)
}
pub fn apply_shadow(
&self,
shadow: &mut X86ASanShadowMap,
frame_base: u64,
) {
let (poison_regions, unpoison_regions) =
self.compute_shadow_operations(frame_base);
for (start, size, value) in &poison_regions {
shadow.poison_range(*start, *size, *value);
}
for (start, size) in &unpoison_regions {
shadow.unpoison_range(*start, *size);
}
}
pub fn poison_all_on_exit(
&self,
shadow: &mut X86ASanShadowMap,
frame_base: u64,
) {
for var in &self.variables {
let var_base = (frame_base as i64 + var.frame_offset) as u64;
let poison_val = if var.use_after_scope {
STACK_USE_AFTER_SCOPE
} else {
STACK_MID_REDZONE
};
shadow.poison_range(var_base, var.size, poison_val);
}
}
pub fn format_layout(&self) -> String {
let mut s = format!(
"Stack frame: {} vars, {} bytes total\n",
self.variables.len(),
self.total_frame_size
);
for var in &self.variables {
s.push_str(&format!(
" {:>24} offset={:>+6} size={:>6} rz=[{:>3},{:>3}]{} {}\n",
var.name,
var.frame_offset,
var.size,
var.left_redzone_size,
var.right_redzone_size,
if var.use_after_scope { " [uas]" } else { "" },
if var.use_after_return {
" [uar]"
} else {
""
},
));
}
s
}
}
impl Default for X86ASanStackFrame {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanFakeStackEntry {
pub function_name: String,
pub frame_base: u64,
pub frame_size: u64,
pub depth: usize,
pub creation_time: u64,
pub is_active: bool,
pub stack_frame: X86ASanStackFrame,
pub real_frame_base: u64,
}
#[derive(Debug)]
pub struct X86ASanFakeStack {
pub entries: VecDeque<X86ASanFakeStackEntry>,
pub max_entries: usize,
pub depth: usize,
pub region_base: u64,
pub region_size: u64,
pub next_offset: u64,
timestamp: u64,
total_pushes: u64,
total_evictions: u64,
}
impl X86ASanFakeStack {
pub fn new(region_base: u64, region_size: u64, max_entries: usize) -> Self {
Self {
entries: VecDeque::with_capacity(max_entries),
max_entries,
depth: 0,
region_base,
region_size,
next_offset: 0,
timestamp: 0,
total_pushes: 0,
total_evictions: 0,
}
}
pub fn default_sized() -> Self {
Self::new(
0, X86_ASAN_FAKE_STACK_MIN_SIZE,
X86_ASAN_FAKE_STACK_MAX_ENTRIES,
)
}
pub fn reset(&mut self, new_base: u64) {
self.entries.clear();
self.depth = 0;
self.region_base = new_base;
self.next_offset = 0;
}
pub fn push(
&mut self,
function_name: &str,
real_frame_base: u64,
stack_frame: &X86ASanStackFrame,
) -> u64 {
self.timestamp += 1;
let frame_size = stack_frame.total_frame_size;
while self.entries.len() >= self.max_entries {
self.evict_one();
}
let frame_base = if self.next_offset + frame_size <= self.region_size {
let base = self.region_base + self.next_offset;
self.next_offset += frame_size;
base
} else {
self.next_offset = 0;
if frame_size > self.region_size {
return 0;
}
while !self.entries.is_empty()
&& self.entries.front().unwrap().frame_base
< self.region_base + frame_size
{
self.entries.pop_front();
}
self.region_base + self.next_offset
};
self.depth += 1;
self.total_pushes += 1;
let entry = X86ASanFakeStackEntry {
function_name: function_name.to_string(),
frame_base,
frame_size,
depth: self.depth,
creation_time: self.timestamp,
is_active: true,
stack_frame: stack_frame.clone(),
real_frame_base,
};
self.entries.push_back(entry);
frame_base
}
pub fn pop(
&mut self,
shadow: &mut X86ASanShadowMap,
function_name: &str,
) -> Option<X86ASanFakeStackEntry> {
if let Some(pos) = self
.entries
.iter()
.rposition(|e| e.is_active && e.function_name == function_name)
{
let mut entry = self.entries.remove(pos).unwrap();
entry.is_active = false;
self.depth = self.depth.saturating_sub(1);
shadow.poison_range(
entry.frame_base,
entry.frame_size,
STACK_UAR_REDZONE,
);
entry.stack_frame.poison_all_on_exit(shadow, entry.real_frame_base);
Some(entry)
} else {
None
}
}
pub fn check_use_after_return(&self, addr: u64) -> Option<&X86ASanFakeStackEntry> {
self.entries
.iter()
.rev()
.find(|e| !e.is_active && addr >= e.frame_base && addr < e.frame_base + e.frame_size)
}
fn evict_one(&mut self) {
if let Some(entry) = self.entries.pop_front() {
self.total_evictions += 1;
if self.next_offset >= entry.frame_base - self.region_base + entry.frame_size {
}
}
}
pub fn active_count(&self) -> usize {
self.entries.iter().filter(|e| e.is_active).count()
}
pub fn total_pushes(&self) -> u64 {
self.total_pushes
}
pub fn total_evictions(&self) -> u64 {
self.total_evictions
}
pub fn all_entries(&self) -> impl Iterator<Item = &X86ASanFakeStackEntry> {
self.entries.iter()
}
}
impl Default for X86ASanFakeStack {
fn default() -> Self {
Self::default_sized()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanScopeTracker {
active_vars: HashMap<String, usize>,
var_depth: HashMap<String, usize>,
scope_depth: usize,
next_var_id: usize,
tracked_frames: HashMap<String, X86ASanStackFrame>,
current_function: String,
}
impl X86ASanScopeTracker {
pub fn new() -> Self {
Self {
active_vars: HashMap::new(),
var_depth: HashMap::new(),
scope_depth: 0,
next_var_id: 0,
tracked_frames: HashMap::new(),
current_function: String::new(),
}
}
pub fn enter_function(&mut self, name: &str) {
self.current_function = name.to_string();
self.scope_depth = 0;
self.active_vars.clear();
self.var_depth.clear();
}
pub fn leave_function(&mut self) {
self.active_vars.clear();
self.var_depth.clear();
self.scope_depth = 0;
}
pub fn enter_scope(&mut self) {
self.scope_depth += 1;
}
pub fn exit_scope(&mut self) -> Vec<String> {
let mut poisoned = Vec::new();
let vars_to_remove: Vec<String> = self
.var_depth
.iter()
.filter(|(_, d)| **d == self.scope_depth)
.map(|(n, _)| n.clone())
.collect();
for var_name in &vars_to_remove {
self.active_vars.remove(var_name);
self.var_depth.remove(var_name);
poisoned.push(var_name.clone());
}
self.scope_depth = self.scope_depth.saturating_sub(1);
poisoned
}
pub fn register_var(&mut self, name: &str) -> usize {
let id = self.next_var_id;
self.next_var_id += 1;
self.active_vars.insert(name.to_string(), id);
self.var_depth.insert(name.to_string(), self.scope_depth);
id
}
pub fn is_in_scope(&self, name: &str) -> bool {
self.active_vars.contains_key(name)
}
pub fn var_id(&self, name: &str) -> Option<usize> {
self.active_vars.get(name).copied()
}
pub fn active_count(&self) -> usize {
self.active_vars.len()
}
pub fn current_depth(&self) -> usize {
self.scope_depth
}
pub fn track_frame(&mut self, name: &str, frame: X86ASanStackFrame) {
self.tracked_frames.insert(name.to_string(), frame);
}
}
impl Default for X86ASanScopeTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanGlobalProtection {
pub name: String,
pub size: u64,
pub alignment: u64,
pub left_redzone_size: u64,
pub right_redzone_size: u64,
pub instrumented: bool,
pub odr_check: bool,
pub odr_indicator: Option<u64>,
pub odr_hash: Option<u64>,
pub module_name: Option<String>,
pub source_file: Option<String>,
pub source_line: Option<u32>,
}
impl X86ASanGlobalProtection {
pub fn new(name: &str, size: u64, alignment: u64) -> Self {
let rz = X86_ASAN_GLOBAL_REDZONE_SIZE.max(alignment).next_multiple_of(32);
Self {
name: name.to_string(),
size,
alignment,
left_redzone_size: rz,
right_redzone_size: rz,
instrumented: false,
odr_check: false,
odr_indicator: None,
odr_hash: None,
module_name: None,
source_file: None,
source_line: None,
}
}
pub fn padded_size(&self) -> u64 {
self.left_redzone_size + self.size + self.right_redzone_size
}
pub fn with_odr_check(mut self, hash: u64, indicator: u64) -> Self {
self.odr_check = true;
self.odr_hash = Some(hash);
self.odr_indicator = Some(indicator);
self
}
pub fn with_source(mut self, file: &str, line: u32) -> Self {
self.source_file = Some(file.to_string());
self.source_line = Some(line);
self
}
pub fn with_module(mut self, module: &str) -> Self {
self.module_name = Some(module.to_string());
self
}
pub fn compute_shadow_regions(
&self,
base_addr: u64,
) -> Vec<(u64, u64, i8)> {
let mut regions = Vec::new();
if self.left_redzone_size > 0 {
regions.push((base_addr, self.left_redzone_size, GLOBAL_REDZONE));
}
let right_start = base_addr + self.left_redzone_size + self.size;
if self.right_redzone_size > 0 {
regions.push((right_start, self.right_redzone_size, GLOBAL_REDZONE));
}
if let Some(odr_addr) = self.odr_indicator {
regions.push((odr_addr, 1, ODR_VIOLATION));
}
regions
}
}
#[derive(Debug, Clone)]
pub struct X86ASanGlobalInstrumentation {
pub globals: Vec<X86ASanGlobalProtection>,
name_index: HashMap<String, usize>,
addr_index: BTreeMap<u64, usize>,
pub total_redzone_bytes: u64,
pub odr_detector: X86ASanODRViolationDetector,
}
impl X86ASanGlobalInstrumentation {
pub fn new() -> Self {
Self {
globals: Vec::new(),
name_index: HashMap::new(),
addr_index: BTreeMap::new(),
total_redzone_bytes: 0,
odr_detector: X86ASanODRViolationDetector::new(),
}
}
pub fn add_global(&mut self, global: X86ASanGlobalProtection, addr: u64) {
let idx = self.globals.len();
self.name_index.insert(global.name.clone(), idx);
self.addr_index.insert(addr, idx);
self.total_redzone_bytes += global.left_redzone_size + global.right_redzone_size;
self.globals.push(global);
}
pub fn apply_to_shadow(
&self,
shadow: &mut X86ASanShadowMap,
base_addrs: &[(u64, &str)],
) {
for (addr, name) in base_addrs {
if let Some(idx) = self.name_index.get(*name) {
let g = &self.globals[*idx];
let base = *addr;
shadow.poison_range(base, g.left_redzone_size, GLOBAL_REDZONE);
let right_start = base + g.left_redzone_size + g.size;
shadow.poison_range(right_start, g.right_redzone_size, GLOBAL_REDZONE);
let var_start = base + g.left_redzone_size;
shadow.unpoison_range(var_start, g.size);
if let Some(odr_addr) = g.odr_indicator {
let val = shadow.get_shadow(odr_addr);
if val == ODR_VIOLATION {
self.odr_detector.report_violation(name);
}
shadow.set_shadow(odr_addr, ODR_VIOLATION);
}
}
}
}
pub fn by_name(&self, name: &str) -> Option<&X86ASanGlobalProtection> {
self.name_index.get(name).map(|&idx| &self.globals[idx])
}
pub fn by_address(&self, addr: u64) -> Option<&X86ASanGlobalProtection> {
self.globals.iter().find(|g| {
false })
.or_else(|| None)
}
pub fn count(&self) -> usize {
self.globals.len()
}
}
impl Default for X86ASanGlobalInstrumentation {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanODRViolationDetector {
pub global_hashes: HashMap<String, u64>,
pub violations: HashSet<String>,
pub first_definition: HashMap<String, (String, u32, u64)>,
}
impl X86ASanODRViolationDetector {
pub fn new() -> Self {
Self {
global_hashes: HashMap::new(),
violations: HashSet::new(),
first_definition: HashMap::new(),
}
}
pub fn register_global(
&mut self,
name: &str,
hash: u64,
file: &str,
line: u32,
size: u64,
) -> bool {
if let Some(&existing_hash) = self.global_hashes.get(name) {
if existing_hash != hash {
self.violations.insert(name.to_string());
return true;
}
false
} else {
self.global_hashes.insert(name.to_string(), hash);
self.first_definition
.insert(name.to_string(), (file.to_string(), line, size));
false
}
}
pub fn compute_hash(name: &str, type_name: &str, size: u64) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
type_name.hash(&mut hasher);
size.hash(&mut hasher);
hasher.finish()
}
pub fn report_violation(&self, name: &str) {
}
pub fn has_violation(&self, name: &str) -> bool {
self.violations.contains(name)
}
pub fn violation_count(&self) -> usize {
self.violations.len()
}
pub fn format_violations(&self) -> String {
let mut report = String::new();
report.push_str("=== ODR Violation Report ===\n");
for name in &self.violations {
report.push_str(&format!(" ODR Violation: {}\n", name));
if let Some((file, line, size)) = self.first_definition.get(name) {
report.push_str(&format!(
" First definition: {} ({} bytes, line {})\n",
file, size, line
));
}
}
report.push_str(&format!(
"Total ODR violations: {}\n",
self.violations.len()
));
report
}
}
impl Default for X86ASanODRViolationDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanContainerOverflowDetector {
pub enabled: bool,
pub intra_object_redzone_size: u64,
pub min_container_size: u64,
containers: HashMap<u64, String>,
}
impl X86ASanContainerOverflowDetector {
pub fn new() -> Self {
Self {
enabled: true,
intra_object_redzone_size: 16, min_container_size: 32,
containers: HashMap::new(),
}
}
pub fn instrument_container(
&mut self,
shadow: &mut X86ASanShadowMap,
addr: u64,
object_size: u64,
container_name: &str,
) {
if !self.enabled || object_size < self.min_container_size {
return;
}
let intra_rz_start = addr + object_size - self.intra_object_redzone_size;
shadow.poison_range(
intra_rz_start,
self.intra_object_redzone_size,
INTRA_OBJECT_REDZONE,
);
self.containers
.insert(addr, container_name.to_string());
}
pub fn is_intra_object_overflow(&self, shadow: &X86ASanShadowMap, addr: u64) -> bool {
let sv = shadow.get_shadow(addr);
sv == INTRA_OBJECT_REDZONE
}
pub fn remove_container(&mut self, addr: u64) {
self.containers.remove(&addr);
}
pub fn tracked_count(&self) -> usize {
self.containers.len()
}
}
impl Default for X86ASanContainerOverflowDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ASanTLS {
pub quarantine_cache: VecDeque<(u64, u64, u64)>, pub alloc_count: u64,
pub free_count: u64,
pub total_allocated: u64,
pub total_freed: u64,
pub activated: bool,
pub thread_name: Option<String>,
}
impl X86ASanTLS {
pub fn new() -> Self {
Self {
quarantine_cache: VecDeque::new(),
alloc_count: 0,
free_count: 0,
total_allocated: 0,
total_freed: 0,
activated: false,
thread_name: None,
}
}
pub fn add_to_quarantine(&mut self, addr: u64, size: u64) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
self.quarantine_cache.push_back((addr, size, now));
let mut total: u64 = self.quarantine_cache.iter().map(|(_, s, _)| s).sum();
while total > X86_ASAN_THREAD_QUARANTINE_SIZE {
if let Some((addr, _, _)) = self.quarantine_cache.pop_front() {
total = self.quarantine_cache.iter().map(|(_, s, _)| s).sum();
} else {
break;
}
}
}
pub fn flush_quarantine(&mut self) -> Vec<(u64, u64)> {
self.quarantine_cache
.drain(..)
.map(|(a, s, _)| (a, s))
.collect()
}
pub fn is_quarantined(&self, addr: u64, size: u64) -> bool {
self.quarantine_cache
.iter()
.any(|(qa, qs, _)| *qa <= addr && addr + size <= *qa + *qs)
}
}
impl Default for X86ASanTLS {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ASanErrorType {
HeapBufferOverflow,
StackBufferOverflow,
GlobalBufferOverflow,
HeapUseAfterFree,
StackUseAfterReturn,
StackUseAfterScope,
UseAfterPoison,
DoubleFree,
InvalidFree,
AllocDeallocMismatch,
MemoryLeak,
ODRViolation,
ContainerOverflow,
ShadowGapAccess,
NullDereference,
WildPointerAccess,
BadFree,
CallocOverflow,
ReallocInvalid,
MemsetParamOverlap,
MemcpyParamOverlap,
StrcatParamOverlap,
StrcpyParamOverlap,
StrncatParamOverlap,
StrncpyParamOverlap,
MemmoveParamOverlap,
}
impl fmt::Display for X86ASanErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
X86ASanErrorType::HeapBufferOverflow => "heap-buffer-overflow",
X86ASanErrorType::StackBufferOverflow => "stack-buffer-overflow",
X86ASanErrorType::GlobalBufferOverflow => "global-buffer-overflow",
X86ASanErrorType::HeapUseAfterFree => "heap-use-after-free",
X86ASanErrorType::StackUseAfterReturn => "stack-use-after-return",
X86ASanErrorType::StackUseAfterScope => "stack-use-after-scope",
X86ASanErrorType::UseAfterPoison => "use-after-poison",
X86ASanErrorType::DoubleFree => "double-free",
X86ASanErrorType::InvalidFree => "invalid-free",
X86ASanErrorType::AllocDeallocMismatch => "alloc-dealloc-mismatch",
X86ASanErrorType::MemoryLeak => "memory-leak",
X86ASanErrorType::ODRViolation => "odr-violation",
X86ASanErrorType::ContainerOverflow => "container-overflow",
X86ASanErrorType::ShadowGapAccess => "shadow-gap-access",
X86ASanErrorType::NullDereference => "null-dereference",
X86ASanErrorType::WildPointerAccess => "wild-pointer-access",
X86ASanErrorType::BadFree => "bad-free",
X86ASanErrorType::CallocOverflow => "calloc-overflow",
X86ASanErrorType::ReallocInvalid => "realloc-invalid-argument",
X86ASanErrorType::MemsetParamOverlap => "memset-param-overlap",
X86ASanErrorType::MemcpyParamOverlap => "memcpy-param-overlap",
X86ASanErrorType::StrcatParamOverlap => "strcat-param-overlap",
X86ASanErrorType::StrcpyParamOverlap => "strcpy-param-overlap",
X86ASanErrorType::StrncatParamOverlap => "strncat-param-overlap",
X86ASanErrorType::StrncpyParamOverlap => "strncpy-param-overlap",
X86ASanErrorType::MemmoveParamOverlap => "memmove-param-overlap",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ASanAccessType {
Read,
Write,
Free,
Alloc,
}
impl fmt::Display for X86ASanAccessType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86ASanAccessType::Read => write!(f, "READ"),
X86ASanAccessType::Write => write!(f, "WRITE"),
X86ASanAccessType::Free => write!(f, "FREE"),
X86ASanAccessType::Alloc => write!(f, "ALLOC"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanStackFrameEntry {
pub function: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
pub module: Option<String>,
pub module_offset: u64,
pub ip: u64,
pub bp: u64,
pub symbolized: bool,
}
impl X86ASanStackFrameEntry {
pub fn new(ip: u64, bp: u64) -> Self {
Self {
function: None,
file: None,
line: None,
column: None,
module: None,
module_offset: ip,
ip,
bp,
symbolized: false,
}
}
pub fn format(&self) -> String {
if self.symbolized {
format!(
" #0 0x{:x} in {} {}:{}:{}",
self.ip,
self.function.as_deref().unwrap_or("??"),
self.file.as_deref().unwrap_or("??"),
self.line.unwrap_or(0),
self.column.unwrap_or(0),
)
} else {
format!(
" #0 0x{:x} ({}+0x{:x})",
self.ip,
self.module.as_deref().unwrap_or("??"),
self.module_offset,
)
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanErrorReport {
pub error_type: X86ASanErrorType,
pub address: u64,
pub access_size: u64,
pub access_type: X86ASanAccessType,
pub shadow_value: i8,
pub shadow_description: String,
pub stack_trace: Vec<X86ASanStackFrameEntry>,
pub allocation_trace: Option<Vec<X86ASanStackFrameEntry>>,
pub deallocation_trace: Option<Vec<X86ASanStackFrameEntry>>,
pub thread_id: u64,
pub thread_name: Option<String>,
pub is_recoverable: bool,
pub timestamp: u64,
pub description: Option<String>,
}
impl X86ASanErrorReport {
pub fn new(
error_type: X86ASanErrorType,
address: u64,
access_size: u64,
access_type: X86ASanAccessType,
shadow_value: i8,
) -> Self {
Self {
error_type,
address,
access_size,
access_type,
shadow_value,
shadow_description: describe(shadow_value).to_string(),
stack_trace: Vec::new(),
allocation_trace: None,
deallocation_trace: None,
thread_id: 0,
thread_name: None,
is_recoverable: true,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
description: None,
}
}
pub fn format_report(&self) -> String {
let mut report = String::new();
let line = "=".repeat(60);
report.push_str(&line);
report.push('\n');
report.push_str(&format!(
"=={}== ERROR: AddressSanitizer: {} on address 0x{:016x} at pc 0x{:016x}\n",
self.thread_id,
self.error_type,
self.address,
self.stack_trace
.first()
.map(|f| f.ip)
.unwrap_or(0),
));
report.push_str(&format!(
"{} of size {} at 0x{:016x} thread T{}\n",
self.access_type, self.access_size, self.address, self.thread_id,
));
if let Some(ref desc) = self.description {
report.push_str(&format!(" {}\n", desc));
}
report.push_str(&format!(
"Address 0x{:016x} is located in the {} region ({})\n",
self.address, self.shadow_description, describe(self.shadow_value),
));
report.push_str("STACK TRACE at error:\n");
for frame in &self.stack_trace {
report.push_str(&frame.format());
report.push('\n');
}
if let Some(ref alloc_trace) = self.allocation_trace {
report.push_str("STACK TRACE at allocation:\n");
for frame in alloc_trace {
report.push_str(&frame.format());
report.push('\n');
}
}
if let Some(ref free_trace) = self.deallocation_trace {
report.push_str("STACK TRACE at deallocation:\n");
for frame in free_trace {
report.push_str(&frame.format());
report.push('\n');
}
}
report.push_str(&format!(
"Shadow byte legend (one shadow byte represents {} application bytes):\n",
X86_ASAN_SHADOW_GRANULARITY
));
report.push_str(
" Addressable: 0\n Partially addressable: 1-7\n",
);
report.push_str(
" Heap left redzone: fa\n Heap right redzone: fb\n",
);
report.push_str(
" Stack left redzone: f1\n Stack mid redzone: f2\n Stack right redzone: f3\n",
);
report.push_str(
" Stack use-after-return: f5\n Stack use-after-scope: f8\n",
);
report.push_str(
" Global redzone: f9\n Freed heap region: fd\n",
);
report.push_str(" Container overflow: fc\n");
if self.is_recoverable {
report.push_str("\n[NOTE] The program can continue.\n");
} else {
report.push_str("\n[FATAL] Aborting.\n");
}
report.push_str(&line);
report
}
pub fn summary(&self) -> String {
format!(
"{} on address 0x{:016x} ({})",
self.error_type, self.address, self.shadow_description,
)
}
}
#[derive(Debug)]
pub struct X86ASanStackTraceCollector {
pub fast_unwind: bool,
pub max_depth: usize,
pub symbolize: bool,
pub external_symbolizer_path: Option<String>,
symbol_table: HashMap<u64, String>,
source_cache: HashMap<u64, (String, u32, u32)>,
module_name: Option<String>,
}
impl X86ASanStackTraceCollector {
pub fn new() -> Self {
Self {
fast_unwind: true,
max_depth: X86_ASAN_MAX_STACK_DEPTH,
symbolize: true,
external_symbolizer_path: None,
symbol_table: HashMap::new(),
source_cache: HashMap::new(),
module_name: None,
}
}
pub fn set_module(&mut self, name: &str) {
self.module_name = Some(name.to_string());
}
pub fn register_symbol(
&mut self,
addr: u64,
name: &str,
file: Option<&str>,
line: Option<u32>,
column: Option<u32>,
) {
self.symbol_table.insert(addr, name.to_string());
if let (Some(f), Some(l), Some(c)) = (file, line, column) {
self.source_cache.insert(addr, (f.to_string(), l, c));
}
}
pub fn capture_stack_trace(&self, ip: u64, bp: u64, sp: u64) -> Vec<X86ASanStackFrameEntry> {
let mut frames = Vec::new();
frames.push(X86ASanStackFrameEntry::new(ip, bp));
if self.fast_unwind {
self.fast_unwind_frames(bp, sp, &mut frames);
}
if self.symbolize {
for frame in &mut frames {
self.symbolize_frame(frame);
}
}
frames
}
fn fast_unwind_frames(
&self,
start_bp: u64,
sp: u64,
frames: &mut Vec<X86ASanStackFrameEntry>,
) {
let mut current_bp = start_bp;
for _depth in 0..self.max_depth {
if current_bp == 0 || current_bp < sp {
break;
}
let caller_bp = current_bp; let return_addr = current_bp + 8;
if caller_bp <= current_bp {
break;
}
frames.push(X86ASanStackFrameEntry::new(return_addr, caller_bp));
current_bp = caller_bp;
if frames.len() >= self.max_depth {
break;
}
}
}
fn symbolize_frame(&self, frame: &mut X86ASanStackFrameEntry) {
if let Some(name) = self.symbol_table.get(&frame.ip) {
frame.function = Some(name.clone());
frame.symbolized = true;
}
if let Some((file, line, col)) = self.source_cache.get(&frame.ip) {
frame.file = Some(file.clone());
frame.line = Some(*line);
frame.column = Some(*col);
}
if let Some(ref module) = self.module_name {
frame.module = Some(module.clone());
}
}
pub fn external_symbolize(&self, ip: u64) -> Option<(String, String, u32, u32)> {
if let Some(ref path) = self.external_symbolizer_path {
if let Some(name) = self.symbol_table.get(&ip) {
if let Some((file, line, col)) = self.source_cache.get(&ip) {
return Some((name.clone(), file.clone(), *line, *col));
}
}
}
None
}
pub fn hash_stack_trace(frames: &[X86ASanStackFrameEntry]) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for frame in frames.iter().take(8) {
frame.ip.hash(&mut hasher);
frame.module_offset.hash(&mut hasher);
}
hasher.finish()
}
pub fn equivalent(a: &[X86ASanStackFrameEntry], b: &[X86ASanStackFrameEntry]) -> bool {
if a.len() < 3 || b.len() < 3 {
return false;
}
for i in 0..3.min(a.len()).min(b.len()) {
if a[i].ip != b[i].ip {
return false;
}
}
true
}
}
impl Default for X86ASanStackTraceCollector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanSuppressionRule {
pub pattern: String,
pub error_type: Option<X86ASanErrorType>,
pub is_regex: bool,
pub source_line: usize,
}
impl X86ASanSuppressionRule {
pub fn new(pattern: &str, error_type: Option<X86ASanErrorType>) -> Self {
Self {
pattern: pattern.to_string(),
error_type,
is_regex: false,
source_line: 0,
}
}
pub fn matches(
&self,
report: &X86ASanErrorReport,
) -> bool {
if let Some(et) = &self.error_type {
if *et != report.error_type {
return false;
}
}
for frame in &report.stack_trace {
if let Some(ref func) = frame.function {
if func.contains(&self.pattern) {
return true;
}
}
}
for trace in [&report.allocation_trace, &report.deallocation_trace] {
if let Some(frames) = trace {
for frame in frames {
if let Some(ref func) = frame.function {
if func.contains(&self.pattern) {
return true;
}
}
}
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct X86ASanErrorSuppression {
pub rules: Vec<X86ASanSuppressionRule>,
pub enabled: bool,
pub suppressed_count: u64,
}
impl X86ASanErrorSuppression {
pub fn new() -> Self {
Self {
rules: Vec::new(),
enabled: false,
suppressed_count: 0,
}
}
pub fn load_from_file(&mut self, path: &str) -> std::io::Result<usize> {
let content = std::fs::read_to_string(path)?;
self.parse_suppressions(&content);
self.enabled = true;
Ok(self.rules.len())
}
pub fn parse_suppressions(&mut self, content: &str) {
let mut current_type: Option<X86ASanErrorType> = None;
let mut line_num = 0usize;
for line in content.lines() {
line_num += 1;
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if trimmed.ends_with(':') {
let header = trimmed.trim_end_matches(':');
current_type = match header {
"heap-buffer-overflow" => Some(X86ASanErrorType::HeapBufferOverflow),
"stack-buffer-overflow" => Some(X86ASanErrorType::StackBufferOverflow),
"global-buffer-overflow" => Some(X86ASanErrorType::GlobalBufferOverflow),
"heap-use-after-free" => Some(X86ASanErrorType::HeapUseAfterFree),
"stack-use-after-return" => Some(X86ASanErrorType::StackUseAfterReturn),
"stack-use-after-scope" => Some(X86ASanErrorType::StackUseAfterScope),
"use-after-poison" => Some(X86ASanErrorType::UseAfterPoison),
"double-free" => Some(X86ASanErrorType::DoubleFree),
"invalid-free" => Some(X86ASanErrorType::InvalidFree),
"alloc-dealloc-mismatch" => {
Some(X86ASanErrorType::AllocDeallocMismatch)
}
"memory-leak" => Some(X86ASanErrorType::MemoryLeak),
"odr-violation" => Some(X86ASanErrorType::ODRViolation),
"container-overflow" => Some(X86ASanErrorType::ContainerOverflow),
_ => None,
};
} else if trimmed.starts_with("fun:") {
let pattern = &trimmed[4..];
let mut rule = X86ASanSuppressionRule::new(pattern, current_type);
rule.source_line = line_num;
self.rules.push(rule);
} else if trimmed.starts_with("interceptor_via_fun:") {
let pattern = &trimmed[21..];
let mut rule = X86ASanSuppressionRule::new(pattern, current_type);
rule.source_line = line_num;
self.rules.push(rule);
}
}
}
pub fn should_suppress(&mut self, report: &X86ASanErrorReport) -> bool {
if !self.enabled || self.rules.is_empty() {
return false;
}
for rule in &self.rules {
if rule.matches(report) {
self.suppressed_count += 1;
return true;
}
}
false
}
}
impl Default for X86ASanErrorSuppression {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASanAllocMeta {
pub id: u64,
pub user_ptr: u64,
pub total_size: u64,
pub requested_size: u64,
pub is_freed: bool,
pub alloc_stack: Vec<X86ASanStackFrameEntry>,
pub free_stack: Option<Vec<X86ASanStackFrameEntry>>,
pub alloc_thread_id: u64,
pub free_thread_id: Option<u64>,
pub is_container: bool,
pub alloc_kind: X86ASanAllocKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ASanAllocKind {
Malloc,
Calloc,
Realloc,
Memalign,
AlignedAlloc,
PosixMemalign,
Reallocarray,
Valloc,
Pvalloc,
Custom,
}
impl X86ASanAllocKind {
pub fn as_str(&self) -> &'static str {
match self {
X86ASanAllocKind::Malloc => "malloc",
X86ASanAllocKind::Calloc => "calloc",
X86ASanAllocKind::Realloc => "realloc",
X86ASanAllocKind::Memalign => "memalign",
X86ASanAllocKind::AlignedAlloc => "aligned_alloc",
X86ASanAllocKind::PosixMemalign => "posix_memalign",
X86ASanAllocKind::Reallocarray => "reallocarray",
X86ASanAllocKind::Valloc => "valloc",
X86ASanAllocKind::Pvalloc => "pvalloc",
X86ASanAllocKind::Custom => "custom",
}
}
}
#[derive(Debug)]
pub struct X86ASanAllocator {
pub allocations: HashMap<u64, X86ASanAllocMeta>,
pub quarantine: VecDeque<X86ASanAllocMeta>,
pub quarantine_max_size: u64,
pub total_allocated: u64,
pub total_freed: u64,
pub current_bytes: u64,
pub peak_bytes: u64,
pub allocation_count: u64,
next_alloc_id: AtomicU64,
shadow: Option<*mut X86ASanShadowMap>,
stack_collector: Option<*mut X86ASanStackTraceCollector>,
secondary_alloc_big_threshold: u64,
}
#[derive(Debug)]
pub struct X86ASanThreadQuarantineBatch {
blocks: Vec<(u64, u64, X86ASanAllocMeta)>,
total_size: u64,
max_size: u64,
}
impl X86ASanThreadQuarantineBatch {
pub fn new(max_size: u64) -> Self {
Self {
blocks: Vec::new(),
total_size: 0,
max_size,
}
}
pub fn add(&mut self, addr: u64, size: u64, meta: X86ASanAllocMeta) {
self.total_size += size;
self.blocks.push((addr, size, meta));
}
pub fn flush(&mut self) -> Vec<(u64, u64, X86ASanAllocMeta)> {
self.total_size = 0;
std::mem::take(&mut self.blocks)
}
pub fn is_full(&self) -> bool {
self.total_size >= self.max_size
}
}
impl X86ASanAllocator {
pub fn new() -> Self {
Self {
allocations: HashMap::new(),
quarantine: VecDeque::new(),
quarantine_max_size: X86_ASAN_QUARANTINE_MAX_SIZE,
total_allocated: 0,
total_freed: 0,
current_bytes: 0,
peak_bytes: 0,
allocation_count: 0,
next_alloc_id: AtomicU64::new(1),
shadow: None,
stack_collector: None,
secondary_alloc_big_threshold: 1024 * 1024, }
}
pub fn attach_shadow(&mut self, shadow: &mut X86ASanShadowMap) {
self.shadow = Some(shadow as *mut X86ASanShadowMap);
}
pub fn attach_stack_collector(
&mut self,
collector: &mut X86ASanStackTraceCollector,
) {
self.stack_collector = Some(collector as *mut X86ASanStackTraceCollector);
}
pub fn alloc(
&mut self,
size: u64,
alignment: u64,
kind: X86ASanAllocKind,
) -> (u64, u64) {
let total = self.compute_allocation_size(size, alignment);
let id = self.next_alloc_id.fetch_add(1, Ordering::Relaxed);
let user_ptr = id * 0x10000 + 0x6000_0000_0000;
let meta = X86ASanAllocMeta {
id,
user_ptr,
total_size: total,
requested_size: size,
is_freed: false,
alloc_stack: Vec::new(), free_stack: None,
alloc_thread_id: 0,
free_thread_id: None,
is_container: false,
alloc_kind: kind,
};
if let Some(shadow_ptr) = self.shadow {
unsafe {
let shadow = &mut *shadow_ptr;
let left_rz = X86_ASAN_HEAP_REDZONE_SIZE;
let right_rz = total - left_rz - size;
shadow.poison_range(user_ptr - left_rz, left_rz, HEAP_LEFT_REDZONE);
shadow.poison_range(
user_ptr + size,
right_rz,
HEAP_RIGHT_REDZONE,
);
shadow.unpoison_range(user_ptr, size);
}
}
self.allocations.insert(user_ptr, meta);
self.total_allocated += size;
self.current_bytes += size;
self.allocation_count += 1;
if self.current_bytes > self.peak_bytes {
self.peak_bytes = self.current_bytes;
}
(user_ptr, size)
}
pub fn free(
&mut self,
ptr: u64,
expected_kind: Option<X86ASanAllocKind>,
) -> Result<(), X86ASanErrorType> {
if let Some(mut meta) = self.allocations.remove(&ptr) {
if meta.is_freed {
return Err(X86ASanErrorType::DoubleFree);
}
if let Some(expected) = expected_kind {
if meta.alloc_kind != expected {
return Err(X86ASanErrorType::AllocDeallocMismatch);
}
}
meta.is_freed = true;
meta.free_thread_id = Some(0);
if let Some(shadow_ptr) = self.shadow {
unsafe {
let shadow = &mut *shadow_ptr;
shadow.poison_range(ptr, meta.requested_size, FREED);
}
}
self.total_freed += meta.requested_size;
self.current_bytes -= meta.requested_size;
self.allocation_count -= 1;
self.quarantine.push_back(meta);
self.drain_quarantine();
Ok(())
} else {
Err(X86ASanErrorType::InvalidFree)
}
}
pub fn realloc(
&mut self,
old_ptr: u64,
new_size: u64,
alignment: u64,
) -> Result<(u64, u64), X86ASanErrorType> {
if old_ptr == 0 {
return Ok(self.alloc(new_size, alignment, X86ASanAllocKind::Realloc));
}
if new_size == 0 {
self.free(old_ptr, None)?;
return Ok((0, 0));
}
if let Some(old_meta) = self.allocations.get(&old_ptr) {
let old_requested = old_meta.requested_size;
let old_is_freed = old_meta.is_freed;
if old_is_freed {
return Err(X86ASanErrorType::HeapUseAfterFree);
}
let (new_ptr, actual_new_size) =
self.alloc(new_size, alignment, X86ASanAllocKind::Realloc);
let copy_size = old_requested.min(new_size);
self.free(old_ptr, None)?;
Ok((new_ptr, actual_new_size))
} else {
Err(X86ASanErrorType::InvalidFree)
}
}
pub fn reallocarray(
&mut self,
old_ptr: u64,
nmemb: u64,
size: u64,
alignment: u64,
) -> Result<(u64, u64), X86ASanErrorType> {
let total = nmemb.checked_mul(size).ok_or(X86ASanErrorType::CallocOverflow)?;
self.realloc(old_ptr, total, alignment)
}
pub fn calloc(
&mut self,
nmemb: u64,
size: u64,
alignment: u64,
) -> Result<(u64, u64), X86ASanErrorType> {
let total = nmemb.checked_mul(size).ok_or(X86ASanErrorType::CallocOverflow)?;
let (ptr, actual) = self.alloc(total, alignment, X86ASanAllocKind::Calloc);
if let Some(shadow_ptr) = self.shadow {
unsafe {
let shadow = &mut *shadow_ptr;
shadow.unpoison_range(ptr, total);
}
}
Ok((ptr, actual))
}
pub fn memalign(
&mut self,
alignment: u64,
size: u64,
) -> (u64, u64) {
let actual_align = alignment.max(16);
self.alloc(size, actual_align, X86ASanAllocKind::Memalign)
}
pub fn aligned_alloc(
&mut self,
alignment: u64,
size: u64,
) -> Result<(u64, u64), &'static str> {
if size % alignment != 0 {
return Err("size not multiple of alignment");
}
if alignment < std::mem::size_of::<*const u8>() as u64 {
return Err("alignment too small");
}
Ok(self.alloc(size, alignment, X86ASanAllocKind::AlignedAlloc))
}
pub fn posix_memalign(
&mut self,
alignment: u64,
size: u64,
) -> (u64, u64) {
self.alloc(size, alignment, X86ASanAllocKind::PosixMemalign)
}
fn compute_allocation_size(&self, size: u64, alignment: u64) -> u64 {
let rz = X86_ASAN_HEAP_REDZONE_SIZE.max(alignment);
let total = rz + size + rz;
total.next_multiple_of(16)
}
fn drain_quarantine(&mut self) {
while self.quarantine_size() > self.quarantine_max_size {
if let Some(meta) = self.quarantine.pop_front() {
if let Some(shadow_ptr) = self.shadow {
unsafe {
let shadow = &mut *shadow_ptr;
shadow.unpoison_range(
meta.user_ptr,
meta.requested_size,
);
}
}
} else {
break;
}
}
}
fn quarantine_size(&self) -> u64 {
self.quarantine
.iter()
.map(|m| m.total_size)
.sum()
}
pub fn is_quarantined(&self, ptr: u64) -> Option<&X86ASanAllocMeta> {
self.quarantine.iter().find(|m| {
ptr >= m.user_ptr && ptr < m.user_ptr + m.requested_size
})
}
pub fn detect_leaks(&self) -> Vec<&X86ASanAllocMeta> {
self.allocations
.values()
.filter(|m| !m.is_freed)
.collect()
}
pub fn stats(&self) -> X86ASanAllocStats {
X86ASanAllocStats {
total_allocated: self.total_allocated,
total_freed: self.total_freed,
current_bytes: self.current_bytes,
peak_bytes: self.peak_bytes,
allocation_count: self.allocation_count,
quarantine_size: self.quarantine_size(),
quarantine_entries: self.quarantine.len() as u64,
large_allocs_secondary: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanAllocStats {
pub total_allocated: u64,
pub total_freed: u64,
pub current_bytes: u64,
pub peak_bytes: u64,
pub allocation_count: u64,
pub quarantine_size: u64,
pub quarantine_entries: u64,
pub large_allocs_secondary: u64,
}
impl Default for X86ASanAllocator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ASanFull {
pub shadow: X86ASanShadowMap,
pub allocator: X86ASanAllocator,
pub stack_trace: X86ASanStackTraceCollector,
pub suppression: X86ASanErrorSuppression,
pub fake_stack: X86ASanFakeStack,
pub scope_tracker: X86ASanScopeTracker,
pub global_instr: X86ASanGlobalInstrumentation,
pub container_overflow: X86ASanContainerOverflowDetector,
pub current_frame: Option<X86ASanStackFrame>,
pub error_reports: Vec<X86ASanErrorReport>,
pub errors_suppressed: u64,
pub flags: X86ASanRuntimeFlags,
pub activated: bool,
pub halt_on_error: bool,
dedup_cache: HashMap<u64, u64>,
tls: X86ASanTLS,
total_reports: u64,
}
#[derive(Debug, Clone)]
pub struct X86ASanRuntimeFlags {
pub detect_stack_overflow: bool,
pub detect_use_after_free: bool,
pub detect_use_after_return: bool,
pub detect_use_after_scope: bool,
pub detect_container_overflow: bool,
pub detect_odr_violation: bool,
pub detect_leaks: bool,
pub detect_init_order: bool,
pub poison_stack: bool,
pub poison_heap: bool,
pub poison_globals: bool,
pub max_redzone_size: u64,
pub verbose: bool,
pub print_cmdline: bool,
pub stack_trace_depth: usize,
pub check_printf: bool,
pub check_memcpy_overlap: bool,
pub store_malloc_context: bool,
pub quarantine_size_mb: u64,
pub use_fake_stack: bool,
pub coverage: bool,
pub fast_unwind: bool,
pub replace_allocator: bool,
pub allocator_may_return_null: bool,
pub sleep_before_dying: u64,
}
impl Default for X86ASanRuntimeFlags {
fn default() -> Self {
Self {
detect_stack_overflow: true,
detect_use_after_free: true,
detect_use_after_return: true,
detect_use_after_scope: true,
detect_container_overflow: true,
detect_odr_violation: true,
detect_leaks: true,
detect_init_order: false,
poison_stack: true,
poison_heap: true,
poison_globals: true,
max_redzone_size: X86_ASAN_MAX_STACK_REDZONE_SIZE,
verbose: false,
print_cmdline: false,
stack_trace_depth: X86_ASAN_MAX_STACK_DEPTH,
check_printf: true,
check_memcpy_overlap: true,
store_malloc_context: true,
quarantine_size_mb: 256,
use_fake_stack: true,
coverage: false,
fast_unwind: true,
replace_allocator: true,
allocator_may_return_null: false,
sleep_before_dying: 0,
}
}
}
impl X86ASanFull {
pub fn new() -> Self {
let mut shadow = X86ASanShadowMap::x86_64();
shadow.allocate_shadow(0x7f_ffff_ffff);
let mut trace = X86ASanStackTraceCollector::new();
trace.fast_unwind = true;
Self {
shadow,
allocator: X86ASanAllocator::new(),
stack_trace: trace,
suppression: X86ASanErrorSuppression::new(),
fake_stack: X86ASanFakeStack::default_sized(),
scope_tracker: X86ASanScopeTracker::new(),
global_instr: X86ASanGlobalInstrumentation::new(),
container_overflow: X86ASanContainerOverflowDetector::new(),
current_frame: None,
error_reports: Vec::new(),
errors_suppressed: 0,
flags: X86ASanRuntimeFlags::default(),
activated: false,
halt_on_error: false,
dedup_cache: HashMap::new(),
tls: X86ASanTLS::new(),
total_reports: 0,
}
}
pub fn new_i386() -> Self {
let mut shadow = X86ASanShadowMap::i386();
shadow.allocate_shadow(0xFFFF_FFFF);
Self {
shadow,
..Self::new()
}
}
pub fn activate(&mut self) {
self.activated = true;
self.allocator.attach_shadow(&mut self.shadow);
self.allocator
.attach_stack_collector(&mut self.stack_trace);
}
pub fn deactivate(&mut self) {
self.activated = false;
}
pub fn check_memory_access(
&mut self,
addr: u64,
size: u64,
is_write: bool,
) -> Result<(), X86ASanErrorReport> {
if !self.activated {
return Ok(());
}
match self.shadow.check_access(addr, size, is_write) {
Ok(()) => Ok(()),
Err(desc) => {
let sv = self.shadow.get_shadow(addr);
let error_type = self.classify_error(sv);
let access_type = if is_write {
X86ASanAccessType::Write
} else {
X86ASanAccessType::Read
};
let mut report = X86ASanErrorReport::new(
error_type,
addr,
size,
access_type,
sv,
);
report.stack_trace = self.stack_trace.capture_stack_trace(
addr, 0, 0, );
if sv == FREED || sv == QUARANTINE {
if let Some(meta) = self.allocator.is_quarantined(addr) {
report.allocation_trace =
Some(meta.alloc_stack.clone());
report.deallocation_trace = meta.free_stack.clone();
}
}
if sv == STACK_UAR_REDZONE {
if let Some(entry) =
self.fake_stack.check_use_after_return(addr)
{
report.description = Some(format!(
"Address points to a stack frame of function '{}' \
which has returned",
entry.function_name,
));
}
}
if sv == STACK_USE_AFTER_SCOPE {
report.description = Some(
"Address points to a stack variable that has \
gone out of lexical scope"
.to_string(),
);
}
Err(report)
}
}
}
pub fn check_load_8(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 8, false)
}
pub fn check_store_8(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 8, true)
}
pub fn check_load_16(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 16, false)
}
pub fn check_store_16(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 16, true)
}
pub fn check_load_32(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 32, false)
}
pub fn check_store_32(&mut self, addr: u64) -> Result<(), X86ASanErrorReport> {
self.check_memory_access(addr, 32, true)
}
pub fn check_access_n(
&mut self,
addr: u64,
n: u64,
access_type: X86ASanAccessType,
) -> Result<(), X86ASanErrorReport> {
match access_type {
X86ASanAccessType::Read | X86ASanAccessType::Alloc => {
self.check_memory_access(addr, n, false)
}
X86ASanAccessType::Write => self.check_memory_access(addr, n, true),
X86ASanAccessType::Free => {
let sv = self.shadow.get_shadow(addr);
if sv == FREED {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::DoubleFree,
addr,
n,
X86ASanAccessType::Free,
sv,
));
}
Ok(())
}
}
}
fn classify_error(&self, shadow_value: i8) -> X86ASanErrorType {
match shadow_value {
HEAP_LEFT_REDZONE | HEAP_RIGHT_REDZONE => {
X86ASanErrorType::HeapBufferOverflow
}
STACK_LEFT_REDZONE
| STACK_MID_REDZONE
| STACK_RIGHT_REDZONE => {
X86ASanErrorType::StackBufferOverflow
}
STACK_UAR_REDZONE => X86ASanErrorType::StackUseAfterReturn,
GLOBAL_REDZONE => X86ASanErrorType::GlobalBufferOverflow,
INTRA_OBJECT_REDZONE => X86ASanErrorType::ContainerOverflow,
FREED | QUARANTINE => X86ASanErrorType::HeapUseAfterFree,
STACK_USE_AFTER_SCOPE => X86ASanErrorType::StackUseAfterScope,
ODR_VIOLATION => X86ASanErrorType::ODRViolation,
USER_POISON => X86ASanErrorType::UseAfterPoison,
SHADOW_GAP | HIGH_SHADOW_GAP => X86ASanErrorType::ShadowGapAccess,
INVALID => X86ASanErrorType::WildPointerAccess,
_ => X86ASanErrorType::WildPointerAccess,
}
}
pub fn report_error(
&mut self,
report: X86ASanErrorReport,
) {
if self.suppression.should_suppress(&report) {
self.errors_suppressed += 1;
return;
}
let hash = X86ASanStackTraceCollector::hash_stack_trace(
&report.stack_trace,
);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Some(&last_time) = self.dedup_cache.get(&hash) {
if now - last_time < X86_ASAN_REPORT_DEDUP_WINDOW {
return;
}
}
self.dedup_cache.insert(hash, now);
if self.total_reports >= X86_ASAN_MAX_REPORTS {
if self.total_reports == X86_ASAN_MAX_REPORTS {
eprintln!(
"==ASAN: Too many errors reported ({}). \
Further reports will be suppressed.",
X86_ASAN_MAX_REPORTS
);
}
self.total_reports += 1;
return;
}
self.total_reports += 1;
self.error_reports.push(report.clone());
if self.flags.verbose {
eprintln!("{}", report.format_report());
}
if self.halt_on_error {
eprintln!("==ASAN: HALTING ON ERROR ==");
std::process::abort();
}
}
pub fn begin_stack_frame(&mut self) {
self.current_frame = Some(X86ASanStackFrame::new());
}
pub fn add_stack_variable(
&mut self,
name: &str,
size: u64,
alignment: u64,
) {
if let Some(ref mut frame) = self.current_frame {
let var = X86ASanStackVar::new(name, 0, size, alignment)
.with_use_after_scope(self.flags.detect_use_after_scope);
frame.add_variable(var);
}
}
pub fn finish_stack_frame(
&mut self,
frame_base: u64,
) -> Option<X86ASanStackFrame> {
if let Some(frame) = self.current_frame.take() {
frame.apply_shadow(&mut self.shadow, frame_base);
Some(frame)
} else {
None
}
}
pub fn push_fake_stack(
&mut self,
function_name: &str,
frame_base: u64,
) -> Option<u64> {
if let Some(ref frame) = self.current_frame {
let fake_base = self.fake_stack.push(function_name, frame_base, frame);
Some(fake_base)
} else {
None
}
}
pub fn pop_fake_stack(&mut self, function_name: &str) {
self.fake_stack.pop(&mut self.shadow, function_name);
}
pub fn register_global(
&mut self,
name: &str,
addr: u64,
size: u64,
alignment: u64,
) {
let global = X86ASanGlobalProtection::new(name, size, alignment);
self.global_instr.add_global(global, addr);
}
pub fn apply_global_instrumentation(
&mut self,
base_addrs: &[(u64, &str)],
) {
self.global_instr
.apply_to_shadow(&mut self.shadow, base_addrs);
}
pub fn instrument_container(
&mut self,
addr: u64,
object_size: u64,
name: &str,
) {
self.container_overflow
.instrument_container(&mut self.shadow, addr, object_size, name);
}
pub fn enter_function(&mut self, name: &str) {
self.scope_tracker.enter_function(name);
}
pub fn leave_function(&mut self) {
self.scope_tracker.leave_function();
}
pub fn enter_scope(&mut self) {
self.scope_tracker.enter_scope();
}
pub fn exit_scope(&mut self, frame_base: u64) {
let poisoned = self.scope_tracker.exit_scope();
if let Some(ref frame) = self.current_frame {
for var_name in &poisoned {
if let Some(var) =
frame.variables.iter().find(|v| &v.name == var_name)
{
let addr =
(frame_base as i64 + var.frame_offset) as u64;
self.shadow
.poison_range(addr, var.size, STACK_USE_AFTER_SCOPE);
}
}
}
}
pub fn register_stack_var(&mut self, name: &str) {
self.scope_tracker.register_var(name);
}
pub fn leak_check(&self) -> Vec<X86ASanErrorReport> {
if !self.flags.detect_leaks {
return Vec::new();
}
let leaks = self.allocator.detect_leaks();
let mut reports = Vec::new();
for meta in leaks {
let report = X86ASanErrorReport::new(
X86ASanErrorType::MemoryLeak,
meta.user_ptr,
meta.requested_size,
X86ASanAccessType::Alloc,
ADDRESSABLE,
);
reports.push(report);
}
reports
}
pub fn print_leak_summary(&self) {
let leaks = self.allocator.detect_leaks();
if leaks.is_empty() {
println!("==ASAN: No memory leaks detected.");
return;
}
println!(
"==ASAN: {} memory leak(s) detected:",
leaks.len()
);
for meta in leaks {
println!(
" - {} bytes at 0x{:016x} (allocated as {})",
meta.requested_size,
meta.user_ptr,
meta.alloc_kind.as_str(),
);
}
}
pub fn stats(&self) -> X86ASanFullStats {
X86ASanFullStats {
shadow_allocated: self.shadow.shadow_allocated,
total_allocations: self.allocator.total_allocated,
current_bytes: self.allocator.current_bytes,
peak_bytes: self.allocator.peak_bytes,
quarantine_size: self.allocator.quarantine_size(),
fake_stack_entries: self.fake_stack.active_count(),
global_count: self.global_instr.count(),
container_count: self.container_overflow.tracked_count(),
error_reports: self.error_reports.len() as u64,
errors_suppressed: self.errors_suppressed,
total_reports: self.total_reports,
activated: self.activated,
}
}
pub fn reset(&mut self) {
self.shadow.reset();
self.allocator = X86ASanAllocator::new();
self.allocator.attach_shadow(&mut self.shadow);
self.allocator
.attach_stack_collector(&mut self.stack_trace);
self.fake_stack = X86ASanFakeStack::default_sized();
self.scope_tracker = X86ASanScopeTracker::new();
self.current_frame = None;
self.error_reports.clear();
self.errors_suppressed = 0;
self.total_reports = 0;
self.dedup_cache.clear();
}
pub fn load_suppressions(&mut self, path: &str) -> std::io::Result<usize> {
self.suppression.load_from_file(path)
}
pub fn poison_memory_region(&mut self, addr: u64, size: u64) {
self.shadow.poison_range(addr, size, USER_POISON);
}
pub fn unpoison_memory_region(&mut self, addr: u64, size: u64) {
self.shadow.unpoison_range(addr, size);
}
pub fn is_poisoned(&self, addr: u64) -> bool {
is_poisoned(self.shadow.get_shadow(addr))
}
}
#[derive(Debug, Clone)]
pub struct X86ASanFullStats {
pub shadow_allocated: bool,
pub total_allocations: u64,
pub current_bytes: u64,
pub peak_bytes: u64,
pub quarantine_size: u64,
pub fake_stack_entries: usize,
pub global_count: usize,
pub container_count: usize,
pub error_reports: u64,
pub errors_suppressed: u64,
pub total_reports: u64,
pub activated: bool,
}
impl Default for X86ASanFull {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanRuntime {
pub enabled: bool,
pub tag_stack: bool,
pub tag_heap: bool,
pub tag_globals: bool,
pub use_short_granules: bool,
pub use_mte: bool,
pub halt_on_error: bool,
pub print_stacktrace: bool,
pub random_seed: u64,
pub kernel_mode: bool,
pub tag_generator: X86HWASanTagGenerator,
pub memory_tags: X86HWASanMemoryTagTable,
pub stack_tags: X86HWASanStackTagging,
pub heap_tags: X86HWASanHeapTagging,
pub global_tags: X86HWASanGlobalTagging,
pub mte_available: bool,
pub mte_sync: bool,
pub kernel: X86HWASanKernel,
pub mismatch_reports: Vec<X86HWASanTagMismatchReport>,
pub initialized: bool,
}
impl X86HWASanRuntime {
pub fn new() -> Self {
Self {
enabled: false,
tag_stack: true,
tag_heap: true,
tag_globals: true,
use_short_granules: true,
use_mte: false,
halt_on_error: false,
print_stacktrace: true,
random_seed: 0,
kernel_mode: false,
tag_generator: X86HWASanTagGenerator::new(0),
memory_tags: X86HWASanMemoryTagTable::new(),
stack_tags: X86HWASanStackTagging::new(),
heap_tags: X86HWASanHeapTagging::new(),
global_tags: X86HWASanGlobalTagging::new(),
mte_available: false,
mte_sync: false,
kernel: X86HWASanKernel::new(),
mismatch_reports: Vec::new(),
initialized: false,
}
}
pub fn init(&mut self, seed: u64) {
self.tag_generator = X86HWASanTagGenerator::new(seed);
self.mte_available = self.probe_mte();
self.initialized = true;
}
pub fn probe_mte(&self) -> bool {
self.use_mte && cfg!(target_feature = "mte") }
pub fn generate_tag(&mut self) -> u8 {
self.tag_generator.generate_tag()
}
pub fn allocate_tag(&mut self) -> u8 {
self.tag_generator.allocate_tag()
}
pub fn free_tag(&mut self, tag: u8) {
self.tag_generator.free_tag(tag);
}
pub fn tag_pointer(ptr: u64, tag: u8) -> u64 {
X86HWASanTaggedPtr::create(ptr, tag).raw()
}
pub fn extract_tag(ptr: u64) -> u8 {
X86HWASanTaggedPtr::get_tag(ptr)
}
pub fn strip_tag(ptr: u64) -> u64 {
X86HWASanTaggedPtr::strip_tag(ptr)
}
pub fn check_memory_access(
&mut self,
ptr: u64,
size: u64,
is_write: bool,
) -> Result<(), X86HWASanTagMismatchReport> {
if !self.enabled || !self.initialized {
return Ok(());
}
let ptr_tag = X86HWASanTaggedPtr::get_tag(ptr);
let untagged = X86HWASanTaggedPtr::strip_tag(ptr);
let start_granule = untagged / X86_HWASAN_GRANULE_SIZE;
let end_granule = (untagged + size - 1) / X86_HWASAN_GRANULE_SIZE;
for granule in start_granule..=end_granule {
let granule_addr = granule * X86_HWASAN_GRANULE_SIZE;
if let Some(mem_tag) = self.memory_tags.get_tag(granule_addr) {
if mem_tag != ptr_tag {
let report = X86HWASanTagMismatchReport::new(
ptr,
granule_addr,
ptr_tag,
mem_tag,
size,
is_write,
);
return Err(report);
}
}
}
Ok(())
}
pub fn check_heap_access(
&mut self,
ptr: u64,
size: u64,
is_write: bool,
) -> Result<(), X86HWASanTagMismatchReport> {
self.heap_tags.check_access(
&self.memory_tags,
ptr,
size,
is_write,
)
}
pub fn tag_heap_allocation(
&mut self,
ptr: u64,
size: u64,
) -> u64 {
self.heap_tags.tag_allocation(ptr, size)
}
pub fn tag_stack_variable(
&mut self,
ptr: u64,
size: u64,
) -> u64 {
self.stack_tags.tag_variable(ptr, size)
}
pub fn tag_global(&mut self, name: &str, ptr: u64, size: u64) -> u64 {
self.global_tags.tag_global(name, ptr, size)
}
pub fn handle_mismatch(&mut self, report: X86HWASanTagMismatchReport) {
self.mismatch_reports.push(report.clone());
if self.print_stacktrace {
eprintln!("{}", report.format());
}
if self.halt_on_error {
std::process::abort();
}
}
pub fn shadow_address(addr: u64) -> u64 {
if addr >= 0xffff_0000_0000_0000 {
(addr >> X86_HWASAN_KERNEL_SHADOW_SCALE) + X86_HWASAN_KERNEL_SHADOW_OFFSET
} else {
(addr >> 4) + 0x8000_0000_0000
}
}
pub fn stats(&self) -> X86HWASanStats {
X86HWASanStats {
tags_generated: self.tag_generator.tags_issued,
heap_allocs_tagged: self.heap_tags.tag_count,
stack_vars_tagged: self.stack_tags.tag_count,
globals_tagged: self.global_tags.tag_count,
short_granules: self.memory_tags.short_granule_count,
mismatches: self.mismatch_reports.len() as u64,
initialized: self.initialized,
}
}
}
#[derive(Debug, Clone)]
pub struct X86HWASanStats {
pub tags_generated: u64,
pub heap_allocs_tagged: u64,
pub stack_vars_tagged: u64,
pub globals_tagged: u64,
pub short_granules: u64,
pub mismatches: u64,
pub initialized: bool,
}
impl Default for X86HWASanRuntime {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanTagGenerator {
pub seed: u64,
lcg_state: u64,
tag_pool: VecDeque<u8>,
used_tags: HashSet<u8>,
pub tags_issued: u64,
}
impl X86HWASanTagGenerator {
pub fn new(seed: u64) -> Self {
let actual_seed = if seed == 0 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
} else {
seed
};
let mut gen = Self {
seed: actual_seed,
lcg_state: actual_seed,
tag_pool: VecDeque::new(),
used_tags: HashSet::new(),
tags_issued: 0,
};
for t in 1..=X86_HWASAN_MAX_TAG {
gen.tag_pool.push_back(t);
}
gen
}
pub fn generate_tag(&mut self) -> u8 {
self.tags_issued += 1;
self.lcg_state = self
.lcg_state
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
let tag = (self.lcg_state % 254) as u8 + 1;
tag.min(X86_HWASAN_MAX_TAG).max(1)
}
pub fn allocate_tag(&mut self) -> u8 {
if let Some(tag) = self.tag_pool.pop_front() {
self.used_tags.insert(tag);
self.tags_issued += 1;
tag
} else {
self.generate_tag()
}
}
pub fn free_tag(&mut self, tag: u8) {
if tag > 0 && tag <= X86_HWASAN_MAX_TAG {
self.used_tags.remove(&tag);
self.tag_pool.push_back(tag);
}
}
pub fn available_tags(&self) -> usize {
self.tag_pool.len()
}
}
pub struct X86HWASanTaggedPtr;
impl X86HWASanTaggedPtr {
#[inline]
pub fn create(addr: u64, tag: u8) -> Self {
let tagged = (addr & X86_HWASAN_ADDR_MASK)
| ((tag as u64) << X86_HWASAN_TAG_SHIFT);
std::mem::forget(tagged);
Self
}
#[inline]
pub fn get_tag(ptr: u64) -> u8 {
((ptr >> X86_HWASAN_TAG_SHIFT) & X86_HWASAN_TAG_MASK) as u8
}
#[inline]
pub fn strip_tag(ptr: u64) -> u64 {
ptr & X86_HWASAN_ADDR_MASK
}
#[inline]
pub fn is_tagged(ptr: u64) -> bool {
Self::get_tag(ptr) != 0
}
#[inline]
pub fn tags_match(a: u64, b: u64) -> bool {
Self::get_tag(a) == Self::get_tag(b)
}
pub fn raw(&self) -> u64 {
0
}
pub fn sign_pointer(ptr: u64, modifier: u64) -> u64 {
ptr ^ modifier
}
}
#[derive(Debug)]
pub struct X86HWASanMemoryTagTable {
pub tags: HashMap<u64, u8>,
pub alloc_trace_ids: HashMap<u64, u64>,
pub free_trace_ids: HashMap<u64, u64>,
pub short_granules: HashMap<u64, u8>,
pub use_short_granules: bool,
pub short_granule_count: u64,
}
impl X86HWASanMemoryTagTable {
pub fn new() -> Self {
Self {
tags: HashMap::new(),
alloc_trace_ids: HashMap::new(),
free_trace_ids: HashMap::new(),
short_granules: HashMap::new(),
use_short_granules: true,
short_granule_count: 0,
}
}
pub fn set_tag(&mut self, addr: u64, tag: u8) {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.tags.insert(granule, tag);
}
pub fn set_tags_range(
&mut self,
addr: u64,
size: u64,
tag: u8,
) {
let start = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
let end = (addr + size + X86_HWASAN_GRANULE_SIZE - 1)
& !(X86_HWASAN_GRANULE_SIZE - 1);
let mut granule = start;
while granule < end {
self.tags.insert(granule, tag);
granule += X86_HWASAN_GRANULE_SIZE;
}
}
pub fn set_short_granule_tags(
&mut self,
addr: u64,
size: u64,
tag: u8,
) {
if !self.use_short_granules {
return;
}
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
let offset = (addr & (X86_HWASAN_GRANULE_SIZE - 1)) as u8;
let short_encoding =
((size.min(15) as u8) << 4) | (tag & 0x0F);
self.short_granules.insert(granule, short_encoding);
self.short_granule_count += 1;
}
pub fn get_tag(&self, addr: u64) -> Option<u8> {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.tags.get(&granule).copied()
}
pub fn check_short_granule(
&self,
addr: u64,
size: u64,
) -> Option<bool> {
if !self.use_short_granules {
return None;
}
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
if let Some(&encoding) = self.short_granules.get(&granule) {
let alloc_size = (encoding >> 4) as u64;
let offset = addr & (X86_HWASAN_GRANULE_SIZE - 1);
if offset + size <= alloc_size {
return Some(true);
} else {
return Some(false); }
}
None
}
pub fn set_alloc_trace(&mut self, addr: u64, trace_id: u64) {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.alloc_trace_ids.insert(granule, trace_id);
}
pub fn set_free_trace(&mut self, addr: u64, trace_id: u64) {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.free_trace_ids.insert(granule, trace_id);
}
pub fn get_alloc_trace(&self, addr: u64) -> Option<u64> {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.alloc_trace_ids.get(&granule).copied()
}
pub fn get_free_trace(&self, addr: u64) -> Option<u64> {
let granule = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
self.free_trace_ids.get(&granule).copied()
}
pub fn clear_range(&mut self, addr: u64, size: u64) {
let start = addr & !(X86_HWASAN_GRANULE_SIZE - 1);
let end = (addr + size + X86_HWASAN_GRANULE_SIZE - 1)
& !(X86_HWASAN_GRANULE_SIZE - 1);
let mut granule = start;
while granule < end {
self.tags.remove(&granule);
self.alloc_trace_ids.remove(&granule);
self.free_trace_ids.remove(&granule);
self.short_granules.remove(&granule);
granule += X86_HWASAN_GRANULE_SIZE;
}
}
}
impl Default for X86HWASanMemoryTagTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanStackTagging {
pub frame_base_tag: u8,
pub enabled: bool,
frame_tags: HashMap<u64, Vec<u8>>,
pub tag_count: u64,
}
impl X86HWASanStackTagging {
pub fn new() -> Self {
Self {
frame_base_tag: 0,
enabled: true,
frame_tags: HashMap::new(),
tag_count: 0,
}
}
pub fn begin_frame(&mut self, tag_gen: &mut X86HWASanTagGenerator) -> u8 {
self.frame_base_tag = tag_gen.generate_tag();
self.frame_tags.clear();
self.frame_base_tag
}
pub fn tag_variable(&mut self, ptr: u64, size: u64) -> u64 {
if !self.enabled {
return ptr;
}
let var_tag = self.frame_base_tag.wrapping_add(self.tag_count as u8);
let granule_tag = if var_tag == 0 { 1 } else { var_tag };
self.tag_count += 1;
X86HWASanTaggedPtr::create(ptr, granule_tag).raw()
}
pub fn check_access(
&self,
tags: &X86HWASanMemoryTagTable,
ptr: u64,
size: u64,
is_write: bool,
) -> Result<(), X86HWASanTagMismatchReport> {
let ptr_tag = X86HWASanTaggedPtr::get_tag(ptr);
let untagged = X86HWASanTaggedPtr::strip_tag(ptr);
if let Some(mem_tag) = tags.get_tag(untagged) {
if mem_tag != ptr_tag && mem_tag != 0 {
return Err(X86HWASanTagMismatchReport::new(
ptr, untagged, ptr_tag, mem_tag, size, is_write,
));
}
}
Ok(())
}
}
impl Default for X86HWASanStackTagging {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanHeapTagging {
pub enabled: bool,
alloc_tags: HashMap<u64, u8>,
pub tag_count: u64,
}
impl X86HWASanHeapTagging {
pub fn new() -> Self {
Self {
enabled: true,
alloc_tags: HashMap::new(),
tag_count: 0,
}
}
pub fn tag_allocation(
&mut self,
ptr: u64,
size: u64,
) -> u64 {
if !self.enabled {
return ptr;
}
let tag = (self.tag_count as u8).wrapping_mul(17).wrapping_add(1);
self.tag_count += 1;
self.alloc_tags.insert(ptr, tag);
X86HWASanTaggedPtr::create(ptr, tag).raw()
}
pub fn check_access(
&self,
tags: &X86HWASanMemoryTagTable,
ptr: u64,
size: u64,
is_write: bool,
) -> Result<(), X86HWASanTagMismatchReport> {
let ptr_tag = X86HWASanTaggedPtr::get_tag(ptr);
let untagged = X86HWASanTaggedPtr::strip_tag(ptr);
if let Some(&alloc_tag) = self.alloc_tags.get(&untagged) {
if alloc_tag != ptr_tag {
return Err(X86HWASanTagMismatchReport::new(
ptr, untagged, ptr_tag, alloc_tag, size, is_write,
));
}
} else if let Some(mem_tag) = tags.get_tag(untagged) {
if mem_tag != ptr_tag && mem_tag != 0 {
return Err(X86HWASanTagMismatchReport::new(
ptr, untagged, ptr_tag, mem_tag, size, is_write,
));
}
}
Ok(())
}
}
impl Default for X86HWASanHeapTagging {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanGlobalTagging {
pub enabled: bool,
global_tags: HashMap<String, u8>,
addr_tags: HashMap<u64, u8>,
pub tag_count: u64,
}
impl X86HWASanGlobalTagging {
pub fn new() -> Self {
Self {
enabled: true,
global_tags: HashMap::new(),
addr_tags: HashMap::new(),
tag_count: 0,
}
}
pub fn tag_global(&mut self, name: &str, ptr: u64, size: u64) -> u64 {
if !self.enabled {
return ptr;
}
let tag = (self.tag_count as u8).wrapping_mul(7).wrapping_add(3);
self.tag_count += 1;
self.global_tags.insert(name.to_string(), tag);
self.addr_tags.insert(ptr, tag);
X86HWASanTaggedPtr::create(ptr, tag).raw()
}
pub fn get_global_tag(&self, name: &str) -> Option<u8> {
self.global_tags.get(name).copied()
}
}
impl Default for X86HWASanGlobalTagging {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86HWASanTagMismatchReport {
pub address: u64,
pub granule_address: u64,
pub pointer_tag: u8,
pub memory_tag: u8,
pub access_size: u64,
pub is_write: bool,
pub alloc_trace_id: Option<u64>,
pub free_trace_id: Option<u64>,
pub likely_uaf: bool,
pub likely_buffer_overflow: bool,
}
impl X86HWASanTagMismatchReport {
pub fn new(
ptr: u64,
granule: u64,
ptr_tag: u8,
mem_tag: u8,
size: u64,
is_write: bool,
) -> Self {
let likely_uaf = mem_tag == 0xFE || mem_tag == 0xFD;
Self {
address: ptr,
granule_address: granule,
pointer_tag: ptr_tag,
memory_tag: mem_tag,
access_size: size,
is_write,
alloc_trace_id: None,
free_trace_id: None,
likely_uaf,
likely_buffer_overflow: !likely_uaf && mem_tag != 0,
}
}
pub fn format(&self) -> String {
let sep = "=".repeat(58);
let mut r = String::new();
r.push_str(&sep);
r.push('\n');
r.push_str("==HWASAN== ERROR: HWAddressSanitizer: tag-mismatch on address");
r.push('\n');
r.push_str(&format!(
"{} of size {} at 0x{:016x} tags: {:02x}/{:02x} (ptr/mem)\n",
if self.is_write { "WRITE" } else { "READ" },
self.access_size,
self.address,
self.pointer_tag,
self.memory_tag,
));
if self.likely_uaf {
r.push_str(" Likely use-after-free detected.\n");
} else if self.likely_buffer_overflow {
r.push_str(" Likely buffer overflow detected.\n");
}
r.push_str(&format!(
" Pointer tag: 0x{:02x}, Memory tag: 0x{:02x}\n",
self.pointer_tag, self.memory_tag,
));
if let Some(tid) = self.alloc_trace_id {
r.push_str(&format!(" Allocation trace id: {}\n", tid));
}
if let Some(tid) = self.free_trace_id {
r.push_str(&format!(" Free trace id: {}\n", tid));
}
r.push_str(" HWASan: tag mismatch on memory access\n");
r.push_str(&sep);
r
}
pub fn summary(&self) -> String {
format!(
"tag-mismatch at 0x{:016x}: ptr_tag=0x{:02x} mem_tag=0x{:02x} size={}",
self.address, self.pointer_tag, self.memory_tag, self.access_size,
)
}
}
#[derive(Debug)]
pub struct X86HWASanKernel {
pub enabled: bool,
pub shadow_offset: u64,
pub shadow_scale: u8,
pub tag_slab: bool,
pub tag_page_alloc: bool,
pub tag_vmalloc: bool,
pub tag_stack: bool,
pub tag_globals: bool,
kernel_tag_counter: u64,
}
impl X86HWASanKernel {
pub fn new() -> Self {
Self {
enabled: false,
shadow_offset: X86_HWASAN_KERNEL_SHADOW_OFFSET,
shadow_scale: X86_HWASAN_KERNEL_SHADOW_SCALE,
tag_slab: true,
tag_page_alloc: true,
tag_vmalloc: true,
tag_stack: true,
tag_globals: true,
kernel_tag_counter: 0,
}
}
pub fn virt_to_shadow(&self, vaddr: u64) -> u64 {
(vaddr >> self.shadow_scale) + self.shadow_offset
}
pub fn tag_slab_allocation(
&mut self,
ptr: u64,
size: u64,
) -> u64 {
if !self.enabled || !self.tag_slab {
return ptr;
}
self.kernel_tag_counter += 1;
let tag = ((self.kernel_tag_counter * 13 + 7) % 254 + 1) as u8;
X86HWASanTaggedPtr::create(ptr, tag).raw()
}
pub fn check_kernel_access(
&self,
tags: &X86HWASanMemoryTagTable,
ptr: u64,
size: u64,
) -> Result<(), X86HWASanTagMismatchReport> {
let ptr_tag = X86HWASanTaggedPtr::get_tag(ptr);
let untagged = X86HWASanTaggedPtr::strip_tag(ptr);
if untagged < 0xffff_0000_0000_0000 {
return Ok(());
}
if let Some(mem_tag) = tags.get_tag(untagged) {
if mem_tag != ptr_tag {
return Err(X86HWASanTagMismatchReport::new(
ptr, untagged, ptr_tag, mem_tag, size, false,
));
}
}
Ok(())
}
pub fn page_align_down(addr: u64, page_size: u64) -> u64 {
addr & !(page_size - 1)
}
pub fn page_align_up(addr: u64, page_size: u64) -> u64 {
(addr + page_size - 1) & !(page_size - 1)
}
pub fn init_boot(&mut self) {
self.enabled = true;
self.kernel_tag_counter = 0;
}
}
impl Default for X86HWASanKernel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ASanFullOrchestrator {
pub asan: X86ASanFull,
pub hwasan: X86HWASanRuntime,
pub flags: X86ASanRuntimeFlags,
pub active: X86ActiveSanitizers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86ActiveSanitizers {
pub asan: bool,
pub hwasan: bool,
pub ubsan: bool,
pub tsan: bool,
pub msan: bool,
pub lsan: bool,
pub dfsan: bool,
}
impl Default for X86ActiveSanitizers {
fn default() -> Self {
Self {
asan: false,
hwasan: false,
ubsan: false,
tsan: false,
msan: false,
lsan: false,
dfsan: false,
}
}
}
impl X86ASanFullOrchestrator {
pub fn new() -> Self {
Self {
asan: X86ASanFull::new(),
hwasan: X86HWASanRuntime::new(),
flags: X86ASanRuntimeFlags::default(),
active: X86ActiveSanitizers {
asan: true,
..Default::default()
},
}
}
pub fn activate_all(&mut self) {
self.asan.activate();
self.hwasan.enabled = true;
self.hwasan.init(42);
self.active.asan = true;
self.active.hwasan = true;
}
pub fn deactivate_all(&mut self) {
self.asan.deactivate();
self.hwasan.enabled = false;
self.active.asan = false;
self.active.hwasan = false;
}
pub fn check_memory_access(
&mut self,
addr: u64,
size: u64,
is_write: bool,
) -> Vec<String> {
let mut errors = Vec::new();
if self.active.asan {
if let Err(report) =
self.asan.check_memory_access(addr, size, is_write)
{
errors.push(report.summary());
}
}
if self.active.hwasan {
if let Err(report) =
self.hwasan.check_memory_access(addr, size, is_write)
{
errors.push(report.summary());
}
}
errors
}
pub fn leak_check(&self) -> Vec<X86ASanErrorReport> {
if self.active.asan {
self.asan.leak_check()
} else {
Vec::new()
}
}
pub fn print_leak_summary(&self) {
self.asan.print_leak_summary();
}
pub fn sanitizer_malloc(&mut self, size: u64) -> (u64, u64) {
if self.active.asan {
self.asan
.allocator
.alloc(size, 16, X86ASanAllocKind::Malloc)
} else {
(0, 0) }
}
pub fn sanitizer_free(&mut self, ptr: u64) -> Result<(), X86ASanErrorType> {
if self.active.asan {
self.asan.allocator.free(ptr, None)
} else {
Ok(())
}
}
pub fn sanitizer_realloc(
&mut self,
old_ptr: u64,
new_size: u64,
) -> Result<(u64, u64), X86ASanErrorType> {
if self.active.asan {
self.asan.allocator.realloc(old_ptr, new_size, 16)
} else {
Ok((0, 0))
}
}
pub fn full_stats(&self) -> X86ASanFullOrchestratorStats {
X86ASanFullOrchestratorStats {
asan_stats: self.asan.stats(),
hwasan_stats: self.hwasan.stats(),
active_sanitizers: self.active,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ASanFullOrchestratorStats {
pub asan_stats: X86ASanFullStats,
pub hwasan_stats: X86HWASanStats,
pub active_sanitizers: X86ActiveSanitizers,
}
impl Default for X86ASanFullOrchestrator {
fn default() -> Self {
Self::new()
}
}
pub struct X86ASanInstrumentationEmitter {
pub shadow_offset: u64,
pub emit_nops: bool,
pub is_64bit: bool,
}
impl X86ASanInstrumentationEmitter {
pub fn new(shadow_offset: u64, is_64bit: bool) -> Self {
Self {
shadow_offset,
emit_nops: false,
is_64bit,
}
}
pub fn emit_load_check(&self, addr_reg: &str, size: u8) -> usize {
20
}
pub fn emit_store_check(&self, addr_reg: &str, size: u8) -> usize {
self.emit_load_check(addr_reg, size)
}
pub fn emit_byte_check(&self, addr_reg: &str) -> usize {
32
}
pub fn emit_stack_prologue(&self, _frame_size: u64) -> String {
String::from("// __asan_stack_malloc_N prologue")
}
pub fn emit_stack_epilogue(&self, _frame_size: u64) -> String {
String::from("// __asan_stack_free_N epilogue")
}
pub fn emit_error_handler_call(&self, access_type: &str, size: u8) -> String {
format!(
"call __asan_report_{}{}",
access_type,
if size == 1 { "1".into() }
else if size == 2 { "2".into() }
else if size == 4 { "4".into() }
else if size == 8 { "8".into() }
else if size == 16 { "16".into() }
else { format!("n_{}", size) }
)
}
pub fn emit_full_sequence(&self, base_reg: &str, size: u8, is_store: bool) -> String {
let mut seq = String::new();
let access = if is_store { "store" } else { "load" };
seq.push_str(&format!(" mov rax, {}\n", base_reg));
seq.push_str(" shr rax, 3\n");
seq.push_str(&format!(
" cmp byte ptr [rax + 0x{:x}], 0\n",
self.shadow_offset
));
seq.push_str(" jne .Lerror\n");
seq.push_str(" jmp .Lok\n");
seq.push_str(".Lerror:\n");
seq.push_str(&format!(
" call __asan_report_{}{}\n",
access, size
));
seq.push_str(".Lok:\n");
seq
}
}
#[derive(Debug)]
pub struct X86ASanPageManager {
pub page_size: u64,
pub resident_pages: u64,
pub max_resident_pages: u64,
page_table: HashMap<u64, bool>,
page_timestamps: HashMap<u64, u64>,
clock: u64,
}
impl X86ASanPageManager {
pub fn new(page_size: u64, max_pages: u64) -> Self {
Self {
page_size,
resident_pages: 0,
max_resident_pages: max_pages,
page_table: HashMap::new(),
page_timestamps: HashMap::new(),
clock: 0,
}
}
pub fn default_pages() -> Self {
Self::new(4096, 65536)
}
pub fn map_shadow_page(&mut self, app_addr: u64, shadow: &X86ASanShadowMap) -> bool {
let shadow_addr = shadow.app_to_shadow(app_addr);
let page_base = shadow_addr & !(self.page_size - 1);
if self.page_table.contains_key(&page_base) {
self.clock += 1;
self.page_timestamps.insert(page_base, self.clock);
return true;
}
if self.resident_pages >= self.max_resident_pages {
self.evict_lru();
}
self.page_table.insert(page_base, true);
self.clock += 1;
self.page_timestamps.insert(page_base, self.clock);
self.resident_pages += 1;
true
}
pub fn unmap_shadow_page(&mut self, app_addr: u64, shadow: &X86ASanShadowMap) {
let shadow_addr = shadow.app_to_shadow(app_addr);
let page_base = shadow_addr & !(self.page_size - 1);
self.page_table.remove(&page_base);
self.page_timestamps.remove(&page_base);
self.resident_pages = self.resident_pages.saturating_sub(1);
}
pub fn is_mapped(&self, app_addr: u64, shadow: &X86ASanShadowMap) -> bool {
let shadow_addr = shadow.app_to_shadow(app_addr);
let page_base = shadow_addr & !(self.page_size - 1);
self.page_table.contains_key(&page_base)
}
fn evict_lru(&mut self) {
if let Some((&page, _)) = self
.page_timestamps
.iter()
.min_by_key(|(_, &ts)| ts)
{
self.page_table.remove(&page);
self.page_timestamps.remove(&page);
self.resident_pages = self.resident_pages.saturating_sub(1);
}
}
pub fn prefault_range(
&mut self,
start: u64,
size: u64,
shadow: &X86ASanShadowMap,
) {
let end = start + size;
let mut addr = start & !(self.page_size - 1);
while addr < end {
self.map_shadow_page(addr, shadow);
addr += self.page_size;
}
}
}
impl Default for X86ASanPageManager {
fn default() -> Self {
Self::default_pages()
}
}
#[derive(Debug)]
pub struct X86ASanLeakScanner {
pub root_set: HashSet<u64>,
pub reachable: HashSet<u64>,
pub directly_lost: Vec<(u64, u64)>,
pub indirectly_lost: Vec<(u64, u64)>,
pub active: bool,
pub max_stack_scan_depth: u64,
}
impl X86ASanLeakScanner {
pub fn new() -> Self {
Self {
root_set: HashSet::new(),
reachable: HashSet::new(),
directly_lost: Vec::new(),
indirectly_lost: Vec::new(),
active: false,
max_stack_scan_depth: 64 * 1024, }
}
pub fn add_root(&mut self, addr: u64) {
self.root_set.insert(addr);
}
pub fn clear_roots(&mut self) {
self.root_set.clear();
self.reachable.clear();
self.directly_lost.clear();
self.indirectly_lost.clear();
}
pub fn detect_leaks(
&mut self,
allocations: &HashMap<u64, X86ASanAllocMeta>,
shadow: &X86ASanShadowMap,
) -> (Vec<(u64, u64)>, Vec<(u64, u64)>) {
if !self.active {
return (Vec::new(), Vec::new());
}
self.mark_reachable(allocations);
let mut directly = Vec::new();
let mut indirectly = Vec::new();
for (&addr, meta) in allocations {
if meta.is_freed {
continue;
}
if self.reachable.contains(&addr) {
continue;
}
if self.is_indirectly_reachable(addr, allocations) {
indirectly.push((addr, meta.requested_size));
} else {
directly.push((addr, meta.requested_size));
}
}
self.directly_lost = directly.clone();
self.indirectly_lost = indirectly.clone();
(directly, indirectly)
}
fn mark_reachable(&mut self, allocations: &HashMap<u64, X86ASanAllocMeta>) {
self.reachable.clear();
let mut worklist: VecDeque<u64> = self.root_set.iter().copied().collect();
while let Some(ptr) = worklist.pop_front() {
for (&alloc_addr, meta) in allocations {
if meta.is_freed {
continue;
}
if ptr >= alloc_addr && ptr < alloc_addr + meta.requested_size {
if self.reachable.insert(alloc_addr) {
worklist.push_back(alloc_addr);
}
}
}
}
}
fn is_indirectly_reachable(
&self,
addr: u64,
allocations: &HashMap<u64, X86ASanAllocMeta>,
) -> bool {
for &reachable_addr in &self.reachable {
if let Some(meta) = allocations.get(&reachable_addr) {
let _ = meta;
let _ = addr;
}
}
false
}
pub fn print_summary(&self) -> String {
let mut report = String::new();
report.push_str("=== LeakSanitizer Report ===\n");
if self.directly_lost.is_empty() && self.indirectly_lost.is_empty() {
report.push_str("No leaks detected.\n");
return report;
}
let direct_bytes: u64 = self.directly_lost.iter().map(|(_, s)| s).sum();
let indirect_bytes: u64 = self.indirectly_lost.iter().map(|(_, s)| s).sum();
report.push_str(&format!(
"Directly lost: {} allocations, {} bytes\n",
self.directly_lost.len(),
direct_bytes,
));
report.push_str(&format!(
"Indirectly lost: {} allocations, {} bytes\n",
self.indirectly_lost.len(),
indirect_bytes,
));
report.push_str(&format!(
"Total lost: {} bytes\n",
direct_bytes + indirect_bytes,
));
for (ptr, size) in &self.directly_lost {
report.push_str(&format!(
" Direct leak of {} bytes at 0x{:016x}\n",
size, ptr,
));
}
report
}
pub fn has_leaks(&self) -> bool {
!self.directly_lost.is_empty() || !self.indirectly_lost.is_empty()
}
}
impl Default for X86ASanLeakScanner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ASanLibcInterceptors {
pub string_interception: bool,
pub memory_interception: bool,
pub check_overlap: bool,
}
impl X86ASanLibcInterceptors {
pub fn new() -> Self {
Self {
string_interception: true,
memory_interception: true,
check_overlap: true,
}
}
pub fn asan_memcpy(
&self,
dst: u64,
src: u64,
n: u64,
shadow: &mut X86ASanShadowMap,
) -> Result<(), X86ASanErrorReport> {
if let Err(e) = shadow.check_access(src, n, false) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::MemcpyParamOverlap,
src,
n,
X86ASanAccessType::Read,
shadow.get_shadow(src),
));
}
if let Err(e) = shadow.check_access(dst, n, true) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::MemcpyParamOverlap,
dst,
n,
X86ASanAccessType::Write,
shadow.get_shadow(dst),
));
}
if self.check_overlap {
let src_end = src + n;
let dst_end = dst + n;
if (dst >= src && dst < src_end) || (dst_end > src && dst_end <= src_end) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::MemcpyParamOverlap,
dst,
n,
X86ASanAccessType::Write,
USER_POISON,
));
}
}
shadow.unpoison_range(dst, n);
Ok(())
}
pub fn asan_memset(
&self,
dst: u64,
_value: u8,
n: u64,
shadow: &mut X86ASanShadowMap,
) -> Result<(), X86ASanErrorReport> {
if let Err(e) = shadow.check_access(dst, n, true) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::MemsetParamOverlap,
dst,
n,
X86ASanAccessType::Write,
shadow.get_shadow(dst),
));
}
Ok(())
}
pub fn asan_strcpy(
&self,
dst: u64,
src: u64,
shadow: &mut X86ASanShadowMap,
) -> Result<(), X86ASanErrorReport> {
let mut offset: u64 = 0;
let max_check = 4096;
while offset < max_check {
if let Err(e) = shadow.check_access(src + offset, 1, false) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::StrcpyParamOverlap,
src + offset,
1,
X86ASanAccessType::Read,
shadow.get_shadow(src + offset),
));
}
if let Err(e) = shadow.check_access(dst + offset, 1, true) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::StrcpyParamOverlap,
dst + offset,
1,
X86ASanAccessType::Write,
shadow.get_shadow(dst + offset),
));
}
let sv = shadow.get_shadow(src + offset);
if sv == ADDRESSABLE {
if offset > 100 {
break;
}
}
offset += 1;
}
Ok(())
}
pub fn asan_strlen(
&self,
src: u64,
shadow: &X86ASanShadowMap,
) -> Result<u64, X86ASanErrorReport> {
let mut offset: u64 = 0;
let max_check = 8192;
while offset < max_check {
if let Err(e) = shadow.check_access(src + offset, 1, false) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::WildPointerAccess,
src + offset,
1,
X86ASanAccessType::Read,
shadow.get_shadow(src + offset),
));
}
if offset > 256 {
return Ok(offset);
}
offset += 1;
}
Ok(offset)
}
pub fn asan_strnlen(
&self,
src: u64,
maxlen: u64,
shadow: &X86ASanShadowMap,
) -> Result<u64, X86ASanErrorReport> {
let mut offset: u64 = 0;
while offset < maxlen {
if let Err(e) = shadow.check_access(src + offset, 1, false) {
return Err(X86ASanErrorReport::new(
X86ASanErrorType::WildPointerAccess,
src + offset,
1,
X86ASanAccessType::Read,
shadow.get_shadow(src + offset),
));
}
offset += 1;
if offset > 256 {
return Ok(offset.min(maxlen));
}
}
Ok(maxlen)
}
}
impl Default for X86ASanLibcInterceptors {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ASanDWARFSymbolizer {
pub units: Vec<X86ASanDwarfUnit>,
pub subprograms: BTreeMap<u64, X86ASanDwarfSubprogram>,
pub line_table: Vec<X86ASanDwarfLineEntry>,
pub available: bool,
}
#[derive(Debug, Clone)]
pub struct X86ASanDwarfUnit {
pub name: String,
pub comp_dir: Option<String>,
pub low_pc: u64,
pub high_pc: u64,
pub debug_info_offset: u64,
}
#[derive(Debug, Clone)]
pub struct X86ASanDwarfSubprogram {
pub name: String,
pub low_pc: u64,
pub high_pc: u64,
pub file_index: u64,
pub decl_line: u64,
}
#[derive(Debug, Clone)]
pub struct X86ASanDwarfLineEntry {
pub address: u64,
pub file_index: u64,
pub line: u64,
pub column: u64,
}
impl X86ASanDWARFSymbolizer {
pub fn new() -> Self {
Self {
units: Vec::new(),
subprograms: BTreeMap::new(),
line_table: Vec::new(),
available: false,
}
}
pub fn load_from_elf(&mut self, _debug_info: &[u8], _debug_line: &[u8]) {
self.available = true;
}
pub fn symbolize(
&self,
ip: u64,
) -> Option<(String, String, u32, u32)> {
if !self.available {
return None;
}
let func_name = self.subprograms
.range(..=ip)
.next_back()
.and_then(|(low_pc, sub)| {
if ip < sub.high_pc {
Some(sub.name.clone())
} else {
None
}
})
.unwrap_or_else(|| "??".to_string());
let file_name = self.units
.first()
.map(|u| u.name.clone())
.unwrap_or_else(|| "??".to_string());
let (line, col) = self.line_table
.iter()
.find(|entry| entry.address == ip)
.map(|entry| (entry.line as u32, entry.column as u32))
.unwrap_or((0, 0));
Some((func_name, file_name, line, col))
}
pub fn add_subprogram(&mut self, name: &str, low_pc: u64, high_pc: u64) {
self.subprograms.insert(
low_pc,
X86ASanDwarfSubprogram {
name: name.to_string(),
low_pc,
high_pc,
file_index: 1,
decl_line: 0,
},
);
}
pub fn add_line_entry(&mut self, address: u64, line: u64, column: u64) {
self.line_table.push(X86ASanDwarfLineEntry {
address,
file_index: 1,
line,
column,
});
}
}
impl Default for X86ASanDWARFSymbolizer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86HWASanMTESupport {
pub available: bool,
pub sync_mode: bool,
pub async_mode: bool,
pub granule_size: u64,
pub tag_faults: u64,
}
impl X86HWASanMTESupport {
pub fn new() -> Self {
Self {
available: false, sync_mode: false,
async_mode: false,
granule_size: X86_HWASAN_GRANULE_SIZE,
tag_faults: 0,
}
}
pub fn probe(&self) -> bool {
false
}
pub fn emit_tag_check(&self, ptr: u64, expected_tag: u8) -> Result<(), &'static str> {
let actual_tag = (ptr >> 56) as u8;
if actual_tag != expected_tag {
self.record_tag_fault();
Err("tag mismatch")
} else {
Ok(())
}
}
fn record_tag_fault(&self) {
}
pub fn set_allocation_tag(&self, addr: u64, tag: u8, table: &mut X86HWASanMemoryTagTable) {
table.set_tag(addr, tag);
}
pub fn load_allocation_tag(&self, addr: u64, table: &X86HWASanMemoryTagTable) -> Option<u8> {
table.get_tag(addr)
}
pub fn insert_random_tag(&self, ptr: u64, exclude: u8) -> u64 {
let mut tag = (ptr.wrapping_mul(0x9E3779B97F4A7C15) >> 56) as u8;
if tag == 0 || tag == exclude {
tag = tag.wrapping_add(1);
}
if tag == 0 || tag == exclude {
tag = tag.wrapping_add(1);
}
(ptr & X86_HWASAN_ADDR_MASK) | ((tag as u64) << 56)
}
}
impl Default for X86HWASanMTESupport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86HWASanColorMap {
pub colors: HashMap<u8, String>,
pub tags: HashMap<String, u8>,
}
impl X86HWASanColorMap {
pub fn new() -> Self {
let mut colors = HashMap::new();
let mut tags = HashMap::new();
let color_names = [
"red", "green", "blue", "yellow", "magenta", "cyan",
"orange", "purple", "lime", "teal", "pink", "brown",
"navy", "maroon", "olive", "coral", "gold", "silver",
"violet", "indigo",
];
for (i, name) in color_names.iter().enumerate() {
let tag = (i as u8) + 1;
colors.insert(tag, name.to_string());
tags.insert(name.to_string(), tag);
}
Self { colors, tags }
}
pub fn color_for_tag(&self, tag: u8) -> &str {
self.colors
.get(&tag)
.map(|s| s.as_str())
.unwrap_or("unknown")
}
pub fn tag_for_color(&self, color: &str) -> Option<u8> {
self.tags.get(color).copied()
}
pub fn format_tag_report(&self, ptr_tag: u8, mem_tag: u8) -> String {
format!(
"tag mismatch: ptr has '{}' (0x{:02x}), mem has '{}' (0x{:02x})",
self.color_for_tag(ptr_tag),
ptr_tag,
self.color_for_tag(mem_tag),
mem_tag,
)
}
}
impl Default for X86HWASanColorMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shadow_byte_constants() {
assert_eq!(ADDRESSABLE, 0);
assert_eq!(PARTIAL1, 1);
assert_eq!(PARTIAL7, 7);
assert_eq!(HEAP_LEFT_REDZONE, -1);
assert_eq!(FREED, -9);
assert_eq!(STACK_USE_AFTER_SCOPE, -10);
}
#[test]
fn test_shadow_byte_is_addressable() {
assert!(is_addressable(0));
assert!(is_addressable(1));
assert!(is_addressable(7));
assert!(!is_addressable(-1));
assert!(!is_addressable(FREED));
}
#[test]
fn test_shadow_byte_is_poisoned() {
assert!(!is_poisoned(0));
assert!(!is_poisoned(7));
assert!(is_poisoned(HEAP_LEFT_REDZONE));
assert!(is_poisoned(FREED));
}
#[test]
fn test_shadow_byte_addr_bytes() {
assert_eq!(addr_bytes(0), 8);
assert_eq!(addr_bytes(1), 1);
assert_eq!(addr_bytes(7), 7);
assert_eq!(addr_bytes(-1), 0);
}
#[test]
fn test_shadow_byte_describe() {
assert_eq!(describe(0), "addressable");
assert_eq!(describe(HEAP_LEFT_REDZONE), "heap left redzone");
assert_eq!(describe(FREED), "freed (heap-use-after-free)");
assert_eq!(describe(STACK_USE_AFTER_SCOPE), "stack-use-after-scope");
}
#[test]
fn test_shadow_byte_error_category() {
assert_eq!(error_category(HEAP_LEFT_REDZONE), "heap-buffer-overflow");
assert_eq!(error_category(STACK_LEFT_REDZONE), "stack-buffer-overflow");
assert_eq!(error_category(FREED), "heap-use-after-free");
assert_eq!(error_category(STACK_USE_AFTER_SCOPE), "stack-use-after-scope");
}
#[test]
fn test_shadow_map_x86_64_creation() {
let map = X86ASanShadowMap::x86_64();
assert_eq!(map.shadow_offset, X86_ASAN_SHADOW_OFFSET_64_DEFAULT);
assert_eq!(map.arch, X86AsanTargetArch::X8664);
}
#[test]
fn test_shadow_map_i386_creation() {
let map = X86ASanShadowMap::i386();
assert_eq!(map.shadow_offset, X86_ASAN_SHADOW_OFFSET_32);
assert_eq!(map.arch, X86AsanTargetArch::I386);
}
#[test]
fn test_shadow_map_app_to_shadow() {
let map = X86ASanShadowMap::x86_64();
let addr: u64 = 0x1000;
let expected = (addr >> 3) + X86_ASAN_SHADOW_OFFSET_64_DEFAULT;
assert_eq!(map.app_to_shadow(addr), expected);
}
#[test]
fn test_shadow_map_shadow_to_app() {
let map = X86ASanShadowMap::x86_64();
let app: u64 = 0x1000;
let shadow = map.app_to_shadow(app);
let back = map.shadow_to_app(shadow);
assert_eq!(back, app & !X86_ASAN_SHADOW_MASK);
}
#[test]
fn test_shadow_map_allocate() {
let mut map = X86ASanShadowMap::x86_64();
assert!(!map.shadow_allocated);
map.allocate_shadow(0x100000);
assert!(map.shadow_allocated);
assert!(!map.shadow_buffer.is_empty());
}
#[test]
fn test_shadow_map_set_get() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let addr: u64 = 0x1000;
assert_eq!(map.get_shadow(addr), ADDRESSABLE);
map.set_shadow(addr, HEAP_LEFT_REDZONE);
assert_eq!(map.get_shadow(addr), HEAP_LEFT_REDZONE);
map.set_shadow(addr, ADDRESSABLE);
assert_eq!(map.get_shadow(addr), ADDRESSABLE);
}
#[test]
fn test_shadow_map_poison_range() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let addr: u64 = 0x1000;
map.poison_range(addr, 32, FREED);
for offset in (0..32).step_by(8) {
assert_eq!(map.get_shadow(addr + offset), FREED);
}
assert_eq!(map.get_shadow(addr + 40), ADDRESSABLE);
}
#[test]
fn test_shadow_map_unpoison_range() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let addr: u64 = 0x1000;
map.poison_range(addr, 32, FREED);
map.unpoison_range(addr, 16);
assert_eq!(map.get_shadow(addr), ADDRESSABLE);
assert_eq!(map.get_shadow(addr + 8), ADDRESSABLE);
assert_eq!(map.get_shadow(addr + 16), FREED);
}
#[test]
fn test_shadow_map_check_access_addressable() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
assert!(map.check_access(0x100, 8, false).is_ok());
}
#[test]
fn test_shadow_map_check_access_poisoned() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.set_shadow(0x100, HEAP_RIGHT_REDZONE);
assert!(map.check_access(0x100, 8, false).is_err());
}
#[test]
fn test_shadow_map_check_access_partial_ok() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.set_shadow(0x100, PARTIAL4);
assert!(map.check_access(0x100, 4, false).is_ok());
}
#[test]
fn test_shadow_map_check_access_partial_fail() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.set_shadow(0x100, PARTIAL4);
assert!(map.check_access(0x100, 5, false).is_err());
}
#[test]
fn test_shadow_map_check_8byte_load() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
assert!(map.check_8byte_load(0x100).is_ok());
map.set_shadow(0x100, HEAP_LEFT_REDZONE);
assert!(map.check_8byte_load(0x100).is_err());
}
#[test]
fn test_shadow_map_check_16byte() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
assert!(map.check_16byte_load(0x100).is_ok());
map.set_shadow(0x108, HEAP_RIGHT_REDZONE);
assert!(map.check_16byte_load(0x100).is_err()); }
#[test]
fn test_shadow_map_reset() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.poison_range(0x100, 64, FREED);
assert_eq!(map.get_shadow(0x100), FREED);
map.reset();
assert_eq!(map.get_shadow(0x100), ADDRESSABLE);
}
#[test]
fn test_shadow_map_find_poisoned_regions() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.poison_range(0x1000, 32, FREED);
map.poison_range(0x2000, 16, HEAP_LEFT_REDZONE);
let regions = map.find_poisoned_regions();
assert!(!regions.is_empty());
let freed_regions: Vec<_> = regions.iter().filter(|(_, _, v)| *v == FREED).collect();
assert!(!freed_regions.is_empty());
}
#[test]
fn test_shadow_map_set_partial_range() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.set_partial_range(0x1004, 12);
assert!(map.get_shadow(0x1000) >= 4);
}
#[test]
fn test_stack_var_creation() {
let var = X86ASanStackVar::new("buf", -64, 32, 8);
assert_eq!(var.name, "buf");
assert_eq!(var.size, 32);
assert_eq!(var.alignment, 8);
assert!(var.left_redzone_size >= X86_ASAN_MIN_REDZONE_SIZE);
assert!(var.right_redzone_size >= X86_ASAN_MIN_REDZONE_SIZE);
}
#[test]
fn test_stack_var_total_size() {
let var = X86ASanStackVar::new("buf", -64, 32, 8);
let total = var.total_size();
assert_eq!(total, var.left_redzone_size + 32 + var.right_redzone_size);
}
#[test]
fn test_stack_var_with_use_after_scope() {
let var = X86ASanStackVar::new("buf", -64, 32, 8).with_use_after_scope();
assert!(var.use_after_scope);
}
#[test]
fn test_stack_var_with_use_after_return() {
let var = X86ASanStackVar::new("buf", -64, 32, 8).with_use_after_return();
assert!(var.use_after_return);
}
#[test]
fn test_stack_frame_add_variable() {
let mut frame = X86ASanStackFrame::new();
assert_eq!(frame.variables.len(), 0);
let var = X86ASanStackVar::new("buf", -64, 32, 8);
frame.add_variable(var);
assert_eq!(frame.variables.len(), 1);
assert!(frame.total_frame_size > 0);
}
#[test]
fn test_stack_frame_add_multiple_variables() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("a", -64, 16, 8));
frame.add_variable(X86ASanStackVar::new("b", -128, 32, 16));
frame.add_variable(X86ASanStackVar::new("c", -256, 64, 32));
assert_eq!(frame.variables.len(), 3);
assert!(frame.total_frame_size >= 16 + 32 + 64 + 6 * 32); }
#[test]
fn test_stack_frame_compute_shadow_operations() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("x", -64, 32, 8));
let (poison, unpoison) = frame.compute_shadow_operations(0x7fff0000);
assert!(!poison.is_empty()); assert!(!unpoison.is_empty()); }
#[test]
fn test_stack_frame_poison_all_on_exit() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(
X86ASanStackVar::new("x", -64, 32, 8).with_use_after_scope(),
);
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
frame.poison_all_on_exit(&mut map, 0x1000);
assert!(frame.variables.len() > 0);
}
#[test]
fn test_stack_frame_format_layout() {
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("buf", -64, 32, 8));
let layout = frame.format_layout();
assert!(layout.contains("buf"));
assert!(layout.contains("vars"));
}
#[test]
fn test_fake_stack_default() {
let fs = X86ASanFakeStack::default_sized();
assert_eq!(fs.max_entries, X86_ASAN_FAKE_STACK_MAX_ENTRIES);
assert_eq!(fs.depth, 0);
}
#[test]
fn test_fake_stack_push_pop() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let mut fs = X86ASanFakeStack::default_sized();
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("x", -64, 32, 8));
let fake_base = fs.push("test_fn", 0x7fff0000, &frame);
assert!(fake_base > 0);
let entry = fs.pop(&mut map, "test_fn");
assert!(entry.is_some());
assert!(!entry.unwrap().is_active);
}
#[test]
fn test_fake_stack_check_use_after_return() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let mut fs = X86ASanFakeStack::default_sized();
let mut frame = X86ASanStackFrame::new();
frame.add_variable(X86ASanStackVar::new("y", -64, 64, 8));
let fake_base = fs.push("uar_fn", 0x7fff0000, &frame);
fs.pop(&mut map, "uar_fn");
let result = fs.check_use_after_return(fake_base + 8);
assert!(result.is_some());
assert_eq!(result.unwrap().function_name, "uar_fn");
}
#[test]
fn test_fake_stack_eviction() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let mut fs = X86ASanFakeStack::new(0, 1024 * 1024, 5);
let frame = X86ASanStackFrame::new();
for i in 0..10 {
fs.push(&format!("fn{}", i), 0x7fff0000, &frame);
}
assert!(fs.active_count() <= 5);
}
#[test]
fn test_scope_tracker_enter_leave() {
let mut st = X86ASanScopeTracker::new();
assert_eq!(st.current_depth(), 0);
st.enter_scope();
assert_eq!(st.current_depth(), 1);
st.enter_scope();
assert_eq!(st.current_depth(), 2);
st.exit_scope();
assert_eq!(st.current_depth(), 1);
st.exit_scope();
assert_eq!(st.current_depth(), 0);
}
#[test]
fn test_scope_tracker_register_var() {
let mut st = X86ASanScopeTracker::new();
st.enter_function("test");
st.enter_scope();
let id = st.register_var("x");
assert!(st.is_in_scope("x"));
st.exit_scope();
assert!(!st.is_in_scope("x"));
}
#[test]
fn test_scope_tracker_nested_scopes() {
let mut st = X86ASanScopeTracker::new();
st.enter_function("nested");
st.enter_scope();
st.register_var("outer");
assert!(st.is_in_scope("outer"));
st.enter_scope();
st.register_var("inner");
assert!(st.is_in_scope("outer"));
assert!(st.is_in_scope("inner"));
let went_out = st.exit_scope();
assert!(went_out.contains(&"inner".to_string()));
assert!(!st.is_in_scope("inner"));
assert!(st.is_in_scope("outer"));
let went_out = st.exit_scope();
assert!(went_out.contains(&"outer".to_string()));
assert!(!st.is_in_scope("outer"));
}
#[test]
fn test_global_protection_creation() {
let gp = X86ASanGlobalProtection::new("my_global", 128, 32);
assert_eq!(gp.name, "my_global");
assert_eq!(gp.size, 128);
assert!(gp.left_redzone_size >= X86_ASAN_GLOBAL_REDZONE_SIZE);
assert!(gp.right_redzone_size >= X86_ASAN_GLOBAL_REDZONE_SIZE);
}
#[test]
fn test_global_protection_padded_size() {
let gp = X86ASanGlobalProtection::new("g", 128, 32);
let padded = gp.padded_size();
assert_eq!(padded, gp.left_redzone_size + 128 + gp.right_redzone_size);
}
#[test]
fn test_global_instrumentation_add() {
let mut gi = X86ASanGlobalInstrumentation::new();
let gp = X86ASanGlobalProtection::new("g", 128, 32);
gi.add_global(gp, 0x600000);
assert_eq!(gi.count(), 1);
}
#[test]
fn test_global_protection_with_odr_check() {
let gp = X86ASanGlobalProtection::new("g", 128, 32)
.with_odr_check(0xDEADBEEF, 0x1000);
assert!(gp.odr_check);
assert_eq!(gp.odr_hash, Some(0xDEADBEEF));
assert_eq!(gp.odr_indicator, Some(0x1000));
}
#[test]
fn test_odr_detector_consistent() {
let mut odr = X86ASanODRViolationDetector::new();
let violated = odr.register_global("global_x", 0xABCD, "a.cc", 10, 64);
assert!(!violated);
let violated = odr.register_global("global_x", 0xABCD, "b.cc", 20, 64);
assert!(!violated);
}
#[test]
fn test_odr_detector_violation() {
let mut odr = X86ASanODRViolationDetector::new();
odr.register_global("global_y", 0x1111, "a.cc", 10, 64);
let violated = odr.register_global("global_y", 0x2222, "b.cc", 20, 128);
assert!(violated);
assert!(odr.has_violation("global_y"));
assert_eq!(odr.violation_count(), 1);
}
#[test]
fn test_odr_compute_hash() {
let h1 = X86ASanODRViolationDetector::compute_hash("foo", "int", 4);
let h2 = X86ASanODRViolationDetector::compute_hash("foo", "int", 4);
let h3 = X86ASanODRViolationDetector::compute_hash("foo", "long", 8);
assert_eq!(h1, h2); assert_ne!(h1, h3); }
#[test]
fn test_container_overflow_default() {
let cod = X86ASanContainerOverflowDetector::new();
assert!(cod.enabled);
assert_eq!(cod.min_container_size, 32);
}
#[test]
fn test_container_overflow_instrument() {
let mut cod = X86ASanContainerOverflowDetector::new();
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
cod.instrument_container(&mut map, 0x1000, 128, "std::vector<int>");
let rz_addr = 0x1000 + 128 - cod.intra_object_redzone_size;
assert!(cod.is_intra_object_overflow(&map, rz_addr));
assert!(!cod.is_intra_object_overflow(&map, 0x1000));
}
#[test]
fn test_allocator_creation() {
let alloc = X86ASanAllocator::new();
assert_eq!(alloc.allocation_count, 0);
assert_eq!(alloc.current_bytes, 0);
}
#[test]
fn test_allocator_alloc() {
let mut alloc = X86ASanAllocator::new();
let (ptr, size) = alloc.alloc(128, 16, X86ASanAllocKind::Malloc);
assert!(ptr > 0);
assert_eq!(size, 128);
assert_eq!(alloc.allocation_count, 1);
assert_eq!(alloc.current_bytes, 128);
}
#[test]
fn test_allocator_alloc_free() {
let mut alloc = X86ASanAllocator::new();
let (ptr, _) = alloc.alloc(256, 16, X86ASanAllocKind::Malloc);
assert_eq!(alloc.allocation_count, 1);
let result = alloc.free(ptr, None);
assert!(result.is_ok());
assert_eq!(alloc.allocation_count, 0);
}
#[test]
fn test_allocator_double_free() {
let mut alloc = X86ASanAllocator::new();
let (ptr, _) = alloc.alloc(64, 8, X86ASanAllocKind::Malloc);
alloc.free(ptr, None).unwrap();
let result = alloc.free(ptr, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), X86ASanErrorType::DoubleFree);
}
#[test]
fn test_allocator_invalid_free() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.free(0xDEADBEEF, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), X86ASanErrorType::InvalidFree);
}
#[test]
fn test_allocator_mismatch_free() {
let mut alloc = X86ASanAllocator::new();
let (ptr, _) = alloc.alloc(128, 16, X86ASanAllocKind::Malloc);
let result = alloc.free(ptr, Some(X86ASanAllocKind::Calloc));
assert!(result.is_err());
assert_eq!(result.unwrap_err(), X86ASanErrorType::AllocDeallocMismatch);
}
#[test]
fn test_allocator_calloc() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.calloc(10, 8, 16);
assert!(result.is_ok());
let (ptr, size) = result.unwrap();
assert_eq!(size, 80);
assert!(ptr > 0);
}
#[test]
fn test_allocator_calloc_overflow() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.calloc(u64::MAX, 2, 16);
assert!(result.is_err());
}
#[test]
fn test_allocator_realloc() {
let mut alloc = X86ASanAllocator::new();
let (old_ptr, _) = alloc.alloc(64, 8, X86ASanAllocKind::Malloc);
let result = alloc.realloc(old_ptr, 128, 16);
assert!(result.is_ok());
let (new_ptr, new_size) = result.unwrap();
assert_eq!(new_size, 128);
assert!(new_ptr != old_ptr); }
#[test]
fn test_allocator_realloc_to_zero() {
let mut alloc = X86ASanAllocator::new();
let (old_ptr, _) = alloc.alloc(64, 8, X86ASanAllocKind::Malloc);
let result = alloc.realloc(old_ptr, 0, 16);
assert!(result.is_ok());
assert_eq!(result.unwrap(), (0, 0));
assert_eq!(alloc.allocation_count, 0);
}
#[test]
fn test_allocator_realloc_null() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.realloc(0, 128, 16);
assert!(result.is_ok());
assert_eq!(alloc.allocation_count, 1);
}
#[test]
fn test_allocator_memalign() {
let mut alloc = X86ASanAllocator::new();
let (ptr, _) = alloc.memalign(64, 256);
assert!(ptr > 0);
}
#[test]
fn test_allocator_aligned_alloc() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.aligned_alloc(16, 64);
assert!(result.is_ok());
}
#[test]
fn test_allocator_aligned_alloc_not_multiple() {
let mut alloc = X86ASanAllocator::new();
let result = alloc.aligned_alloc(16, 63);
assert!(result.is_err());
}
#[test]
fn test_allocator_quarantine() {
let mut alloc = X86ASanAllocator::new();
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
alloc.attach_shadow(&mut map);
let (ptr, _) = alloc.alloc(256, 16, X86ASanAllocKind::Malloc);
alloc.free(ptr, None).unwrap();
assert!(alloc.is_quarantined(ptr).is_some());
}
#[test]
fn test_allocator_detect_leaks() {
let mut alloc = X86ASanAllocator::new();
let (ptr1, _) = alloc.alloc(100, 8, X86ASanAllocKind::Malloc);
let (ptr2, _) = alloc.alloc(200, 8, X86ASanAllocKind::Malloc);
alloc.free(ptr2, None).unwrap();
let leaks = alloc.detect_leaks();
assert_eq!(leaks.len(), 1); }
#[test]
fn test_allocator_stats() {
let mut alloc = X86ASanAllocator::new();
alloc.alloc(100, 8, X86ASanAllocKind::Malloc);
alloc.alloc(200, 8, X86ASanAllocKind::Calloc);
let stats = alloc.stats();
assert_eq!(stats.allocation_count, 2);
assert_eq!(stats.current_bytes, 300);
}
#[test]
fn test_alloc_kind_as_str() {
assert_eq!(X86ASanAllocKind::Malloc.as_str(), "malloc");
assert_eq!(X86ASanAllocKind::Calloc.as_str(), "calloc");
assert_eq!(X86ASanAllocKind::Realloc.as_str(), "realloc");
assert_eq!(X86ASanAllocKind::AlignedAlloc.as_str(), "aligned_alloc");
}
#[test]
fn test_stack_frame_entry_new() {
let entry = X86ASanStackFrameEntry::new(0x400100, 0x7fff0000);
assert_eq!(entry.ip, 0x400100);
assert_eq!(entry.bp, 0x7fff0000);
assert!(!entry.symbolized);
}
#[test]
fn test_stack_frame_entry_format() {
let entry = X86ASanStackFrameEntry::new(0x400123, 0x7fff0000);
let formatted = entry.format();
assert!(formatted.contains("0x400123"));
}
#[test]
fn test_stack_trace_collector_creation() {
let sc = X86ASanStackTraceCollector::new();
assert!(sc.fast_unwind);
assert_eq!(sc.max_depth, X86_ASAN_MAX_STACK_DEPTH);
assert!(sc.symbolize);
}
#[test]
fn test_stack_trace_capture() {
let collector = X86ASanStackTraceCollector::new();
let frames = collector.capture_stack_trace(0x400200, 0x7fff0000, 0x7ffeff00);
assert!(!frames.is_empty());
assert_eq!(frames[0].ip, 0x400200);
}
#[test]
fn test_stack_trace_register_symbol() {
let mut collector = X86ASanStackTraceCollector::new();
collector.register_symbol(
0x400300,
"my_function",
Some("main.cc"),
Some(42),
Some(10),
);
let frames = collector.capture_stack_trace(0x400300, 0x7fff0000, 0x7ffeff00);
let frame = &frames[0];
assert_eq!(frame.function, Some("my_function".to_string()));
assert!(frame.symbolized);
}
#[test]
fn test_stack_trace_hash() {
let a = vec![
X86ASanStackFrameEntry::new(0x400100, 0x7fff0000),
X86ASanStackFrameEntry::new(0x400200, 0x7ffff000),
];
let b = vec![
X86ASanStackFrameEntry::new(0x400100, 0x7fff0000),
X86ASanStackFrameEntry::new(0x400200, 0x7ffff000),
];
let c = vec![
X86ASanStackFrameEntry::new(0x400999, 0x7fff0000),
];
assert_eq!(
X86ASanStackTraceCollector::hash_stack_trace(&a),
X86ASanStackTraceCollector::hash_stack_trace(&b)
);
assert_ne!(
X86ASanStackTraceCollector::hash_stack_trace(&a),
X86ASanStackTraceCollector::hash_stack_trace(&c)
);
}
#[test]
fn test_error_report_new() {
let report = X86ASanErrorReport::new(
X86ASanErrorType::HeapBufferOverflow,
0x1000,
8,
X86ASanAccessType::Write,
HEAP_RIGHT_REDZONE,
);
assert_eq!(report.error_type, X86ASanErrorType::HeapBufferOverflow);
assert_eq!(report.address, 0x1000);
assert_eq!(report.access_size, 8);
assert!(!report.is_recoverable || report.is_recoverable); }
#[test]
fn test_error_report_format() {
let report = X86ASanErrorReport::new(
X86ASanErrorType::HeapUseAfterFree,
0x1000,
4,
X86ASanAccessType::Read,
FREED,
);
let formatted = report.format_report();
assert!(formatted.contains("heap-use-after-free"));
assert!(formatted.contains("0x0000000000001000"));
assert!(formatted.contains("READ"));
}
#[test]
fn test_error_report_summary() {
let report = X86ASanErrorReport::new(
X86ASanErrorType::StackBufferOverflow,
0x2000,
8,
X86ASanAccessType::Write,
STACK_RIGHT_REDZONE,
);
let summary = report.summary();
assert!(summary.contains("stack-buffer-overflow"));
assert!(summary.contains("0x2000"));
}
#[test]
fn test_error_type_display() {
assert_eq!(
format!("{}", X86ASanErrorType::HeapBufferOverflow),
"heap-buffer-overflow"
);
assert_eq!(
format!("{}", X86ASanErrorType::DoubleFree),
"double-free"
);
assert_eq!(
format!("{}", X86ASanErrorType::MemoryLeak),
"memory-leak"
);
}
#[test]
fn test_access_type_display() {
assert_eq!(format!("{}", X86ASanAccessType::Read), "READ");
assert_eq!(format!("{}", X86ASanAccessType::Write), "WRITE");
assert_eq!(format!("{}", X86ASanAccessType::Free), "FREE");
}
#[test]
fn test_suppression_parse_empty() {
let mut supp = X86ASanErrorSuppression::new();
supp.parse_suppressions("");
assert!(supp.rules.is_empty());
}
#[test]
fn test_suppression_parse_function() {
let mut supp = X86ASanErrorSuppression::new();
let input = "heap-buffer-overflow:\nfun:bad_function\n";
supp.parse_suppressions(input);
assert_eq!(supp.rules.len(), 1);
assert_eq!(supp.rules[0].pattern, "bad_function");
assert_eq!(
supp.rules[0].error_type,
Some(X86ASanErrorType::HeapBufferOverflow)
);
}
#[test]
fn test_suppression_matches() {
let mut supp = X86ASanErrorSuppression::new();
supp.parse_suppressions("fun:vulnerable_fn\n");
supp.enabled = true;
let mut report = X86ASanErrorReport::new(
X86ASanErrorType::HeapBufferOverflow,
0x1000,
8,
X86ASanAccessType::Write,
HEAP_RIGHT_REDZONE,
);
assert!(!supp.should_suppress(&report));
let mut frame = X86ASanStackFrameEntry::new(0x400100, 0x7fff0000);
frame.function = Some("vulnerable_fn".to_string());
report.stack_trace.push(frame);
assert!(supp.should_suppress(&report));
assert_eq!(supp.suppressed_count, 1);
}
#[test]
fn test_asan_full_new() {
let asan = X86ASanFull::new();
assert!(!asan.activated);
assert!(asan.shadow.shadow_allocated);
assert!(asan.error_reports.is_empty());
}
#[test]
fn test_asan_full_i386() {
let asan = X86ASanFull::new_i386();
assert_eq!(asan.shadow.arch, X86AsanTargetArch::I386);
assert_eq!(asan.shadow.shadow_offset, X86_ASAN_SHADOW_OFFSET_32);
}
#[test]
fn test_asan_full_activate_deactivate() {
let mut asan = X86ASanFull::new();
assert!(!asan.activated);
asan.activate();
assert!(asan.activated);
asan.deactivate();
assert!(!asan.activated);
}
#[test]
fn test_asan_full_check_memory_access_clean() {
let mut asan = X86ASanFull::new();
asan.activate();
let result = asan.check_memory_access(0x1000, 8, false);
assert!(result.is_ok());
}
#[test]
fn test_asan_full_check_memory_access_poisoned() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.shadow.poison_range(0x1000, 32, FREED);
let result = asan.check_memory_access(0x1000, 8, false);
assert!(result.is_err());
let report = result.unwrap_err();
assert_eq!(report.error_type, X86ASanErrorType::HeapUseAfterFree);
}
#[test]
fn test_asan_full_deactivated_no_check() {
let mut asan = X86ASanFull::new();
asan.shadow.poison_range(0x1000, 32, FREED);
let result = asan.check_memory_access(0x1000, 8, false);
assert!(result.is_ok());
}
#[test]
fn test_asan_full_stack_frame() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.begin_stack_frame();
asan.add_stack_variable("buf", 128, 32);
asan.add_stack_variable("idx", 8, 8);
let frame = asan.finish_stack_frame(0x7fff0000);
assert!(frame.is_some());
assert_eq!(frame.unwrap().variables.len(), 2);
}
#[test]
fn test_asan_full_fake_stack_push_pop() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.begin_stack_frame();
asan.add_stack_variable("local", 64, 8);
let fake_base = asan.push_fake_stack("my_func", 0x7fff0000);
assert!(fake_base.is_some());
asan.pop_fake_stack("my_func");
}
#[test]
fn test_asan_full_register_global() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.register_global("important_global", 0x600000, 256, 32);
assert_eq!(asan.global_instr.count(), 1);
}
#[test]
fn test_asan_full_scope_tracking() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.enter_function("test_scope");
asan.enter_scope();
asan.register_stack_var("x");
assert!(asan.scope_tracker.is_in_scope("x"));
asan.exit_scope(0x7fff0000);
assert!(!asan.scope_tracker.is_in_scope("x"));
}
#[test]
fn test_asan_full_leak_check() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.allocator.alloc(128, 16, X86ASanAllocKind::Malloc);
let leaks = asan.leak_check();
assert!(!leaks.is_empty());
assert_eq!(leaks[0].error_type, X86ASanErrorType::MemoryLeak);
}
#[test]
fn test_asan_full_poison_unpoison_memory() {
let mut asan = X86ASanFull::new();
asan.activate();
let addr: u64 = 0x5000;
assert!(!asan.is_poisoned(addr));
asan.poison_memory_region(addr, 64);
assert!(asan.is_poisoned(addr));
asan.unpoison_memory_region(addr, 64);
assert!(!asan.is_poisoned(addr));
}
#[test]
fn test_asan_full_reset() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.shadow.poison_range(0x1000, 64, FREED);
asan.allocator.alloc(128, 16, X86ASanAllocKind::Malloc);
asan.reset();
assert!(asan.error_reports.is_empty());
assert!(asan.allocator.detect_leaks().is_empty());
assert_eq!(asan.shadow.get_shadow(0x1000), ADDRESSABLE);
}
#[test]
fn test_asan_full_stats() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.allocator.alloc(256, 16, X86ASanAllocKind::Malloc);
let stats = asan.stats();
assert!(stats.shadow_allocated);
assert!(stats.total_allocations > 0);
}
#[test]
fn test_asan_full_classify_error() {
let asan = X86ASanFull::new();
assert_eq!(
asan.classify_error(HEAP_LEFT_REDZONE),
X86ASanErrorType::HeapBufferOverflow
);
assert_eq!(
asan.classify_error(STACK_LEFT_REDZONE),
X86ASanErrorType::StackBufferOverflow
);
assert_eq!(
asan.classify_error(FREED),
X86ASanErrorType::HeapUseAfterFree
);
assert_eq!(
asan.classify_error(GLOBAL_REDZONE),
X86ASanErrorType::GlobalBufferOverflow
);
assert_eq!(
asan.classify_error(STACK_UAR_REDZONE),
X86ASanErrorType::StackUseAfterReturn
);
assert_eq!(
asan.classify_error(STACK_USE_AFTER_SCOPE),
X86ASanErrorType::StackUseAfterScope
);
}
#[test]
fn test_asan_full_report_error() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.halt_on_error = false;
let report = X86ASanErrorReport::new(
X86ASanErrorType::HeapBufferOverflow,
0x1000,
8,
X86ASanAccessType::Write,
HEAP_RIGHT_REDZONE,
);
asan.report_error(report);
assert_eq!(asan.error_reports.len(), 1);
}
#[test]
fn test_asan_full_report_deduplication() {
let mut asan = X86ASanFull::new();
asan.activate();
let mut report1 = X86ASanErrorReport::new(
X86ASanErrorType::HeapBufferOverflow,
0x1000,
8,
X86ASanAccessType::Write,
HEAP_RIGHT_REDZONE,
);
report1.stack_trace = vec![X86ASanStackFrameEntry::new(0x400100, 0x7fff0000)];
let mut report2 = report1.clone();
asan.report_error(report1);
assert_eq!(asan.error_reports.len(), 1);
asan.report_error(report2);
assert_eq!(asan.error_reports.len(), 1);
}
#[test]
fn test_asan_full_load_suppressions_empty() {
let mut asan = X86ASanFull::new();
let result = asan.load_suppressions("/nonexistent/path");
assert!(result.is_err());
}
#[test]
fn test_hwasan_tag_generator_new() {
let gen = X86HWASanTagGenerator::new(42);
assert_eq!(gen.seed, 42);
}
#[test]
fn test_hwasan_tag_generator_generate_tag() {
let mut gen = X86HWASanTagGenerator::new(12345);
let tag = gen.generate_tag();
assert!(tag >= 1);
assert!(tag <= X86_HWASAN_MAX_TAG);
}
#[test]
fn test_hwasan_tag_generator_unique_tags() {
let mut gen = X86HWASanTagGenerator::new(67890);
let mut seen = HashSet::new();
for _ in 0..50 {
let tag = gen.generate_tag();
seen.insert(tag);
}
assert!(seen.len() >= 20);
}
#[test]
fn test_hwasan_tag_generator_no_zero_tag() {
let mut gen = X86HWASanTagGenerator::new(11111);
for _ in 0..100 {
let tag = gen.generate_tag();
assert_ne!(tag, 0, "Tag generator should never produce tag 0");
}
}
#[test]
fn test_hwasan_tag_generator_allocate_free() {
let mut gen = X86HWASanTagGenerator::new(22222);
let initial = gen.available_tags();
let tag = gen.allocate_tag();
assert!(gen.available_tags() < initial);
gen.free_tag(tag);
assert_eq!(gen.available_tags(), initial);
}
#[test]
fn test_hwasan_tagged_ptr_get_tag() {
let addr: u64 = 0x7fff00001000;
let tag: u8 = 42;
let tagged = (addr & X86_HWASAN_ADDR_MASK) | ((tag as u64) << X86_HWASAN_TAG_SHIFT);
assert_eq!(X86HWASanTaggedPtr::get_tag(tagged), 42);
}
#[test]
fn test_hwasan_tagged_ptr_strip_tag() {
let addr: u64 = 0x7fff00001000;
let tagged = (addr & X86_HWASAN_ADDR_MASK) | (0xABu64 << X86_HWASAN_TAG_SHIFT);
assert_eq!(X86HWASanTaggedPtr::strip_tag(tagged), addr);
}
#[test]
fn test_hwasan_tagged_ptr_is_tagged() {
let addr: u64 = 0x7fff00001000;
let tagged = (addr & X86_HWASAN_ADDR_MASK) | (0x42u64 << X86_HWASAN_TAG_SHIFT);
assert!(X86HWASanTaggedPtr::is_tagged(tagged));
assert!(!X86HWASanTaggedPtr::is_tagged(addr));
}
#[test]
fn test_hwasan_tagged_ptr_tags_match() {
let tag_a = 0x10;
let tag_b = 0x10;
let tag_c = 0x20;
let ptr_a = (0x1000u64 & X86_HWASAN_ADDR_MASK) | ((tag_a as u64) << X86_HWASAN_TAG_SHIFT);
let ptr_b = (0x2000u64 & X86_HWASAN_ADDR_MASK) | ((tag_b as u64) << X86_HWASAN_TAG_SHIFT);
let ptr_c = (0x3000u64 & X86_HWASAN_ADDR_MASK) | ((tag_c as u64) << X86_HWASAN_TAG_SHIFT);
assert!(X86HWASanTaggedPtr::tags_match(ptr_a, ptr_b));
assert!(!X86HWASanTaggedPtr::tags_match(ptr_a, ptr_c));
}
#[test]
fn test_hwasan_memory_tag_table_set_get() {
let mut table = X86HWASanMemoryTagTable::new();
let addr: u64 = 0x1000;
table.set_tag(addr, 42);
assert_eq!(table.get_tag(addr), Some(42));
assert_eq!(table.get_tag(addr + X86_HWASAN_GRANULE_SIZE), None);
}
#[test]
fn test_hwasan_memory_tag_table_neighbor() {
let mut table = X86HWASanMemoryTagTable::new();
table.set_tag(0x1000, 1);
table.set_tag(0x1010, 2);
assert_eq!(table.get_tag(0x1000), Some(1));
assert_eq!(table.get_tag(0x1010), Some(2));
assert_eq!(table.get_tag(0x1008), Some(1)); }
#[test]
fn test_hwasan_memory_tag_table_range() {
let mut table = X86HWASanMemoryTagTable::new();
table.set_tags_range(0x1000, 64, 7);
for offset in (0..64).step_by(16) {
assert_eq!(table.get_tag(0x1000 + offset), Some(7));
}
assert_eq!(table.get_tag(0x1000 + 64), None);
}
#[test]
fn test_hwasan_memory_tag_table_clear_range() {
let mut table = X86HWASanMemoryTagTable::new();
table.set_tags_range(0x1000, 64, 7);
table.clear_range(0x1000, 32);
assert_eq!(table.get_tag(0x1000), None);
assert_eq!(table.get_tag(0x1010), None);
assert_eq!(table.get_tag(0x1020), Some(7));
}
#[test]
fn test_hwasan_memory_tag_table_trace_ids() {
let mut table = X86HWASanMemoryTagTable::new();
table.set_alloc_trace(0x1000, 100);
table.set_free_trace(0x1000, 200);
assert_eq!(table.get_alloc_trace(0x1000), Some(100));
assert_eq!(table.get_free_trace(0x1000), Some(200));
}
#[test]
fn test_hwasan_memory_tag_table_short_granules() {
let mut table = X86HWASanMemoryTagTable::new();
table.set_short_granule_tags(0x1000, 7, 3);
assert_eq!(table.short_granule_count, 1);
let result = table.check_short_granule(0x1000, 5);
assert_eq!(result, Some(true));
let result = table.check_short_granule(0x1000, 8);
assert_eq!(result, Some(false));
}
#[test]
fn test_hwasan_stack_tagging_begin_frame() {
let mut st = X86HWASanStackTagging::new();
let mut gen = X86HWASanTagGenerator::new(42);
let tag = st.begin_frame(&mut gen);
assert!(tag >= 1 && tag <= X86_HWASAN_MAX_TAG);
}
#[test]
fn test_hwasan_stack_tagging_tag_variable() {
let mut st = X86HWASanStackTagging::new();
let mut gen = X86HWASanTagGenerator::new(42);
st.begin_frame(&mut gen);
let tagged = st.tag_variable(0x7fff0000, 32);
let extracted = X86HWASanTaggedPtr::get_tag(tagged);
assert!(extracted != 0);
}
#[test]
fn test_hwasan_heap_tagging() {
let mut ht = X86HWASanHeapTagging::new();
let tagged = ht.tag_allocation(0x600000, 128);
let tag = X86HWASanTaggedPtr::get_tag(tagged);
assert!(tag != 0);
assert!(ht.alloc_tags.contains_key(&0x600000));
}
#[test]
fn test_hwasan_heap_tagging_disabled() {
let mut ht = X86HWASanHeapTagging::new();
ht.enabled = false;
let tagged = ht.tag_allocation(0x600000, 128);
assert_eq!(tagged, 0x600000); }
#[test]
fn test_hwasan_global_tagging() {
let mut gt = X86HWASanGlobalTagging::new();
let tagged = gt.tag_global("my_global_var", 0x700000, 256);
let tag = X86HWASanTaggedPtr::get_tag(tagged);
assert!(tag != 0);
assert_eq!(gt.get_global_tag("my_global_var"), Some(tag));
}
#[test]
fn test_hwasan_tag_mismatch_report_new() {
let report = X86HWASanTagMismatchReport::new(
0x7fff00001000,
0x7fff00001000,
0x42,
0x99,
8,
true,
);
assert_eq!(report.pointer_tag, 0x42);
assert_eq!(report.memory_tag, 0x99);
assert!(report.is_write);
assert!(report.likely_buffer_overflow);
}
#[test]
fn test_hwasan_tag_mismatch_report_uaf() {
let report = X86HWASanTagMismatchReport::new(
0x7fff00001000,
0x7fff00001000,
0x10,
0xFE,
4,
false,
);
assert!(report.likely_uaf);
}
#[test]
fn test_hwasan_tag_mismatch_report_format() {
let report = X86HWASanTagMismatchReport::new(
0x7fff00001000,
0x7fff00001000,
0xAB,
0xCD,
4,
true,
);
let formatted = report.format();
assert!(formatted.contains("tag-mismatch"));
assert!(formatted.contains("WRITE"));
assert!(formatted.contains("ab/cd"));
}
#[test]
fn test_hwasan_tag_mismatch_report_summary() {
let report = X86HWASanTagMismatchReport::new(
0x7fff00001000,
0x7fff00001000,
1,
2,
8,
false,
);
let summary = report.summary();
assert!(summary.contains("tag-mismatch"));
assert!(summary.contains("0x01"));
assert!(summary.contains("0x02"));
}
#[test]
fn test_hwasan_runtime_new() {
let rt = X86HWASanRuntime::new();
assert!(!rt.enabled);
assert!(!rt.initialized);
assert!(rt.tag_stack);
assert!(rt.tag_heap);
}
#[test]
fn test_hwasan_runtime_init() {
let mut rt = X86HWASanRuntime::new();
rt.init(12345);
assert!(rt.initialized);
assert_eq!(rt.tag_generator.seed, 12345);
}
#[test]
fn test_hwasan_runtime_generate_tag() {
let mut rt = X86HWASanRuntime::new();
rt.init(42);
let tag = rt.generate_tag();
assert!(tag >= 1 && tag <= X86_HWASAN_MAX_TAG);
}
#[test]
fn test_hwasan_runtime_tag_pointer() {
let mut rt = X86HWASanRuntime::new();
rt.init(42);
let tagged = rt.tag_pointer(0x1000, 0xAB);
let extracted = rt.extract_tag(tagged);
assert_eq!(extracted, 0xAB);
let stripped = rt.strip_tag(tagged);
assert_eq!(stripped, 0x1000);
}
#[test]
fn test_hwasan_runtime_probe_mte() {
let mut rt = X86HWASanRuntime::new();
rt.use_mte = true;
assert!(!rt.probe_mte());
}
#[test]
fn test_hwasan_runtime_shadow_address() {
let user_addr: u64 = 0x7fff00001000;
let kernel_addr: u64 = 0xffffff0000001000;
let user_shadow = X86HWASanRuntime::shadow_address(user_addr);
let kernel_shadow = X86HWASanRuntime::shadow_address(kernel_addr);
assert!(kernel_shadow > X86_HWASAN_KERNEL_SHADOW_OFFSET);
assert!(user_shadow < X86_HWASAN_KERNEL_SHADOW_OFFSET);
}
#[test]
fn test_hwasan_runtime_check_disabled() {
let mut rt = X86HWASanRuntime::new();
rt.init(42);
let result = rt.check_memory_access(0x1000, 8, true);
assert!(result.is_ok());
}
#[test]
fn test_hwasan_runtime_stats() {
let mut rt = X86HWASanRuntime::new();
rt.init(42);
let stats = rt.stats();
assert!(!stats.initialized || stats.initialized); assert_eq!(stats.mismatches, 0);
}
#[test]
fn test_hwasan_kernel_new() {
let k = X86HWASanKernel::new();
assert!(!k.enabled);
assert_eq!(k.shadow_offset, X86_HWASAN_KERNEL_SHADOW_OFFSET);
}
#[test]
fn test_hwasan_kernel_virt_to_shadow() {
let k = X86HWASanKernel::new();
let vaddr: u64 = 0xffff888000000000;
let shadow = k.virt_to_shadow(vaddr);
let expected = (vaddr >> X86_HWASAN_KERNEL_SHADOW_SCALE)
+ X86_HWASAN_KERNEL_SHADOW_OFFSET;
assert_eq!(shadow, expected);
}
#[test]
fn test_hwasan_kernel_slab_tagging() {
let mut k = X86HWASanKernel::new();
k.enabled = true;
k.tag_slab = true;
let tagged = k.tag_slab_allocation(0xffff888000001000, 64);
let tag = X86HWASanTaggedPtr::get_tag(tagged);
assert!(tag != 0);
}
#[test]
fn test_hwasan_kernel_disabled_tagging() {
let k = X86HWASanKernel::new();
let tagged = k.tag_slab_allocation(0xffff888000001000, 64);
assert_eq!(tagged, 0xffff888000001000); }
#[test]
fn test_hwasan_kernel_page_align() {
let page_size: u64 = 4096;
assert_eq!(
X86HWASanKernel::page_align_down(0x12345, page_size),
0x12000
);
assert_eq!(
X86HWASanKernel::page_align_down(0x12000, page_size),
0x12000
);
assert_eq!(
X86HWASanKernel::page_align_up(0x12345, page_size),
0x13000
);
assert_eq!(
X86HWASanKernel::page_align_up(0x12000, page_size),
0x12000
);
}
#[test]
fn test_hwasan_kernel_init_boot() {
let mut k = X86HWASanKernel::new();
assert!(!k.enabled);
k.init_boot();
assert!(k.enabled);
}
#[test]
fn test_orchestrator_new() {
let orch = X86ASanFullOrchestrator::new();
assert!(orch.active.asan);
assert!(!orch.active.hwasan);
}
#[test]
fn test_orchestrator_activate_all() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
assert!(orch.active.asan);
assert!(orch.active.hwasan);
assert!(orch.asan.activated);
assert!(orch.hwasan.enabled);
}
#[test]
fn test_orchestrator_deactivate_all() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
orch.deactivate_all();
assert!(!orch.active.asan);
assert!(!orch.active.hwasan);
assert!(!orch.asan.activated);
assert!(!orch.hwasan.enabled);
}
#[test]
fn test_orchestrator_check_memory() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
let errors = orch.check_memory_access(0x10000, 8, false);
assert!(errors.is_empty());
}
#[test]
fn test_orchestrator_malloc_free() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
let (ptr, size) = orch.sanitizer_malloc(128);
assert!(ptr > 0);
assert_eq!(size, 128);
let result = orch.sanitizer_free(ptr);
assert!(result.is_ok());
}
#[test]
fn test_orchestrator_full_stats() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
orch.sanitizer_malloc(256);
let stats = orch.full_stats();
assert!(stats.asan_stats.total_allocations > 0);
assert!(stats.active_sanitizers.asan);
}
#[test]
fn test_target_arch_from_pointer_width() {
assert_eq!(
X86AsanTargetArch::from_pointer_width(64),
X86AsanTargetArch::X8664
);
assert_eq!(
X86AsanTargetArch::from_pointer_width(32),
X86AsanTargetArch::I386
);
}
#[test]
fn test_shadow_map_x86_64_aslr() {
let map = X86ASanShadowMap::x86_64_aslr();
assert_eq!(map.shadow_offset, X86_ASAN_SHADOW_OFFSET_64_ASLR);
}
#[test]
fn test_tls_new() {
let tls = X86ASanTLS::new();
assert!(!tls.activated);
assert_eq!(tls.alloc_count, 0);
assert!(tls.quarantine_cache.is_empty());
}
#[test]
fn test_tls_add_to_quarantine() {
let mut tls = X86ASanTLS::new();
tls.add_to_quarantine(0x1000, 32);
assert!(!tls.quarantine_cache.is_empty());
}
#[test]
fn test_tls_is_quarantined() {
let mut tls = X86ASanTLS::new();
tls.add_to_quarantine(0x1000, 64);
assert!(tls.is_quarantined(0x1000, 8));
assert!(tls.is_quarantined(0x1030, 4));
assert!(!tls.is_quarantined(0x2000, 8));
}
#[test]
fn test_tls_flush_quarantine() {
let mut tls = X86ASanTLS::new();
tls.add_to_quarantine(0x1000, 32);
tls.add_to_quarantine(0x2000, 64);
let flushed = tls.flush_quarantine();
assert_eq!(flushed.len(), 2);
assert!(tls.quarantine_cache.is_empty());
}
#[test]
fn test_quarantine_batch_new() {
let batch = X86ASanThreadQuarantineBatch::new(1024);
assert_eq!(batch.total_size, 0);
assert!(!batch.is_full());
}
#[test]
fn test_quarantine_batch_add() {
let mut batch = X86ASanThreadQuarantineBatch::new(1024);
let meta = X86ASanAllocMeta {
id: 1,
user_ptr: 0x1000,
total_size: 64,
requested_size: 32,
is_freed: true,
alloc_stack: Vec::new(),
free_stack: None,
alloc_thread_id: 0,
free_thread_id: None,
is_container: false,
alloc_kind: X86ASanAllocKind::Malloc,
};
batch.add(0x1000, 32, meta);
assert_eq!(batch.total_size, 32);
}
#[test]
fn test_quarantine_batch_flush() {
let mut batch = X86ASanThreadQuarantineBatch::new(1024);
let meta = X86ASanAllocMeta {
id: 1,
user_ptr: 0x1000,
total_size: 64,
requested_size: 32,
is_freed: true,
alloc_stack: Vec::new(),
free_stack: None,
alloc_thread_id: 0,
free_thread_id: None,
is_container: false,
alloc_kind: X86ASanAllocKind::Malloc,
};
batch.add(0x1000, 32, meta);
let flushed = batch.flush();
assert_eq!(flushed.len(), 1);
assert_eq!(batch.total_size, 0);
}
#[test]
fn test_asan_shadow_scale_constant() {
assert_eq!(X86_ASAN_SHADOW_SCALE, 3);
}
#[test]
fn test_asan_shadow_granularity_constant() {
assert_eq!(X86_ASAN_SHADOW_GRANULARITY, 8);
}
#[test]
fn test_asan_shadow_mask_constant() {
assert_eq!(X86_ASAN_SHADOW_MASK, 7);
}
#[test]
fn test_hwasan_granule_size_constant() {
assert_eq!(X86_HWASAN_GRANULE_SIZE, 16);
}
#[test]
fn test_hwasan_tag_mask_constant() {
assert_eq!(X86_HWASAN_TAG_MASK, 0xFF);
}
#[test]
fn test_hwasan_addr_mask_constant() {
assert_eq!(X86_HWASAN_ADDR_MASK, (1u64 << 56) - 1);
}
#[test]
fn test_full_asan_workflow() {
let mut asan = X86ASanFull::new();
asan.activate();
let (ptr, size) = asan.allocator.alloc(256, 16, X86ASanAllocKind::Malloc);
assert!(ptr > 0);
assert_eq!(size, 256);
assert!(asan.check_memory_access(ptr, 8, true).is_ok());
asan.allocator.free(ptr, None).unwrap();
let result = asan.check_memory_access(ptr, 8, false);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().error_type,
X86ASanErrorType::HeapUseAfterFree
);
let result = asan.allocator.free(ptr, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), X86ASanErrorType::DoubleFree);
}
#[test]
fn test_full_stack_instrumentation_workflow() {
let mut asan = X86ASanFull::new();
asan.activate();
asan.begin_stack_frame();
asan.add_stack_variable("buffer", 64, 16);
asan.add_stack_variable("index", 4, 4);
let fake_base =
asan.push_fake_stack("process_data", 0x7fff0000);
assert!(fake_base.is_some());
let frame = asan.finish_stack_frame(0x7fff0000);
assert!(frame.is_some());
asan.pop_fake_stack("process_data");
let fake_addr = fake_base.unwrap() + 16;
asan.shadow.set_shadow(fake_addr, STACK_UAR_REDZONE);
let result = asan.check_memory_access(fake_addr, 8, false);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().error_type,
X86ASanErrorType::StackUseAfterReturn
);
}
#[test]
fn test_full_global_odr_workflow() {
let mut asan = X86ASanFull::new();
asan.activate();
let hash = X86ASanODRViolationDetector::compute_hash(
"config", "struct Config", 128,
);
let gp = X86ASanGlobalProtection::new("config", 128, 32)
.with_odr_check(hash, 0x700000);
asan.global_instr.add_global(gp, 0x600000);
let different_hash = 0xDEADBEEF;
let violated = asan.global_instr.odr_detector.register_global(
"config",
different_hash,
"other.cc",
42,
64,
);
assert!(violated);
assert!(asan.global_instr.odr_detector.has_violation("config"));
}
#[test]
fn test_full_hwasan_workflow() {
let mut rt = X86HWASanRuntime::new();
rt.init(12345);
rt.enabled = true;
let tagged = rt.tag_pointer(0x600000, 0x42);
assert_eq!(rt.extract_tag(tagged), 0x42);
rt.memory_tags.set_tag(0x600000, 0x42);
let result = rt.check_memory_access(tagged, 8, false);
assert!(result.is_ok());
rt.memory_tags.set_tag(0x600000, 0x99);
let result = rt.check_memory_access(tagged, 8, false);
assert!(result.is_err());
let report = result.unwrap_err();
assert_eq!(report.pointer_tag, 0x42);
assert_eq!(report.memory_tag, 0x99);
}
#[test]
fn test_combined_asan_hwasan_workflow() {
let mut orch = X86ASanFullOrchestrator::new();
orch.activate_all();
let (ptr, _) = orch.sanitizer_malloc(128);
let errors = orch.check_memory_access(ptr, 8, true);
assert!(errors.is_empty());
orch.sanitizer_free(ptr).unwrap();
let errors = orch.check_memory_access(ptr, 4, false);
assert!(!errors.is_empty());
let leaks = orch.leak_check();
let leaked_count = leaks.len();
}
#[test]
fn test_shadow_map_zero_address() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
map.set_shadow(0, HEAP_LEFT_REDZONE);
assert!(map.check_access(0, 1, false).is_err());
}
#[test]
fn test_shadow_map_large_address() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x7fff_ffff_ffff);
let addr: u64 = 0x7fff_ffff_fff0;
map.set_shadow(addr, ADDRESSABLE);
assert_eq!(map.get_shadow(addr), ADDRESSABLE);
}
#[test]
fn test_allocator_zero_size() {
let mut alloc = X86ASanAllocator::new();
let (ptr, size) = alloc.alloc(0, 8, X86ASanAllocKind::Malloc);
assert_eq!(size, 0);
}
#[test]
fn test_error_report_with_allocation_trace() {
let report = X86ASanErrorReport::new(
X86ASanErrorType::HeapUseAfterFree,
0x1000,
8,
X86ASanAccessType::Read,
FREED,
);
let formatted = report.format_report();
assert!(!formatted.is_empty());
}
#[test]
fn test_suppression_comment_lines() {
let mut supp = X86ASanErrorSuppression::new();
supp.parse_suppressions("# This is a comment\nfun:real_fn\n");
assert_eq!(supp.rules.len(), 1);
assert_eq!(supp.rules[0].pattern, "real_fn");
}
#[test]
fn test_multiple_allocs_and_frees() {
let mut alloc = X86ASanAllocator::new();
let ptrs: Vec<u64> = (0..10)
.map(|_| alloc.alloc(64, 8, X86ASanAllocKind::Malloc).0)
.collect();
assert_eq!(alloc.allocation_count, 10);
for &ptr in &ptrs[..5] {
alloc.free(ptr, None).unwrap();
}
assert_eq!(alloc.allocation_count, 5);
}
#[test]
fn test_tag_generator_deterministic() {
let mut gen1 = X86HWASanTagGenerator::new(42);
let mut gen2 = X86HWASanTagGenerator::new(42);
for _ in 0..10 {
assert_eq!(gen1.generate_tag(), gen2.generate_tag());
}
}
#[test]
fn test_hwasan_check_all_granules() {
let mut table = X86HWASanMemoryTagTable::new();
let mut rt = X86HWASanRuntime::new();
rt.init(42);
rt.enabled = true;
table.set_tag(0x1000, 7);
table.set_tag(0x1010, 7);
table.set_tag(0x1020, 7);
rt.memory_tags = table;
let tagged = rt.tag_pointer(0x1008, 7);
let result = rt.check_memory_access(tagged, 16, true);
assert!(result.is_ok());
let tagged = rt.tag_pointer(0x1008, 9); let result = rt.check_memory_access(tagged, 16, true);
assert!(result.is_err());
}
#[test]
fn test_bulk_poison_unpoison() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x100000);
map.bulk_poison(0x10000, 65536, FREED);
for addr in (0x10000..0x20000).step_by(256) {
assert_eq!(map.get_shadow(addr), FREED);
}
map.bulk_unpoison(0x10000, 65536);
assert_eq!(map.get_shadow(0x10000), ADDRESSABLE);
assert_eq!(map.get_shadow(0x15000), ADDRESSABLE);
}
#[test]
fn test_many_stack_variables() {
let mut frame = X86ASanStackFrame::new();
for i in 0..100 {
frame.add_variable(X86ASanStackVar::new(
&format!("var_{}", i),
32,
8,
));
}
assert_eq!(frame.variables.len(), 100);
assert!(frame.total_frame_size > 100 * (32 + 2 * X86_ASAN_STACK_REDZONE_SIZE));
}
#[test]
fn test_many_globals() {
let mut gi = X86ASanGlobalInstrumentation::new();
for i in 0..200 {
let gp = X86ASanGlobalProtection::new(&format!("g_{}", i), 64, 16);
gi.add_global(gp, 0x600000 + i * 128);
}
assert_eq!(gi.count(), 200);
}
#[test]
fn test_many_fake_stack_entries() {
let mut map = X86ASanShadowMap::x86_64();
map.allocate_shadow(0x20000);
let mut fs = X86ASanFakeStack::new(0, 10 * 1024 * 1024, 64);
let frame = X86ASanStackFrame::new();
for i in 0..100 {
fs.push(&format!("fn_{}", i), 0x7fff0000 + i * 0x1000, &frame);
}
assert!(fs.active_count() <= 64);
}
#[test]
fn test_hwasan_many_tags() {
let mut gen = X86HWASanTagGenerator::new(1);
let mut seen = HashSet::new();
for _ in 0..254 {
let tag = gen.generate_tag();
seen.insert(tag);
}
assert!(seen.len() > 200);
}
}