confidence 2014: andreas bogk: preventing violation of memory safety in c/c++ software
DESCRIPTION
The hacker community has a lot of words for situations in which access to the wrong part of memory leads to an exploitable vulnerability: buffer overflow, integer overflows, stack smashing, heap overflow, use-after-free, double free and so on. Different words are used because the techniques to trigger the faulty memory access and to subsequently use that to gain code execution vary, but they all share a common root cause: violation of spatial and temporal memory safety. If one looks at the C/C++ standard, the situations that tend to be exploitable are “unspecified”. Usually, compiler writers take that as an excuse to cut corners, to gain that extra bit of performance in the benchmarks. Because, you know, who cares you’re exploitable when you make a mistake, look how fast it is! However, the standards also allow the compiler to introduce safety checks, to see whether access to a pointer actually touches the inside of an allocated object instead of the outside (spatial memory safety), and to make sure that the pointer being accessed actually points to an object that has been allocated, but not yet been freed again (temporal memory safety). Such compilers do exist, in the form of LLVM with specialized optimizer passes that introduce runtime safety checks. This talk will look into the details of the implementation, the performance impact, practical handling, and of course, whether it really delivers the promised 100% protection against buffer overflows.TRANSCRIPT
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Applying science to eliminate 100% of buffer overflows
CONFidence, Krakow, 2014Andreas Bogk, [email protected], @andreasdotorg
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
The problem
void foo (char* arg) {
char some[16];
char* p = some;
while (*p++ = *arg++);
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
The problem: spatial memory safety
void foo (char* arg) {
char some[16];
char* p = some;
while (*p++ = *arg++);
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
The other problem
void bar (char* arg) {
char* p = malloc(16);
free(p);
while (*p++ = *arg++);
free(p);
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
The other problem:temporal memory safety
void bar (char* arg) {
char* p = malloc(16);
free(p);
while (*p++ = *arg++);
free(p);
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Existing approaches
● Use a safe language!
● Mitigations: ASLR, DEP, stack canaries
● Memory debugging tools:
– Valgrind
– gcc and llvm memory sanitizer
– SAFEcode
– Ccured
– SafeC
– Cyclone
– Etc...
–
–
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
A word on notation
red.is>compiler_generated(code);
black.is>user_written(code);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Object-based approach
if (IsPoisoned(address)) {
ReportError(address);
}
*address = ...; // or: ... = *address;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Intrastructural safety
struct {
char id[8];
int account_balance;
} bank_account;
char* ptr = &(bank_account.id);
strcpy(ptr, "overflow...");
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Pointer-based approach
● Every pointer represented by three words: pointer value, base, bound
● We call that a „fat pointer“
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Meet SoftBoundCETS
● Santosh Nagarakatte, Jianzhou Zhao, Milo M. K. Martin, Steve Zdancewic; UPenn
● SoftBound: spatial safety
● CETS: temporal safety
● Uses disjoint fat pointers
● Proof of correctness (but caveat emptor)
● Implemented as LLVM optimizer pass
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Meet SoftBoundCETS
● Source compatibility
● Complete coverage
● Separate compilation
● Low overhead
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: instrumenting dereference
check(ptr, ptr_base, ptr_bound,
sizeof(*ptr));
value = *ptr;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: implementation of check
void check(ptr, base, bound, size) {
if ((ptr < base)
|| (ptr + size > bound)) {
abort();
}
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
„Beware of bugs in the above code; I have only proved it correct, not tried it.“
– Donald E. Knuth
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: correct implementation of check
void check(ptr, base, bound, size) {
if ((ptr < base)
|| (ptr + size > bound)
|| (ptr + size < ptr)) {
abort();
}
}
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: memory allocation
ptr = malloc(size);
ptr_base = ptr;
ptr_bound = ptr + size;
if (ptr == NULL) ptr_bound = NULL;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: stack allocation
int array[100];
ptr = &array;
ptr_base = &array[0];
ptr_bound = ptr_base +
sizeof(array);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: Pointer arithmetic
newptr = ptr + index;
// or &ptr[index]
newptr_base = ptr_base;
newptr_bound = ptr_bound;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: narrowing
struct { ... int num; ... } *n;
...
p = &(n>num);
p_base = max(&(n>num), n_base);
p_bound = min(p_base + sizeof(n>num),
n_bound);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: more narrowing
struct { ... int arr[5]; ... } *n;
...
p = &(n>arr[2]);
p_base = max(&(n>arr), n_base);
p_bound = min(p_base + sizeof(n>arr),
n_bound);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: loading metadata
int** ptr;
int* new_ptr;
...
check(ptr, ptr_base, ptr_bound,sizeof(*ptr));
newptr = *ptr;
newptr_base = table_lookup(ptr)>base;
newptr_bound = table_lookup(ptr)>bound;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: storing metadata
int** ptr;
int* new_ptr;
...
check(ptr, ptr_base, ptr_bound, sizeof(*ptr));
(*ptr) = new_ptr;
table_lookup(ptr)>base = newptr_base;
table_lookup(ptr)>bound = newptr_bound;
●
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: augmenting function calls
int func(char* s)
{ ... }
int value = func(ptr);
int func(char* s,void* s_base,void* s_bound)
{ ... }
int value = func(ptr, ptr_base, ptr_bound);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Spatial: loose ends
● Global variables
● Separate compilation and library code
● Memcpy()
● Function pointers
● Creating pointers from integers
● Arbitrary casts and unions
● Variable argument functions
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: allocation
ptr = malloc(size);
ptr_key = next_key++;
ptr_lock_addr = allocate_lock();
*(ptr_lock_addr) = ptr_key;
freeable_ptrs_map.insert(ptr_key, ptr);
●
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: free
if (freeable_ptrs_map.lookup(ptr_key) != ptr) {
abort();
}
freeable_ptrs_map.remove(ptr_key);
free(ptr);
*(ptr_lock_addr) = INVALID_KEY;
deallocate_lock(ptr_lock_addr);
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: dereference check
if (ptr_key != *ptr_lock_addr) { abort(); }
value = *ptr;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: pointer arithmetic
newptr = ptr + offset;
// or &ptr[index]
newptr_key = ptr_key;
newptr_lock_addr = ptr_lock_addr;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: propagating metadata
int** ptr;
int* newptr;
if (ptr_key != *ptr_lock_addr) { abort(); }
newptr = *ptr;
newptr_key = table_lookup(ptr)>key;
newptr_lock_addr = table_lookup(ptr)>lock_addr;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: propagating metadata
int** ptr;
int* newptr;
if (ptr_key != *ptr_lock_addr) { abort(); }
(*ptr) = newptr;
table_lookup(ptr)>key = newptr_key;
table_lookup(ptr)>lock_addr = newptr_lock_addr;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: globals
int var; // global variable
ptr = &var;
ptr_key = GLOBAL_KEY;
ptr_lock_addr = GLOBAL_LOCK_ADDR;
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Temporal: loose ends
● Threads. Shared state is evil. Zalgo, etc.
● Shared memory. Shared state... see above.
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Own contribution
● Introduced two function attributes to control instrumentation process
● Ported SoftBoundCETS to FreeBSD
● Instrumented FreeBSD libc and executable startup code
● Deleted ton of now useless wrappers
● Goal: build all of FreeBSD world with safe memory access
● Status: PoC works!
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Demo Time!
● Everybody loves a good demo!
● Sufficiently advanced technology is indistinguishable from a properly rigged fake.
TOP SECRET//HCS/SI/TK//ORCON/NOFORN
Thanks and references
● SoftBoundCETS: http://www.cs.rutgers.edu/~santosh.nagarakatte/softbound/
● SoftBoundCETS on FreeBSD: https://github.com/andreas23/freebsd/tree/softbounds
● Many thanks to Hannes Mehnert for joint work on FreeBSD port
● Many thanks to Santosh Nagarakatte, Milo M. K. Martin, Steve Zdancewic for answering questions and providing access to beta versions of code