From 7509ead79f22628fafd0dd325da26d443dce6c0f Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Mon, 12 Jan 2026 11:12:57 -0500 Subject: [PATCH 1/4] - Use a threadlocal for stack addresses instead of the global table - Merge adjacent GEP instructions into one resolve_gep call - Try to de-allocate multiple stack addresses in one call --- libresolve/src/lib.rs | 3 + libresolve/src/remediate.rs | 168 ++++++++++++++----- libresolve/src/shadowobjs.rs | 4 + llvm-plugin/Makefile | 7 +- llvm-plugin/src/CMakeLists.txt | 2 +- llvm-plugin/src/CVEAssert/bounds_check.cpp | 184 +++++++++++++-------- 6 files changed, 254 insertions(+), 114 deletions(-) diff --git a/libresolve/src/lib.rs b/libresolve/src/lib.rs index 02fb6334..ff9c341e 100644 --- a/libresolve/src/lib.rs +++ b/libresolve/src/lib.rs @@ -2,6 +2,9 @@ // LGPL-3; See LICENSE.txt in the repo root for details. #![feature(btree_cursors)] +#![feature(macro_metavar_expr_concat)] +#![feature(test)] +extern crate test; mod remediate; mod shadowobjs; diff --git a/libresolve/src/remediate.rs b/libresolve/src/remediate.rs index 30c6b9c3..8a0d4bd1 100644 --- a/libresolve/src/remediate.rs +++ b/libresolve/src/remediate.rs @@ -4,7 +4,9 @@ use libc::{ c_char, c_void, calloc, free, malloc, realloc, strdup, strlen, strndup, strnlen, }; -use crate::shadowobjs::{ALIVE_OBJ_LIST, AllocType, FREED_OBJ_LIST, ShadowObject, Vaddr}; +use crate::shadowobjs::{ + ALIVE_OBJ_LIST, AllocType, FREED_OBJ_LIST, STACK_OBJ_LIST, ShadowObject, Vaddr, +}; use log::{error, info, trace, warn}; @@ -24,26 +26,55 @@ use log::{error, info, trace, warn}; #[unsafe(no_mangle)] pub extern "C" fn resolve_stack_obj(ptr: *mut c_void, size: usize) -> () { let base = ptr as Vaddr; - { - let mut obj_list = ALIVE_OBJ_LIST.lock(); - obj_list.add_shadow_object(AllocType::Stack, base, size); - } + + STACK_OBJ_LIST.with_borrow_mut(|l| { + l.add_shadow_object(AllocType::Stack, base, size); + }); info!("[STACK] Object allocated with size: {size}, address: 0x{base:x}"); } +/* #[unsafe(no_mangle)] +pub extern "C" fn resolve_invalidate_stack(base: *mut c_void) { pub extern "C" fn resolve_invalidate_stack(base: *mut c_void) { let base = base as Vaddr; - { - let mut obj_list = ALIVE_OBJ_LIST.lock(); - obj_list.invalidate_at(base); - } + STACK_OBJ_LIST.with_borrow_mut(|l| { + l.invalidate_at(base); + }); + + info!("[STACK] Free range 0x{base:x}"); +} +*/ + +macro_rules! invalidate_stacks { + ($name:ident; $($ns:literal),+) => { + +#[unsafe(no_mangle)] +pub extern "C" fn $name($(${concat(base_, $ns)}: *mut c_void, )+) { + + STACK_OBJ_LIST.with_borrow_mut(|l| { + $( + l.invalidate_at(${concat(base_, $ns)} as Vaddr); + info!("[STACK] Free address 0x{:x}", ${concat(base_, $ns)} as Vaddr); + )+ + }); + +} - info!("[STACK] Free addr 0x{base:x}"); + } } +// the x64 ABI allows up to 6 arguments to be passed via register, +// so provide that many functions as we cannot define a variadic +invalidate_stacks!(resolve_invalidate_stack; 1); +invalidate_stacks!(resolve_invalidate_stack_2; 1, 2); +invalidate_stacks!(resolve_invalidate_stack_3; 1, 2, 3); +invalidate_stacks!(resolve_invalidate_stack_4; 1, 2, 3, 4); +invalidate_stacks!(resolve_invalidate_stack_5; 1, 2, 3, 4, 5); +invalidate_stacks!(resolve_invalidate_stack_6; 1, 2, 3, 4, 5, 6); + /** * @brief - Allocator logging interface for malloc * @input - size of the allocation in bytes @@ -81,10 +112,37 @@ pub extern "C" fn resolve_malloc(size: usize) -> *mut c_void { */ #[unsafe(no_mangle)] -pub extern "C" fn resolve_gep(ptr: *mut c_void, derived: *mut c_void) -> *mut c_void { +pub extern "C" fn resolve_gep(ptr: *mut c_void, derived: *mut c_void, max_access: usize) -> *mut c_void { let base = ptr as Vaddr; let derived = derived as Vaddr; + + let contains_or_err = |obj: &ShadowObject| { + // If shadow object exists then check if the access is within bounds + if obj.contains(ShadowObject::limit(derived, max_access)) { + trace!( + "[GEP] ptr {max_access}x{derived:x} valid for base 0x{base:x}, obj: {}@0x{:x}", + obj.size(), + obj.base + ); + return derived as *mut c_void; + } + + error!( + "[GEP] ptr {max_access}x{derived:x} not valid for base 0x{base:x}, obj: {}@0x{:x}", + obj.size(), + obj.base + ); + + // Pointer known-invalid, return sentinel to indicate failure + 0 as *mut c_void + }; + + // Check the local stack list first since it is cheaper. + if let Some(sobj) = STACK_OBJ_LIST.with_borrow(|l| l.search_intersection(base).cloned()) { + return contains_or_err(&sobj); + }; + let sobj_table = ALIVE_OBJ_LIST.lock(); // Look up the shadow object corresponding to this access. @@ -101,24 +159,7 @@ pub extern "C" fn resolve_gep(ptr: *mut c_void, derived: *mut c_void) -> *mut c_ return derived as *mut c_void; }; - // If shadow object exists then check if the access is within bounds - if sobj.contains(derived as Vaddr) { - info!( - "[GEP] ptr 0x{derived:x} valid for base 0x{base:x}, obj: {}@0x{:x}", - sobj.size(), - sobj.base - ); - return derived as *mut c_void; - } - - error!( - "[GEP] ptr 0x{derived:x} not valid for base 0x{base:x}, obj: {}@0x{:x}", - sobj.size(), - sobj.base - ); - - // Return 1-past limit of allocation @ ptr - sobj.past_limit() as *mut c_void + contains_or_err(sobj) } @@ -317,27 +358,39 @@ pub extern "C" fn resolve_strndup(ptr: *mut c_char, size: usize) -> *mut c_char pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bool { let base = base_ptr as Vaddr; - let sobj_table = ALIVE_OBJ_LIST.lock(); + // Nullptr clearly are not valid, and may be returned by resolve_gep if it is an invalid call. + if base == 0 { + return false; + } - // Look up the shadow object corresponding to this access - if let Some(sobj) = sobj_table.search_intersection(base) { + let contains_or_err = |obj: &ShadowObject| { // If shadow object exists then check if the access is within bounds - if sobj.contains(ShadowObject::limit(base, size)) { + if obj.contains(ShadowObject::limit(base, size)) { // Access in Bounds trace!( "[BOUNDS] Access allowed {size}@0x{base:x} for allocation {}@0x{:x}", - sobj.size(), - sobj.base + obj.size(), + obj.base ); return true; } else { error!( "[BOUNDS] OOB access at {size}@0x{base:x} too big for allocation {}@0x{:x}", - sobj.size(), - sobj.base + obj.size(), + obj.base ); return false; } + }; + + if let Some(sobj) = STACK_OBJ_LIST.with_borrow(|l| l.search_intersection(base).cloned()) { + return contains_or_err(&sobj); + } + + // Look up the shadow object corresponding to this access + let sobj_table = ALIVE_OBJ_LIST.lock(); + if let Some(sobj) = sobj_table.search_intersection(base) { + return contains_or_err(sobj); } // Check if this is an invalid pointer for one of the known shadow objects @@ -350,6 +403,8 @@ pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bo return false; } + warn!("[BOUNDS] unknown pointer 0x:{base:x}"); + // Not a tracked pointer, assume good to avoid trapping on otherwise valid pointers // TODO: add a strict mode to reject here / add extra tracking. true @@ -359,13 +414,21 @@ pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bo pub extern "C" fn resolve_obj_type(base_ptr: *mut c_void) -> AllocType { let base = base_ptr as Vaddr; - let find_in = |table: &crate::MutexWrap| { - let t = table.lock(); - t.search_intersection(base).map(|o| o.alloc_type) + let find_in = |table: &crate::shadowobjs::ShadowObjectTable| { + table.search_intersection(base).map(|o| o.alloc_type) }; // Why does this search freed before alive? - let alloc_type = find_in(&FREED_OBJ_LIST).or_else(|| find_in(&ALIVE_OBJ_LIST)); + let alloc_type = STACK_OBJ_LIST + .with_borrow(|l| find_in(l)) + .or_else(|| { + let l = FREED_OBJ_LIST.lock(); + find_in(&l) + }) + .or_else(|| { + let l = ALIVE_OBJ_LIST.lock(); + find_in(&l) + }); alloc_type.unwrap_or(AllocType::Unknown) } @@ -378,7 +441,7 @@ pub extern "C" fn resolve_obj_type(base_ptr: *mut c_void) -> AllocType { */ #[unsafe(no_mangle)] pub extern "C" fn resolve_report_sanitize_mem_inst_triggered(ptr: *mut c_void) { - info!( + error!( "[SANITIZE] Applying sanitizer to address 0x{:x}", ptr as Vaddr ); @@ -396,6 +459,7 @@ pub extern "C" fn resolve_report_sanitizer_triggered() -> () { mod tests { use super::*; use crate::{resolve_init, shadowobjs::AllocType}; + use test::Bencher; #[test] fn test_malloc_free() { @@ -479,4 +543,24 @@ mod tests { resolve_free(p); } } + + + #[bench] + fn bench_resolve_stack(b: &mut Bencher) { + resolve_init(); + + let addrs: Vec<_> = (0x7FFF_0000_0000_0000..0x7FFF_0000_0001_0000) + .map(|a: usize| a as *mut c_void) + .collect(); + + b.iter(|| { + addrs.iter().for_each(|a| resolve_stack_obj(*a, 1)); + + addrs.iter().for_each(|&a| { + let _ = resolve_gep(a, a, 1); + }); + + addrs.iter().for_each(|a| resolve_invalidate_stack(*a)); + }); + } } diff --git a/libresolve/src/shadowobjs.rs b/libresolve/src/shadowobjs.rs index 878a47d2..368a616a 100644 --- a/libresolve/src/shadowobjs.rs +++ b/libresolve/src/shadowobjs.rs @@ -2,6 +2,7 @@ // LGPL-3; See LICENSE.txt in the repo root for details. use crate::MutexWrap; +use std::cell::RefCell; use std::collections::BTreeMap; use std::ops::RangeInclusive; use std::ops::Bound::Included; @@ -131,6 +132,9 @@ impl ShadowObjectTable { } // static object lists to store all objects +thread_local! { + pub static STACK_OBJ_LIST: RefCell = RefCell::new(ShadowObjectTable::new()); +} pub static ALIVE_OBJ_LIST: MutexWrap = MutexWrap::new(ShadowObjectTable::new()); pub static FREED_OBJ_LIST: MutexWrap = MutexWrap::new(ShadowObjectTable::new()); diff --git a/llvm-plugin/Makefile b/llvm-plugin/Makefile index 8c74d06b..2d58f9f5 100644 --- a/llvm-plugin/Makefile +++ b/llvm-plugin/Makefile @@ -1,10 +1,11 @@ -all: build test -.PHONY: clangformat test +.PHONY: all build clangformat test + +all: build test build: src mkdir -p build - cd build && cmake ../src && make -j4 + cmake -S ./src -B ./build && cd build && make -j4 clangformat: src cd build && make clangformat diff --git a/llvm-plugin/src/CMakeLists.txt b/llvm-plugin/src/CMakeLists.txt index e6b73235..de5841a8 100644 --- a/llvm-plugin/src/CMakeLists.txt +++ b/llvm-plugin/src/CMakeLists.txt @@ -13,7 +13,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() -# set(CMAKE_CXX_STANDARD 17 STRING "") +set(CMAKE_CXX_STANDARD 20 STRING "") # LLVM is normally built without RTTI. Be consistent with that. if(NOT LLVM_ENABLE_RTTI) diff --git a/llvm-plugin/src/CVEAssert/bounds_check.cpp b/llvm-plugin/src/CVEAssert/bounds_check.cpp index aa52c872..3ed6b71b 100644 --- a/llvm-plugin/src/CVEAssert/bounds_check.cpp +++ b/llvm-plugin/src/CVEAssert/bounds_check.cpp @@ -304,58 +304,92 @@ void instrumentAlloca(Function *F) { // Initialize list to store pointers to alloca and instructions std::vector toFreeList; - auto invalidateFn = M->getOrInsertFunction( - "resolve_invalidate_stack", - FunctionType::get(void_ty, { ptr_ty }, false) - ); - - auto handle_alloca = [&](auto* allocaInst) { - bool hasStart = false; - bool hasEnd = false; - - Type *allocatedType = allocaInst->getAllocatedType(); - uint64_t typeSize = DL.getTypeAllocSize(allocatedType); - - for (auto* user: allocaInst->users()) { - if( auto* call = dyn_cast(user)) { - auto called = call->getCalledFunction(); - if (called && called->getName().starts_with("llvm.lifetime.start")) { - hasStart = true; - builder.SetInsertPoint(call->getNextNode()); - builder.CreateCall(getResolveStackObj(M), { allocaInst, ConstantInt::get(size_ty, typeSize)}); - } - - if (called && called->getName().starts_with("llvm.lifetime.end")) { - hasEnd = true; - builder.SetInsertPoint(call->getNextNode()); - builder.CreateCall(invalidateFn, { allocaInst}); - } - } - } - - // This is probably always true unless we are given malformed input. - assert(hasStart == hasEnd); - if (hasStart) { return; } - // Otherwise Insert after the alloca instruction - builder.SetInsertPoint(allocaInst->getNextNode()); - builder.CreateCall(getResolveStackObj(M), { allocaInst, ConstantInt::get(size_ty, typeSize)}); - // If we have not added an invalidate call already make sure we do so later. - toFreeList.push_back(allocaInst); - }; - for (auto &BB: *F) { for (auto &instr: BB) { if (auto *inst = dyn_cast(&instr)) { - handle_alloca(inst); + toFreeList.push_back(inst); } } } + for (auto* allocaInst: toFreeList) { + // Insert after the alloca instruction + builder.SetInsertPoint(allocaInst->getNextNode()); + Value* allocatedPtr = allocaInst; + Value *sizeVal = nullptr; + Type *allocatedType = allocaInst->getAllocatedType(); + uint64_t typeSize = DL.getTypeAllocSize(allocatedType); + sizeVal = ConstantInt::get(size_ty, typeSize); + builder.CreateCall(getResolveStackObj(M), { allocatedPtr, sizeVal }); + } + // Find low and high allocations and pass to resolve_invaliate_stack if (toFreeList.empty()) { return; } + auto invalidateFn = M->getOrInsertFunction( + "resolve_invalidate_stack", + FunctionType::get(void_ty, { ptr_ty }, false) + ); + + + auto invalidateFn2 = M->getOrInsertFunction( + "resolve_invalidate_stack_2", + FunctionType::get(void_ty, { ptr_ty, ptr_ty }, false) + ); + + auto invalidateFn3 = M->getOrInsertFunction( + "resolve_invalidate_stack_3", + FunctionType::get(void_ty, { ptr_ty, ptr_ty, ptr_ty }, false) + ); + + auto invalidateFn4 = M->getOrInsertFunction( + "resolve_invalidate_stack_4", + FunctionType::get(void_ty, { ptr_ty, ptr_ty, ptr_ty, ptr_ty }, false) + ); + + auto invalidateFn5 = M->getOrInsertFunction( + "resolve_invalidate_stack_5", + FunctionType::get(void_ty, { ptr_ty, ptr_ty, ptr_ty, ptr_ty, ptr_ty }, false) + ); + + auto invalidateFn6 = M->getOrInsertFunction( + "resolve_invalidate_stack_6", + FunctionType::get(void_ty, { ptr_ty, ptr_ty, ptr_ty, ptr_ty, ptr_ty, ptr_ty }, false) + ); + + + // Try to reduce the number of calls to invalidate each of the stack addrs. + // the x64 ABI allows us to pass up to 6 arguments in registers, so libresolve provides functions with up to arity 6. + auto invalidate_all_at = [&](auto* inst) { + builder.SetInsertPoint(inst); + auto size = toFreeList.size(); + for (auto i = 0; i < toFreeList.size(); i += 6) { + switch ((size - i) % 6) { + case 1: + builder.CreateCall(invalidateFn, { toFreeList[i] }); + break; + case 2: + builder.CreateCall(invalidateFn2, { toFreeList[i], toFreeList[i+1] }); + break; + case 3: + builder.CreateCall(invalidateFn3, { toFreeList[i], toFreeList[i+1], toFreeList[i+2] }); + break; + case 4: + builder.CreateCall(invalidateFn4, { toFreeList[i], toFreeList[i+1], toFreeList[i+2], toFreeList[i+3] }); + break; + case 5: + builder.CreateCall(invalidateFn5, { toFreeList[i], toFreeList[i+1], toFreeList[i+2], toFreeList[i+3], toFreeList[i+4] }); + break; + // 6 + case 0: + builder.CreateCall(invalidateFn6, { toFreeList[i], toFreeList[i+1], toFreeList[i+2], toFreeList[i+3], toFreeList[i+4], toFreeList[i+5] }); + break; + } + } + }; + // Stack grows down, so first allocation is high, last is low // Hmm.. compiler seems to be reordering the allocas in ways // that break this assumption @@ -364,11 +398,7 @@ void instrumentAlloca(Function *F) { for (auto &BB: *F) { for (auto &instr: BB) { if (auto *inst = dyn_cast(&instr)) { - builder.SetInsertPoint(inst); - // builder.CreateCall(invalidateFn, { low, high }); - for (auto *alloca: toFreeList) { - builder.CreateCall(invalidateFn, { alloca }); - } + invalidate_all_at(inst); } } } @@ -476,49 +506,67 @@ void instrumentGEP(Function *F) { const DataLayout &DL = M->getDataLayout(); std::vector gepList; auto ptr_ty = PointerType::get(Ctx, 0); + auto size_ty = Type::getInt64Ty(Ctx); FunctionType *resolveGEPFnTy = FunctionType::get( ptr_ty, - { ptr_ty, ptr_ty }, + { ptr_ty, ptr_ty, size_ty }, false ); - FunctionCallee resolveGEPFn = M->getOrInsertFunction( + FunctionCallee resolveGepFn = M->getOrInsertFunction( "resolve_gep", resolveGEPFnTy ); - for (auto &BB : *F) { - for (auto &inst: BB) { - if (auto *gep = dyn_cast(&inst)) { - gepList.push_back(gep); - } - } - } + std::unordered_set visitedGep; - for (auto GEPInst: gepList) { - builder.SetInsertPoint(GEPInst->getNextNode()); + //auto resolveGepFn = getOrCreateResolveGepSanitizer(M, Ctx, strategy); - // Get the pointer operand and offset from GEP - Value *basePtr = GEPInst->getPointerOperand(); - Value * derivedPtr = GEPInst; - - // Don't assume gep is inbounds, otherwise our remdiation risks being optimized away - GEPInst->setIsInBounds(false); + auto handle_gep = [&](auto* gep) { + + if (visitedGep.contains(gep)) { + return; + } - auto resolveGEPCall = builder.CreateCall(resolveGEPFn, { basePtr, derivedPtr }); + Value* basePtr = gep->getPointerOperand(); + GetElementPtrInst* derivedPtr = gep; + gep->setIsInBounds(false); + + // If we are chaining geps we don't need to check each individually, only the total range in the end. + while (derivedPtr->hasOneUser()) { + if (auto* gep2 = dyn_cast(derivedPtr->user_back())) { + gep2->setIsInBounds(false); + visitedGep.insert(gep2); + derivedPtr = gep2; + } else { + break; + } + } - // Collect users of gep instruction before mutation SmallVector gep_users; - for (User *U : GEPInst->users()) { + for (User *U : derivedPtr->users()) { gep_users.push_back(U); } + builder.SetInsertPoint(derivedPtr->getNextNode()); + auto resolveGepCall = builder.CreateCall(resolveGepFn, { basePtr, derivedPtr, ConstantExpr::getSizeOf(derivedPtr->getResultElementType()) }); + // Iterate over all the users of the gep instruction and - // replace there operands with resolve_gep result + // replace their operands with resolve_gep result for (User *U : gep_users) { - if (U != resolveGEPCall) { - U->replaceUsesOfWith(GEPInst, resolveGEPCall); + if (U != resolveGepCall) { + U->replaceUsesOfWith(derivedPtr, resolveGepCall); + } + } + + visitedGep.insert(gep); + }; + + for (auto &BB : *F) { + for (auto &inst: BB) { + if (auto *gep = dyn_cast(&inst)) { + handle_gep(gep); } } } From e83712a0199f27b9e31c3f149144e4e5c73d7979 Mon Sep 17 00:00:00 2001 From: Ethan Lazaro Date: Tue, 20 Jan 2026 15:44:40 -0500 Subject: [PATCH 2/4] bounds_check.cpp: WIP verifying that instrumentation still works. --- llvm-plugin/src/CVEAssert/bounds_check.cpp | 57 +++++++++++++++------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/llvm-plugin/src/CVEAssert/bounds_check.cpp b/llvm-plugin/src/CVEAssert/bounds_check.cpp index 3ed6b71b..8a479a1b 100644 --- a/llvm-plugin/src/CVEAssert/bounds_check.cpp +++ b/llvm-plugin/src/CVEAssert/bounds_check.cpp @@ -301,32 +301,55 @@ void instrumentAlloca(Function *F) { auto size_ty = Type::getInt64Ty(Ctx); auto void_ty = Type::getVoidTy(Ctx); - // Initialize list to store pointers to alloca and instructions - std::vector toFreeList; + // // Initialize list to store pointers to alloca and instructions + // std::vector toFreeList; + + auto handle_alloca = [&](auto* allocaInst) { + bool hasStart = false; + bool hasEnd = false; + + Type *allocatedType = allocaInst->getAllocatedType(); + uint64_t typeSize = DL.getTypeAllocSize(allocatedType); + + for (auto* user: allocaInst->users()) { + if( auto* call = dyn_cast(user)) { + auto called = call->getCalledFunction(); + if (called && called->getName().starts_with("llvm.lifetime.start")) { + hasStart = true; + builder.SetInsertPoint(call->getNextNode()); + builder.CreateCall(getResolveStackObj(M), { allocaInst, ConstantInt::get(size_ty, typeSize)}); + } + + if (called && called->getName().starts_with("llvm.lifetime.end")) { + hasEnd = true; + builder.SetInsertPoint(call->getNextNode()); + builder.CreateCall(invalidateFn, { allocaInst}); + } + } + } + + // This is probably always true unless we are given malformed input. + assert(hasStart == hasEnd); + if (hasStart) { return; } + // Otherwise Insert after the alloca instruction + builder.SetInsertPoint(allocaInst->getNextNode()); + builder.CreateCall(getResolveStackObj(M), { allocaInst, ConstantInt::get(size_ty, typeSize)}); + // If we have not added an invalidate call already make sure we do so later. + toFreeList.push_back(allocaInst); + }; for (auto &BB: *F) { for (auto &instr: BB) { if (auto *inst = dyn_cast(&instr)) { - toFreeList.push_back(inst); + handle_alloca(inst); } } } - for (auto* allocaInst: toFreeList) { - // Insert after the alloca instruction - builder.SetInsertPoint(allocaInst->getNextNode()); - Value* allocatedPtr = allocaInst; - Value *sizeVal = nullptr; - Type *allocatedType = allocaInst->getAllocatedType(); - uint64_t typeSize = DL.getTypeAllocSize(allocatedType); - sizeVal = ConstantInt::get(size_ty, typeSize); - builder.CreateCall(getResolveStackObj(M), { allocatedPtr, sizeVal }); - } - // Find low and high allocations and pass to resolve_invaliate_stack - if (toFreeList.empty()) { - return; - } + // if (toFreeList.empty()) { + // return; + // } auto invalidateFn = M->getOrInsertFunction( "resolve_invalidate_stack", From cb14f6c328fca019c2db87e6cd6c000190b260ab Mon Sep 17 00:00:00 2001 From: Ethan Lazaro Date: Tue, 20 Jan 2026 15:47:49 -0500 Subject: [PATCH 3/4] bounds_check.cpp: WIP testing to see if this works correctly. Uncommented toFreeList. --- llvm-plugin/src/CVEAssert/bounds_check.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/llvm-plugin/src/CVEAssert/bounds_check.cpp b/llvm-plugin/src/CVEAssert/bounds_check.cpp index 8a479a1b..b19a9371 100644 --- a/llvm-plugin/src/CVEAssert/bounds_check.cpp +++ b/llvm-plugin/src/CVEAssert/bounds_check.cpp @@ -302,7 +302,7 @@ void instrumentAlloca(Function *F) { auto void_ty = Type::getVoidTy(Ctx); // // Initialize list to store pointers to alloca and instructions - // std::vector toFreeList; + std::vector toFreeList; auto handle_alloca = [&](auto* allocaInst) { bool hasStart = false; @@ -341,15 +341,16 @@ void instrumentAlloca(Function *F) { for (auto &BB: *F) { for (auto &instr: BB) { if (auto *inst = dyn_cast(&instr)) { + toFreeList.push(inst); handle_alloca(inst); } } } // Find low and high allocations and pass to resolve_invaliate_stack - // if (toFreeList.empty()) { - // return; - // } + if (toFreeList.empty()) { + return; + } auto invalidateFn = M->getOrInsertFunction( "resolve_invalidate_stack", From a008d4a2976b7e008948e312ec43fa281231390f Mon Sep 17 00:00:00 2001 From: Ethan Lazaro Date: Tue, 20 Jan 2026 15:50:13 -0500 Subject: [PATCH 4/4] bounds_check.cpp: Mispelled vector function name use 'push_back'. --- llvm-plugin/src/CVEAssert/bounds_check.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm-plugin/src/CVEAssert/bounds_check.cpp b/llvm-plugin/src/CVEAssert/bounds_check.cpp index b19a9371..55535c2e 100644 --- a/llvm-plugin/src/CVEAssert/bounds_check.cpp +++ b/llvm-plugin/src/CVEAssert/bounds_check.cpp @@ -341,7 +341,7 @@ void instrumentAlloca(Function *F) { for (auto &BB: *F) { for (auto &instr: BB) { if (auto *inst = dyn_cast(&instr)) { - toFreeList.push(inst); + toFreeList.push_back(inst); handle_alloca(inst); } }