Temporal-Safety Proofs for Systems Code Thomas A. Henzinger Ranjit Jhala Rupak Majumdar George Necula Westley Weimer Grégoire Sutre UC Berkeley
2 Reliability & Trust Reliability (verification): Check that the system is “bug free” Low level systems code Locking disciplines, interface specs, … Temporal, path-sensitive properties Model checking Trust (certification): Questionable code written by third parties: device drivers, mobile code Model checkers are buggy (!) Simply, efficiently checkable avoid redoing verification
3 Our Work Verification: making model checking scale Lazy Abstraction [POPL ’02] Certification: Proof carrying code based mechanism PCC requires annotations Lazy Abstraction: Automatically generates annotations Provides a small decomposition of the proof BLAST Verifying/certifying device drivers
4 Verification
5 Verification: Abstraction-Refinement Abstract Explanation YES (Trace) BUG Feasible ??? Check Refine NO SAFE Seed Abstraction Program Abstraction Infeasible Why infeasible ? Is model unsafe ? [Kurshan et al, Clarke et al, SLAM,...]
6 Model Checking & Abstraction ERROR STATES Init Existentially lift transition relation Partition the state space
7 ERROR STATES Init Model Checking & Abstraction Problem: Abstraction too coarse Solution: Refine abstraction
8 ERROR STATES Init Model Checking & Abstraction Problem: Abstraction too coarse Solution: Refine abstraction
9 Abstract Only Where Required ERROR STATES Init Abstraction is very expensive Why abstract regions that are never visited ? Reachable States On-the-fly abstraction: driven by the search
10 Refine Only Where Required Why be precise everywhere ? Don ’ t refine error-free regions ERROR STATES Init ERROR FREE
11 Refine Only Where Required Why be precise everywhere ? Don ’ t refine error-free regions Different precision for different regions Local Refinement : driven by the search ERROR STATES Init ERROR FREE
12 Benefits of Lazy Abstraction Abstract only where required Reachable state space maybe very sparse Construct the abstraction on-the-fly Use greater precision only where required Different precisions/abstractions for different regions Refine locally Reuse work from earlier phases Batch-oriented ) lose work from previous runs Integrate the three phases
13 Example Q: Is Error Reachable ? Example ( ) { 1: do { lock(); old = new; 2: if (*) { 3: unlock(); new ++; } 4: } while ( new != old); 5: unlock (); return; } unlock()lock() unlock()
14 Example ( ) { 1: do { lock(); old = new; 2: if (*) { 3: unlock(); new ++; } 4: } while ( new != old); 5: unlock (); return; } Example:CFA lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock()
15 Example:CFA Q: Is Error Reachable ? ret unlock()lock() unlock() Example ( ) { 1: do { lock(); old = new; 2: if (*) { 3: unlock(); new ++; } 4: } while ( new != old); 5: unlock (); return; }
16 Step 1: Search Set of predicates: LOCK=0, LOCK=1 1 LOCK=0 3 LOCK=1 lock(); old = new [>][>] unlock() 5 LOCK=0 unlock() new++ [new==old] 4 LOCK=0 2 LOCK=1 Err LOCK=0 lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock()
17 Q: When can: Step 2: Analyze Counterexample 1 LOCK=0 2 LOCK=1 3 4 LOCK=0 5 Err LOCK=0 n Err ops States that can = PRE (,ops) Err R n Æ PRE (,ops) = ? Err States at node n = R n
18 Step 2: Analyze Counterexample 1 LOCK=0 2 LOCK=1 3 4 LOCK=0 5 Err LOCK=0 lock(); old = new [new==old] unlock() LOCK=0 LOCK=0 Æ new = old [>][>] unlock(); new++ LOCK=1 Æ new+1 = old lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock() LOCK=0 Æ new+1 = new R n Æ PRE (,ops) = ? Err
19 Step 2: Analyze Counterexample 1 LOCK=0 2 LOCK=1 3 4 LOCK=0 5 Err LOCK=0 LOCK=0 Æ new = old LOCK=0 Æ new+1 = new LOCK=1 Æ new+1 = old Track the predicate: new = old lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock()
20 Step 3: Resume Search 1 LOCK=0 lock(); old = new [new==old] ? 5 [new!=old] 1 LOCK=0 Æ : new = old µ LOCK =0 Set of predicates: LOCK=0, LOCK=1, new = old 3 LOCK=1 Æ new = old [>][>] unlock() new++ 4 LOCK=0 Æ : new = old 2 LOCK=1 Æ new = old lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock()
21 Step 3: Resume Search 1 LOCK=0 2 LOCK=1 Æ new = old 3 4 LOCK=0 Æ : new = old ? 51 LOCK=0 Æ : new = old 4 LOCK=1 Æ new=old 51 ? ret LOCK=0Æ new=old ERROR Unreachable Set of predicates: LOCK=0, LOCK=1, new = old lock(); old = new [>][>] [>][>] [new==old] [new!=old] ret unlock() new++ unlock()
22 Predicate Discovery Information lost in substitution Keep substitutions explicit Ask a proof of unsatisfiability Pick predicates appearing in proof 1 LOCK=0 2 LOCK=1 3 4 LOCK=0 5 Err LOCK=0 lock(); old = new [>][>] unlock(); new++ [new==old] unlock() LOCK=0 Æ new+1 = new
23 Example ( ) { 0: if (*) { 6: do { got_lock = 0; 7: if (*) { 8: lock(); got_lock ++; } 9: if (got_lock) { 10: unlock(); } 11: } while (*) ; } 1: do { lock(); old = new; 2: if (*) { 3: unlock(); new ++; } 4: } while ( new != old); 5: unlock (); return; } Local Refinement 1: do { lock(); old = new; 2: if (*) { 3: unlock(); new ++; } 4: } while ( new != old); 5: unlock (); return; } ret 6: do { got_lock = 0; 7: if (*) { 8: lock(); got_lock ++; } 9: if (got_lock) { 10: unlock(); } 11: } while (*) ; }
24 Local Refinement [>][>] 1 LOCK=0 0 6 [>][>] Err ret Refine right subtree only Different abstractions for subtrees Search on left subtree not repeated
25 Leaves Covered (Reuse work) 0 LOCK= LOCK=0 Æ … COVERED ! Leaves covered: Avoid repeating search when paths merge ret
26 Certification
27 What is a Certificate ? Proof Carrying Code (PCC) Program + Spec Annotations VC Generator Verification Condition Annotations: Loop invariants, func. Precondition/Postcondition Certificate: Annotations + Proof of VC Consumer reconstructs VC, checks proof Validity of VC guarantees correctness
28 Annotations For each cntrl location q, invariant I(q) Verification condition Init µ I(q 0 ) Annotations & VCs Start set includes initial states Error location is not reached States closed under post I(q ) = false For each edge q ! op q’ : POST (I(q),op) µ I(q’)
29 Ç LOCK=0 Æ : new = old Invariants grow on Trees LOCK=0 2 3 LOCK=1 Æ new = old LOCK=0 Æ : new = old Ç LOCK=1 Æ new=old LOCK=1 Æ new=old LOCK=0 Æ : new = old 1 LOCK=0 2 LOCK=1 Æ new = old 3 4 ? 51 LOCK=0 Æ : new = old 4 LOCK=1 Æ new=old 5 1 ? ret LOCK=0 Æ new=old Reachability Tree Invariants covered
30 Proving the VC Each condition dischargeable automatically (Vampyre, CVC … ) Tree yields a small decomposition Entire proof can be extracted from model checker ’ s data structures
31 BLAST LAZY ABSTRACTION Berkeley Lazy Abstraction Software verification Tool 10K Lines of Ocaml Analyze Linux/Windows Device Drivers CIL (C ! CFA) REGION STRUCTURE BDD Engine (Boolean ops) Simplify (Post # ) Vampyre (focus)
32 BLAST All of C modeled except: Function pointers Recursive functions “ Logical ” Model of memory Pointer arithmetic imprecise Fragile on heap dependant properties Safety checking: Checks if a given label is reachable in the C program Monitor automata specified in C
33 Experiments parport.c mouclass.c [fixed] kbflter.c [fixed] floppy.c cdaudio.c tlan.c aha152x.c qpmouse.c ide.c Prf Size (bytes) Pred. Disc. Time(sec) Total Time(sec) Active Preds Total Preds LinesProgram Linux Lock 3 state Windows DDK IRP 22 state
34 Conclusions Lazy Abstraction Reachability Tree yields certificate Implemented BLAST Finds (only) real bugs in large device drivers … and gives proofs for correct ones ! Future work: Smarter abstractions Program analysis for model reduction Recursive functions …
35 BLAST Berkeley Lazy Abstraction Software * Tool