Verifying concurrent Go code in Coq with Goose Tej Chajed , Joseph Tassarotti*, Frans Kaashoek, Nickolai Zeldovich MIT and *Boston College
Systems verification, broadly impl proof specification 2
Systems verification requires connecting implementation to proof model of impl impl proof specification 3
Systems verification requires connecting implementation to proof this talk model of impl impl previous [SOSP 2019] proof and current work specification 3
We aim to verify realistic systems PDOS (the part that does verification) Systems: running code, interacts with outside world Realistic: reasonably e ffi cient, concurrency Verification: functional correctness, focus on crash safety 4
Goal: implement in a systems language (1) write ordinary imperative code (2) import into Coq model of impl impl (3) prove something about the model 5
Goose: write code in Go and prove with Iris Why Go (vs. C or Rust) ? Simple, good tooling Why Iris (vs. VST) ? Concurrency, extensibility 6
Implementing in Go helps build the software (1a) write ordinary imperative code (2) import into Coq model of impl impl (1b) test (3) prove something (1c) debug about the model (1d) profile (1e) benchmark 7
Goose: import subset of Go into a Coq model Go Goose ✓ locks ✓ fork ✓ structs ✓ pointers ✗ interfaces 8
Goose: import subset of Go into a Coq model Coq Go goose translator Goose ✓ locks ✓ fork ✓ structs ✓ pointers ✗ interfaces 8
Goose: import subset of Go into a Coq model Coq Go GooseLang goose translator λ ref,conc with Goose ✓ locks external ops ✓ fork ✓ structs ✓ pointers ✗ interfaces 8
Goose: import subset of Go into a Coq model Coq Go GooseLang goose translator λ ref,conc with Goose ✓ locks external ops ✓ fork ✓ structs carry out ✓ pointers proofs in Iris ✗ interfaces 8
Our systems verification research using Goose Persistent key-value store using file system (unverified) Mail server using file system (appeared in SOSP ’19) Concurrent file system using disk (in progress) 9
Go is a systems language C-like: functions, structs, pointers Exposes system calls E ffi cient runtime (garbage collection, threads) 10
Goose code Looks like standard Go, but avoids most of the standard library Use narrow interfaces for file system or disk More of Go is supported frequently 11
Challenges in implementing Goose Defining GooseLang, a semantic model of Go Translating Go to GooseLang 12
GooseLang, a semantic model of Go e ::= x | λ x. e | e 1 e 2 // λ -calculus | ref e | !e | e 1 ← e 2 // heap operations | fork e | cmpxchg // concurrency | call op e // external operations 13
GooseLang, a semantic model of Go e ::= x | λ x. e | e 1 e 2 // λ -calculus | ref e | !e | e 1 ← e 2 // heap operations | fork e | cmpxchg // concurrency | call op e // external operations 13
GooseLang, a semantic model of Go e ::= x | λ x. e | e 1 e 2 // λ -calculus | ref e | !e | e 1 ← e 2 // heap operations | fork e | cmpxchg // concurrency | call op e // external operations v ::= U64 x | Loc z | … // literals | Pair | InjL | InjR // sums, products 13
Excerpt from GooseLang: slices x = (ptr, len) Definition sliceAppend := … λ s, x. ptr let s’ := alloc (s.len + #1) () in len … (* fill s’ *) (s’, s.len + #1). Coq 14
Excerpt from GooseLang: slices x = (ptr, len) Definition sliceAppend := … λ s, x. ptr let s’ := alloc (s.len + #1) () in len … (* fill s’ *) (s’, s.len + #1). Coq Go Definition example := func example(x []uint64) { x1 := x[1] λ x. goose append(x, 5) let x1 := !(x.ptr + ₗ #1) in } sliceAppend x #5;; #(). 14
Excerpt from GooseLang: modeling concurrency and locking func coin() bool { m := new(sync.Mutex) x := new(bool) go func() { m.Lock() *x = true m.Unlock() }() m.Lock() v := *x m.Unlock() return v 15
Excerpt from GooseLang: modeling concurrency and locking Definition coin: val := func coin() bool { λ <>. m := new(sync.Mutex) let: “m” := lock.new #() in x := new(bool) let: “x” := ref #(zero_val boolT) in go func() { goose fork (lock.acquire “m”;; m.Lock() “x” ← #true;; *x = true m.Unlock() lock.release “m”);; }() lock.acquire “m”;; m.Lock() let: “v” := !”x” in v := *x lock.release “m”;; m.Unlock() “v”. return v 15
Excerpt from GooseLang: modeling concurrency and locking Definition coin: val := func coin() bool { λ <>. m := new(sync.Mutex) let: “m” := lock.new #() in x := new(bool) let: “x” := ref #(zero_val boolT) in go func() { goose fork (lock.acquire “m”;; m.Lock() “x” ← #true;; *x = true m.Unlock() lock.release “m”);; }() lock.acquire “m”;; m.Lock() let: “v” := !”x” in v := *x lock.release “m”;; m.Unlock() “v”. return v 16
Excerpt from GooseLang: modeling concurrency and locking Definition coin: val := func coin() bool { λ <>. m := new(sync.Mutex) let: “m” := lock.new #() in x := new(bool) let: “x” := ref #(zero_val boolT) in go func() { goose fork (lock.acquire “m”;; m.Lock() “x” ← #true;; *x = true m.Unlock() lock.release “m”);; }() lock.acquire “m”;; m.Lock() let: “v” := !”x” in v := *x lock.release “m”;; m.Unlock() “v”. return v 17
Excerpt from GooseLang: modeling concurrency and locking Definition coin: val := func coin() bool { λ <>. m := new(sync.Mutex) let: “m” := lock.new #() in x := new(bool) let: “x” := ref #(zero_val boolT) in go func() { goose fork (lock.acquire “m”;; m.Lock() “x” ← #true;; *x = true m.Unlock() lock.release “m”);; }() lock.acquire “m”;; m.Lock() let: “v” := !”x” in v := *x lock.release “m”;; m.Unlock() “v”. return v 18
Challenge in modeling Go: weak memory func uhOh(x *uint64) { Definition uhOh: val := go func() { λ x. goose *x = 1 fork (x ← #1 print(“set x”) print “set x” !x);; }() print “x=” !x. print(“x=”, *x) Sequential consistency } x86-TSO If we first see “set x”, then 19
Challenge in modeling Go: weak memory func uhOh(x *uint64) { Definition uhOh: val := go func() { λ x. goose *x = 1 fork (x ← #1 ✓ print(“set x”) print “set x” !x);; }() print “x=” !x. print(“x=”, *x) Sequential consistency } x86-TSO If we first see “set x”, then sequential consistency means x=1 19
Challenge in modeling Go: weak memory func uhOh(x *uint64) { Definition uhOh: val := go func() { λ x. goose *x = 1 ✗ fork (x ← #1 ✓ print(“set x”) print “set x” !x);; }() print “x=” !x. print(“x=”, *x) Sequential consistency } x86-TSO If we first see “set x”, then sequential consistency means x=1 but TSO allows x=0 19
Challenge in modeling Go: weak memory func uhOh(x *uint64) { Definition uhOh: val := go func() { λ x. goose *x = 1 ✗ fork (x ← #1 ✓ print(“set x”) print “set x” !x);; }() print “x=” !x. print(“x=”, *x) Sequential consistency } x86-TSO If we first see “set x”, then sequential consistency means x=1 but TSO allows x=0 19
Disallow racy loads and stores Definition Store: val := λ p, v. BeginStore p;; FinishStore p v. Notation “p ← v” := (Store p v). Track in-progress stores Concurrent store/store and load/store are undefined 20
Compatibility with Iris gives us amazing verification technology Concurrent separation logic with higher-order ghost state Iris Proof Mode (IPM) for interactive proofs 21
Compatibility with Iris gives us amazing verification technology Concurrent separation logic with higher-order ghost state Iris Proof Mode (IPM) for interactive proofs Connect to our unwritten POPL 2021 paper for crash safety 21
Proofs using non-atomic memory Load (non-atomic) Store { p ↦ v } { p ↦ v 0 } ! p p ← v { p ↦ v } { λ v . p ↦ v } These triples are sound because is exclusive access to p ↦ v p 22
Proofs using non-atomic memory Load (non-atomic) Store { p ↦ v } { p ↦ v 0 } ! p p ← v { p ↦ v } { λ v . p ↦ v } These triples are sound because exclude using locks is exclusive access to p ↦ v p exclude by using local variables 22
GooseLang programs can make system calls Import disk. import “github.com/tchajed/goose/ machine/disk" goose Definition Copy: val := func Copy() { b := disk.Read(0) λ _. disk.Write(1, b) let b := call ReadOp #0 in } call WriteOp (#1, b). Language is parameterized by external calls Currently implementing GooseLang + file-system ops in terms of GooseLang + disk ops 23
Semantics of GooseLang Small-step operational semantics, mostly standard and following design of HeapLang For testing, have executable semantics (interpreter + soundness proof) 24
Previous approach: shallow embedding as semantic model GooseLang was a free monad instead of a λ -calculus Go code had to explicitly sequence e ff ectful operations Pure operations were expressed directly in Gallina 25
GooseLang is a mix of shallow and deep embedding Heap operations, concurrency are deeply represented Data structures are shallowly built out of sums 26
Recommend
More recommend