Title: Static EXtended Checking for Cyclone
1Static EXtended CheckingforCyclone
2The Legacy of C
- 1988 Buffer overrun exploited by Morris Worm.
- . . .
- 1999 Buffer overruns in Active-X.
- 2001 Buffer overruns in WU-FTP.
- 2002 Buffer overruns in Windows RPC.
- 2003 Buffer overruns in MS SQL server.
- 2004 Buffer overruns in RealPlayer.
- 2005 Buffer overrun in LSASS.
- 2006 Buffer overruns in ATI Nvidia drivers.
- 2007 Buffer overrun in Vista ANI code.
3Not the Only Problem...
- C suffers from many problems...
- Must bypass the type system to do simple things
(e.g., allocate and initialize an object). - Libraries put the onus on the users (e.g., check
return codes). - Not enough info at runtime for needed
checks.(e.g., printf is passed arguments of the
right type). - Programmer controlled memory management (leads
to data corruption, leaks). - ifdef-testing-hell
4Why not throw out the C code?
- Alas, theres a lot of it.
- Vista gt 50 million lines of code
- Suns Java runtime gt 700 thousand lines
- Java is only portable thanks to ifdef
- Besides, C occupies a useful space
- Its ported to every architecture in the world.
- It gives programmers control over data structure
layout, memory management, instructions, etc. - Useful for devices, kernels, runtimes, etc.
- It makes costs manifest.
- Crucial for embedded or real-time systems.
5Cyclone
- A type-safe dialect of C.
- Goals
- well-typed program won't crash
- in particular, rule out buffer overruns the
like - looks behaves like C
- familiar syntax and semantics
- retain control over boxing, allocation,
bit-twiddling, and to some degree, memory
management. - some useful additions polymorphism, subtyping,
tagged unions, pattern matching, exceptions, etc. - plays nicely with C
6Cyclone Users
- In-kernel Network Monitoring Penn
- MediaNet Maryland Cornell
- Open Kernel Environment Leiden
- RBClick Router Utah
- xTCP Utah Washington
- GeekOS Maryland
- Protocol Parsing ATT
- Minix modules Netherlands
- Ported to a variety of platforms
- Nintendo DS!
- Cyclone compiler, tools, libraries
- Over 100 KLOC
- Plus many sample apps, benchmarks, etc.
- Good to eat your own dog food
7Lots of other tools for C
- e.g., SPLint, Jeckyll, Metal, Deputy, Prefast,
... - But Ccured Cyclone focus on soundness.
- Ccured Scheme
- no annotation by programmer
- insert meta-data tests
- use global optimizer to eliminate overheads
- Cyclone Modula
- programmer must annotate types
- no run-time type tests
- local analysis only
8Cyclone/GCC vs. Java/GCC
9- Macro-benchmarks
- Ported a variety of security-critical apps
- little overhead (e.g., 2 for the Boa
Webserver.) - lots of bugs found
10AES C version
- int cipherUpdateRounds(cipherInstance cipher,
keyInstance key, BYTE input, - int inputLen, BYTE
outBuffer, int rounds) -
- int j, t
- word8 block4MAXBC
-
- if (cipher NULL key NULL
cipher-gtblockLen ! key-gtblockLen) - return BAD_CIPHER_STATE
- for (j 0 j lt cipher-gtblockLen/32 j)
- for(t 0 t lt 4 t) blocktj
input4jt 0xFF - switch (key-gtdirection)
- case DIR_ENCRYPT rijndaelEncryptRound(block,
key-gtkeyLen, cipher-gtblockLen,
key-gtkeySched, rounds) - break
- case DIR_DECRYPT rijndaelDecryptRound(block,
key-gtkeyLen, cipher-gtblockLen,
key-gtkeySched, rounds) - break
- default return BAD_KEY_DIR
-
- for (j 0 j lt cipher-gtblockLen/32 j)
- for(t 0 t lt 4 t) outBuffer4jt
(BYTE) blocktj
11AES Cyclone version
- int cipherUpdateRounds(cipherInstance cipher,
keyInstance key, BYTE ?input, - int inputLen, BYTE
?outBuffer, int rounds) -
- int j, t
- word8 block4MAXBC
-
- if (cipher NULL key NULL
cipher-gtblockLen ! key-gtblockLen) - return BAD_CIPHER_STATE
- for (j 0 j lt cipher-gtblockLen/32 j)
- for(t 0 t lt 4 t) blocktj
input4jt 0xFF - switch (key-gtdirection)
- case DIR_ENCRYPT rijndaelEncryptRound(block,
key-gtkeyLen, cipher-gtblockLen,
key-gtkeySched, rounds) - break
- case DIR_DECRYPT rijndaelDecryptRound(block,
key-gtkeyLen, cipher-gtblockLen,
key-gtkeySched, rounds) - break
- default return BAD_KEY_DIR
-
- for (j 0 j lt cipher-gtblockLen/32 j)
- for(t 0 t lt 4 t) outBuffer4jt
(BYTE) blocktj
12Refined pointer qualifiers
- Fat pointers arbitrary arithmetic but the
representation is different (3 words) - char ? a "fat" pointer to a sequence of
characters. - numelts(s) returns number of elements in
sequence s (0 when s
NULL) - Thin pointers same representation as C, but
restrictions on pointer arithmetic. - char NULL or a pointer to at least one
character. - char _at_ a pointer to at least one character.
- char _at_numelts42 pointer to a sequence of 42
characters.
13Fat pointers
- To support dynamic checks, we must insert extra
information (e.g., bounds for an array) - Similar to Ccureds representation.
14Can we eliminate fat pointers?
- Fat pointers are convenient, but
- expensive
- break compatibility
- give us failure points
15Aha!
- int f(int n, int A)_at_requires(n
numelts(A))_at_ensures(0 lt result lt n) - Hoare-Style Specifications.
- Say, like ESC/Java, Spec, or SPLint.
16Static EXtended Checking
- Add support for specifications to types
- _at_requires, _at_ensures, _at_throws
- quantifier-free 1st-order, multi-sorted logic
- Calculate verification conditions (VCs)
- For each possible failure point,
- compute a predicate of the form A ? C
- where A describes the state of the machine
- and C is a condition ensuring the failure cannot
occur (e.g., index in bounds) - Throw at VCs at a prover
- if prover can show VC is true, can omit check
17Example strcpy
- strcpy(char ?d, char ?s)
-
- while (s ! 0)
- d s
- s
- d
-
- d 0
-
Run-time checks are inserted to ensure that s
and d are not NULL and in bounds. 6 words
passed in instead of 2.
18Better
- strcpy(char ?d, char ?s)
-
- unsigned i, n numelts(s)
- assert(n lt numelts(d))
- for (i0 i lt n si ! 0 i)
- di si
- di 0
-
This assert is dynamic. But its presenceis
enough to eliminate the checks.
19Even Better
- strncpy(char d, char s, uint n)
- _at_assert(n lt numelts(d) n lt
numelts(s)) -
- unsigned i
- for (i0 i lt n si ! 0 i)
- di si
- di 0
-
No fat pointers or dynamic checks. But caller
must check the pre-condition.
20Ensures Throws Specs
- val_t lookup(key_t x)
- _at_ensures(result ! NULL x ! NULL)
- _at_throws(x NULL exn NullExn
- x ! NULL exn LookupFail)
- void insert(key_t k, val_t v)
- _at_requires(k!NULL v!NULL)
- _at_throws(false)
-
21How Effective?
- For the 165 files (78 Kloc) that make up the
standard libraries and compiler - CLibs stdio, string,
- CycLib list, array, splay, dict, set, bignum,
- Compiler lex, parse, typing, analyze, xlate to
C, - with almost no specifications, eliminated 96 of
the (static) checks - null 33,121 out of 34,437 (96)
- bounds 13,402 out of 14,022 (95)
- 225s for bootstrap compared to 221s with all
checks turned off (2 slower) on this laptop. - Optimization standpoint seems pretty good.
22Not all Rosy
- Don't do as well at array-intensive code.
- For instance, on the AES reference
- 75 of the checks (377 out of 504)
- 2 slower than all checks turned off.
- 24 slower than original C code.(still passing
around fat pointers) - The primary culprits
- loop invariants are too weak.
- lack of context (i.e., pre/post-conditions).
- prover only understands limited constraints.
23Challenges
- Assumed I could use off-the-shelf technology.
- But ran into a few problems
- scalable VC generation
- textbooks dont tell you this stuff!
- usable theorem provers
- (not the real focus.)
- some foundational issues
- semantic model, soundness
- see ICFP06 and ESOP07 papers on Hoare
Type-Theory.
24Verification-Condition Generation
- We started with textbook strongest
post-conditions - SPx e A Aa/x ? xea /x (a fresh)
- SPS1S2 A SPS2 (SPS1 A)
- SPif (e) S1 else S2 A
- SPS1(A ? e?0) ? SPS2(A ? e0)
251st Problem with Textbook SP
- SPx e A Aa/x ? xea/x
- What if e has effects?
- In particular, what if e is itself an assignment?
- Solution use a monadic interpretation
- SP Exp ? Assn ? Term ? Assn
- Terms are pure (i.e., logic).
26For Example
- SPx A (x, A)
- SPe1 e2 A let (t1,A1) SPe1 A
- (t2,A2) SPe2 A1
- in (t1 t2, A2)
- SPx e A let (t,A1) SPe A
- in (ta/x, A1a/x ? x ta/x)
27One Issue
- Of course, this over sequentializes the code.
- C has very liberal order of evaluation rules
which are hopelessly unusable for any sound
analysis. - So our back-end forces the evaluation to be
left-to-right to match our analysis.
28Next Problem Diamonds
- SPif (e1) S11 else S12
- if (e2) S21 else S22
- ...
- if (en) Sn1 else Sn2A
- Textbook approach explodes paths into a tree.
- SPif (e) S1 else S2 A
- SPS1(A ? e?0) ? SPS2(A ? e0)
- This simply doesn't scale.
- e.g., one procedure had assn with 1.5B nodes.
- WP has same problem. (see Flanagan Leino)
29Solution
- Factor out a local environment A xe1 ?
ye2 ? ? Bwhere neither B nor ei contains
program variables (i.e., x,y,) - Only the environment needs to change on update
SPx 3 xe1 ? ye2 ? ? B
x3 ? ye2 ? ? B - So most of the assertion (B) remains unchanged
and can be shared.
30Diamond Problem Revisited
- SPif (e) S1 else S2 xe1 ? ye2 ? ? B
- (SPS1 xe1 ? ye2 ? ?B?e?0) ?
- (SPS2 xe1 ? ye2 ? ?B?e0)
- (xt1 ?yt2? ? B1) ?
- (xu1?yu2 ? ? B2)
- xax ? yay ? ?
- ((ax t1 ? ay t2 ? ? B1) ?
- (ax u1 ? ay u2? ? B2))
31How does the environment help?
SPif (a) x3 else x y if (b) x5 else
skip xe1 ? ye2 ? B
?
xv ? ye2
?
?
?
b0 ? vt
b?0 ? v5
?
?
?
a?0 ? t3
B
a0 ? te2
32Tah-Dah!
- I've rediscovered SSA.
- monadic translation sequentializes and names
intermediate results. - only need to add fresh variables when two paths
compute different values for a variable. - so the added equations for conditionals
correspond to ?-nodes. - Like SSA, worst-case O(n2) but in practice O(n).
- Best part all of the VCs for a given procedure
share the same assertion DAG.
33Scaling
34Space Scaling
35So far so good
- Of course, I've glossed over the hard bits
- memory
- loops
- procedures
- Let's talk about memory first
36What about Memory?
- As in ESC, use a functional array
- t upd(tm,ta,tv) sel(tm,ta)
- with McCarthy axioms
- sel(upd(m,a,e),a) e
- (a1 ltgt a2) ? sel(upd(m,a1,e),a2) sel(m,a2)
- upd(upd(m,a,e1),a,e2) upd(m,a,e2)
- Then reading writing treated as access to a
distinguished mem variable - SPp e A Aa/mem ? memupd(a?p,e)
37Widening
- Given A?B, calculate some C such that A ? C and
B ? C and C lt A, B. - Then we can compute a fixed-point for loop
invariants iteratively - start with pre-condition P
- process loop-test body to get P'
- see if P' ? P. If so, we're done.
- if not, widen P?P' and iterate.
- (glossing over variable scope issues.)
38Our Widening
- Conceptually, to widen A?B
- Calculate the DNF
- really only traverse assertion DAG by memoizing
- Factor out syntactically common primitive
relations - In practice, we do a bit of closure first.
- e.g., normalize terms relations.
- e.g., xe expands to x ? e ? x ? e.
- Captures any primitive relation that was found on
every path.
39Back to Loops
- The invariants we generate aren't great.
- worst case is that we get "true"
- we do catch loop-invariant variables.
- if x starts off at i, is incremented and is
guarded by x lt e lt MAXINT then we can get x gt
i. - But
- covers simple for-loops well
- it's fast only a couple of iterations
- user can override with explicit invariant
40Procedures
- Originally, intra-procedural only
- Programmers could specify pre/post-conditions.
- Recently, extended to inter-procedural
- Calculate SP's and propagate to callers.
- If too large, we widen it.
- Go back and strengthen pre-condition of
(non-escaping) callee's by taking "disjunction"
of all call sites' assertions.
41Proving
- Original plan was to use off-the-shelf
technology. - eg., Simplify, SAT solvers, etc.
- But ran into problems
- Simplify unsound treatment of ints
- SAT too slow (esp. with 64-bits)
42Proving done in two stages
- First stage
- Given a VC A ? C
- Widen A to a set of primitive relns.
- Calculate DNF for C and check that each
disjunct is a subset of A. - (C is quite small so no blowup here.)
- This catches a lot
- all but about 2 of the checks we eliminate!
- void f(int _at_x) x
- if (x ! NULL) x
- for (i0 i lt numelts(A) i)Ai
432nd Stage
- Given A ? C, try to show A ? ?C inconsistent.
- Conceptually
- explore DNF tree (i.e., program paths)
- the real exponential blow up is here.
- so we have a programmer-controlled throttle on
the number of paths we'll explore (default 33). - accumulate a set of primitive facts.
- at leaves, run difference constraint algorithm
- Convert facts to constraints of form (x - y) lt
c. - Calculate shortest paths, look for negative
cycles.
44Summary of SEXC-Cyclone
- Simple assertions aimed at type errors.
- Started with strongest post-conditions.
- Effects Rewrote as monadic translation.
- Diamond Factored variables into an environment
to preserve sharing (SSA). - Memory use functional arrays
- Loops Simple but effective widening for
calculating invariants. - Extended to inter-procedural summaries.
- Simple, but fast custom prover.
- From 75-95 of checks eliminated.
45Currently
- Memory
- The functional array encoding of memory doesn't
work well -- should incorporate alias analysis. - e.g., cant accomodate malloc/free
- doesnt yield modular specs (need modifies)
- Can we adapt separation logic? Will it actually
help? - Whats the full type theory look like?
- Work with A. Nanevski L. Birkedal a start.
-
46False Positives
- We still have 2,000 checks left.
- I suspect that most are not needed.
- How to draw the eye to the ones that are?
- strengthen pre-conditions artificially(e.g.,
assume no aliasing, overflow, etc.) - if we still can't prove the check, then it should
be moved up to a "higher-rank" warning.
47Lots of Borrowed Ideas
- ESC M3 Java
- Touchstone, Special-J, Ccured
- SPLint (LCLint)
48More info...
- http//cyclone.thelanguage.org
- Acks
- Yanling Wang (Cornell)
- Aleks Nanevski (MSR)
- Mike Hicks, Nikhil Swamy (Maryland)
- Trevor Jim (ATT)
- Dan Grossman (Washington)