CETS CompilerEnforced Temporal Safety for C Santosh Nagarakatte


















- Slides: 18
CETS: Compiler-Enforced Temporal Safety for C Santosh Nagarakatte, Jianzhou Zhao, Milo M. K. Martin, Steve Zdancewic Lara Khamisy, Kevin Matthews, and Chris Pratt
Introduction ● Description: Compiler-enforced temporal safety (CERT) for C programs. ● Background: Temporal memory safety errors are a prevalent source of software bugs in unmanaged languages such as C. Existing schemes that attempt to retrofit temporal safety for such languages have high runtime overheads and/or are incomplete, thereby limiting their effectiveness as debugging aid. ● Solution: CETS is a pass that will instrument IR to detect all temporal safety violations.
Overview of Memory Violations ● Spatial - the pointer refers to the wrong place in the address space ○ ○ Buffer overflow Dereference uninitialized pointer ● Temporal - the place in the address space is no longer valid ○ ○ Dangling pointer dereference Double free Both types of memory errors can result in crashes, silent data corruption, and severe security vulnerabilities.
Examples of Temporal Safety Violations Heap-based Stack-based int *a, *b; a = malloc(8); … b = a; . . . free(a); … … = *b; int *a; void foo() { int b; a = &b; } int main() { foo(); … = *a; }
Motivation for Detecting Temporal Safety Violations ● C/C++ provide low-level control/management of memory in OS, embedded software, etc ● Lack of bounds checking and manual memory management leads to temporal safety violations ● Temporal safety violations lead to crashes, silent data corruption, and severe security vulnerabilities
Issues with Other Methods of Detecting Temporal Violations (e. g. Valgrind Memcheck) ● High runtime overhead ● High memory overhead ● Failure to detect all temporal errors ○ ○ ○ To the stack Reallocated heap addresses Arbitrary casts ● Requiring annotations inserted by the programmer
Program Instrumentation for Detecting Temporal Safety Violation ● ● Binary Instrumentation Hardware-assisted Instrumentation Source-level Instrumentation Compiler-based Instrumentation
CETS Approach ● Compiler-based Instrumentation Apply Optimizations C Program IR of Program Apply pass to insert checks IR with Checks Apply Optimizations Again Optimized IR with Checks
Quick Look at CETS Functionality and Limitations ● Functionality ○ ○ ○ CETS uses compiler based instrumentation. Identifier based scheme, which assigns a unique key for each allocation region to identify dangling pointers Pointers are tracked using disjoint shadowspace (in order not to affect the program memory layout) ● Limitations ○ ○ The method does not detect spatial violations Must be combined with existing spatial safety mechanisms for 100% memory safety.
Lock-and-Key Identifier Based Approach ● CETS augments each pointer with two additional word-sized fields: (1) a unique allocation key and (2) a lock address that points to a lock location data ptr 1 lock ptr 1_key Memory Per Pointer Metadata ptr 1_key lock address Heap Allocation ptr 1 = malloc(size); ptr 1_key = counter++; ptr 1_lock_addr = allocate_lock(); *(ptr 1_lock_addr) = ptr 1_key; freeable_ptrs_map. insert(ptr 1_key, ptr 1);
Lock-and-Key Identifier Based Approach - Cont. ● CETS propagates metadata to newly allocated pointer to the same memory location data ptr 1 ptr 2 lock Key 1 Memory Per Pointer Metadata ptr 1_key lock address Pointer metadata propagation ptr 2 = ptr 1 + offset; ptr 2_key = ptr 1_key ptr 2_lock_addr = ptr 1_lock_addr;
Lock-and-Key Identifier Based Approach - Cont. ● CETS performs dangling pointer check data lock ptr 1_key Memory Per Pointer Metadata ptr 1_key lock address ptr 2 ptr 1_key lock address Dangling pointer check if (ptr 1_key != *(ptr 1_lock_addr) { abort(); } value = *ptr 1; // original load
Lock-and-Key Identifier Based Approach - Cont. ● Heap deallocation actions data lock ptr 1_key Memory Per Pointer Metadata ptr 1_key lock address ptr 2 ptr 1_key lock address Heap deallocation if (freeable_ptrs_map. lookup(ptr 1_key) != ptr 1) { abort(); } freeable_ptrs_map. remove(ptr 1_key); free(ptr 1); *(ptr 1_lock_addr) = INVALID_KEY; deallocate_lock(ptr 1_lock_addr);
Call Stack Based Allocation / Deallocation ● To detect dangling pointers to the call stack, a key and corresponding lock address is also associated with each stack frame ● This key and lock address pair is given to any pointer derived from the stack pointer (and thus points to an object on the stack). void func() { // Function prologue local_key = next_key++; local_lock_addr++; // allocate lock address *(local_lock_addr) = local_key; int var; // local variable ptr = &var; // ptr defined in main ptr_key = local_key; ptr_lock_addr = local_lock_addr; // Function epilogue *(local_lock_addr) = INVALID_KEY; local_lock_addr--; // deallocate lock address }
Benchmarks ● The key advantage of compiler based is being able to perform optimizations before and after we insert instrumentation code ○ No temporal checks are required for any pointer that is directly derived from the stack pointer within the corresponding function call, because the stack frame is guaranteed to live until the function exits ● Functional correctness: CETS successfully detected all temporal errors without false violations. ● Performance: Overall runtime overhead of 48% ○ ○ ○ Compared to 77% with alternate implementation 116% when checking for spatial and temporal 122% overhead from other checker, such as Valgrind Memcheck
CETS Optimizations ● Unnecessary checks ○ ○ Pointers to globals Stack pointers within a function call ● Redundant checks
Conclusion ● CETS is a compiler based temporal safety detection method ● It allows for optimizations pre and post instrumentation code addition ● It doesn’t change the memory layout of the original program (compared with source code instrumentation methods) ● It was run correctly on NIST-SAMATE benchmark and was able to find all temporal errors ● By doing post IR instrumentation optimizations passes it was shown to produce 48% overhead, compared with existing methods of 77%.
Thank You Questions?