CIS 4615 meeting -*- Outline -*- * Bad Exception Handling Based on chapter 9 of the book: 24 Deadly Sins of Software Security by M. Howard, D. LeBlanc, and J. Viega (McGraw-Hill, 2010). ** background ------------------------------------------ EXCEPTION HANDLING BACKGROUND Java, C#, and C++ are similar try-catch blocks set up handlers try { char* pSz = new char[count]; } catch (...) { cerr << "Out of memory!\n"; abort(); } finally { printf("bye!"); } The catch(...) catches A clause like catch(std::bad_alloc& e) is ------------------------------------------ ... all exceptions (generally bad) ... better ------------------------------------------ C's Structured Exception Handling (SEH) __try, __except, __finally macros mimic C++ exception handling. ------------------------------------------ ** attack *** plan ------------------------------------------ ATTACK STEPS 1. Find code that catches exceptions but 2. Provoke app to throw that exception 3. Exploit ------------------------------------------ ... continues without repairing the problem, leaving the app in an unstable state (with invariants violated, for example) ... the app's instability (e.g., through heap or buffer overflow attacks) Heap attacks would be a problem if the object's invariant is violated... E.g., by causing a double free by writing over memory that is exposed by corrupting indexes to do buffer overflow attack *** invariants ------------------------------------------ HOW TO KNOW IF AN OBJECT IS IN A GOOD STATE? Use an invariant! def: an *object invariant* is a property that should be ------------------------------------------ ... true in all visible states when not executing a method Like: no guns in the gate area of an airport ------------------------------------------ INVARIANTS: STABILITY PROPERTIES Example (in Java): import java.util.ArrayList; public class IntSet { private int size = 0; private ArrayList elems = (ArrayList) new ArrayList(); // invariant: size is the number of elements in elems //@ private invariant size == elems.size(); // invariant: no duplicate elements in elems /*@ private invariant @ (\forall int j; 0 <= j && j < elems.size(); @ (\forall int k; 0 <= k && k < elems.size(); @ (elems.get(j).equals(elems.get(k))) ==> j == k)); @*/ public IntSet() {} public boolean has(int i) { for (int e : elems) { if (e == i) { return true; } } return false; } public void add(int i) { if (has(i)) { return; } elems.add(i); } public void delete(int i) { if(elems.remove(new Integer(i))) { size--; } } // ... } ------------------------------------------ Can consider the conjunction of those properties to be "the invariant" The invariant should be true whenever the methods are not being run. See the delete method, in which size may be out of sync Q: How does the code maintain that invariant? Basis is the constructor: which trivially establishes it. Every method is an inductive step: starts with invariant, and then re-establishes it at the end. Thus every time a method is not executing, the invariant is true. Q: What could happen if the invariant is false? the methods may return wrong values, or may operate incorrectly. *** using invariants with exceptions ------------------------------------------ USING INVARIANTS WITH EXCEPTIONS Assumptions: invariant holds at start of each method Obligations: establish invariant at end of constructor establish invariant when exit method, (except destructor) either normally or by exception establish invariant before calling another method ------------------------------------------ ** redemption ------------------------------------------ REDEMPTION STEPS - Write down invariants for all classes - Check that either: - invariants are re-established when exceptions are caught, or - the program exits (aborts) Don't catch all exceptions with catch(...) or catch(Exception e) unless aborting the program Don't handle segmentation fault signals Handle signals using safe functions ------------------------------------------