39
Introductio n to Runtime Analysis Ben Livshits Based in part of Stanford class slides from http://www.stanford.edu/class/cs295 / and slides from Ben Zorn’s talk slides

Ben Livshits Based in part of Stanford class slides from and slides from Ben Zorn’s talk slides

Embed Size (px)

Citation preview

Introduction to

Runtime Analysis

Ben Livshits

Based in part of Stanford class slides from http://www.stanford.edu/class/cs295/and slides from Ben Zorn’s talk slides

2

My Lectures• Lecture 1: Introduction to static

analysis

• Lecture 2: Introduction to runtime analysis

• Lecture 3: Applications of static and runtime analysis

reliability & bug finding

securityperformance

3

Primary Focus: Runtime Monitoring•Purify for finding memory errors [1992]

•Detecting memory leaks with Purify and GC [1992]

•Detecting dangling pointers & buffer overruns with DieHard [2006]

•Detecting buffer overruns with StackGard [1997]

4

Memory Errors in C/C++• Dangling pointers: If the program mistakenly frees a live object,

the allocator may overwrite its contents with a new object or heap metadata.

• Buffer overflows: Out-of-bound writes can corrupt the contents of live objects on the heap.

• Heap metadata overwrites: If heap metadata is stored near heap objects, an out-of-bound write can corrupt it.

• Uninitialized reads: Reading values from newly-allocated or unallocated memory leads to undefined behavior.

• Invalid frees: Passing illegal addresses to free can corrupt the heap or lead to undefined behavior.

• Double frees: Repeated calls to free of objects that have already been freed cause freelist-based allocators to fail.

5

FINDING MEMORY ERRORS IN C/C++ PROGRAMS

Purify

6

Purify• C/C++ are not memory-safe•Neither the compiler nor the runtime system enforces type abstractions•What is memory-safe vs. type-safe?

• Possible to read or write outside of your intended data structure•Among other bad behaviors•What else is possible that you can’t do in Java or Scheme or ML or F#?

7

Memory State• Each byte of memory is in one of 3 states:•Unallocated: cannot be read or written•Allocated but uninitialized: cannot be read•Allocated and initialized: anything goes

8

Memory State: Automaton per Byte• Check the state of each byte on each

access• Binary instrumentation• Add code before each load and store

• Represent states as giant array• 2 bits per byte of memory•What is the memory overhead?• 25%!!• Catches byte-level errors•Won’t catch bit-level errors

9

What Can We Detect?• We can only detect bad accesses if

they are to unallocated or uninitialized memory•Try to make all bad accesses be of those two forms•We can make this part of our custom memory allocator

10

Red Zones and Aging Freed Memory

• Red Zones•Leave buffer space between allocated objects that is never allocated•Guarantees that walking off the end of an array accesses unallocated memory

• Aging Freed Memory•When memory is freed, do not reallocate immediately•Helps catch dangling pointer errors

11

Summary of Purify• One of the first

commercially successful runtime tools

• Was an independent company that got bought by IBM Rational

• Overhead can vary from 25% to 40x

12

Memory Checking Tools Compared

• This is where buffer overruns come from!

• Why can’t we catch them?

13

PROGRESS & OPEN PROBLEMS

Memory leak detection

14

Memory Leak Detection• Memory leaks are at least as serious as

memory corruption errors• Also very difficult to find•Manifest only over hours, days, weeks• Often persist in production code•Managed languages such as Java and C# don’t really help

15

How Do We Find Memory Leaks?

• We can find many memory leaks using techniques borrowed from garbage collection • Any memory with no pointers to it is leaked• There is no way to free this memory

• Run a garbage collector• But don’t free any garbage• Just detect the garbage• Any inaccessible memory is leaked memory• Can we do this in C/C++ at all? Sort of…

16

But How Do We Do This in C/C++?• It is sometimes hard to tell what is

accessible in a C/C++ program?

• Cases• No pointers to a malloc’d block: definitely garbage

• No pointers to head of a malloc’d block: maybe garbage

• Pointers to the head of a malloc’d block: not garbage by usual definition

17

How Do We Do Detection?• From time to time, run a garbage collector• Use mark and sweep• Report areas of memory that are definitely or probably

garbage

• No type safety ==> no memory safety• Is this as easy as in Java?

• Bookkeeping• Need to report who malloc’d the blocks originally• Store this information in the red zone between objects

• Used in Purify, but watch out for memory overhead

18

But This Only Works for C/C++

• A Limitation• Only finds leaks to unreachable objects• Doesn’t cover leaks in languages with GC

• Retaining data structures longer than needed• In practice, also a serious source of leaks, especially in Java . . .

19

Memory Leaks in Java• Look for objects not

accessed for a “long time”. For each object• Track it from the moment it

is allocated• Record the time of the last

access (read or write)• Discard information when

object is de-allocated

• Periodically• Scan all objects• Warn about objects unused

for a “long time”

20

TOLERATING MEMORY ERRORS AT RUNTIME

DieHard

• Buffer overflow

char *c = malloc(100);c[101] = ‘a’;

• Dangling reference

char *p1 = malloc(100);char *p2 = p1;

free(p1);p2[0] = ‘x’;

a

Focus on Heap Memory Errors

22

c

0 99

p1

0 99

p2

x

Research Vision• Increase robustness of

installed code base• Potentially improve

millions of lines of code• Minimize effort – ideally

no source mods, no recompilation

• Reduce requirement to patch• Patches are expensive

(detect, write, deploy)• Patches may introduce

new errors

• Trade resources for robustness• E.g., more memory implies

higher reliability

• Make deployment easy• Change the allocator DLL, no

changes to code needed

• Make existing programs more fault tolerant• Define semantics of programs

with errors• Programs complete with

correct result despite errors

23

DieHard: Probabilistic Memory Safety• Emery D. Berger and Benjamin G. Zorn,  "DieHard: Probabilistic Memory

Safety for Unsafe Languages", PLDI’06

• DieHard: correct execution in face of errors with high probability

• Plug-compatible replacement for malloc/free in C lib• Define “infinite heap semantics”• Programs execute as if each object allocated with unbounded memory• All frees ignored

• Approximating infinite heaps: 3 key ideas1. Overprovisioning2. Randomization3. Replication

• Allows analytic/probabilistic reasoning about safety

24

Overprovisioning, Randomization

25

Expand size requests by a factor of M (e.g., M=2)

1 2 3 4 5

1 2 3 4 5

Randomize object placement

12 34 5

Pr(write corrupts) = ½ ?

Pr(write corrupts) = ½ !

Replication (optional)

26

Replicate process with different randomization seeds

1 234 5

P2

12 345

P3

input

Broadcast input to all replicas

Compare outputs of replicas, kill when replica disagrees

1 23 45

P1

Voter

DieHard Implementation Details• Allocation• Segregate objects by size

(log2), bitmap allocator

• Within size class, place objects randomly in address space

• Separate metadata from user data

• Fill objects with random values – for detecting uninitialized reads

• De-allocation• Expansion factor => frees deferred• Extra checks for illegal free

27

DieHard CPU Performance (no replication)

cfrac espresso lindsay p2c roboop Geo. Mean0

0.2

0.4

0.6

0.8

1

1.2

1.4

Runtime on Windows

malloc DieHard

Nor

mal

ized

runti

me

28

Correctness Results• Synthetic:• Tolerates high rate of synthetically injected errors in SPEC programs

• Spec benchmarks:• Detected two previously unreported benign bugs (197.parser and espresso)

• Avoiding real errors:• Successfully hides buffer overflow error in Squid web cache server (v 2.3s5)• Avoids dangling pointer error in Mozilla • DoS in glibc & Windows

29

30

AVOIDING SECURITY EXPLOITS

StackGuard

31

Aleph One Fires The Opening Shot• “Smashing the

Stack for Fun and Profit”• Aleph One (AKA Elias

Levy), Phrack 49, August 1996

• It is a cook book for how to create exploits for “stack smashing” attacks

• Prior to this paper, buffer overflow attacks were known, but not widely exploited• “Validate all input

parameters” is a security principle going back to the 1960s

• After this paper, attacks became rampant• Stack smashing vulns are

massively common, easy to discover, and easy to exploit

32

What is a “Stack Smash”?• Buffer overflow:• Program accepts string

input, placing it in a buffer• Program fails to correctly

check the length of the input• Attacker gets to overwrite

adjacent state, corrupting it

• Stack Smash:• Special case of a buffer

overflow that corrupts the activation record

33

What is a “Stack Smash”?• Return address• Overflow changes it to point somewhere else

• “Shell Code”• Point to exploit code that was encoded as CPU instructions in the attacker’s string• That code does exec(“/bin/sh”) hence “shell code”

34

Why? Et pourquoi pas?• Why are we so vulnerable

to something so trivial?• Because C chose to represent

strings as null terminated instead of (base, bound) tuples• Because strings grow up and

stacks grow down• Because we use

Von Neumann architectures that store code and data in the same memory

• But these things are hard to change … mostly

• Try to move away from Von Neumann architecture by making key regions of memory be non-executable

• Problem: x86 memory architecture does not distinguish between “readable” and “executable” per page

35

Non-Executable Stack, 1997• “Solar Designer” introduces the Linux non-

executable stack patch• Fun with x86 segmentation registers maps the stack

differently from the heap and static data• Results in a non-executable stack

• Effective against naïve Stack Smash attacks• Bypassable:• Inject your shell code into the heap (still executable)• Point return address at your shell code in the heap

36

StackGuard, 1998• Compile in integrity

checks for activation records• Insert a “canary word” (after the Welsh miner’s canary)

• If the canary word is damaged, then your stack is corrupted• Instead of jumping to attacker code, abort the program• Log the intrusion attempt

37

StackGuard Prototype• Written in a few days by one intern• Less than 100 lines of code patch to GCC• Helped a lot that the GCC function preamble and function

post amble code generator routines were nicely isolated

• First canary was hardcoded 0xDEADBEEF• Easily spoofable, but worked for proof of concept

38

Canary Spoof Resistance• The random canary:• Pull a random integer

from the OS /dev/random at process startup time• Simple in concept, but

in practice it is very painful to make reading from /dev/random work while still inside crt0.o• Made it work, but

motivated us to seek something simpler

• “Terminator” canary:• CR, LF, 00, -1: the symbols

that terminate various string library functions• Rationale: will cause all the

standard string mashers to terminate while trying to write the canary cannot spoof the canary and successfully write beyond it• Still vulnerable to attacks

against poorly used memcpy() code, but buffer overflows thought to be rare

39

XOR Random Canary• 1999, “Emsi” creates

the frame pointer attack• Frame pointer stored

below the canary corruptible• Change FP to point to a fake activation record constructed on the heap• Function return code will

believe FP, interpret the fake activation record, and jump to shell code• Bypasses both Terminator

and Random Canaries

• XOR Random Canary• XOR the correct

return address with the random canary• Integrity check must

match both the random number, and the correct return address

40

L3: Putting These Ideas Together• Focus on malware

detection and prevention

• Much harder to “fix” than stack-based buffer overruns

• Externally exploitable bugs

• Nozzle• Runtime detector for heap spraying attacks• False positive rates: 10-6

• Overhead: 5-10%

• Zozzle• Static/statistical detector • False positive rates: 10-6

• Overhead: very small