Suan Hsi Yong University of Wisconsin – Madison

Slides:



Advertisements
Similar presentations
Automatic Memory Management Noam Rinetzky Schreiber 123A /seminar/seminar1415a.html.
Advertisements

Computer Security: Principles and Practice EECS710: Information Security Professor Hossein Saiedian Fall 2014 Chapter 10: Buffer Overflow.
Lecture 16 Buffer Overflow modified from slides of Lawrie Brown.
INF 212 ANALYSIS OF PROG. LANGS Type Systems Instructors: Crista Lopes Copyright © Instructors.
Hastings Purify: Fast Detection of Memory Leaks and Access Errors.
Chapter 8 Runtime Support. How program structures are implemented in a computer memory? The evolution of programming language design has led to the creation.
SAFECode SAFECode: Enforcing Alias Analysis for Weakly Typed Languages Dinakar Dhurjati University of Illinois at Urbana-Champaign Joint work with Sumant.
May 22, 2002OSQ Retreat 1 CCured: Taming C Pointers George Necula Scott McPeak Wes Weimer
Java Security. Topics Intro to the Java Sandbox Language Level Security Run Time Security Evolution of Security Sandbox Models The Security Manager.
Security Exploiting Overflows. Introduction r See the following link for more info: operating-systems-and-applications-in-
CS 11 C track: lecture 5 Last week: pointers This week: Pointer arithmetic Arrays and pointers Dynamic memory allocation The stack and the heap.
Presentation of Failure- Oblivious Computing vs. Rx OS Seminar, winter 2005 by Lauge Wullf and Jacob Munk-Stander January 4 th, 2006.
Computer Science Detecting Memory Access Errors via Illegal Write Monitoring Ongoing Research by Emre Can Sezer.
Computer Science and Software Engineering University of Wisconsin - Platteville 2. Pointer Yan Shi CS/SE2630 Lecture Notes.
1 Records Record aggregate of data elements –Possibly heterogeneous –Elements/slots are identified by names –Elements in same fixed order in all records.
C++ Memory Overview 4 major memory segments Key differences from Java
C Programming Day 4. 2 Copyright © 2005, Infosys Technologies Ltd ER/CORP/CRS/LA07/003 Version No. 1.0 More on Pointers Constant Pointers Two ways to.
The Fail-Safe C to Java translator Yuhki Kamijima (Tohoku Univ.)
An Undergraduate Course on Software Bug Detection Tools and Techniques Eric Larson Seattle University March 3, 2006.
Pointers in C Computer Organization I 1 August 2009 © McQuain, Feng & Ribbens Memory and Addresses Memory is just a sequence of byte-sized.
A Tool for Pro-active Defense Against the Buffer Overrun Attack D. Bruschi, E. Rosti, R. Banfi Presented By: Warshavsky Alex.
Protecting C Programs from Attacks via Invalid Pointer Dereferences Suan Hsi Yong, Susan Horwitz University of Wisconsin – Madison.
Debugging via Run-Time Type Checking Title Page Alexey Loginov, Suan Yong, Susan Horwitz, Thomas Reps University of Wisconsin - Madison.
1 Lecture07: Memory Model 5/2/2012 Slides modified from Yin Lou, Cornell CS2022: Introduction to C.
VM: Chapter 7 Buffer Overflows. csci5233 computer security & integrity (VM: Ch. 7) 2 Outline Impact of buffer overflows What is a buffer overflow? Types.
Beyond Stack Smashing: Recent Advances In Exploiting Buffer Overruns Jonathan Pincus and Brandon Baker Microsoft Researchers IEEE Security and.
Announcements Assignment 2 Out Today Quiz today - so I need to shut up at 4:25 1.
Chapter 10 Buffer Overflow 1. A very common attack mechanism o First used by the Morris Worm in 1988 Still of major concern o Legacy of buggy code in.
DYNAMIC MEMORY ALLOCATION. Disadvantages of ARRAYS MEMORY ALLOCATION OF ARRAY IS STATIC: Less resource utilization. For example: If the maximum elements.
Language-Based Security: Overview of Types Deepak Garg Foundations of Security and Privacy October 27, 2009.
Memory-Related Perils and Pitfalls in C
Dynamic Allocation in C
Secure Coding Rules for C++ Copyright © 2016 Curt Hill
EECE 309: Software Engineering
Object Lifetime and Pointers
SE-1021 Software Engineering II
Dynamic Storage Allocation
Data Types In Text: Chapter 6.
Computer Organization and Design Pointers, Arrays and Strings in C
CMSC 345 Defensive Programming Practices from Software Engineering 6th Edition by Ian Sommerville.
Storage 18-May-18.
YAHMD - Yet Another Heap Memory Debugger
Types for Programs and Proofs
CSE 374 Programming Concepts & Tools
Module 30 (Unix/Linux Security Issues II)
Type Checking Generalizes the concept of operands and operators to include subprograms and assignments Type checking is the activity of ensuring that the.
Type Checking, and Scopes
Compilers Principles, Techniques, & Tools Taught by Jing Zhang
Seminar in automatic tools for analyzing programs with dynamic memory
CNIT 133 Interactive Web Pags – JavaScript and AJAX
Pointers and Memory Overview
Checking Memory Management
Java Review: Reference Types
Secure Coding Rules for C++ Copyright © Curt Hill
Clear1 and Clear2 clear1(int array[], int size) { int i; for (i = 0; i < size; i += 1) array[i] = 0; } clear2(int *array, int size) {
High Coverage Detection of Input-Related Security Faults
Optimizing Malloc and Free
Object Oriented Programming COP3330 / CGS5409
Chapter 15 Pointers, Dynamic Data, and Reference Types
Runtime Monitoring of C Programs for Security and Correctness
Pointers, Dynamic Data, and Reference Types
Closure Representations in Higher-Order Programming Languages
Pointers.
Pointers The C programming language gives us the ability to directly manipulate the contents of memory addresses via pointers. Unfortunately, this power.
Dynamic Allocation in C
Code-Pointer Integrity
CETS: Compiler-Enforced Temporal Safety for C
COP 3330 Object-oriented Programming in C++
Pointers, Dynamic Data, and Reference Types
SPL – PS2 C++ Memory Handling.
Presentation transcript:

Suan Hsi Yong University of Wisconsin – Madison 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 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 Runtime Type-Checking Conclusion Description Experiments Runtime Type-Checking Conclusion

(Joint work with Susan Horwitz) 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) Our approach, which is targeted for the C language, but is applicable to related languages, is to dynamically detect invalid writes via pointers. When an invalid write is detected, we report an error and halt execution, so that no damage can be done. Our goal is to have the instrumentation be used in deployed code, so we want to be efficient, that is, to incur a low runtime overhead. We also want no false positives; since we’ll halt execution when an error is detected, we don’t want to halt the program on a false error. Finally, we want our technique to apply to legacy code, so we require no changes to the code by the programmer.

Memory Safety Violations a[i]  *(a+i) Pointer dereference to invalid target (‘direct’ memory accesses are OK) Each pointer has well-defined target objects 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 Writes to 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 To illustrate how stack smashing is usually accomplished, here’s an example which we will revisit throughout the talk. We have a buffer of size 12, and a pointer p which points to it. We have a loop which reads input and writes it into buf via *p, incrementing p until a null character is read. Notice that there is no bounds check, [click] so that p may go out of bounds, and eventually overwrite the return address on the stack. An attacker could thus write a suitable value into the return address, so that when the function returns, control is transferred to the attacker’s code. The goal of our tool is to detect invalid writes via pointer dereferences before damage can be done. 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 This is the approach often called fat pointers, where each pointer is associated with information about what it’s supposed to point to, so that at a pointer dereference, the pointer’s address is checked to see if it is within the bounds of the array or object it’s supposed to point to. Two tools that use fat pointers are CCured and Cyclone, which are two variants of the C language designed to be less prone to pointer-related errors and vulnerabilities. The advantage of this approach is that it detects all invalid pointer dereferences. There are several disadvantages, however: Using fat pointers, it’s difficult to detect uses of dangling pointers, so they effectively limit the programmer’s control of memory management; They require the use of garbage collection, or a region-based model, which is a major component of Cyclone These tools don’t handle all of C; that is, they reject some legal C programs, so programmer effort is necessary to port existing programs to these languages.

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 To demonstrate how fat pointers work, we revisit the example from earlier, with the pointer p pointing to buf, potentially going out of bounds and overwriting the return address. A fat pointer is a pointer with additional information associated with it: specifically, the address and size of the referent, or what the pointer is supposed to point to. So in this example, p is supposed to point to buf, so [click] we record this information here, saying that p is supposed to point to buf, which has size 12. Any dereference of p is checked against this information, [click] so an access within bounds is OK, while an out-of-bounds access is flagged as an error.  return address

Tagged Memory associates information with target rather than pointer    Tagged Memory associates information with target rather than pointer p char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’);  buf This brings us to our approach. What we do is, instead of associating information with the pointer, we associate information with the target. Specifically, we maintain a mirror of memory, with one bit for each byte, marking whether it is OK or bad to reference a location via a pointer: shown here, a green check mark indicates an OK location, while a red X indicates the location should not be accessed via a pointer. [click] So this in-bound dereference is OK because the mirror of buf is marked OK while the out-of-bounds dereference is flagged as an error, because the mirror is marked with an X  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?  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  An important question we must consider is: which locations should be marked ‘OK’? Naively, we can mark all user-defined variables, on the stack or the heap, as ‘OK’ when it is allocated, and ‘not OK’ when it is deallocated. In this program, we have two user-defined locations, buf and p, which we mark as ‘OK’. [click] In the first access, *p is marked OK, so the dereference is OK while in the second access, it’s marked not-OK, so the dereference is flagged as an error. Note that the mirror of the return address will always be marked ‘out-of-bounds’, so stack smashing attacks will always be detected.    return address

Which locations are valid?  Which locations are valid? FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();  p  buf   However, consider a modified version of the running example. Here we have a function pointer fp pointing to foo; the middle portion is the same, and after the loop there is a function call via fp In this program, we have three user-defined locations, fp buf and p, which we mark as ‘OK’. [click] Note that if *p goes out of bounds and writes into fp, we will still consider it OK, and not report an error. This is an undesirable behavior, since if an attacker writes a suitable value here, the subsequent call to fp can transfer control to malicious code. [click] So, we want fp to be marked with an X rather than OK. [click] Therefore, we perform static analysis, to reduce the number of locations that we mark as OK.   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) So we introduce the notion of an ‘unsafe pointer’, which is a pointer that may point to invalid memory. That is, a pointer p is unsafe if either p is assigned a non-pointer value, like an integer or a value from input, or p is the result of pointer arithmetic. Recall that we treat an array access a[i] as equivalent to *(a+i), so effectively all array accesses are treated as unsafe pointers. Or, p is unsafe if it may be a dangling pointer, that is, if it may point to freed or deallocated memory. The idea is that only writes via unsafe pointers will be checked at runtime. A dereference of a safe pointer will not be checked, since it can never point to invalid memory. Also, for efficiency, we don’t check reads: We found this to be a good policy, as the most malicious attacks need to write into inappropriate memory. Since there are typically many more reads than writes, this allows our approach to be more efficient, without sacrificing protection.

Example: Unsafe Dereferences FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); Let’s see how our static analysis will work in the running example. In this program, there are two pointers, fp and p. Since p is involved in pointer arithmetic, p++ here, we mark p as an unsafe pointer. fp is safe, since it can only point to a valid location, that is, the function foo. dereferences: *p unsafe *fp safe

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

Example: Tracked Locations FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); So, going back to the example, recall that we have p as an unsafe pointer, and fp a safe pointer. There are three locations in this program: fp, buf, and p. The points-to analysis is trivial: as shown here, p may point to buf, and fp may point to foo. A tracked location is a location that may be pointed to by an unsafe pointer; since p is the only unsafe pointer, and p may point to buf, but is the only tracked location. p and fp can be untracked. p buf fp locations: untracked tracked p buf points-to graph: foo fp dereferences: *p unsafe *fp safe

Example: Tracked Locations  Example: Tracked Locations FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();  p  buf   So, going back to the example, recall that we have p as an unsafe pointer, and fp a safe pointer. There are three locations in this program: fp, buf, and p. The points-to analysis is trivial: as shown here, p may point to buf, and fp may point to foo. A tracked location is a location that may be pointed to by an unsafe pointer; since p is the only unsafe pointer, and p may point to buf, but is the only tracked location. p and fp can be untracked.  fp p buf fp locations: untracked tracked p buf points-to graph: foo fp dereferences: *p unsafe *fp safe 

Tool Overview C source file instru- mented C library Static Analysis Instrumenter C Compiler So here is an overview of our system. We begin with a C source file, and first perform static analysis to identify unsafe pointers and tracked locations. Next, the instrumenter takes the C source file and information about unsafe pointers and tracked variables, and generates the instrumented program. The instrumented code is in C, and is compiled by a regular C compiler to generate the instrumented executable. [click] For accurate coverage, the instrumented program needs to be linked with instrumented versions of the C library function. unsafe dereferences tracked variables instru- mented C source file instru- mented exec- utable

Outline Introduction Security Tool Runtime Type-Checking Conclusion Description Experiments Runtime Type-Checking 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 As a first experiment, we downloaded two Linux programs with known root exploits, and simulated attacks to gain root access The first is the c-finger daemon, which has a standard buffer overrun attack. The second is a sophisticated attack on traceroute, which has a multiple-free bug: that is, the program attempts to free a location that has already been freed. With a very clever method the attack program uses the erroneous call to free to modify an entry in the global offset table, which stores the address of library calls, so that a subsequent library function call will transfer control to the attacker’s code, rather than to the expected library function. Both of the attacks failed when running our instrumented version of the programs, with the invalid pointer dereference being reported, and the program halting.

Runtime Overhead Flow-insensitive: 57% Flow-sensitive: 51% Cyclone (340%) Cyclone Olden Spec 95 Spec 2000

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 increasing size (LOC)

Checking Reads and Writes Runtime overhead: 2.6x slowdown Cyclone Olden Spec 95 Spec 2000

Comparing with CCured/Cyclone Next, we compare the performance of our tool with CCured and Cyclone, the two tools most closely related to ours that use fat pointers. We ran these on some programs from the Cyclone benchmark. On average, our tool ran about twice as slow, CCured almost 5 times, and Cyclone about 1½ times. Since this is far from an exhaustive comparison, the thing to take away from this graph is that our performance is reasonably comparable to these other tools, and we have the advantage of supporting legacy C code with, while both CCured and Cyclone required some programmer intervention to compile. In fact, the Cyclone bars are from the Cyclone versions of the programs they distribute, which has been manually converted to Cyclone from C, which may account for its better performance.

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 In conclusion, then, We have implemented a tool for detecting invalid pointer dereferences that has a low runtime overhead; it doesn’t report false positives; it is portable, and doesn’t require programmer changes to source code; and it guards against a wide range of vulnerabilities: stack smashing, accessing freed memory, and others as well.

Outline Introduction Security Tool Runtime Type-Checking Conclusion Description Experiments Runtime Type-Checking Conclusion

(Joint work with Susan Horwitz, Alexey Loginov, Tom Reps) 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

Example 2: Bad Pointer Access int *i, sumEven = 0; for(i = intArray; ...; i += 2) sumEven += *i; i User memory intArray ptrArray ORIGINAL we find same error as purify, in different way i intArray ptrArray padding PURIFY 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];

Example 3: User Memory Management int *intArray = (int *) myMalloc(10 * sizeof(int)); int **ptrArray = (int **) myMalloc(10 * sizeof(int *)); i User memory intArray ptrArray ORIGINAL we find bug that purify doesn’t myMemory i User memory myMemory PURIFY

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

Runtime Type Checking Tool (memory) (mirror) k . . . unalloc f p int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k );

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit f . . . unalloc p . . . unalloc instrument a declaration

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit f . . . unalloc uninit p . . . unalloc instrument a declaration

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit f . . . unalloc 2.3 float uninit p . . . unalloc uninit instrument an assignment

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit f . . . unalloc 2.3 float uninit p . . . unalloc ( &f ) uninit pointer instrument an assignment

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit f . . . unalloc 2.3 OK float uninit p . . . unalloc ( &f ) pointer uninit instrument a use (of a pointer)

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc uninit OK f . . . unalloc 2.3 float uninit p . . . unalloc ( &f ) pointer uninit instrument a pointer dereference

Runtime Type Checking Tool (memory) (mirror) int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); warning! k . . . unalloc 2.3 float uninit f . . . unalloc 2.3 float uninit p . . . unalloc ( &f ) uninit pointer instrument an assignment

Runtime Type Checking Tool (memory) (mirror) error! int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); k . . . unalloc 2.3 uninit float f . . . unalloc 2.3 float uninit p . . . unalloc ( &f ) uninit pointer instrument a use (of an int)

Runtime Type Checking Found errors in SPEC 95 and Olden benchmarks, and Solaris utilities But, overhead is high 3x-80x slowdown, average 37x Use Static Analysis Flow insensitive: Type Safety Levels Flow sensitive: redundant checks, may-be-uninitialized

Type Safety Levels Analysis Classify program expressions into: safe: runtime type always equals static type no instrumentation needed unsafe: runtime type may not equal static type must be fully instrumented tracked: runtime type always equals static type, but may be pointed-to by unsafe dereference must initialize mirror to static type

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels > >>

Determining Type-Safety Levels Compute points-to set for each pointer Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels pt p {f}

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels x x 5.0 VALUEfloat VALUEzero &i VALUEvalid-ptr int nodes

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels *p *p y+z VALUEint p+i VALUEptr Assume nomalized code: only *p, not **p. Distinguish between “ptr” and “valid-ptr”. &i VALUEvalid-ptr nodes

Determining Type-Safety Levels x = y; Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels x y = *p = 5.0; VALUEfloat = *p x = y + z; VALUEint = x edges

Determining Type-Safety Levels T = “uninitialized” Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels valid-pointer zero pointer int char float …  |  = “multiply-typed”

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels float VALUEfloat T(x) T(y) T(y) x y =

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels T(*p) T(k) {k} p pt *p y = *p {k} p pt k T(k) T(y)

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels runtime-type(x) static-type(x)  x unsafe int k; float f = 2.3; int *p = &f; k = *p; } runtime-type(k) = float k unsafe static-type(k) = int

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels runtime-type(x) static-type(x)  x unsafe int k; float f = 2.3; int *p = &f; k = *p; } runtime-type(*p) = float *p unsafe static-type(*p) = int

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels 2. runtime-type(p) ≠ valid-ptr  *p unsafe int *p = &k; p = p + 1; *p = 5;  *p unsafe

Determining Type-Safety Levels Points-to Analysis Build Assignment Graph Compute Runtime Types Compute Type-Safety Levels 3.*p unsafe,  x tracked {x} p pt int k; float f = 2.3; int *p = &f; k = *p; } *p unsafe f tracked p may-point-to f

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); {f} p pt 1. Points-to analysis >>

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); VALfloat f = VALvalid-ptr p = *p k = {f} p pt 2. Assignment graph

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); float float VALfloat f = valid-ptr valid-ptr VALvalid-ptr p = float float *p k = T(*p) T(f) {f} p pt 3. Runtime types

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); float tracked f valid-ptr safe p float unsafe k float unsafe *p {f} p pt 4. Type-safety levels

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); tracked f safe p unsafe k unsafe *p Instrumentation: w/o using type-safety levels

Type-Safety Levels: Example int k; float f; int *p; f = 2.3; p = (int *)&f; k = *p; print_int( k ); tracked f safe p unsafe k unsafe *p Instrumentation: using type-safety levels

Outline Introduction Security Tool Runtime Type-Checking Conclusion Description Experiments Runtime Type-Checking Conclusion

Runtime Overhead Unoptimized (37x) Optimized (23x)

Outline Introduction Security Tool Runtime Type-Checking Conclusion Description Experiments Runtime Type-Checking Flow-insensitive Analysis Flow-sensitive Analyses Results Conclusion Here’s an outline of my talk. I’ve given a brief introduction. I’ll now describe some related approaches, which will lead into a description of our approach. I’ll then give some experimental results before concluding.

Conclusion Two applications of runtime monitoring Security tool: efficient Runtime type-checking: for debugging Effective in detecting errors Static analysis to improve runtime overhead Flow-insensitive: type-safety levels Flow-sensitive refinements

Future Work Better static analysis: fewer unsafe and tracked locations escape analysis / scope analysis better points-to analysis Different mechanism for tagging e.g. hash lookup Apply ideas to other languages and environments e.g. absorb overhead on multiprocessor

Related Work Run-time Error Detection Reducing Instrumentation Purify, Insure++, Valgrind Safe C, CCured, Cyclone Safe languages (Java), dynamically-typed languages (Lisp) Reducing Instrumentation Java (remove array-bounds checks) Lisp and Scheme (remove tag-handling operations, e.g., Henglein) CCured (remove pointer checks: Necula et al)

Suan Hsi Yong University of Wisconsin – Madison Efficient Runtime Monitoring of C Programs for Security and Correctness END Suan Hsi Yong University of Wisconsin – Madison Good Afternoon. My name is Suan Yong, and I’ll be presenting our work on “Protecting C Programs from Attacks via Invalid Pointer Dereferences”, which is joint work with Susan Horwitz at the University of Wisconsin – Madison.

Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0];  Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); So I’ll now demonstrate what our tool would do when running this program. Initially, the mirror of all memory is implicitly marked not-OK. This is how our tool works: by default a location is not-OK unless it is explicitly marked OK. p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0];  Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); In the first line, when fp is allocated, since fp is untracked, we leave the mirror of fp as ‘bad’. Note that this is OK because fp should never be accessed via an unsafe pointer dereference. fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0];  Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();  buf - In the second line, when buf is allocated, since buf is tracked, we mark its mirror as OK.  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0];  Example: Allocation FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p  buf - Next, when p is allocated, since p is untracked, its mirror is left as not-OK.  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Checking Writes  Example: Checking Writes FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p   buf - The first time through the loop, since we’re writing into an unsafe pointer dereference *p, we check its mirror and find it to be OK.  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Checking Writes  Example: Checking Writes FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p  buf We increment p…  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Checking Writes  Example: Checking Writes FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)(); p  buf  … and again find *p to be marked OK.  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp

Example: Checking Writes  Example: Checking Writes FN_PTR fp = &foo; char buf[12]; char *p = &buf[0]; do { *p = getchar(); } while(*p++ != ‘\0’); (*fp)();  p  buf  This process continues until p goes out of bounds and points to fp. Now, when trying to write to fp, we find that the mirror of *p is marked ‘bad’, so we flag this as an error, and halt the program, to prevent the potential exploit from doing damage.  fp p buf fp locations: untracked tracked pointers: unsafe safe p fp 

Back