#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
pub const X86_CFI_TYPE_ID_SIZE: u32 = 32;
pub const X86_CFI_MAX_BITMASK_TYPES: u32 = 64;
pub const X86_CFI_FUNCTION_ALIGNMENT: u32 = 16;
pub const X86_KCFI_HASH_SIZE: u32 = 32;
pub const X86_KCFI_PREFIX_BYTE: u8 = 0xCC;
pub const X86_CFI_SHADOW_STACK_ENTRY_SIZE: u32 = 8;
pub const X86_CFI_CHECK_STUB_SIZE: u32 = 16;
#[derive(Debug, Clone)]
pub struct X86CFITypeEntry {
pub type_id: String,
pub type_hash: u64,
pub table_index: u32,
pub used_in_vcall: bool,
pub used_in_icall: bool,
pub bitmask_pos: Option<u32>,
}
impl X86CFITypeEntry {
pub fn new(type_id: &str, type_hash: u64, table_index: u32) -> Self {
Self {
type_id: type_id.to_string(),
type_hash,
table_index,
used_in_vcall: false,
used_in_icall: false,
bitmask_pos: None,
}
}
pub fn as_vcall(mut self) -> Self {
self.used_in_vcall = true;
self
}
pub fn as_icall(mut self) -> Self {
self.used_in_icall = true;
self
}
pub fn with_bitmask_pos(mut self, pos: u32) -> Self {
self.bitmask_pos = Some(pos);
self
}
}
#[derive(Debug, Clone)]
pub struct X86CFITypeTable {
pub types: Vec<X86CFITypeEntry>,
pub type_id_to_index: HashMap<String, u32>,
pub hash_to_index: HashMap<u64, u32>,
pub type_count: u32,
}
impl X86CFITypeTable {
pub fn new() -> Self {
Self {
types: Vec::new(),
type_id_to_index: HashMap::new(),
hash_to_index: HashMap::new(),
type_count: 0,
}
}
pub fn register_type(&mut self, type_id: &str, type_hash: u64) -> u32 {
if let Some(&idx) = self.type_id_to_index.get(type_id) {
return idx;
}
let idx = self.type_count;
let entry = X86CFITypeEntry::new(type_id, type_hash, idx);
self.types.push(entry);
self.type_id_to_index.insert(type_id.to_string(), idx);
self.hash_to_index.insert(type_hash, idx);
self.type_count += 1;
idx
}
pub fn lookup_by_id(&self, type_id: &str) -> Option<&X86CFITypeEntry> {
self.type_id_to_index
.get(type_id)
.and_then(|&idx| self.types.get(idx as usize))
}
pub fn lookup_by_hash(&self, type_hash: u64) -> Option<&X86CFITypeEntry> {
self.hash_to_index
.get(&type_hash)
.and_then(|&idx| self.types.get(idx as usize))
}
pub fn get_by_index(&self, index: u32) -> Option<&X86CFITypeEntry> {
self.types.get(index as usize)
}
pub fn len(&self) -> usize {
self.types.len()
}
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
}
impl Default for X86CFITypeTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CFITypeTest {
pub pointer: u64,
pub metadata: String,
pub expected_hash: u64,
pub source_loc: Option<String>,
}
impl X86CFITypeTest {
pub fn new(pointer: u64, metadata: &str, expected_hash: u64) -> Self {
Self {
pointer,
metadata: metadata.to_string(),
expected_hash,
source_loc: None,
}
}
pub fn test(&self, runtime_hash: u64) -> bool {
runtime_hash == self.expected_hash
}
pub fn generate_bitmask_test(&self, valid_types: &[u64]) -> X86CFIBitmaskCheck {
let mut bitmask: u64 = 0;
for &type_hash in valid_types {
let pos = (type_hash % X86_CFI_MAX_BITMASK_TYPES as u64) as u32;
bitmask |= 1u64 << pos;
}
X86CFIBitmaskCheck {
pointer: self.pointer,
bitmask,
expected_hash: self.expected_hash,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CFIBitmaskCheck {
pub pointer: u64,
pub bitmask: u64,
pub expected_hash: u64,
}
impl X86CFIBitmaskCheck {
pub fn check(&self, type_id: u64) -> bool {
let pos = (type_id % X86_CFI_MAX_BITMASK_TYPES as u64) as u32;
let mask_bit = 1u64 << pos;
(self.bitmask & mask_bit) != 0
}
pub fn emit_pseudo_ir(&self) -> String {
format!(
" ; CFI bitmask check\n %pos = and i64 %type_id, {}\n %bit = shl i64 1, %pos\n %ok = and i64 {}, %bit\n %pass = icmp ne i64 %ok, 0",
X86_CFI_MAX_BITMASK_TYPES - 1,
self.bitmask,
)
}
}
#[derive(Debug, Clone)]
pub struct X86CFITypeCheckedLoad {
pub pointer: u64,
pub type_metadata: String,
pub hash_offset: i32,
pub is_vcall: bool,
}
impl X86CFITypeCheckedLoad {
pub fn new(pointer: u64, type_metadata: &str, hash_offset: i32) -> Self {
Self {
pointer,
type_metadata: type_metadata.to_string(),
hash_offset,
is_vcall: false,
}
}
pub fn as_vcall(mut self) -> Self {
self.is_vcall = true;
self
}
pub fn hash_address(&self) -> u64 {
(self.pointer as i64 + self.hash_offset as i64) as u64
}
pub fn emit_pseudo_ir(&self) -> String {
format!(
" ; CFI type checked load\n %vtable = load ptr, ptr %{ptr}\n %hash_addr = getelementptr i8, ptr %vtable, i32 {off}\n %hash = load i32, ptr %hash_addr\n %ok = call i1 @llvm.type.test(ptr %{ptr}, metadata !\"{meta}\")\n br i1 %ok, label %cfi.cont, label %cfi.fail",
ptr = self.pointer,
off = self.hash_offset,
meta = self.type_metadata,
)
}
}
#[derive(Debug, Clone)]
pub struct X86CFIBitsetTest {
pub pointer: u64,
pub bitset_name: String,
pub bitset_data: Vec<u64>,
pub range_start: Option<u64>,
pub range_end: Option<u64>,
}
impl X86CFIBitsetTest {
pub fn new(pointer: u64, bitset_name: &str) -> Self {
Self {
pointer,
bitset_name: bitset_name.to_string(),
bitset_data: Vec::new(),
range_start: None,
range_end: None,
}
}
pub fn with_bitset_data(mut self, data: Vec<u64>) -> Self {
self.bitset_data = data;
self
}
pub fn with_range(mut self, start: u64, end: u64) -> Self {
self.range_start = Some(start);
self.range_end = Some(end);
self
}
pub fn in_range(&self, addr: u64) -> bool {
if let (Some(start), Some(end)) = (self.range_start, self.range_end) {
addr >= start && addr < end
} else {
false
}
}
pub fn test_bit(&self, bit_index: u64) -> bool {
let word_idx = (bit_index / 64) as usize;
let bit_pos = (bit_index % 64) as u32;
if word_idx < self.bitset_data.len() {
(self.bitset_data[word_idx] & (1u64 << bit_pos)) != 0
} else {
false
}
}
pub fn emit_pseudo_ir(&self) -> String {
let mut ir = String::new();
ir.push_str(&format!(" ; CFI bitset test \"{}\"\n", self.bitset_name));
if let (Some(start), Some(_end)) = (self.range_start, self.range_end) {
ir.push_str(&format!(
" %in.range = icmp ult i64 %ptr, {}\n",
start + (self.bitset_data.len() as u64 * 64)
));
}
ir.push_str(" %ok = call i1 @llvm.bitset.test(ptr %ptr, metadata !\"bitset_name\")\n");
ir
}
}
#[derive(Debug, Clone)]
pub struct X86CFICrossDSOConfig {
pub enabled: bool,
pub external_metadata_path: Option<String>,
pub use_shadow: bool,
pub shadow_base: u64,
pub shadow_scale: u32,
}
impl Default for X86CFICrossDSOConfig {
fn default() -> Self {
Self {
enabled: false,
external_metadata_path: None,
use_shadow: false,
shadow_base: 0,
shadow_scale: 3,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CFICrossDSOCheck {
pub call_site: u64,
pub type_id: String,
pub in_main_binary: bool,
pub dso_name: Option<String>,
}
impl X86CFICrossDSOCheck {
pub fn new(call_site: u64, type_id: &str) -> Self {
Self {
call_site,
type_id: type_id.to_string(),
in_main_binary: true,
dso_name: None,
}
}
pub fn cross_dso(mut self, dso_name: &str) -> Self {
self.in_main_binary = false;
self.dso_name = Some(dso_name.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct X86CFIShadow {
pub base: u64,
pub size: u64,
pub scale: u32,
pub shadow: Vec<u8>,
}
impl X86CFIShadow {
pub fn new(base: u64, size: u64, scale: u32) -> Self {
Self {
base,
size,
scale,
shadow: vec![0u8; size as usize],
}
}
pub fn app_to_shadow(&self, app_addr: u64) -> u64 {
((app_addr - self.base) >> self.scale) + self.base
}
pub fn set(&mut self, app_addr: u64, value: u8) {
let shadow_addr = self.app_to_shadow(app_addr);
let offset = (shadow_addr - self.base) as usize;
if offset < self.shadow.len() {
self.shadow[offset] = value;
}
}
pub fn get(&self, app_addr: u64) -> u8 {
let shadow_addr = self.app_to_shadow(app_addr);
let offset = (shadow_addr - self.base) as usize;
if offset < self.shadow.len() {
self.shadow[offset]
} else {
0
}
}
pub fn fill_range(&mut self, app_start: u64, app_end: u64, value: u8) {
for addr in (app_start..app_end).step_by(1 << self.scale) {
self.set(addr, value);
}
}
}
impl Default for X86CFIShadow {
fn default() -> Self {
Self::new(0, 0x10000000, 3)
}
}
#[derive(Debug, Clone)]
pub struct X86CFIShadowStackEntry {
pub return_address: u64,
pub frame_pointer: u64,
pub valid: bool,
pub call_site: u64,
}
#[derive(Debug, Clone)]
pub struct X86CFIShadowStack {
pub entries: Vec<X86CFIShadowStackEntry>,
pub ssp: u64,
pub base: u64,
pub limit: u64,
pub active: bool,
pub total_pushes: u64,
pub total_pops: u64,
pub mismatches: u64,
}
impl X86CFIShadowStack {
pub fn new(base: u64, size: u64) -> Self {
Self {
entries: Vec::new(),
ssp: base,
base,
limit: base + size,
active: false,
total_pushes: 0,
total_pops: 0,
mismatches: 0,
}
}
pub fn push(&mut self, return_address: u64, frame_pointer: u64, call_site: u64) {
if self.ssp + X86_CFI_SHADOW_STACK_ENTRY_SIZE as u64 >= self.limit {
self.mismatches += 1;
return;
}
self.entries.push(X86CFIShadowStackEntry {
return_address,
frame_pointer,
valid: true,
call_site,
});
self.ssp += X86_CFI_SHADOW_STACK_ENTRY_SIZE as u64;
self.total_pushes += 1;
}
pub fn pop(&mut self, expected_address: u64) -> bool {
self.total_pops += 1;
if let Some(entry) = self.entries.pop() {
self.ssp = self
.ssp
.saturating_sub(X86_CFI_SHADOW_STACK_ENTRY_SIZE as u64);
if entry.return_address != expected_address {
self.mismatches += 1;
return false;
}
return true;
}
self.mismatches += 1;
false
}
pub fn peek(&self) -> Option<u64> {
self.entries.last().map(|e| e.return_address)
}
pub fn depth(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn reset(&mut self) {
self.entries.clear();
self.ssp = self.base;
self.mismatches = 0;
self.total_pushes = 0;
self.total_pops = 0;
}
}
impl Default for X86CFIShadowStack {
fn default() -> Self {
Self::new(0x7f0000000000, 0x100000)
}
}
pub type X86KCFIHashFn = fn(u64, u64) -> u32;
#[derive(Debug, Clone)]
pub struct X86KCFIConfig {
pub enabled: bool,
pub prefix_byte: u8,
pub hash_size: u32,
pub check_icall: bool,
pub check_ijmp: bool,
pub hash_fn_name: String,
}
impl Default for X86KCFIConfig {
fn default() -> Self {
Self {
enabled: false,
prefix_byte: X86_KCFI_PREFIX_BYTE,
hash_size: X86_KCFI_HASH_SIZE,
check_icall: true,
check_ijmp: true,
hash_fn_name: "kcfi_hash".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86KCFITargetEntry {
pub target_address: u64,
pub type_hash: u32,
pub function_name: Option<String>,
pub is_icall_target: bool,
}
impl X86KCFITargetEntry {
pub fn new(target_address: u64, type_hash: u32) -> Self {
Self {
target_address,
type_hash,
function_name: None,
is_icall_target: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86KCFIRegistry {
pub targets: Vec<X86KCFITargetEntry>,
pub addr_to_index: HashMap<u64, usize>,
pub hash_to_targets: HashMap<u32, Vec<usize>>,
}
impl X86KCFIRegistry {
pub fn new() -> Self {
Self {
targets: Vec::new(),
addr_to_index: HashMap::new(),
hash_to_targets: HashMap::new(),
}
}
pub fn register_target(&mut self, target_address: u64, type_hash: u32) {
let idx = self.targets.len();
let entry = X86KCFITargetEntry::new(target_address, type_hash);
self.targets.push(entry);
self.addr_to_index.insert(target_address, idx);
self.hash_to_targets
.entry(type_hash)
.or_insert_with(Vec::new)
.push(idx);
}
pub fn is_valid_target(&self, target_address: u64, type_hash: u32) -> bool {
if let Some(&idx) = self.addr_to_index.get(&target_address) {
if let Some(entry) = self.targets.get(idx) {
return entry.type_hash == type_hash;
}
}
false
}
pub fn targets_with_hash(&self, type_hash: u32) -> Vec<&X86KCFITargetEntry> {
self.hash_to_targets
.get(&type_hash)
.map(|indices| {
indices
.iter()
.filter_map(|&i| self.targets.get(i))
.collect()
})
.unwrap_or_default()
}
pub fn compute_hash(function_type: &str, salt: u64) -> u32 {
let mut hash: u32 = 0x811c9dc5u32.wrapping_add(salt as u32);
for byte in function_type.as_bytes() {
hash ^= *byte as u32;
hash = hash.wrapping_mul(0x01000193);
}
hash
}
pub fn len(&self) -> usize {
self.targets.len()
}
pub fn is_empty(&self) -> bool {
self.targets.is_empty()
}
}
impl Default for X86KCFIRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CFIICallCheck {
pub call_site: u64,
pub function_type: String,
pub expected_hash: u64,
pub target_register: Option<String>,
pub is_cross_dso: bool,
pub use_kcfi: bool,
}
impl X86CFIICallCheck {
pub fn new(call_site: u64, function_type: &str, expected_hash: u64) -> Self {
Self {
call_site,
function_type: function_type.to_string(),
expected_hash,
target_register: None,
is_cross_dso: false,
use_kcfi: false,
}
}
pub fn in_register(mut self, reg: &str) -> Self {
self.target_register = Some(reg.to_string());
self
}
pub fn with_kcfi(mut self) -> Self {
self.use_kcfi = true;
self
}
pub fn emit_pseudo_ir(&self) -> String {
let reg = self.target_register.as_deref().unwrap_or("unknown");
format!(
" ; CFI icall check at {:#x}\n %target = load ptr, ptr %{reg}\n %ttype = ...\n %ok = call i1 @llvm.type.test(ptr %target, metadata !\"{ty}\")\n br i1 %ok, label %call.ok, label %trap",
self.call_site,
ty = self.function_type,
)
}
}
#[derive(Debug, Clone)]
pub struct X86CFIVCallCheck {
pub call_site: u64,
pub object_ptr: u64,
pub class_type: String,
pub method_name: String,
pub vtable_offset: i32,
pub vtable_entry_index: u32,
pub strict_vtable_check: bool,
}
impl X86CFIVCallCheck {
pub fn new(
call_site: u64,
object_ptr: u64,
class_type: &str,
method_name: &str,
vtable_offset: i32,
) -> Self {
Self {
call_site,
object_ptr,
class_type: class_type.to_string(),
method_name: method_name.to_string(),
vtable_offset,
vtable_entry_index: 0,
strict_vtable_check: false,
}
}
pub fn at_vtable_index(mut self, index: u32) -> Self {
self.vtable_entry_index = index;
self
}
pub fn strict(mut self) -> Self {
self.strict_vtable_check = true;
self
}
pub fn emit_pseudo_ir(&self) -> String {
format!(
" ; CFI vcall check at {:#x}\n %vtable = load ptr, ptr %obj\n ; Load type hash from vtable (offset -8 or similar)\n %hash_addr = getelementptr i8, ptr %vtable, i32 {voff}\n %hash = load i32, ptr %hash_addr\n %ok = icmp eq i32 %hash, ...\n br i1 %ok, label %vcall.ok, label %trap",
self.call_site,
voff = -8i32,
)
}
}
#[derive(Debug, Clone)]
pub struct X86CFICheckLowering {
pub check_kind: X86CFICheckKind,
pub instructions: Vec<X86CFIInstr>,
pub traps_on_failure: bool,
pub trap_target: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CFICheckKind {
ICall,
VCall,
Bitset,
KCFI,
ShadowStack,
CrossDSO,
}
impl fmt::Display for X86CFICheckKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ICall => write!(f, "icall"),
Self::VCall => write!(f, "vcall"),
Self::Bitset => write!(f, "bitset"),
Self::KCFI => write!(f, "kcfi"),
Self::ShadowStack => write!(f, "shadow-stack"),
Self::CrossDSO => write!(f, "cross-dso"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86CFIInstr {
pub mnemonic: String,
pub operand: String,
pub is_label: bool,
}
impl X86CFIInstr {
pub fn new(mnemonic: &str, operand: &str) -> Self {
Self {
mnemonic: mnemonic.to_string(),
operand: operand.to_string(),
is_label: false,
}
}
pub fn label(name: &str) -> Self {
Self {
mnemonic: String::new(),
operand: name.to_string(),
is_label: true,
}
}
}
impl X86CFICheckLowering {
pub fn new(kind: X86CFICheckKind) -> Self {
Self {
check_kind: kind,
instructions: Vec::new(),
traps_on_failure: true,
trap_target: None,
}
}
pub fn emit(&mut self, mnemonic: &str, operand: &str) {
self.instructions.push(X86CFIInstr::new(mnemonic, operand));
}
pub fn emit_label(&mut self, name: &str) {
self.instructions.push(X86CFIInstr::label(name));
}
pub fn build_bitmask_check(
target_reg: &str,
scratch_reg: &str,
type_hash: u64,
bitmask: u64,
failure_label: &str,
) -> Self {
let mut lowering = Self::new(X86CFICheckKind::Bitset);
lowering.emit("mov", &format!("{}, [{} - 8]", scratch_reg, target_reg));
lowering.emit(
"and",
&format!("{}, 0x{:x}", scratch_reg, X86_CFI_MAX_BITMASK_TYPES - 1),
);
lowering.emit("bt", &format!("{}, {}", bitmask, scratch_reg));
lowering.emit("jnc", failure_label);
lowering
}
pub fn build_kcfi_check(
target_addr: u64,
type_hash: u32,
temp_reg: &str,
failure_label: &str,
) -> Self {
let mut lowering = Self::new(X86CFICheckKind::KCFI);
lowering.emit("mov", &format!("{}b, [{} - 5]", temp_reg, target_addr));
lowering.emit("cmp", &format!("{}b, {}", temp_reg, X86_KCFI_PREFIX_BYTE));
lowering.emit("jne", failure_label);
lowering.emit("mov", &format!("{}, [{} - 4]", temp_reg, target_addr));
lowering.emit("cmp", &format!("{}, {}", temp_reg, type_hash));
lowering.emit("jne", failure_label);
lowering
}
pub fn build_shadow_stack_check(
return_address: u64,
shadow_ptr: &str,
failure_label: &str,
) -> Self {
let mut lowering = Self::new(X86CFICheckKind::ShadowStack);
lowering.emit("pop", shadow_ptr); lowering.emit("cmp", &format!("{}, {}", shadow_ptr, return_address));
lowering.emit("jne", failure_label);
lowering
}
pub fn build_trap() -> Self {
let mut lowering = Self::new(X86CFICheckKind::ICall);
lowering.emit("ud2", "");
lowering
}
pub fn emit_asm(&self) -> String {
let mut asm = String::new();
for instr in &self.instructions {
if instr.is_label {
asm.push_str(&format!("{}:\n", instr.operand));
} else if instr.operand.is_empty() {
asm.push_str(&format!(" {}\n", instr.mnemonic));
} else {
asm.push_str(&format!(" {} {}\n", instr.mnemonic, instr.operand));
}
}
asm
}
pub fn instr_count(&self) -> usize {
self.instructions.len()
}
}
#[derive(Debug, Clone)]
pub struct X86CFIFunctionInstrumentation {
pub function_name: String,
pub has_icall: bool,
pub has_vcall: bool,
pub needs_shadow_stack_prologue: bool,
pub needs_shadow_stack_epilogue: bool,
pub forward_edge_checks: Vec<X86CFICheckLowering>,
pub backward_edge_checks: Vec<X86CFICheckLowering>,
pub function_type_hash: Option<u64>,
pub has_kcfi_prefix: bool,
}
impl X86CFIFunctionInstrumentation {
pub fn new(function_name: &str) -> Self {
Self {
function_name: function_name.to_string(),
has_icall: false,
has_vcall: false,
needs_shadow_stack_prologue: false,
needs_shadow_stack_epilogue: false,
forward_edge_checks: Vec::new(),
backward_edge_checks: Vec::new(),
function_type_hash: None,
has_kcfi_prefix: false,
}
}
pub fn add_forward_check(&mut self, check: X86CFICheckLowering) {
self.forward_edge_checks.push(check);
}
pub fn add_backward_check(&mut self, check: X86CFICheckLowering) {
self.backward_edge_checks.push(check);
}
pub fn total_checks(&self) -> usize {
self.forward_edge_checks.len() + self.backward_edge_checks.len()
}
}
#[derive(Debug, Clone)]
pub struct X86CFIViolationReport {
pub violation_address: u64,
pub check_kind: X86CFICheckKind,
pub target_address: u64,
pub expected_type_hash: Option<u64>,
pub actual_type_hash: Option<u64>,
pub function_name: Option<String>,
pub timestamp: Option<u64>,
}
impl X86CFIViolationReport {
pub fn new(violation_address: u64, check_kind: X86CFICheckKind, target_address: u64) -> Self {
Self {
violation_address,
check_kind,
target_address,
expected_type_hash: None,
actual_type_hash: None,
function_name: None,
timestamp: None,
}
}
pub fn format(&self) -> String {
format!(
"CFI violation: {} check failed at {:#x}\n Target: {:#x}",
self.check_kind, self.violation_address, self.target_address
)
}
}
#[derive(Debug, Clone)]
pub struct X86CFICodeGen {
pub type_table: X86CFITypeTable,
pub icall_checks: Vec<X86CFIICallCheck>,
pub vcall_checks: Vec<X86CFIVCallCheck>,
pub bitset_tests: Vec<X86CFIBitsetTest>,
pub type_checked_loads: Vec<X86CFITypeCheckedLoad>,
pub type_tests: Vec<X86CFITypeTest>,
pub cross_dso: X86CFICrossDSOConfig,
pub cross_dso_checks: Vec<X86CFICrossDSOCheck>,
pub cfi_shadow: Option<X86CFIShadow>,
pub shadow_stack: X86CFIShadowStack,
pub backward_edge_enabled: bool,
pub kcfi_config: X86KCFIConfig,
pub kcfi_registry: X86KCFIRegistry,
pub function_instrumentations: HashMap<String, X86CFIFunctionInstrumentation>,
pub violations: Vec<X86CFIViolationReport>,
pub stats: X86CFICodeGenStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86CFICodeGenStats {
pub types_registered: u64,
pub icall_checks: u64,
pub vcall_checks: u64,
pub bitset_tests: u64,
pub type_checked_loads: u64,
pub type_tests: u64,
pub cross_dso_checks: u64,
pub shadow_stack_pushes: u64,
pub shadow_stack_pops: u64,
pub violations: u64,
pub functions_instrumented: u64,
pub kcfi_targets: u64,
pub cfi_active: bool,
}
impl X86CFICodeGen {
pub fn new() -> Self {
Self {
type_table: X86CFITypeTable::new(),
icall_checks: Vec::new(),
vcall_checks: Vec::new(),
bitset_tests: Vec::new(),
type_checked_loads: Vec::new(),
type_tests: Vec::new(),
cross_dso: X86CFICrossDSOConfig::default(),
cross_dso_checks: Vec::new(),
cfi_shadow: None,
shadow_stack: X86CFIShadowStack::default(),
backward_edge_enabled: false,
kcfi_config: X86KCFIConfig::default(),
kcfi_registry: X86KCFIRegistry::new(),
function_instrumentations: HashMap::new(),
violations: Vec::new(),
stats: X86CFICodeGenStats::default(),
}
}
pub fn enable_full_cfi(&mut self) {
self.stats.cfi_active = true;
}
pub fn enable_backward_edge(&mut self, shadow_base: u64, shadow_size: u64) {
self.backward_edge_enabled = true;
self.shadow_stack = X86CFIShadowStack::new(shadow_base, shadow_size);
self.shadow_stack.active = true;
}
pub fn enable_cross_dso(&mut self, shadow_base: u64, shadow_size: u64) {
self.cross_dso.enabled = true;
self.cross_dso.use_shadow = true;
self.cross_dso.shadow_base = shadow_base;
self.cfi_shadow = Some(X86CFIShadow::new(
shadow_base,
shadow_size,
self.cross_dso.shadow_scale,
));
}
pub fn enable_kcfi(&mut self) {
self.kcfi_config.enabled = true;
}
pub fn register_type(&mut self, type_id: &str) -> u64 {
let hash = Self::compute_type_hash(type_id);
self.type_table.register_type(type_id, hash);
self.stats.types_registered += 1;
hash
}
pub fn compute_type_hash(type_id: &str) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in type_id.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn lookup_type(&self, type_hash: u64) -> Option<&X86CFITypeEntry> {
self.type_table.lookup_by_hash(type_hash)
}
pub fn type_count(&self) -> usize {
self.type_table.len()
}
pub fn add_icall_check(&mut self, call_site: u64, function_type: &str, register: &str) {
let hash = Self::compute_type_hash(function_type);
self.register_type(function_type);
let check = X86CFIICallCheck::new(call_site, function_type, hash).in_register(register);
self.icall_checks.push(check);
self.stats.icall_checks += 1;
}
pub fn add_kcfi_icall_check(&mut self, call_site: u64, function_type: &str, register: &str) {
let hash = Self::compute_type_hash(function_type);
self.register_type(function_type);
let check = X86CFIICallCheck::new(call_site, function_type, hash)
.in_register(register)
.with_kcfi();
self.icall_checks.push(check);
self.stats.icall_checks += 1;
}
pub fn add_vcall_check(
&mut self,
call_site: u64,
object_ptr: u64,
class_type: &str,
method_name: &str,
vtable_offset: i32,
) {
self.register_type(class_type);
let check = X86CFIVCallCheck::new(
call_site,
object_ptr,
class_type,
method_name,
vtable_offset,
);
self.vcall_checks.push(check);
self.stats.vcall_checks += 1;
}
pub fn lower_type_test(&mut self, pointer: u64, metadata: &str, expected_hash: u64) {
let test = X86CFITypeTest::new(pointer, metadata, expected_hash);
self.type_tests.push(test);
self.stats.type_tests += 1;
}
pub fn lower_type_checked_load(&mut self, pointer: u64, type_metadata: &str, hash_offset: i32) {
let load = X86CFITypeCheckedLoad::new(pointer, type_metadata, hash_offset);
self.type_checked_loads.push(load);
self.stats.type_checked_loads += 1;
}
pub fn lower_bitset_test(&mut self, pointer: u64, bitset_name: &str) -> &mut X86CFIBitsetTest {
let test = X86CFIBitsetTest::new(pointer, bitset_name);
self.bitset_tests.push(test);
self.stats.bitset_tests += 1;
self.bitset_tests.last_mut().unwrap()
}
pub fn add_cross_dso_check(&mut self, call_site: u64, type_id: &str, dso_name: Option<&str>) {
let mut check = X86CFICrossDSOCheck::new(call_site, type_id);
if let Some(dso) = dso_name {
check = check.cross_dso(dso);
}
self.cross_dso_checks.push(check);
self.stats.cross_dso_checks += 1;
}
pub fn register_kcfi_target(&mut self, target_address: u64, function_type: &str) -> u32 {
let type_hash = X86KCFIRegistry::compute_hash(function_type, 0);
self.kcfi_registry
.register_target(target_address, type_hash);
self.stats.kcfi_targets += 1;
type_hash
}
pub fn kcfi_check_target(&self, target_address: u64, function_type: &str) -> bool {
let type_hash = X86KCFIRegistry::compute_hash(function_type, 0);
self.kcfi_registry
.is_valid_target(target_address, type_hash)
}
pub fn shadow_stack_push(&mut self, return_address: u64, frame_pointer: u64, call_site: u64) {
if !self.backward_edge_enabled {
return;
}
self.shadow_stack
.push(return_address, frame_pointer, call_site);
self.stats.shadow_stack_pushes += 1;
}
pub fn shadow_stack_pop(&mut self, expected_address: u64) -> bool {
if !self.backward_edge_enabled {
return true; }
let result = self.shadow_stack.pop(expected_address);
self.stats.shadow_stack_pops += 1;
if !result {
self.report_violation(
expected_address,
X86CFICheckKind::ShadowStack,
expected_address,
);
}
result
}
pub fn begin_function(&mut self, func_name: &str) {
let instr = X86CFIFunctionInstrumentation::new(func_name);
self.function_instrumentations
.insert(func_name.to_string(), instr);
}
pub fn get_function_instr(&self, func_name: &str) -> Option<&X86CFIFunctionInstrumentation> {
self.function_instrumentations.get(func_name)
}
pub fn get_function_instr_mut(
&mut self,
func_name: &str,
) -> Option<&mut X86CFIFunctionInstrumentation> {
self.function_instrumentations.get_mut(func_name)
}
pub fn add_function_forward_check(&mut self, func_name: &str, check: X86CFICheckLowering) {
if let Some(instr) = self.function_instrumentations.get_mut(func_name) {
instr.add_forward_check(check);
instr.has_icall = true;
}
}
pub fn end_function(&mut self, func_name: &str) {
if let Some(instr) = self.function_instrumentations.get(func_name) {
self.stats.functions_instrumented += 1;
if let Some(type_hash) = instr.function_type_hash {
if self.kcfi_config.enabled {
let _ = type_hash;
}
}
}
}
pub fn report_violation(
&mut self,
violation_address: u64,
check_kind: X86CFICheckKind,
target_address: u64,
) {
self.violations.push(X86CFIViolationReport::new(
violation_address,
check_kind,
target_address,
));
self.stats.violations += 1;
}
pub fn violation_count(&self) -> usize {
self.violations.len()
}
pub fn format_violations(&self) -> String {
self.violations
.iter()
.map(|v| v.format())
.collect::<Vec<_>>()
.join("\n")
}
pub fn run_lowering_pipeline(&mut self) -> Vec<X86CFICheckLowering> {
let mut all_checks = Vec::new();
for icall in &self.icall_checks {
if icall.use_kcfi {
let kcfi_check = X86CFICheckLowering::build_kcfi_check(
icall.call_site,
icall.expected_hash as u32,
"eax",
".Lcfi_fail",
);
all_checks.push(kcfi_check);
} else {
let bitmask: u64 = if self.type_table.len() <= X86_CFI_MAX_BITMASK_TYPES as usize {
(1u64 << self.type_table.len()) - 1
} else {
u64::MAX
};
let bitmask_check = X86CFICheckLowering::build_bitmask_check(
icall.target_register.as_deref().unwrap_or("rax"),
"ecx",
icall.expected_hash,
bitmask,
".Lcfi_fail",
);
all_checks.push(bitmask_check);
}
}
for _vcall in &self.vcall_checks {
let vcall_check = X86CFICheckLowering::new(X86CFICheckKind::VCall);
all_checks.push(vcall_check);
}
all_checks.push(X86CFICheckLowering::build_trap());
all_checks
}
pub fn cfi_shadow_set(&mut self, addr: u64, value: u8) {
if let Some(ref mut shadow) = self.cfi_shadow {
shadow.set(addr, value);
}
}
pub fn cfi_shadow_get(&self, addr: u64) -> u8 {
self.cfi_shadow.as_ref().map_or(0, |s| s.get(addr))
}
pub fn get_stats(&self) -> &X86CFICodeGenStats {
&self.stats
}
pub fn reset(&mut self) {
self.type_table = X86CFITypeTable::new();
self.icall_checks.clear();
self.vcall_checks.clear();
self.bitset_tests.clear();
self.type_checked_loads.clear();
self.type_tests.clear();
self.cross_dso_checks.clear();
self.cfi_shadow = None;
self.shadow_stack.reset();
self.kcfi_registry = X86KCFIRegistry::new();
self.function_instrumentations.clear();
self.violations.clear();
self.stats = X86CFICodeGenStats::default();
}
}
impl Default for X86CFICodeGen {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_table_new() {
let table = X86CFITypeTable::new();
assert!(table.is_empty());
assert_eq!(table.len(), 0);
}
#[test]
fn test_type_table_register() {
let mut table = X86CFITypeTable::new();
let idx = table.register_type("MyClass", 0xABCD);
assert_eq!(idx, 0);
assert_eq!(table.len(), 1);
}
#[test]
fn test_type_table_dedup() {
let mut table = X86CFITypeTable::new();
let idx1 = table.register_type("MyClass", 0xABCD);
let idx2 = table.register_type("MyClass", 0xABCD);
assert_eq!(idx1, idx2);
assert_eq!(table.len(), 1);
}
#[test]
fn test_type_table_lookup_by_id() {
let mut table = X86CFITypeTable::new();
table.register_type("Base", 0x1111);
assert!(table.lookup_by_id("Base").is_some());
assert!(table.lookup_by_id("Nonexistent").is_none());
}
#[test]
fn test_type_table_lookup_by_hash() {
let mut table = X86CFITypeTable::new();
table.register_type("Derived", 0x2222);
assert!(table.lookup_by_hash(0x2222).is_some());
assert!(table.lookup_by_hash(0x9999).is_none());
}
#[test]
fn test_type_table_get_by_index() {
let mut table = X86CFITypeTable::new();
table.register_type("T1", 0x1);
table.register_type("T2", 0x2);
assert_eq!(table.get_by_index(0).unwrap().type_id, "T1");
assert_eq!(table.get_by_index(1).unwrap().type_id, "T2");
assert!(table.get_by_index(99).is_none());
}
#[test]
fn test_type_entry_new() {
let entry = X86CFITypeEntry::new("MyType", 0xABCD, 0);
assert_eq!(entry.type_id, "MyType");
assert_eq!(entry.type_hash, 0xABCD);
assert_eq!(entry.table_index, 0);
assert!(!entry.used_in_vcall);
assert!(!entry.used_in_icall);
}
#[test]
fn test_type_entry_flags() {
let entry = X86CFITypeEntry::new("T", 0x1, 0)
.as_vcall()
.as_icall()
.with_bitmask_pos(5);
assert!(entry.used_in_vcall);
assert!(entry.used_in_icall);
assert_eq!(entry.bitmask_pos, Some(5));
}
#[test]
fn test_bitmask_check() {
let check = X86CFIBitmaskCheck {
pointer: 0x4000,
bitmask: 0b1010, expected_hash: 0xABCD,
};
assert!(check.check(1));
assert!(!check.check(2));
assert!(check.check(3));
}
#[test]
fn test_bitmask_check_emit_pseudo_ir() {
let check = X86CFIBitmaskCheck {
pointer: 0x4000,
bitmask: 0xFF00,
expected_hash: 0x42,
};
let ir = check.emit_pseudo_ir();
assert!(ir.contains("CFI bitmask check"));
assert!(ir.contains("shl"));
assert!(ir.contains("and"));
}
#[test]
fn test_type_test_new() {
let test = X86CFITypeTest::new(0x5000, "!cfi_type_MyClass", 0xABCD);
assert_eq!(test.pointer, 0x5000);
assert_eq!(test.expected_hash, 0xABCD);
}
#[test]
fn test_type_test_pass() {
let test = X86CFITypeTest::new(0x5000, "!t", 0x42);
assert!(test.test(0x42));
}
#[test]
fn test_type_test_fail() {
let test = X86CFITypeTest::new(0x5000, "!t", 0x42);
assert!(!test.test(0x99));
}
#[test]
fn test_type_test_generate_bitmask() {
let test = X86CFITypeTest::new(0x5000, "!t", 0x42);
let valid_types = vec![0x42, 0x106, 0x234];
let bm_check = test.generate_bitmask_test(&valid_types);
assert!(bm_check.check(0x42));
assert!(bm_check.check(0x106));
assert!(bm_check.check(0x234));
}
#[test]
fn test_type_checked_load_new() {
let load = X86CFITypeCheckedLoad::new(0x6000, "!meta_Base", -8);
assert_eq!(load.pointer, 0x6000);
assert_eq!(load.hash_offset, -8);
assert!(!load.is_vcall);
}
#[test]
fn test_type_checked_load_as_vcall() {
let load = X86CFITypeCheckedLoad::new(0x6000, "!meta_Base", -8).as_vcall();
assert!(load.is_vcall);
}
#[test]
fn test_type_checked_load_hash_address() {
let load = X86CFITypeCheckedLoad::new(0x6000, "!meta", -8);
assert_eq!(load.hash_address(), 0x6000 - 8);
}
#[test]
fn test_type_checked_load_emit_pseudo_ir() {
let load = X86CFITypeCheckedLoad::new(0x7000, "!meta_Base", -8);
let ir = load.emit_pseudo_ir();
assert!(ir.contains("llvm.type.test"));
assert!(ir.contains("cfi.cont"));
assert!(ir.contains("cfi.fail"));
}
#[test]
fn test_bitset_test_new() {
let bs = X86CFIBitsetTest::new(0x8000, "bs_func_ptrs");
assert_eq!(bs.pointer, 0x8000);
assert_eq!(bs.bitset_name, "bs_func_ptrs");
assert!(bs.bitset_data.is_empty());
}
#[test]
fn test_bitset_test_with_data() {
let bs = X86CFIBitsetTest::new(0x8000, "bs").with_bitset_data(vec![0b1010, 0xFFFF]);
assert_eq!(bs.bitset_data.len(), 2);
}
#[test]
fn test_bitset_test_range() {
let bs = X86CFIBitsetTest::new(0x8000, "bs").with_range(0x1000, 0x2000);
assert!(bs.in_range(0x1500));
assert!(!bs.in_range(0x999));
assert!(!bs.in_range(0x2500));
}
#[test]
fn test_bitset_test_bit() {
let bs = X86CFIBitsetTest::new(0x8000, "bs").with_bitset_data(vec![0b1010]);
assert!(bs.test_bit(1)); assert!(bs.test_bit(3)); assert!(!bs.test_bit(0)); assert!(!bs.test_bit(2)); assert!(!bs.test_bit(99)); }
#[test]
fn test_bitset_test_emit_pseudo_ir() {
let bs = X86CFIBitsetTest::new(0x8000, "my_bitset")
.with_range(0x1000, 0x2000)
.with_bitset_data(vec![0xFF]);
let ir = bs.emit_pseudo_ir();
assert!(ir.contains("CFI bitset test"));
assert!(ir.contains("llvm.bitset.test"));
}
#[test]
fn test_cross_dso_check_new() {
let check = X86CFICrossDSOCheck::new(0x9000, "FuncPtr");
assert!(check.in_main_binary);
assert!(check.dso_name.is_none());
}
#[test]
fn test_cross_dso_check_dso() {
let check = X86CFICrossDSOCheck::new(0x9000, "FuncPtr").cross_dso("libfoo.so");
assert!(!check.in_main_binary);
assert_eq!(check.dso_name, Some("libfoo.so".to_string()));
}
#[test]
fn test_cfi_shadow_new() {
let shadow = X86CFIShadow::new(0x7f000000, 0x1000, 3);
assert_eq!(shadow.base, 0x7f000000);
assert_eq!(shadow.size, 0x1000);
assert_eq!(shadow.scale, 3);
}
#[test]
fn test_cfi_shadow_set_get() {
let mut shadow = X86CFIShadow::new(0x100000, 0x1000, 3);
let app_addr = 0x100000;
shadow.set(app_addr, 0xAB);
assert_eq!(shadow.get(app_addr), 0xAB);
}
#[test]
fn test_cfi_shadow_fill_range() {
let mut shadow = X86CFIShadow::new(0x100000, 0x1000, 3);
shadow.fill_range(0x100000, 0x100100, 0xFF);
assert_eq!(shadow.get(0x100000), 0xFF);
}
#[test]
fn test_shadow_stack_new() {
let ss = X86CFIShadowStack::new(0x7f000000, 0x1000);
assert_eq!(ss.base, 0x7f000000);
assert_eq!(ss.limit, 0x7f000000 + 0x1000);
assert!(!ss.active);
assert!(ss.is_empty());
}
#[test]
fn test_shadow_stack_push_pop() {
let mut ss = X86CFIShadowStack::new(0x7000, 0x1000);
ss.active = true;
ss.push(0x401234, 0x7ff0, 0x401000);
assert_eq!(ss.depth(), 1);
assert_eq!(ss.peek(), Some(0x401234));
assert!(ss.pop(0x401234));
assert_eq!(ss.depth(), 0);
}
#[test]
fn test_shadow_stack_pop_mismatch() {
let mut ss = X86CFIShadowStack::new(0x7000, 0x1000);
ss.active = true;
ss.push(0xAAAA, 0x1000, 0x5000);
assert!(!ss.pop(0xBBBB));
assert_eq!(ss.mismatches, 1);
}
#[test]
fn test_shadow_stack_pop_empty() {
let mut ss = X86CFIShadowStack::new(0x7000, 0x1000);
ss.active = true;
assert!(!ss.pop(0x1234));
assert_eq!(ss.mismatches, 1);
}
#[test]
fn test_shadow_stack_reset() {
let mut ss = X86CFIShadowStack::new(0x7000, 0x1000);
ss.active = true;
ss.push(0x1, 0x2, 0x3);
ss.push(0x4, 0x5, 0x6);
ss.reset();
assert!(ss.is_empty());
assert_eq!(ss.depth(), 0);
assert_eq!(ss.mismatches, 0);
}
#[test]
fn test_kcfi_registry_new() {
let reg = X86KCFIRegistry::new();
assert!(reg.is_empty());
assert_eq!(reg.len(), 0);
}
#[test]
fn test_kcfi_register_target() {
let mut reg = X86KCFIRegistry::new();
reg.register_target(0x4000, 0xABCD);
assert_eq!(reg.len(), 1);
assert!(reg.is_valid_target(0x4000, 0xABCD));
}
#[test]
fn test_kcfi_invalid_target() {
let mut reg = X86KCFIRegistry::new();
reg.register_target(0x4000, 0xABCD);
assert!(!reg.is_valid_target(0x4000, 0xFFFF)); assert!(!reg.is_valid_target(0x5000, 0xABCD)); }
#[test]
fn test_kcfi_compute_hash() {
let h1 = X86KCFIRegistry::compute_hash("void(int)", 0);
let h2 = X86KCFIRegistry::compute_hash("void(int)", 0);
assert_eq!(h1, h2); let h3 = X86KCFIRegistry::compute_hash("int(float)", 0);
assert_ne!(h1, h3); }
#[test]
fn test_kcfi_targets_with_hash() {
let mut reg = X86KCFIRegistry::new();
reg.register_target(0x1000, 0xAAAA);
reg.register_target(0x2000, 0xAAAA);
reg.register_target(0x3000, 0xBBBB);
let targets = reg.targets_with_hash(0xAAAA);
assert_eq!(targets.len(), 2);
let targets_b = reg.targets_with_hash(0xBBBB);
assert_eq!(targets_b.len(), 1);
}
#[test]
fn test_check_lowering_new() {
let lowering = X86CFICheckLowering::new(X86CFICheckKind::ICall);
assert_eq!(lowering.check_kind, X86CFICheckKind::ICall);
assert!(lowering.traps_on_failure);
assert_eq!(lowering.instr_count(), 0);
}
#[test]
fn test_check_lowering_emit() {
let mut lowering = X86CFICheckLowering::new(X86CFICheckKind::Bitset);
lowering.emit("mov", "eax, [rcx - 8]");
lowering.emit("cmp", "eax, 0x42");
lowering.emit("jne", ".Lfail");
assert_eq!(lowering.instr_count(), 3);
}
#[test]
fn test_check_lowering_emit_label() {
let mut lowering = X86CFICheckLowering::new(X86CFICheckKind::ICall);
lowering.emit_label(".Lcfi_ok");
lowering.emit("call", "rax");
assert_eq!(lowering.instr_count(), 2);
}
#[test]
fn test_build_bitmask_check() {
let check = X86CFICheckLowering::build_bitmask_check("rax", "ecx", 0x42, 0xFF00, ".Lfail");
assert!(check.instr_count() > 0);
assert_eq!(check.instructions[0].mnemonic, "mov");
}
#[test]
fn test_build_kcfi_check() {
let check = X86CFICheckLowering::build_kcfi_check(0x4000, 0xABCD, "eax", ".Lfail");
assert!(check.instr_count() > 0);
}
#[test]
fn test_build_shadow_stack_check() {
let check = X86CFICheckLowering::build_shadow_stack_check(0x401000, "r10", ".Lfail");
assert!(check.instr_count() > 0);
assert_eq!(check.instructions[0].mnemonic, "pop");
}
#[test]
fn test_build_trap() {
let check = X86CFICheckLowering::build_trap();
assert_eq!(check.instructions[0].mnemonic, "ud2");
}
#[test]
fn test_emit_asm() {
let mut check = X86CFICheckLowering::new(X86CFICheckKind::ICall);
check.emit("mov", "eax, [rcx]");
check.emit("cmp", "eax, 0x42");
let asm = check.emit_asm();
assert!(asm.contains("mov"));
assert!(asm.contains("cmp"));
}
#[test]
fn test_check_kind_display() {
assert_eq!(format!("{}", X86CFICheckKind::ICall), "icall");
assert_eq!(format!("{}", X86CFICheckKind::VCall), "vcall");
assert_eq!(format!("{}", X86CFICheckKind::Bitset), "bitset");
assert_eq!(format!("{}", X86CFICheckKind::KCFI), "kcfi");
assert_eq!(format!("{}", X86CFICheckKind::ShadowStack), "shadow-stack");
assert_eq!(format!("{}", X86CFICheckKind::CrossDSO), "cross-dso");
}
#[test]
fn test_func_instr_new() {
let fi = X86CFIFunctionInstrumentation::new("my_func");
assert_eq!(fi.function_name, "my_func");
assert!(!fi.has_icall);
assert!(!fi.has_vcall);
assert_eq!(fi.total_checks(), 0);
}
#[test]
fn test_func_instr_add_checks() {
let mut fi = X86CFIFunctionInstrumentation::new("func");
fi.add_forward_check(X86CFICheckLowering::new(X86CFICheckKind::ICall));
fi.add_backward_check(X86CFICheckLowering::new(X86CFICheckKind::ShadowStack));
assert_eq!(fi.total_checks(), 2);
}
#[test]
fn test_violation_report_new() {
let report = X86CFIViolationReport::new(0x4000, X86CFICheckKind::ICall, 0xDEAD);
assert_eq!(report.violation_address, 0x4000);
assert_eq!(report.target_address, 0xDEAD);
assert!(report.format().contains("CFI violation"));
}
#[test]
fn test_codegen_new() {
let cg = X86CFICodeGen::new();
assert!(!cg.stats.cfi_active);
assert!(!cg.backward_edge_enabled);
assert_eq!(cg.type_count(), 0);
}
#[test]
fn test_codegen_enable_full_cfi() {
let mut cg = X86CFICodeGen::new();
cg.enable_full_cfi();
assert!(cg.stats.cfi_active);
}
#[test]
fn test_codegen_enable_backward_edge() {
let mut cg = X86CFICodeGen::new();
cg.enable_backward_edge(0x7000000, 0x10000);
assert!(cg.backward_edge_enabled);
assert!(cg.shadow_stack.active);
}
#[test]
fn test_codegen_enable_cross_dso() {
let mut cg = X86CFICodeGen::new();
cg.enable_cross_dso(0x8000000, 0x100000);
assert!(cg.cross_dso.enabled);
assert!(cg.cfi_shadow.is_some());
}
#[test]
fn test_codegen_enable_kcfi() {
let mut cg = X86CFICodeGen::new();
cg.enable_kcfi();
assert!(cg.kcfi_config.enabled);
}
#[test]
fn test_codegen_register_type() {
let mut cg = X86CFICodeGen::new();
let hash = cg.register_type("MyClass");
assert!(hash != 0);
assert_eq!(cg.type_count(), 1);
assert_eq!(cg.stats.types_registered, 1);
}
#[test]
fn test_codegen_compute_type_hash() {
let h1 = X86CFICodeGen::compute_type_hash("int()");
let h2 = X86CFICodeGen::compute_type_hash("int()");
assert_eq!(h1, h2);
let h3 = X86CFICodeGen::compute_type_hash("float()");
assert_ne!(h1, h3);
}
#[test]
fn test_codegen_add_icall_check() {
let mut cg = X86CFICodeGen::new();
cg.add_icall_check(0x4000, "void(int)", "rax");
assert_eq!(cg.icall_checks.len(), 1);
assert_eq!(cg.stats.icall_checks, 1);
assert_eq!(cg.type_count(), 1);
}
#[test]
fn test_codegen_add_kcfi_icall_check() {
let mut cg = X86CFICodeGen::new();
cg.enable_kcfi();
cg.add_kcfi_icall_check(0x5000, "int(char*)", "rbx");
assert!(cg.icall_checks[0].use_kcfi);
assert_eq!(cg.stats.icall_checks, 1);
}
#[test]
fn test_codegen_add_vcall_check() {
let mut cg = X86CFICodeGen::new();
cg.add_vcall_check(0x6000, 0x1000, "Base", "foo", -8);
assert_eq!(cg.vcall_checks.len(), 1);
assert_eq!(cg.stats.vcall_checks, 1);
}
#[test]
fn test_codegen_lower_type_test() {
let mut cg = X86CFICodeGen::new();
cg.lower_type_test(0x7000, "!meta_Base", 0xABCD);
assert_eq!(cg.type_tests.len(), 1);
assert_eq!(cg.stats.type_tests, 1);
}
#[test]
fn test_codegen_lower_type_checked_load() {
let mut cg = X86CFICodeGen::new();
cg.lower_type_checked_load(0x8000, "!meta_Base", -8);
assert_eq!(cg.type_checked_loads.len(), 1);
assert_eq!(cg.stats.type_checked_loads, 1);
}
#[test]
fn test_codegen_lower_bitset_test() {
let mut cg = X86CFICodeGen::new();
{
let test = cg.lower_bitset_test(0x9000, "bs1");
test.with_range(0x1000, 0x2000);
}
assert_eq!(cg.bitset_tests.len(), 1);
assert_eq!(cg.stats.bitset_tests, 1);
}
#[test]
fn test_codegen_add_cross_dso_check() {
let mut cg = X86CFICodeGen::new();
cg.add_cross_dso_check(0xA000, "FnType", Some("libfoo.so"));
assert_eq!(cg.cross_dso_checks.len(), 1);
assert_eq!(
cg.cross_dso_checks[0].dso_name.as_deref(),
Some("libfoo.so")
);
}
#[test]
fn test_codegen_register_kcfi_target() {
let mut cg = X86CFICodeGen::new();
cg.enable_kcfi();
let hash = cg.register_kcfi_target(0xB000, "void(void)");
assert!(hash != 0);
assert_eq!(cg.stats.kcfi_targets, 1);
assert!(cg.kcfi_check_target(0xB000, "void(void)"));
}
#[test]
fn test_codegen_shadow_stack_push_pop() {
let mut cg = X86CFICodeGen::new();
cg.enable_backward_edge(0x7000, 0x1000);
cg.shadow_stack_push(0x401234, 0x7ff0, 0x401000);
assert_eq!(cg.stats.shadow_stack_pushes, 1);
assert!(cg.shadow_stack_pop(0x401234));
assert_eq!(cg.stats.shadow_stack_pops, 1);
assert_eq!(cg.stats.violations, 0);
}
#[test]
fn test_codegen_shadow_stack_pop_violation() {
let mut cg = X86CFICodeGen::new();
cg.enable_backward_edge(0x7000, 0x1000);
cg.shadow_stack_push(0xAAAA, 0x1000, 0x5000);
assert!(!cg.shadow_stack_pop(0xBBBB));
assert_eq!(cg.stats.violations, 1);
}
#[test]
fn test_codegen_shadow_stack_disabled_bypass() {
let mut cg = X86CFICodeGen::new();
assert!(cg.shadow_stack_pop(0xDEAD));
assert_eq!(cg.stats.shadow_stack_pops, 0);
}
#[test]
fn test_codegen_begin_end_function() {
let mut cg = X86CFICodeGen::new();
cg.begin_function("worker");
assert!(cg.get_function_instr("worker").is_some());
cg.end_function("worker");
assert_eq!(cg.stats.functions_instrumented, 1);
}
#[test]
fn test_codegen_add_function_forward_check() {
let mut cg = X86CFICodeGen::new();
cg.begin_function("fn");
let check = X86CFICheckLowering::new(X86CFICheckKind::ICall);
cg.add_function_forward_check("fn", check);
let instr = cg.get_function_instr("fn").unwrap();
assert_eq!(instr.forward_edge_checks.len(), 1);
assert!(instr.has_icall);
}
#[test]
fn test_codegen_report_violation() {
let mut cg = X86CFICodeGen::new();
cg.report_violation(0xDEAD, X86CFICheckKind::ICall, 0xBEEF);
assert_eq!(cg.violation_count(), 1);
assert_eq!(cg.stats.violations, 1);
}
#[test]
fn test_codegen_format_violations() {
let mut cg = X86CFICodeGen::new();
cg.report_violation(0x1000, X86CFICheckKind::VCall, 0x2000);
cg.report_violation(0x3000, X86CFICheckKind::ShadowStack, 0x4000);
let formatted = cg.format_violations();
assert!(formatted.contains("vcall"));
assert!(formatted.contains("shadow-stack"));
}
#[test]
fn test_codegen_run_lowering_pipeline() {
let mut cg = X86CFICodeGen::new();
cg.add_icall_check(0x4000, "int()", "rax");
cg.add_icall_check(0x4100, "void(char*)", "rbx");
let checks = cg.run_lowering_pipeline();
assert!(checks.len() >= 3);
}
#[test]
fn test_codegen_cfi_shadow_set_get() {
let mut cg = X86CFICodeGen::new();
cg.enable_cross_dso(0x100000, 0x10000);
cg.cfi_shadow_set(0x100100, 0xAB);
assert_eq!(cg.cfi_shadow_get(0x100100), 0xAB);
}
#[test]
fn test_codegen_get_stats() {
let mut cg = X86CFICodeGen::new();
cg.enable_full_cfi();
cg.register_type("T1");
cg.register_type("T2");
cg.add_icall_check(0x4000, "T1", "rax");
let stats = cg.get_stats();
assert_eq!(stats.types_registered, 2);
assert_eq!(stats.icall_checks, 1);
assert!(stats.cfi_active);
}
#[test]
fn test_codegen_reset() {
let mut cg = X86CFICodeGen::new();
cg.enable_full_cfi();
cg.register_type("T");
cg.add_icall_check(0x4000, "T", "rax");
cg.enable_backward_edge(0x7000, 0x1000);
cg.shadow_stack_push(0x1234, 0x1000, 0x4000);
cg.reset();
assert_eq!(cg.type_count(), 0);
assert_eq!(cg.icall_checks.len(), 0);
assert_eq!(cg.stats.icall_checks, 0);
assert!(!cg.shadow_stack.active);
}
#[test]
fn test_codegen_comprehensive_workflow() {
let mut cg = X86CFICodeGen::new();
cg.enable_full_cfi();
cg.enable_backward_edge(0x7f000000, 0x100000);
cg.enable_cross_dso(0x80000000, 0x1000000);
cg.enable_kcfi();
let h1 = cg.register_type("Base");
let h2 = cg.register_type("Derived");
let h3 = cg.register_type("void(int)");
assert_eq!(cg.type_count(), 3);
cg.add_icall_check(0x401000, "void(int)", "rax");
cg.add_vcall_check(0x401100, 0x7fff1000, "Base", "virtualMethod", -8);
cg.lower_type_test(0x402000, "!meta_Base", h1);
cg.lower_type_checked_load(0x402100, "!meta_Base", -8);
{
let bs = cg.lower_bitset_test(0x403000, "bs_func_table");
bs.with_range(0x400000, 0x500000)
.with_bitset_data(vec![0xFFFFFFFF, 0x0F]);
}
cg.add_cross_dso_check(0x404000, "void(int)", Some("libplugin.so"));
let kcfi_hash = cg.register_kcfi_target(0x405000, "void(void)");
assert!(kcfi_hash != 0);
cg.shadow_stack_push(0x401234, 0x7fffef00, 0x401000);
cg.shadow_stack_push(0x402345, 0x7fffee00, 0x402000);
cg.begin_function("main");
let bitmask_check =
X86CFICheckLowering::build_bitmask_check("rax", "ecx", h3, 0xFF, ".Lfail");
cg.add_function_forward_check("main", bitmask_check);
cg.end_function("main");
assert_eq!(cg.stats.types_registered, 3);
assert_eq!(cg.stats.icall_checks, 1);
assert_eq!(cg.stats.vcall_checks, 1);
assert_eq!(cg.stats.type_tests, 1);
assert_eq!(cg.stats.type_checked_loads, 1);
assert_eq!(cg.stats.bitset_tests, 1);
assert_eq!(cg.stats.cross_dso_checks, 1);
assert_eq!(cg.stats.kcfi_targets, 1);
assert_eq!(cg.stats.shadow_stack_pushes, 2);
assert_eq!(cg.stats.functions_instrumented, 1);
}
#[test]
fn test_icall_check_emit_pseudo_ir() {
let check = X86CFIICallCheck::new(0x4000, "int(float)", 0xABCD);
let ir = check.emit_pseudo_ir();
assert!(ir.contains("llvm.type.test"));
assert!(ir.contains("int(float)"));
}
#[test]
fn test_vcall_check_emit_pseudo_ir() {
let check = X86CFIVCallCheck::new(0x5000, 0x1000, "Base", "f", -8);
let ir = check.emit_pseudo_ir();
assert!(ir.contains("CFI vcall check"));
assert!(ir.contains("vtable"));
}
#[test]
fn test_function_instr_kcfi_prefix() {
let mut fi = X86CFIFunctionInstrumentation::new("kernel_fn");
assert!(!fi.has_kcfi_prefix);
fi.has_kcfi_prefix = true;
assert!(fi.has_kcfi_prefix);
}
#[test]
fn test_violation_report_with_details() {
let mut report = X86CFIViolationReport::new(0xDEAD, X86CFICheckKind::KCFI, 0xBEEF);
report.expected_type_hash = Some(0x1234);
report.actual_type_hash = Some(0x5678);
report.function_name = Some("do_work".to_string());
let formatted = report.format();
assert!(formatted.contains("0xdead"));
assert!(formatted.contains("0xbeef"));
}
#[test]
fn test_check_kind_all_values() {
let kinds = vec![
X86CFICheckKind::ICall,
X86CFICheckKind::VCall,
X86CFICheckKind::Bitset,
X86CFICheckKind::KCFI,
X86CFICheckKind::ShadowStack,
X86CFICheckKind::CrossDSO,
];
for kind in kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_cross_dso_config_default() {
let cfg = X86CFICrossDSOConfig::default();
assert!(!cfg.enabled);
assert!(!cfg.use_shadow);
assert_eq!(cfg.shadow_scale, 3);
}
#[test]
fn test_kcfi_config_default() {
let cfg = X86KCFIConfig::default();
assert!(!cfg.enabled);
assert_eq!(cfg.prefix_byte, X86_KCFI_PREFIX_BYTE);
assert!(cfg.check_icall);
}
}