Deductive Verification Tools Tutorial for Dagstuhl Seminar 16201 while(i>0) { i--; } ………… ………… ………… Note: slide numbers 18th May 2016 Alexander J. Summers ETH Zurich, Switzerland
Deductive Verification Tools while(i>0) { i--; } Take a program
Deductive Verification Tools while(i>0) { i--; } ………… ………… ………… spec Take a program annotated with some specifications Automate a reasoning technique to find proof / errors typically: consider each program point only once This part of the tutorial: example deductive verification tools and their use how to construct new ones Not here: static analysers, proof assistants, …
Deductive Verification Tools full software stack for secure remote apps safety, liveness, functionality of distributed systems Java concurrency, GPU software, kernel code Ironclad IronFleet VerCors VCC Chalice VeriFast C and Java code (e.g. Linux device driver, Java Card) C code (e.g. MS Hypervisor) Race-free OO concurrency features (PL) Viper Intermediate Verification Language (more later…)
Concurrency Reasoning Ingredients Ironclad IronFleet VerCors VCC Chalice VeriFast Invariants (per object / thread) Two-state (transition) invariants count <= old(count) Methodology for local verification Ownership as auxiliary state Other reasoning encodable e.g. claims for shared access Viper
Concurrency Reasoning Ingredients Ironclad IronFleet VerCors VCC Chalice VeriFast Separation logic (for ownership) x.count ↦ 4 Reasoning Libraries (e.g. monitors) Shared Boxes with invariants (rely-guarantee reasoning) Lemma function pointers (ghost state maintenance) Viper
Concurrency Reasoning Ingredients Ironclad IronFleet VerCors VCC Chalice VeriFast Access permissions (ownership) acc(x.count) Fractional permissions (sharing) Modular lock ordering specification (deadlock avoidance) Two-state monitor/lock invariants (CSL, plus some rely-guarantee) Viper
Demo (Owicki-Gries in Chalice) class OwickiGries { requires rd(mu) && waitlevel << mu // may acquire lock var count: int ghost var g0: int requires (b ? acc(this.g0,50) : acc(this.g1,50)) ghost var g1: int ensures b ==> acc(this.g0,50) && this.g0 == old(this.g0) + 1 invariant acc(this.count) && acc(this.g0,50) ensures !b ==> acc(this.g1,50) && this.g1 == old(this.g1) + 1 && acc(this.g1,50) && this.count == this.g0 + this.g1 ensures rd(mu) { method Main() { acquire(this) var og := new OwickiGries{ count := 0, g0 :=0, g1 := 0 } this.count := this.count + 1 if(b) { share og // monitor available this.g0 := this.g0 + 1 } else { fork tk0 := og.Worker(false) this.g1 := this.g1 + 1 fork tk1 := og.Worker(true) join tk0; join tk1 release(this) acquire og; unshare og assert og.count == 2 http://rise4fun.com/Chalice } method Worker(b: bool) http://chalice.codeplex.com/
Tool Design We did not develop a step-by-step proof provide only the essence of the formal argument What is this essence? which assertions are essential information? modularity? to abstract control flow? which proof steps user directed (not syntactically)? which reasoning ingredients are natively supported? How can these ingredients be implemented? Option 1: implement custom support (hard) Option 2: design an encoding using an existing tool often: find a different way to model same idea Only sound if x.next will not be modified in future
Example: modelling ownership transfer Partial heaps 4 inv 3 count 1 acquire (this) // Inhale the monitor invariant count := count + 1 …
Example: modelling ownership transfer Partial heaps 4 inv 3 count 2 acquire (this) // Inhale the monitor invariant count := count + 1 … release(this) // Exhale the monitor invariant
Example: modelling ownership transfer Partial heaps Total heaps + accounting 3 4 4 inv inv 3 count 3 count 1 1 2 4 7 acquire (this) // Inhale the monitor invariant count := count + 1 … release(this) // Exhale the monitor invariant
Example: modelling ownership transfer Partial heaps Total heaps + accounting 3 4 4 inv inv 3 count 3 count 2 2 2 4 7 acquire (this) // Inhale the monitor invariant count := count + 1 … release(this) // Exhale the monitor invariant
Implementing the reasoning Partial heaps Total heaps + accounting 3 4 4 inv inv 3 count 3 count 2 2 2 4 7 maintain “heap chunks” in tool Add/remove for inhale/exhale Abstract over concrete values Value reasoning via SMT solver Symbolic execution (e.g. VeriFast) generate instrumented program track permissions as auxiliary state keep some heap values consistent Encode resulting program (Boogie) Embed proof obligations (e.g. Chalice)
Building up tool stacks… Ironclad IronFleet VerCors VCC Chalice VeriFast Dafny Viper Encoding to express sufficient proof obligations (somehow) Encoding need not preserve operational semantics May involve auxiliary state, indirect modelling of features (Very) useful: support for desired reasoning ingredients Boogie Z3
Example: modelling ownership transfer Partial heaps Total heaps + accounting 3 4 4 inv inv 3 count 3 count 2 2 2 4 7 acquire (this) // Inhale the monitor invariant count := count + 1 … release(this) // Exhale the monitor invariant // inhale / gain ownership assert Mask[this,count] == 1 Heap[this,count]++ Mask[this.count]-- // exhale / release ownership
Demo 2 (encoding Chalice in Viper) class OwickiGries { var count: int ghost var g0: int ghost var g1: int invariant acc(this.count) && acc(this.g0,50) && acc(this.g1,50) && this.count == this.g0 + this.g1 method Main() { var og := new OwickiGries{ count := 0, g0 :=0, g1 := 0 } share og // monitor available fork tk0 := og.Worker(false) fork tk1 := og.Worker(true) join tk0; join tk1 acquire og; unshare og assert og.count == 2 } method Worker(b: bool) requires rd(mu) && waitlevel << mu // may acquire lock requires (b ? acc(this.g0,50) : acc(this.g1,50)) ensures b ==> acc(this.g0,50) && this.g0 == old(this.g0) + 1 ensures !b ==> acc(this.g1,50) && this.g1 == old(this.g1) + 1 ensures rd(mu) { acquire(this) this.count := this.count + 1 if(b) { this.g0 := this.g0 + 1 } else { this.g1 := this.g1 + 1 release(this) http://viper.ethz.ch/examples/blank-example.html http://viper.ethz.ch/ field count: Int field g0: Int field g1: Int // monitor invariant define inv (acc(og.count) && acc(og.g0,1/2) && acc(og.g1,1/2) && og.count == og.g0 + og.g1) method Main() { var og : Ref; og := new(count,g0,g1); // specify the fields which the object has og.count := 0; og.g0 := 0; og.g1 := 0; // share monitor: exhale invariant exhale inv // fork first thread := Worker(og,true) (uses g0 ghost variable) label l0 exhale acc(og.g0,1/2) // fork second thread := Worker(og,false) (uses g1 ghost variable) label l1 exhale acc(og.g1,1/2) // join thread 0 (Worker(og,true)) inhale acc(og.g0,1/2) && og.g0 == old[l0](og.g0) + 1 // join thread 1 (Worker(og,false)) inhale acc(og.g1,1/2) && og.g1 == old[l1](og.g1) + 1 // acquire the monitor (for remaining permissions) - can be unshared in source program inhale inv assert og.count == 2 } method Worker(og:Ref, b:Bool) // mu-related deadlock checking: omitted requires (b ? acc(og.g0,1/2) : acc(og.g1,1/2)) ensures (b ==> acc(og.g0,1/2) && og.g0 == old(og.g0) + 1) ensures (!b ==> acc(og.g1,1/2) && og.g1 == old(og.g1) + 1) { og.count := og.count + 1 if(b) { og.g0 := og.g0 + 1 } else { og.g1 := og.g1 + 1
The Viper Project Viper IVL We have designed a new intermediate verification language Reusable, native support for various common reasoning ingredients e.g. permissions, inhale/exhale Idea: front-end tools that encode a problem into a Viper program We provide (two) Viper Verifiers In use for several projects (later): Developing full-blown verifiers Hand-coding examples Building prototypes quickly Program + properties Viper IVL Viper Verifiers Automatic Provers etc. ✔ ✘
Viper: a toolbox for concurrency verification Object-based (but not OO – no classes/inheritance) Declarations: fields, methods, predicates, functions Statements: assignments, loops, calls, inhale/exhale loops have invariants, methods have pre/post specs Assertion language: permissions, rich functional specs user-defined predicates (implemented or abstract) mathematical (heap-dependent) pure functions Very simple type system four basic types: Int, Bool, Perm, Ref (all references) sequence and set types: Seq[T], Set[T] (+ operations) Domains allow user to specify custom types via extra domain functions and quantified axioms e.g. tuples, maps, arrays…
Demo (encoding RSL) field val : Int // integer pointer value predicate R(x: Ref) predicate W(x: Ref) field alreadyRead : Set[Int] // for updating invariant // x.store(i,release) define RELEASE(i,x) { if(i!=0) { exhale acc(a.val) && a.val == 7 } method unknownInt() returns (x:Int) // t := x.load(acq) define ACQUIRE(t,x) { t := unknownInt() if(t==0 || t in x.alreadyRead) { // do nothing } else { inhale acc(a.val) && a.val == 7 // a.val pointsto 7 x.alreadyRead := x.alreadyRead union Set(t) define leftPre (acc(a.val) && a.val == 0 && acc(W(x))) define leftPost true define rightPre acc(R(x)) && acc(x.alreadyRead) && x.alreadyRead == Set[Int]() define rightPost true method outerScope() { var a: Ref var x: Ref x := new(); // atomic RELEASE(0,x); inhale acc(R(x)) && acc(W(x)) && acc(x.alreadyRead) && x.alreadyRead == Set[Int]() a := new(); // non-atomic inhale acc(a.val) a.val := 0 // fork both threads exhale leftPre exhale rightPre // join both threads inhale leftPost inhale rightPost method leftThread(a:Ref, x:Ref) requires leftPre ensures leftPost { a.val := 7 assert acc(W(x)) RELEASE(1,x) method rightThread(a:Ref, x:Ref) requires rightPre ensures rightPost var t : Int assert acc(R(x)) ACQUIRE(t,x) if (t != 0) { assert a.val == 7
Some Viper Applications Python ….. Java (VerCors) Chalice OpenCL (VerCors) Viper Tools released 1.5 years ago open-source, publicly available in use both at ETH and outside Chalice: re-implementation concurrency verification Scala (small fragment) Finite blocking verification VerCors (ERC) project Marieke Huisman (UTwente) Java, GPU code, kernel code SCION router code (Python) Adrian Perrig (ETH Zurich) (coming soon) JavaScript (Philippa Gardner) Fine-grained concurrency logics [Your tool name here? ]
Conclusion: Deductive Verification Tools while(i>0) { i--; } ………… ………… ………… spec Provide powerful reasoning abstractions for direct users, but also for other tool developers to plug into Divide a concurrency verification task between: expressing appropriate specifications per program encoding (in a tool) of a concurrency reasoning technique leveraging intermediate tools and reasoning abstractions ultimately exploiting SMT solvers (+ other automatic tools?)