Automated verification of systems software with Serval Xi Wang Joint work with Luke Nelson, James Bornholt, Ronghui Gu, Andrew Baumann, and Emina Torlak University of Washington Columbia University Microsoft Research 1
Today: eliminating bugs in low-level systems software Process Process Process OS Kernel / security monitor • Low-level bugs: buffer overflow, div by zero • Logic bugs: implementation does something unintended • Design bugs: unintended design is insecure 2
Example: undefined behavior uint64_t mul(uint16_t a, uint16_t b) { uint32_t c = a * b; return c; } Question: what’s the result of mul(60000, 60000)? • (a) 3,600,000,000 • (b) 18,446,744,073,014,584,320 • (c) something else 3
Eliminating bugs with formal verification seL4 (SOSP’09) Process Process Process Ironclad, Jitk (OSDI’14) CertiKOS (PLDI’16) OS Kernel / security monitor Komodo (SOSP’17) 4
Eliminating bugs with formal verification seL4 (SOSP’09) • Strong correctness guarantees Process Process Process Ironclad Apps (OSDI’14) • Require manual proofs FSCQ (SOSP’15) • CertiKOS 200k lines of proof CertiKOS (PLDI’16) OS Kernel / security monitor Komodo (SOSP’17) • Multiple person-years 5
Prior work: automated (push-button) verification Yggdrasil Automated OSDI’16 verifier Hyperkernel SOSP'17 Finite Decidable implementation specification Nickel OSDI'18 6
Prior work: automated (push-button) verification Implementation Specification • No proofs on implementation Automated verifier • Requires finite implementation • Restricts specification SMT solver ✔ ✘ 7
Challenges How to lower effort of writing Implementation Specification automated verifiers? Automated verifier How to find and fix performance bottlenecks? SMT solver How to retrofit to existing ✔ ✘ systems? 8
Contributions • Serval: a framework for writing automated verifiers • ARM, RISC-V, x86, LLVM, BPF • Scaling via symbolic optimizations • Experience • Retrofitted CertiKOS and Komodo for Serval • Found 30+ new bugs in Linux BPF JIT and 3 in Keystone no guarantees on concurrency or side channels 9
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 10
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 11
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 12
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 13
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 14
Verifying a system with Serval System specification RISC-V instructions RISC-V verifier Serval Rosette Z3 SMT solver 15
Example: proving refinement for sign 0: sltz a1 a0 1: bnez a1 4 2: sgtz a0 a0 3: ret 4: li a0 -1 (define (sign x) (cond 5: ret [(negative? x) -1] [(positive? x) 1] [(zero? x) 0])) RISC-V verifier Serval 16
Verifier = interpreter + symbolic optimization ✔ 2. Symbolic profiling 1. Write a verifier to find bottleneck as interpreter 3. Apply symbolic optimizations 17
Verifier [1/3]: writing an interpreter (struct cpu (pc regs ...) #:mutable) System RISC-V x86-32 specification instructions instructions (define (interpret c program) (define pc (cpu-pc c)) RISC-V x86-32 (define insn (fetch pc program)) verifier verifier (match insn [('li rd imm) (set-cpu-pc! c (+ 1 pc)) (set-cpu-reg! c rd imm)] [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 18
Verifier [1/3]: writing an interpreter (struct cpu (pc regs ...) #:mutable) System RISC-V x86-32 specification instructions instructions (define (interpret c program) (define pc (cpu-pc c)) RISC-V x86-32 (define insn (fetch pc program)) verifier verifier (match insn [('li rd imm) (set-cpu-pc! c (+ 1 pc)) (set-cpu-reg! c rd imm)] [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 19
Verifier [1/3]: writing an interpreter (struct cpu (pc regs ...) #:mutable) (define (interpret c program) (define pc (cpu-pc c)) (define insn (fetch pc program)) (match insn [('li rd imm) (set-cpu-pc! c (+ 1 pc)) (set-cpu-reg! c rd imm)] [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 20
Verifier [1/3]: writing an interpreter (struct cpu (pc regs ...) #:mutable) (define (interpret c program) (define pc (cpu-pc c)) (define insn (fetch pc program)) (match insn [('li rd imm) (set-cpu-pc! c (+ 1 pc)) (set-cpu-reg! c rd imm)] [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 21
Verifier [1/3]: writing an interpreter (struct cpu (pc regs ...) #:mutable) (define (interpret c program) (define pc (cpu-pc c)) (define insn (fetch pc program)) (match insn • Easy to write [('li rd imm) • Reuse CPU test suite (set-cpu-pc! c (+ 1 pc)) (set-cpu-reg! c rd imm)] [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 22
Verifier [2/3]: identifying bottlenecks in symbolic evaluation 😁 0: sltz a1 a0 1: bnez a1 4 2: sgtz a0 a0 3: ret 4: li a0 -1 (define (sign x) (cond 5: ret [(negative? x) -1] [(positive? x) 1] RISC-V verifier [(zero? x) 0])) Serval 23
Verifier [2/3]: identifying bottlenecks in symbolic evaluation 🤰 0: sltz a1 a0 1: bnez a1 4 2: sgtz a0 a0 3: ret 4: li a0 -1 (define (sign x) (cond 5: ret slow/timeout [(negative? x) -1] [(positive? x) 1] RISC-V verifier [(zero? x) 0])) Serval 24
Verifier [2/3]: identifying bottlenecks in symbolic evaluation 25
Verifier [2/3]: identifying bottlenecks in symbolic evaluation fetch 26
Verifier [2/3]: identifying bottlenecks in symbolic evaluation (struct cpu (pc regs) #:mutable) (define (interpret c program) 0: sltz a1 a0 (define pc (cpu-pc c)) 1: bnez a1 4 (define insn (fetch pc program)) 2: sgtz a0 a0 (match insn [('li rd imm) 3: ret (set-cpu-pc! c (+ 1 pc)) 4: li a0 -1 (set-cpu-reg! c rd imm)] 5: ret [('bnez rs imm) (if (! (= (cpu-reg c rs) 0)) (set-cpu-pc! c imm) (set-cpu-pc! c (+ 1 pc)))] ...)) 27
Merge states to avoid path explosion PC → 0 a0 → X X < 0 ¬(X < 0) 0: sltz a1 a0 a1 → Y 1: bnez a1 4 PC → 1 PC → 1 2: sgtz a0 a0 a0 → X a0 → X 3: ret a1 → 1 a1 → 0 4: li a0 -1 5: ret PC → 1 a0 → X a1 → if(X < 0, 1, 0) 28
Bottleneck: state explosion due to symbolic PC Conditional jump PC → 1 a0 → X 0: sltz a1 a0 a1 → if(X < 0, 1, 0) 1: bnez a1 4 2: sgtz a0 a0 3: ret ... ... 4: li a0 -1 5: ret PC → if(X < 0, 4, 2) a0 → X a1 → if(X < 0, 1, 0) 29
Bottleneck: state explosion due to symbolic PC Conditional jump PC → if(...) a0 → X 0: sltz a1 a0 a1 → if(...) 1: bnez a1 4 2: sgtz a0 a0 3: ret 4: li a0 -1 PC → 0 PC → 1 PC → 2 5: ret PC → 3 PC → 4 PC → 5 30
Verifier [3/3]: Repairing with symbolic optimizations • Symbolic optimization: "peephole" on symbolic state • Fine-tune symbolic evaluation • Use domain knowledge 31
Verifier [3/3]: Repairing with symbolic optimizations (define (interpret c program) - (define pc (cpu-pc c)) (define insn (fetch pc program)) (match insn (define (interpret c program) ...)) + ( serval:split-pc [cpu pc] c (define insn (fetch pc program)) (match insn ...))) • Match on symbolic structure of PC • Evaluate separately using each concrete PC value • Merge states afterwards 32
Verifier [3/3]: Repairing with symbolic optimizations PC → if(X < 0, 4, 2) a0 → X PC → if(X < 0, 4, 2) a1 → if(...) a0 → X a1 → if(...) split-pc PC → 0 PC → 1 PC → 2 PC → 4 PC → 2 PC → 3 PC → 4 PC → 5 33
Verifier [3/3]: Repairing with symbolic optimizations PC → if(X < 0, 4, 2) a0 → X PC → if(X < 0, 4, 2) a1 → if(...) Domain knowledge: a0 → X a1 → if(...) • Split PC to avoid state explosion • Merge other registers to avoid path explosion PC → 0 PC → 1 PC → 2 PC → 4 PC → 2 PC → 3 PC → 4 PC → 5 34
Symbolic optimizations are essential to scaling verification • Symbolic program counter • Symbolic memory address • Symbolic system register • ... and more 35
Verifier summary • Verifier = interpreter + symbolic optimizations • Easy to test verifiers • Systematic way to scale symbolic evaluation • Caveats: • Symbolic profiling cannot identify expensive SMT operations • Repair requires expertise - recent work SymFix (VMCAI'20) 36
Implementation RISC-V x86 ARM LLVM BPF verifier verifier verifier verifier verifier Serval Rosette Z3 SMT solver 37
Experience • Can existing systems be retrofitted for Serval? • Are Serval’s verifiers reusable? 38
Retrofitting previously verified systems • Port CertiKOS (PLDI’16) and Komodo (SOSP’17) to RISC-V • Retrofit to automated verification • Apply the RISC-V verifier to binary image • Prove functional correctness and noninterference • ≈ 4 weeks each 39
Recommend
More recommend