860 likes | 1.02k Views
Efficient Runtime Monitoring of C Programs for Security and Correctness. Suan Hsi Yong University of Wisconsin – Madison. Program Errors. incorrect results, crash system, corrupt data security vulnerabilities buffer overrun, stale pointers, format strings difficult to detect
E N D
Efficient Runtime Monitoringof C Programsfor Security and Correctness Suan Hsi Yong University of Wisconsin – Madison
Program Errors • incorrect results, crash system, corrupt data • security vulnerabilities • buffer overrun, stale pointers, format strings • difficult to detect • Static Tools: slow, imprecise • Testing/Debugging: incomplete coverage • Runtime Detection: high overhead
Incorrect Behavior in C Programs • C specifications defines ‘correct’ and ‘incorrect’ behaviors • syntax and type system not strong enough to statically ensure correct behavior • easy to write program with incorrect behavior • Ensuring correct behavior at runtime is complicated: • expensive • restricts flexibility (e.g. ‘well-typed’) • changes expected behavior (e.g. garbage collection)
Efficient Runtime Monitoring • Security tool [FSE’03] • Detect invalid pointer dereferences • Efficient: for use in deployed code • Preserves flexibility: can apply to legacy code • Runtime Type-Checking [FASE’01, RV’02, FMSD’04] • Report type errors • Heavyweight debugging tool
Outline • Introduction • Security Tool • Description • Experiments • Runtime Type-Checking • Description • Experiments • Conclusion
Security Tool (Joint work with Susan Horwitz) • Enforce memory safety at runtime • halt execution when detected • Efficient (low overhead) • No false positives • No source code modification needed • Portable (source level instrumentation)
Memory Safety Violations a[i] *(a+i) • Pointer dereference to invalid target • (‘direct’ memory accesses are OK) • Each pointer has well-defined targetobjects it can validly point to • Example violations: buffer overrun, stale pointer • Invalid write more dangerous than read • Write exploits: can corrupt data, gain control • Read exploits: obtain confidential data • For efficiency: by default we only check writes; but can check reads also
Exploiting Invalid Writesto gain control of program • Idea: overwrite vulnerable location with address of malicious code • Vulnerable locations include • return address (stack smashing) • function pointer • setjmp buffer • global offset table • others…
Stack Smashing char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); p buf return address
Preventing Attack • Protect vulnerable location(s) only • StackGuard: return address only • efficient, automatic • platform dependent, doesn’t protect other locations • Prevent all invalid accesses • Check each dereference to see if target is valid • Fat Pointers • Tagged Memory (our approach)
Fat Pointers • Record information about what a pointer should point to • CCured, Cyclone • detects all invalid accesses • less flexibility in memory management:requires garbage collection(or region-based model, for Cyclone) • doesn’t handle all of C(rejects some legal C programs) • requires programmer changes to source
Fat Pointers • associate information with pointer: address and size of referent p buf 12 char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); buf return address
Tagged Memory • associates information with target rather than pointer p char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); buf return address
Fat Pointer vs. Tagged Memory • Fat Pointers • Guaranteed to catch all invalid accesses • Difficult to implement flexibly and efficiently • Casting; memory management • Tagged Memory • Can be flexible and efficient • Guaranteed only to catch invalid accesses to non-user memory • May catch more invalid accesses with better static analysis
Which locations are valid? • naively: all user-defined locations are valid char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); p buf return address
Which locations are valid? FN_PTRfp= &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p buf fp • Static Analysis, to mark fewer locations as ‘valid’
Static Analysis • Unsafe dereferences: may refer to invalid memory • only unsafe dereferences are checked at runtime • safe dereferences are not checked • Tracked locations: may be validly accesssed via unsafe dereference • mirror is marked valid () when allocated,and invalid () when freed. • untracked locations always invalid ():never a target of unsafe dereference
Unsafe Dereferences • Writes Only vs. Read/Write • Flow-insensitive analysis: • *p is unsafe if: • p is assigned a non-pointer value, or • p is the result of pointer arithmetic, or • p may point to stack or freed heap location • Flow-sensitive (dataflow) analysis • Redundant Checks • Array Range (Interval) Analysis a[i] *(a+i)
dereferences: *p unsafe *fp safe Example: Unsafe Dereferences FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();
Tracked Locations • locations that may be validly accessed via unsafe dereference • fewer tracked locations means better performance and coverage • less overhead to mark and clear ‘valid’ tag • increase likelihood of catching invalid access • identify with points-to analysis [Das’00] • for each unsafe dereference *p,all locations in p’s points-to set are tracked
locations: dereferences: points-to graph: untracked *p unsafe p p fp untracked *fp safe fp tracked buf foo buf Example: Tracked Locations FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();
locations: dereferences: points-to graph: untracked *p unsafe p p fp untracked *fp safe fp tracked buf foo buf Example: Tracked Locations FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p buf fp
Tool Overview Csourcefile instru-mentedClibrary Static Analysis Instrumenter CCompiler unsafe dereferences tracked variables instru-mentedC sourcefile instru-mentedexec-utable
Outline • Introduction • Security Tool • Description • Experiments • Runtime Type-Checking • Description • Experiments • Conclusion
Experiments (Checking Writes) • Tool successfully detected two simulated attacks via known vulnerabilities • cfingerd: buffer overrun attack • traceroute: modifying Global Offset Table entry via multiple-free bug
Runtime Overhead Flow-insensitive: 57% Flow-sensitive: 51% (340%) Cyclone Olden Spec 95 Spec 2000
increasing size (LOC) Runtime Overhead Flow-insensitive: 57% Flow-sensitive: 51% (340%)
increasing size (LOC) Analysis/Instrument/Compile Time • Slowdown vs. Compile Time Flow-sensitive: 18.5x Flow-insensitive: 3.4x 98x 380x 16x
Checking Reads and Writes Runtime overhead: 2.6x slowdown Cyclone Olden Spec 95 Spec 2000
Summary: Security Tool • Tool for detecting invalid pointer dereferences that • has low runtime overhead • does not report false positives • is portable, and does not require programmer changes to source code • protects against a wide range of vulnerabilities, including stack smashing and using freed memory
Outline • Introduction • Security Tool • Description • Experiments • Runtime Type-Checking • Description • Experiments • Conclusion
Runtime Type Checking (Joint work with Susan Horwitz, Alexey Loginov, Tom Reps) • Debugging tool, for use during development/testing • Higher overhead acceptable (~20x) • Related tools: Purify, Insure++, Valgrind • Goal is to detect runtime type violations • value of one type is used in context of incompatible type • Scalar types only (Structs and array broken down into components)
Example 1: Unions union U { int u1; int *u2; } u; int *p; u.u1 = 20; // write int into u.u1 p = u.u2; // copy int from u.u2 -- suspicious! *p = 0; // bad pointer deref -- error!
Example 2: Bad Pointer Access int *intArray = (int *) malloc(15 * sizeof(int)); int **ptrArray = (int **) malloc(15 * sizeof(int *)); User memory intArray ptrArray
intArray ptrArray i intArray ptrArray padding PURIFY Example 2: Bad Pointer Access int *i, sumEven = 0; for(i = intArray; ...; i += 2)sumEven += *i; i User memory ORIGINAL User memory
Example 3: User Memory Management char * myMalloc(size_t size) { static char *myMemory, *current; ... if(first_time){ myMemory = (char *) malloc(BLKSIZE); } ... return &myMemory[current += size]; }
i i PURIFY myMemory Example 3: User Memory Management int *intArray = (int *) myMalloc(10 * sizeof(int)); int **ptrArray = (int **) myMalloc(10 * sizeof(int *)); User memory intArray ptrArray ORIGINAL myMemory User memory
Example 4: Simulated Inheritance struct Base { int a1; int a2; } base; struct Sub { int b1; int b2; char b3; } sub; void f(struct Base *s) { s->a1 = ... s->a2 = ... } : f(&base); f(&sub); :
Example 4: Simulated Inheritance struct Base { int a1; int a2; } base; struct Sub { int b1; float f1; int b2; char b3; } sub; void f(struct Base *s) { s->a1 = ... s->a2 = ... } : f(&base); f(&sub); :
Runtime Type-Checking • Track type of values in memory • unalloc, uninit, integral, real, pointer, zero • store in mirror of memory: 4 bits for each byte • Warning when bad runtime type is written into a location • Error when bad runtime type is used
(memory) (mirror) k . . . unalloc f . . . unalloc p . . . unalloc Runtime Type Checking Tool int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k );
k . . . unalloc f . . . unalloc p . . . unalloc Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit instrument a declaration
k . . . unalloc uninit f . . . unalloc p . . . unalloc uninit Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit instrument a declaration
k . . . unalloc f . . . unalloc p . . . unalloc Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit uninit float 2.3 uninit instrument an assignment
k . . . unalloc f . . . unalloc p . . . unalloc Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit uninit float 2.3 ( &f ) uninit pointer instrument an assignment
k . . . unalloc f . . . unalloc p . . . unalloc OK Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit uninit float 2.3 ( &f ) uninit pointer instrument a use (of a pointer)
k . . . unalloc f . . . unalloc p . . . unalloc OK Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); uninit uninit float 2.3 ( &f ) uninit pointer instrument a pointer dereference
k . . . unalloc f . . . unalloc p . . . unalloc warning! Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); 2.3 float uninit uninit float 2.3 ( &f ) uninit pointer instrument an assignment
k . . . unalloc error! f . . . unalloc p . . . unalloc Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); 2.3 uninit float uninit float 2.3 ( &f ) uninit pointer instrument a use (of an int)