use std::collections::HashMap;
use std::sync::atomic::{AtomicU8, Ordering};
pub const HWASAN_GRANULE_SIZE: u32 = 16;
pub const HWASAN_TAG_BITS: u8 = 4;
pub const HWASAN_TAG_MASK: u64 = 0xFF00000000000000;
pub const HWASAN_TAG_SHIFT: u32 = 56;
pub const HWASAN_MAX_TAG: u8 = 0x0F;
pub const HWASAN_UNTAGGED: u8 = 0;
pub const HWASAN_ADDR_MASK: u64 = 0x00FFFFFFFFFFFFFF;
pub const HWASAN_SHORT_GRANULE_MASK: u16 = 0xFFFF;
pub const HWASAN_MTE_GRANULE_SIZE: u32 = 16;
pub const HWASAN_MTE_TAG_MASK: u64 = 0xF;
pub const HWASAN_KERNEL_SHADOW_OFFSET: u64 = 0xFFFF000000000000;
pub const HWASAN_KERNEL_SHADOW_SCALE: u32 = 16;
#[derive(Debug, Clone)]
pub struct TagGenerator {
pub seed: u64,
pub has_mte: bool,
lcg_state: u64,
}
impl TagGenerator {
pub fn new(seed: u64) -> Self {
TagGenerator {
seed,
has_mte: false,
lcg_state: seed,
}
}
pub fn generate_tag(&mut self) -> u8 {
if self.has_mte {
self.mte_generate_tag()
} else {
let tag = self.lcg_next() as u8 % HWASAN_MAX_TAG;
if tag == 0 { 1 } else { tag }
}
}
fn mte_generate_tag(&mut self) -> u8 {
let tag = self.lcg_next() as u8 & HWASAN_MAX_TAG;
if tag == 0 { 1 } else { tag }
}
fn lcg_next(&mut self) -> u64 {
self.lcg_state = self.lcg_state.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.lcg_state
}
pub fn allocate_tag(&mut self) -> u8 {
let tag = self.generate_tag();
tag
}
pub fn free_tag(&mut self, _tag: u8) {
}
}
#[derive(Debug, Clone)]
pub struct TaggedPointer;
impl TaggedPointer {
pub fn tag_pointer(ptr: u64, tag: u8) -> u64 {
(ptr & HWASAN_ADDR_MASK) | ((tag as u64) << HWASAN_TAG_SHIFT)
}
pub fn get_tag(ptr: u64) -> u8 {
((ptr & HWASAN_TAG_MASK) >> HWASAN_TAG_SHIFT) as u8
}
pub fn strip_tag(ptr: u64) -> u64 {
ptr & HWASAN_ADDR_MASK
}
pub fn is_tagged(ptr: u64) -> bool {
(ptr & HWASAN_TAG_MASK) != 0
}
pub fn tags_match(p1: u64, p2: u64) -> bool {
Self::get_tag(p1) == Self::get_tag(p2)
}
pub fn sign_pointer(ptr: u64, modifier: u64) -> u64 {
let tag = Self::get_tag(ptr);
let addr = Self::strip_tag(ptr);
let pac = (addr & 0xFFFFFFFF) ^ modifier;
Self::tag_pointer(addr | (pac << 32), tag)
}
}
#[derive(Debug, Clone)]
pub struct MemoryTagTable {
pub tags: HashMap<u64, u8>,
pub alloc_trace: HashMap<u64, u32>,
pub free_trace: HashMap<u64, u32>,
pub short_granules: HashMap<u64, u16>,
pub use_short_granules: bool,
}
impl MemoryTagTable {
pub fn new() -> Self {
MemoryTagTable {
tags: HashMap::new(),
alloc_trace: HashMap::new(),
free_trace: HashMap::new(),
short_granules: HashMap::new(),
use_short_granules: false,
}
}
pub fn set_memory_tag(&mut self, addr: u64, size: usize, tag: u8) {
let granularity = HWASAN_GRANULE_SIZE as u64;
let start = addr & !(granularity - 1);
let end = (addr + size as u64 + granularity - 1) & !(granularity - 1);
for granule_addr in (start..end).step_by(granularity as usize) {
self.tags.insert(granule_addr, tag);
}
}
pub fn set_short_granule_tags(&mut self, granule_addr: u64, byte_tags: &[u8]) {
if !self.use_short_granules { return; }
let mut mask: u16 = 0;
for (i, &bt) in byte_tags.iter().enumerate().take(16) {
let granule_tag = self.tags.get(&granule_addr).copied().unwrap_or(0);
if bt == granule_tag {
mask |= 1u16 << i;
}
}
self.short_granules.insert(granule_addr, mask);
}
pub fn get_memory_tag(&self, addr: u64) -> u8 {
let granule_addr = addr & !(HWASAN_GRANULE_SIZE as u64 - 1);
self.tags.get(&granule_addr).copied().unwrap_or(HWASAN_UNTAGGED)
}
pub fn set_alloc_trace(&mut self, addr: u64, trace_id: u32) {
let granule_addr = addr & !(HWASAN_GRANULE_SIZE as u64 - 1);
self.alloc_trace.insert(granule_addr, trace_id);
}
pub fn set_free_trace(&mut self, addr: u64, trace_id: u32) {
let granule_addr = addr & !(HWASAN_GRANULE_SIZE as u64 - 1);
self.free_trace.insert(granule_addr, trace_id);
}
pub fn check_short_granule(&self, addr: u64) -> bool {
if !self.use_short_granules { return true; }
let granule_addr = addr & !(HWASAN_GRANULE_SIZE as u64 - 1);
let byte_offset = (addr - granule_addr) as usize;
let mask = self.short_granules.get(&granule_addr).copied().unwrap_or(0);
(mask & (1u16 << byte_offset)) != 0
}
}
impl Default for MemoryTagTable {
fn default() -> Self {
MemoryTagTable::new()
}
}
#[derive(Debug, Clone)]
pub struct StackTagging {
pub stack_tag: u8,
pub enabled: bool,
pub tag_generator: TagGenerator,
}
impl StackTagging {
pub fn new(seed: u64) -> Self {
StackTagging {
stack_tag: HWASAN_UNTAGGED,
enabled: false,
tag_generator: TagGenerator::new(seed),
}
}
pub fn emit_prologue(&mut self, func_name: &str, stack_size: u64) -> Vec<String> {
if !self.enabled { return Vec::new(); }
self.stack_tag = self.tag_generator.generate_tag();
vec![
format!(" ; HWASan stack prologue for {}", func_name),
format!(" ; Stack size: {} bytes", stack_size),
format!(" ; Stack tag: {}", self.stack_tag),
" ; tag stack frame memory".to_string(),
" ; stg %sp, [%sp] // store tag to stack granule 0".to_string(),
" ; addg %tmp, %sp, #16, #1 // add tag to SP+16".to_string(),
" ; stg %tmp, [%sp, #16] // store tag to stack granule 1".to_string(),
format!(" ; Total granules: {}", (stack_size + 15) / 16),
]
}
pub fn emit_epilogue(&mut self, func_name: &str) -> Vec<String> {
if !self.enabled { return Vec::new(); }
vec![
format!(" ; HWASan stack epilogue for {}", func_name),
" ; untag stack frame (set to 0 for use-after-return detection)".to_string(),
]
}
pub fn emit_llvm_stack_prologue(&self, func_name: &str, sp_reg: &str, frame_size: u64) -> Vec<String> {
vec![
format!(" ; HWASan stack tag: {} frame {} bytes", func_name, frame_size),
format!(" ; IRG: insert random tag into stack pointer"),
format!(" %tagged_sp = call i64 @llvm.aarch64.irg(i64 {}, i64 0)", sp_reg),
format!(" ; STG: store allocation tag for each granule"),
format!(" call void @llvm.aarch64.stg(i64 %tagged_sp, i64 %tagged_sp, i64 {})", frame_size),
]
}
}
#[derive(Debug, Clone)]
pub struct HeapTagging {
pub tag_generator: TagGenerator,
pub tag_table: MemoryTagTable,
pub enabled: bool,
pub next_trace_id: u32,
}
impl HeapTagging {
pub fn new(seed: u64) -> Self {
HeapTagging {
tag_generator: TagGenerator::new(seed),
tag_table: MemoryTagTable::new(),
enabled: false,
next_trace_id: 1,
}
}
pub fn instrument_malloc(&mut self, size: usize) -> (u64, u8) {
let addr = 0x10000000u64; let tag = self.tag_generator.allocate_tag();
let tagged_ptr = TaggedPointer::tag_pointer(addr, tag);
self.tag_table.set_memory_tag(addr, size, tag);
let trace_id = self.next_trace_id;
self.next_trace_id += 1;
self.tag_table.set_alloc_trace(addr, trace_id);
(tagged_ptr, tag)
}
pub fn instrument_free(&mut self, ptr: u64) -> Option<String> {
let tag = TaggedPointer::get_tag(ptr);
let addr = TaggedPointer::strip_tag(ptr);
let mem_tag = self.tag_table.get_memory_tag(addr);
if tag != mem_tag && mem_tag != HWASAN_UNTAGGED {
return Some(format!(
"HWASan: tag mismatch on free: ptr_tag={}, mem_tag={} at 0x{:x}",
tag, mem_tag, addr
));
}
let free_tag = self.tag_generator.generate_tag();
self.tag_table.set_memory_tag(addr, 1, free_tag);
self.tag_table.set_free_trace(addr, self.next_trace_id);
self.next_trace_id += 1;
None
}
pub fn emit_llvm_malloc_instrument(&self, size_reg: &str, result_reg: &str) -> Vec<String> {
vec![
" ; HWASan heap alloc tag".to_string(),
format!(" ; Call malloc with size {}", size_reg),
format!(" %{}_tag = call i32 @__hwasan_generate_tag()", result_reg),
format!(" ; IRG: tag the pointer"),
format!(" %{}_tagged = call i64 @llvm.aarch64.irg(i64 %{}, i64 %{}_tag)", result_reg, result_reg, result_reg),
format!(" ; STG: tag the allocated memory"),
format!(" call void @__hwasan_tag_memory(i64 %{}_tagged, i64 {}, i32 %{}_tag)", result_reg, size_reg, result_reg),
]
}
pub fn emit_llvm_free_check(&self, ptr_reg: &str) -> Vec<String> {
vec![
" ; HWASan heap free check".to_string(),
format!(
" call void @__hwasan_check_free(ptr {})",
ptr_reg
),
]
}
}
#[derive(Debug, Clone)]
pub struct GlobalTagging {
pub tag_generator: TagGenerator,
pub global_tags: HashMap<String, (u64, u8)>,
pub enabled: bool,
}
impl GlobalTagging {
pub fn new(seed: u64) -> Self {
GlobalTagging {
tag_generator: TagGenerator::new(seed),
global_tags: HashMap::new(),
enabled: false,
}
}
pub fn register_global(&mut self, name: &str, addr: u64, size: usize) -> u8 {
let tag = self.tag_generator.allocate_tag();
self.global_tags.insert(name.to_string(), (addr, tag));
tag
}
pub fn emit_llvm_global_check(&self, global_name: &str, access_ptr: &str) -> Vec<String> {
if let Some(&(_addr, tag)) = self.global_tags.get(global_name) {
vec![
format!(" ; HWASan global access check for {}", global_name),
format!(" ; Expected tag: {}", tag),
format!(" %ptr_tag = lshr i64 {}, 56", access_ptr),
format!(" %expected_tag = zext i8 {} to i64", tag),
format!(" %tag_ok = icmp eq i64 %ptr_tag, %expected_tag"),
" br i1 %tag_ok, label %global_ok, label %hwasan_mismatch".to_string(),
]
} else {
vec![]
}
}
}
#[derive(Debug, Clone)]
pub struct TagMismatchReport {
pub address: u64,
pub pointer_tag: u8,
pub memory_tag: u8,
pub access_type: String,
pub access_size: usize,
pub location: String,
pub thread_id: u64,
pub alloc_trace_id: Option<u32>,
pub free_trace_id: Option<u32>,
pub likely_uaf: bool,
pub likely_buffer_overflow: bool,
}
impl TagMismatchReport {
pub fn new(
address: u64,
ptr_tag: u8,
mem_tag: u8,
access_type: &str,
access_size: usize,
location: &str,
) -> Self {
TagMismatchReport {
address,
pointer_tag: ptr_tag,
memory_tag: mem_tag,
access_type: access_type.to_string(),
access_size,
location: location.to_string(),
thread_id: 0,
alloc_trace_id: None,
free_trace_id: None,
likely_uaf: false,
likely_buffer_overflow: false,
}
}
pub fn format(&self) -> String {
let mut report = String::new();
report.push_str("=================================================================\n");
report.push_str("HWASan: tag-mismatch on address 0x");
report.push_str(&format!("{:016x}\n", self.address));
report.push_str(&format!(
" {} of size {} at {}\n",
self.access_type, self.access_size, self.location
));
report.push_str(&format!(
" pointer tag: 0x{:x}\n",
self.pointer_tag
));
report.push_str(&format!(
" memory tag: 0x{:x}\n",
self.memory_tag
));
if let Some(tid) = self.alloc_trace_id {
report.push_str(&format!(" alloc trace: #{}\n", tid));
}
if let Some(tid) = self.free_trace_id {
report.push_str(&format!(" free trace: #{}\n", tid));
}
if self.likely_uaf {
report.push_str(" Likely use-after-free detected.\n");
if let Some(tid) = self.free_trace_id {
report.push_str(&format!(
" Memory was freed at trace #{}\n",
tid
));
}
}
if self.likely_buffer_overflow {
report.push_str(" Likely buffer overflow detected.\n");
}
report.push_str("=================================================================\n");
report
}
pub fn emit_llvm_handler() -> Vec<String> {
vec![
"hwasan_mismatch:".to_string(),
" ; Tag mismatch handler".to_string(),
" call void @__hwasan_tag_mismatch4(i64 %addr, i8 %ptr_tag, i8 %mem_tag, i64 %access_info)".to_string(),
" ; Halt or continue based on HWASan flags".to_string(),
" call void @__hwasan_handle_tag_mismatch()".to_string(),
" unreachable".to_string(),
]
}
}
#[derive(Debug, Clone)]
pub struct MTESupport {
pub available: bool,
pub sync_mode: bool,
pub async_mode: bool,
}
impl MTESupport {
pub fn new() -> Self {
MTESupport {
available: false,
sync_mode: false,
async_mode: false,
}
}
pub fn probe() -> Self {
MTESupport {
available: false,
sync_mode: false,
async_mode: false,
}
}
pub fn emit_mte_instructions(&self) -> Vec<String> {
if !self.available { return Vec::new(); }
let mut ops = vec![
" ; ARM MTE instructions".to_string(),
" ; IRG Xd, Xn: Insert Random Tag".to_string(),
" ; STG Xt, [Xn]: Store Allocation Tag".to_string(),
" ; LDG Xt, [Xn]: Load Allocation Tag".to_string(),
" ; STZG Xt, [Xn]: Store Zero Tag (for deallocation)".to_string(),
];
if self.sync_mode {
ops.push(" ; MTE sync: tag check fault on mismatch".to_string());
}
if self.async_mode {
ops.push(" ; MTE async: tag check logged, no immediate fault".to_string());
}
ops
}
pub fn emit_mte_intrinsics() -> Vec<String> {
vec![
"declare i64 @llvm.aarch64.irg(i64, i64)".to_string(),
"declare void @llvm.aarch64.stg(i64, i64, i64)".to_string(),
"declare void @llvm.aarch64.stzg(i64, i64, i64)".to_string(),
"declare i64 @llvm.aarch64.ldg(i64)".to_string(),
"declare void @llvm.aarch64.addg(i64, i32)".to_string(),
"declare void @llvm.aarch64.subp(i64, i64)".to_string(),
"declare i64 @llvm.aarch64.gmi(i64, i64)".to_string(),
]
}
}
impl Default for MTESupport {
fn default() -> Self {
MTESupport::new()
}
}
#[derive(Debug, Clone)]
pub struct KernelHWASan {
pub shadow_offset: u64,
pub shadow_scale: u32,
pub tag_slab: bool,
pub tag_page_alloc: bool,
pub tag_vmalloc: bool,
pub tag_stack: bool,
pub tag_globals: bool,
pub tags: MemoryTagTable,
}
impl KernelHWASan {
pub fn new() -> Self {
KernelHWASan {
shadow_offset: HWASAN_KERNEL_SHADOW_OFFSET,
shadow_scale: HWASAN_KERNEL_SHADOW_SCALE,
tag_slab: true,
tag_page_alloc: true,
tag_vmalloc: true,
tag_stack: true,
tag_globals: true,
tags: MemoryTagTable::new(),
}
}
pub fn virt_to_shadow(&self, virt_addr: u64) -> u64 {
((virt_addr >> self.shadow_scale) + self.shadow_offset) & !(HWASAN_GRANULE_SIZE as u64 - 1)
}
pub fn tag_slab_allocation(&mut self, addr: u64, size: usize) -> u8 {
if !self.tag_slab { return HWASAN_UNTAGGED; }
let tag = (addr >> 4) as u8 & HWASAN_MAX_TAG;
let tag = if tag == 0 { 1 } else { tag };
self.tags.set_memory_tag(addr, size, tag);
tag
}
pub fn emit_llvm_kernel_check(&self, ptr_reg: &str, size: usize) -> Vec<String> {
vec![
format!(" ; KASAN HW check: {} bytes at {}", size, ptr_reg),
format!(" %kas_ptr_tag = lshr i64 {}, 56", ptr_reg),
format!(" %kas_mem = and i64 {}, {:#x}", ptr_reg, (1u64 << 56) - 1),
format!(
" %kas_shadow = lshr i64 %kas_mem, {}",
self.shadow_scale
),
" %kas_shadow_addr = add i64 %kas_shadow, %shadow_offset".to_string(),
" %kas_mem_tag = load i8, ptr %kas_shadow_addr".to_string(),
" %kas_tag_ok = icmp eq i64 %kas_ptr_tag, %kas_mem_tag".to_string(),
" br i1 %kas_tag_ok, label %kas_ok, label %kas_mismatch".to_string(),
]
}
pub fn emit_boot_init() -> Vec<String> {
vec![
" ; KASAN HW boot initialization".to_string(),
" ; 1. Enable TBI in TCR_EL1".to_string(),
" ; 2. Set up shadow memory region".to_string(),
" ; 3. Tag kernel image and data sections".to_string(),
" ; 4. Initialize tag generation seed".to_string(),
" call void @__hwasan_kernel_init()".to_string(),
]
}
}
impl Default for KernelHWASan {
fn default() -> Self {
KernelHWASan::new()
}
}
#[derive(Debug, Clone)]
pub struct HWASanConfig {
pub enabled: bool,
pub use_mte: bool,
pub tag_heap: bool,
pub tag_stack: bool,
pub tag_globals: bool,
pub use_short_granules: bool,
pub halt_on_error: bool,
pub print_stacktrace: bool,
pub kernel_mode: bool,
pub random_seed: u64,
pub blacklist: Vec<String>,
}
impl Default for HWASanConfig {
fn default() -> Self {
HWASanConfig {
enabled: true,
use_mte: false,
tag_heap: true,
tag_stack: true,
tag_globals: true,
use_short_granules: false,
halt_on_error: true,
print_stacktrace: true,
kernel_mode: false,
random_seed: 12345,
blacklist: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct HWASanRuntime {
pub config: HWASanConfig,
pub tag_generator: TagGenerator,
pub memory_tags: MemoryTagTable,
pub stack_tagging: StackTagging,
pub heap_tagging: HeapTagging,
pub global_tagging: GlobalTagging,
pub mte_support: MTESupport,
pub kernel_hwasan: Option<KernelHWASan>,
pub mismatch_reports: Vec<TagMismatchReport>,
pub initialized: bool,
}
impl HWASanRuntime {
pub fn new(config: HWASanConfig) -> Self {
let seed = config.random_seed;
HWASanRuntime {
stack_tagging: StackTagging::new(seed),
heap_tagging: HeapTagging::new(seed),
global_tagging: GlobalTagging::new(seed),
tag_generator: TagGenerator::new(seed),
memory_tags: MemoryTagTable::new(),
mte_support: if config.use_mte { MTESupport::probe() } else { MTESupport::new() },
kernel_hwasan: if config.kernel_mode { Some(KernelHWASan::new()) } else { None },
mismatch_reports: Vec::new(),
initialized: false,
config,
}
}
pub fn initialize(&mut self) {
if self.initialized { return; }
self.initialized = true;
if self.config.tag_heap {
self.heap_tagging.enabled = true;
}
if self.config.tag_stack {
self.stack_tagging.enabled = true;
}
if self.config.tag_globals {
self.global_tagging.enabled = true;
}
self.memory_tags.use_short_granules = self.config.use_short_granules;
}
pub fn check_memory_access(
&mut self,
ptr: u64,
access_type: &str,
size: usize,
location: &str,
) -> Option<TagMismatchReport> {
let ptr_tag = TaggedPointer::get_tag(ptr);
let addr = TaggedPointer::strip_tag(ptr);
let mem_tag = self.memory_tags.get_memory_tag(addr);
if ptr_tag == mem_tag || mem_tag == HWASAN_UNTAGGED {
return None;
}
let mut report = TagMismatchReport::new(
addr, ptr_tag, mem_tag, access_type, size, location,
);
if self.memory_tags.free_trace.contains_key(&(addr & !15)) {
report.likely_uaf = true;
report.free_trace_id = self.memory_tags
.free_trace
.get(&(addr & !15))
.copied();
} else {
report.likely_buffer_overflow = true;
}
report.alloc_trace_id = self.memory_tags
.alloc_trace
.get(&(addr & !15))
.copied();
self.mismatch_reports.push(report.clone());
Some(report)
}
pub fn emit_runtime_declarations() -> Vec<String> {
vec![
"declare void @__hwasan_init()".to_string(),
"declare void @__hwasan_tag_memory(i64, i64, i32)".to_string(),
"declare i32 @__hwasan_generate_tag()".to_string(),
"declare void @__hwasan_check_load(i64, i64)".to_string(),
"declare void @__hwasan_check_store(i64, i64)".to_string(),
"declare void @__hwasan_tag_mismatch4(i64, i8, i8, i64)".to_string(),
"declare void @__hwasan_handle_tag_mismatch()".to_string(),
"declare void @__hwasan_check_free(ptr)".to_string(),
"declare void @__hwasan_kernel_init()".to_string(),
]
}
pub fn emit_llvm_module(&self, module_name: &str) -> String {
let mut out = String::new();
out.push_str(&format!("; HWASan instrumented module: {}\n", module_name));
out.push_str(&format!("; MTE: {}\n", self.mte_support.available));
out.push_str(&format!("; Kernel mode: {}\n", self.config.kernel_mode));
out.push('\n');
out.push_str("; MTE intrinsics\n");
for decl in MTESupport::emit_mte_intrinsics() {
out.push_str(&decl);
out.push('\n');
}
out.push_str("\n; Runtime declarations\n");
for decl in HWASanRuntime::emit_runtime_declarations() {
out.push_str(&decl);
out.push('\n');
}
if self.config.tag_stack {
out.push_str("\n; Stack tagging callbacks\n");
out.push_str("declare void @__hwasan_stack_tag(i64, i64)\n");
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tagged_pointer_set_get() {
let ptr = 0x123456789ABCDEFu64;
let tagged = TaggedPointer::tag_pointer(ptr, 0x0A);
assert_eq!(TaggedPointer::get_tag(tagged), 0x0A);
assert_eq!(TaggedPointer::strip_tag(tagged), ptr);
}
#[test]
fn test_tagged_pointer_strip() {
let tagged = TaggedPointer::tag_pointer(0xFFFF, 0x0F);
assert_eq!(TaggedPointer::strip_tag(tagged), 0xFFFF);
}
#[test]
fn test_tagged_pointer_is_tagged() {
assert!(!TaggedPointer::is_tagged(0x1000));
assert!(TaggedPointer::is_tagged(
TaggedPointer::tag_pointer(0x1000, 0x01)
));
}
#[test]
fn test_tagged_pointer_tags_match() {
let p1 = TaggedPointer::tag_pointer(0x1000, 5);
let p2 = TaggedPointer::tag_pointer(0x2000, 5);
let p3 = TaggedPointer::tag_pointer(0x3000, 6);
assert!(TaggedPointer::tags_match(p1, p2));
assert!(!TaggedPointer::tags_match(p1, p3));
}
#[test]
fn test_tag_generator_produces_valid_tag() {
let mut gen = TagGenerator::new(42);
for _ in 0..100 {
let tag = gen.generate_tag();
assert!(tag >= 1 && tag <= HWASAN_MAX_TAG);
}
}
#[test]
fn test_tag_generator_no_zero_tag() {
let mut gen = TagGenerator::new(123);
for _ in 0..200 {
let tag = gen.generate_tag();
assert_ne!(tag, 0);
}
}
#[test]
fn test_memory_tag_table_set_get() {
let mut table = MemoryTagTable::new();
table.set_memory_tag(0x1000, 32, 7);
assert_eq!(table.get_memory_tag(0x1000), 7);
assert_eq!(table.get_memory_tag(0x100F), 7);
assert_eq!(table.get_memory_tag(0x1020), 7);
}
#[test]
fn test_memory_tag_table_neighbor() {
let mut table = MemoryTagTable::new();
table.set_memory_tag(0x1000, 16, 5);
assert_eq!(table.get_memory_tag(0x1010), 0);
}
#[test]
fn test_memory_tag_table_alloc_free_trace() {
let mut table = MemoryTagTable::new();
table.set_alloc_trace(0x1000, 42);
table.set_free_trace(0x1000, 99);
assert_eq!(table.alloc_trace.get(&0x1000), Some(&42));
assert_eq!(table.free_trace.get(&0x1000), Some(&99));
}
#[test]
fn test_tag_mismatch_report_format() {
let report = TagMismatchReport::new(
0x1234, 3, 7, "load", 4, "main.c:42",
);
let formatted = report.format();
assert!(formatted.contains("tag-mismatch"));
assert!(formatted.contains("0x1234"));
assert!(formatted.contains("pointer tag: 0x3"));
assert!(formatted.contains("memory tag: 0x7"));
}
#[test]
fn test_tag_mismatch_report_uaf() {
let mut report = TagMismatchReport::new(
0x5678, 1, 2, "store", 8, "main.c:100",
);
report.likely_uaf = true;
report.free_trace_id = Some(5);
let formatted = report.format();
assert!(formatted.contains("use-after-free"));
}
#[test]
fn test_mte_intrinsics() {
let intrinsics = MTESupport::emit_mte_intrinsics();
assert!(intrinsics.iter().any(|s| s.contains("llvm.aarch64.irg")));
assert!(intrinsics.iter().any(|s| s.contains("llvm.aarch64.stg")));
assert!(intrinsics.iter().any(|s| s.contains("llvm.aarch64.ldg")));
}
#[test]
fn test_hwasan_runtime_init() {
let config = HWASanConfig::default();
let mut rt = HWASanRuntime::new(config);
rt.initialize();
assert!(rt.initialized);
assert!(rt.heap_tagging.enabled);
assert!(rt.stack_tagging.enabled);
}
#[test]
fn test_hwasan_check_access_clean() {
let config = HWASanConfig::default();
let mut rt = HWASanRuntime::new(config);
rt.memory_tags.set_memory_tag(0x1000, 16, 5);
let tagged_ptr = TaggedPointer::tag_pointer(0x1000, 5);
let result = rt.check_memory_access(tagged_ptr, "load", 4, "test.c:1");
assert!(result.is_none());
}
#[test]
fn test_hwasan_check_access_mismatch() {
let config = HWASanConfig::default();
let mut rt = HWASanRuntime::new(config);
rt.memory_tags.set_memory_tag(0x1000, 16, 5);
let tagged_ptr = TaggedPointer::tag_pointer(0x1000, 3); let result = rt.check_memory_access(tagged_ptr, "load", 4, "test.c:1");
assert!(result.is_some());
assert_eq!(rt.mismatch_reports.len(), 1);
}
#[test]
fn test_stack_tagging_prologue() {
let mut st = StackTagging::new(42);
st.enabled = true;
let ops = st.emit_prologue("test_func", 64);
assert!(!ops.is_empty());
assert!(ops.iter().any(|s| s.contains("test_func")));
}
#[test]
fn test_stack_tagging_disabled() {
let mut st = StackTagging::new(42);
let ops = st.emit_prologue("test_func", 64);
assert!(ops.is_empty());
}
#[test]
fn test_heap_tagging_malloc() {
let mut ht = HeapTagging::new(42);
ht.enabled = true;
let (_ptr, tag) = ht.instrument_malloc(128);
assert_ne!(tag, 0);
}
#[test]
fn test_heap_tagging_free_mismatch() {
let mut ht = HeapTagging::new(42);
ht.enabled = true;
let (ptr, _tag) = ht.instrument_malloc(128);
let wrong_ptr = TaggedPointer::tag_pointer(
TaggedPointer::strip_tag(ptr), 7 );
let result = ht.instrument_free(wrong_ptr);
assert!(result.is_some());
assert!(result.unwrap().contains("tag mismatch"));
}
#[test]
fn test_kernel_virt_to_shadow() {
let kasan = KernelHWASan::new();
let shadow = kasan.virt_to_shadow(0xFFFF000000001000);
assert!(shadow > 0);
}
#[test]
fn test_kernel_slab_tagging() {
let mut kasan = KernelHWASan::new();
let tag = kasan.tag_slab_allocation(0xFFFF000000001000, 64);
assert_ne!(tag, 0);
}
#[test]
fn test_short_granule() {
let mut table = MemoryTagTable::new();
table.use_short_granules = true;
table.set_memory_tag(0x1000, 16, 5);
table.set_short_granule_tags(0x1000, &[0,0,0,5,0,0,0,5,0,0,0,0,0,0,0,0]);
assert!(table.check_short_granule(0x1003)); assert!(!table.check_short_granule(0x1001)); }
#[test]
fn test_runtime_declarations() {
let decls = HWASanRuntime::emit_runtime_declarations();
assert!(decls.iter().any(|d| d.contains("__hwasan_init")));
assert!(decls.iter().any(|d| d.contains("__hwasan_tag_mismatch4")));
}
#[test]
fn test_emit_llvm_module() {
let config = HWASanConfig::default();
let rt = HWASanRuntime::new(config);
let module = rt.emit_llvm_module("test");
assert!(module.contains("HWASan instrumented module"));
assert!(module.contains("llvm.aarch64.irg"));
}
#[test]
fn test_kernel_mode() {
let mut config = HWASanConfig::default();
config.kernel_mode = true;
let rt = HWASanRuntime::new(config);
assert!(rt.kernel_hwasan.is_some());
}
}