After my last post on formal analysis of C code, people pointed out several other options to me and I wanted to do a follow up because the conclusion is a little more positive now.

Firstly, I noted last time that Z3 was the best, Why3-compatible SMT solver for my problem but that it came with a non-commercial license. Craig Stuntz pointed out that Microsoft sell a commercial license for $10K. Sure, that's quite a lot for a hobbyist project, but it's easy for a corporation and a lot easier than having to negotiate specially.

The first additional thing that I've looked into is AutoCorres. I mentioned last time that SeL4's refinement proof to C code involved a C parser in Isabelle that output structures in the Simpl framework and that Simpl was pretty intimidating.

AutoCorres is a tool for verifiably simplifing the Simpl structures and thus making proofs easier. Let's repeat the example from last time and show how AutoCorres helps. Here's the test C function:

int add(int a, int b) { return a+b; }

And here's the raw result from the C parser, exactly as I showed last time:

test_global_addresses.add_body ≡ TRY Guard SignedArithmetic ⦃-2147483648 ≤ sint ´a + sint ´b ∧ sint ´a + sint ´b ≤ 2147483647⦄ (creturn global_exn_var_'_update ret__int_'_update (λs. a_' s + b_' s));; Guard DontReach {} SKIP CATCH SKIP END

And here's what AutoCorres makes of it:

add' ?a ?b ≡ DO oguard (λs. INT_MIN ≤ ?a + ?b); oguard (λs. ?a + ?b ≤ INT_MAX); oreturn (?a + ?b) OD

That's very impressive! That's using a Maybe monad (using the Haskell name) in Isabelle to handle the possible overflow. Obviously that's a lot easier to deal with than the raw Simpl. Here's the field addition function that I was using for the other examples in the previous post:

void add(int *out, const int *x, const int *y) { unsigned int i; unsigned int size = 10; for (i = 0; i < size; i++) { out[i] = x[i] + y[i]; } }

And here it is after AutoCorres:

add' ?out ?x ?y ≡ do i ← whileLoop (λi s. i < 0xA) (λi. do guard (λs. is_valid_w32 s (ptr_coerce (?out +⇩p uint i))); guard (λs. is_valid_w32 s (ptr_coerce (?x +⇩p uint i))); guard (λs. is_valid_w32 s (ptr_coerce (?y +⇩p uint i))); guard (λs. INT_MIN ≤ sint (ucast s[ptr_coerce (?x +⇩p uint i)]) + sint (ucast s[ptr_coerce (?y +⇩p uint i)])); guard (λs. sint (ucast s[ptr_coerce (?x +⇩p uint i)]) + sint (ucast s[ptr_coerce (?y +⇩p uint i)]) ≤ INT_MAX); modify (λs. s[ptr_coerce (?out +⇩p uint i) := s[ptr_coerce (?x +⇩p uint i)] + s[ptr_coerce (?y +⇩p uint i)]]); return (i + 1) od) 0; skip od

While the AutoCorres output looks very clean, there isn't any documentation at the moment which means that it's a little hard to actually use. (There are a couple of papers on the home page that are well worth reading - but I didn't find anything like a user guide.) There are some examples from which it's possible to make some guesses, but I wasn't able to get very far in a proof. Also, since AutoCorres builds on Simpl and C parser, it has the same limitations - most notably that the address of local variables can't be taken.

None the less, AutoCorres holds a lot of promise.

Next, is VST from Andrew Appel and others (yes, that Appel). VST is a framework for proving the behaviour of C code with Coq by using one of the intermediate languages from CompCert. Coq (which I'm pronouncing as “coke”) is a proof system like Isabelle and CompCert is a formally verified compiler. That means that it's possible to have a chain of formal verification from C code all the way to the semantics of the machine language! Additionally, the C parser isn't correctness critical because VST proves properties using the same parse tree as the verified compiler. That's an extremely strong verification standard.

CompCert is a mixture of GPL and “non-commercial” licensed code. It appears that everything needed for VST is licensed as GPL or another free license. So, one can also use VST to verify C code and then use a different compiler to build it if the non-commercial restriction is a problem. That moves the CompCert C parser and the other compiler into the trusted set, but so it goes.

The VST logic also appears to be very capable. It doesn't have a problem with pointers to local variables and should even have the capability to support reasoning about concurrent programs. The restrictions that it does have are local changes: only one memory load per line (at the moment) or side effect per statement and void functions need an explicit return at the end. (Unfortunately, one doesn't get an error from violating these requirements - rather the proof cannot be completed.)

VST thankfully, has brief documentation online which is expanded upon in the form of a book ($15 as a DRMed download). The book contains a very abstract articulation of the underlying logic which, to be honest, defeated me initially. I'd recommend skipping chapters 2 through 21 on a first reading and coming back to them later. They are useful, but they're also a lot clearer after having played with the software for a while.

Here's the same example function, translated into VST style. I switched back from having a special output array because that was something that I changed to make the SPARK verification easier but it was never part of the original code and VST doesn't have problem with overlapping the input and output. I've also switched from a for loop to a while loop because the documentation and examples for the latter are better. Otherwise, the changes are just to limit each statement to a single memory access and to include the final return.

void add(int *a, int *b) { int t1, t2; int i = 0; while (i < 10) { t1 = a[i]; t2 = b[i]; a[i] = t1 + t2; i++; } return; }

We can use clightgen from CompCert to parse that into the intermediate language that CompCert and VST use. (A word to the wise - if you want to install VST, use exactly Coq 8.4p2, not the latest version which is 8.4p4.) Then we can use Coq to prove its behaviour.

Compared to Isabelle, Coq's proofs aren't as clear as the Isar style proofs in Isabelle (although the terseness is more useful when proofs are this complex) and neither Proof General nor CoqIDE are as nice as Isabelle's jedit mode. But it's not otherwise dramatically different.

Here's a specification of add in VST/Coq's language:

Definition bound_int (v : val) (b : Z) := match v with | Vint i => -b < (Int.signed i) < b | _ => False end. Definition partial_add (i: Z) (l: Z -> val) (r: Z -> val) (j: Z) := if zlt j i then Val.add (l j) (r j) else (l j). Definition add_spec := DECLARE _add WITH a0 : val, b0 : val, sh : share, orig_a : Z -> val, orig_b : Z -> val PRE [_a OF (tptr tint), _b OF (tptr tint)] PROP (writable_share sh; forall i, 0 <= i < 10 -> is_int (orig_a i); forall i, 0 <= i < 10 -> is_int (orig_b i); forall i, 0 <= i < 10 -> bound_int (orig_a i) 134217728; forall i, 0 <= i < 10 -> bound_int (orig_b i) 134217728) LOCAL (`(eq a0) (eval_id _a); `(eq b0) (eval_id _b); `isptr (eval_id _a); `isptr (eval_id _b)) SEP (`(array_at tint sh orig_a 0 10 a0); `(array_at tint sh orig_b 0 10 b0)) POST [ tvoid ] PROP () LOCAL () SEP (`(array_at tint sh (partial_add 10 orig_a orig_b) 0 10 a0); `(array_at tint sh orig_b 0 10 b0)).

The partial_add function implements a map that reflects the state of the a array at step i of the loop. I've written it like that so that it can also be used in the loop invariant.

And here's the proof: It is quite long, but at least half of that is because I've not used Coq before and I don't know what I'm doing. I wouldn't call it readable however.

Definition inv orig_a a0 orig_b b0 sh := EX i:Z, (PROP (0 <= i < 11; isptr a0; isptr b0) LOCAL (`(eq a0) (eval_id _a); `(eq b0) (eval_id _b); `(eq (Vint (Int.repr i))) (eval_id _i)) SEP (`(array_at tint sh (partial_add i orig_a orig_b) 0 10 a0); `(array_at tint sh orig_b 0 10 b0))). Lemma mod_nop : forall (i : Z) (m : Z), 0 <= i < m -> m > 0 -> i mod m = i. Proof. intros. rewrite Zdiv.Zmod_eq. assert(i/m=0). apply Zdiv_small. exact H. rewrite H1. omega. exact H0. Qed. Lemma body_sumarray: semax_body Vprog Gprog f_add add_spec. Proof. start_function. forward. forward_while (inv orig_a a0 orig_b b0 sh) ( PROP () LOCAL () SEP (`(array_at tint sh (partial_add 10 orig_a orig_b) 0 10 a0); `(array_at tint sh orig_b 0 10 b0))). apply exp_right with 0. entailer!. quick_typecheck. entailer. cancel. assert(i=10). omega. rewrite H7. cancel. forward. entailer!. unfold partial_add. assert(is_int (orig_a i)). auto. assert(is_int (orig_b i)). auto. if_tac. omega. exact H7. forward. entailer!. forward. entailer. apply prop_right. assert(Vint (Int.add _id1 _id) = partial_add (i+1) orig_a orig_b (Int.signed (Int.repr i))). Focus 2. symmetry in H9. apply H9. unfold partial_add. assert(Int.signed (Int.repr i) = i). rewrite Int.signed_repr. reflexivity. unfold Int.max_signed, Int.min_signed. simpl. omega. rewrite H9. if_tac. unfold Val.add. rewrite Int.signed_repr_eq in H7. rewrite mod_nop in H7. if_tac in H7. unfold partial_add in H7. if_tac in H7. omega. rewrite Int.signed_repr_eq in H6. rewrite mod_nop in H6. if_tac in H6. unfold Int.add, Int.unsigned, Int.intval, Int.repr. assert(bound_int (orig_a i) 134217728). auto. unfold bound_int in H12. symmetry in H7, H6. rewrite H7. rewrite H6. reflexivity. omega. assert(Int.modulus = 4294967296). auto. rewrite H13. omega. assert(Int.modulus = 4294967296). auto. omega. assert(i < Int.half_modulus). assert(Int.half_modulus = 2147483648). auto. rewrite H12. rewrite H12 in H11. omega. omega. assert(Int.modulus = 4294967296). auto. omega. assert(Int.modulus = 4294967296). auto. rewrite H11. omega. omega. forward. unfold inv. apply exp_right with (Zsucc i). entailer!. apply derives_refl'. apply equal_f. apply array_at_ext. intros. unfold upd. if_tac. rewrite H10. unfold Z.succ. reflexivity. unfold partial_add. if_tac. if_tac. reflexivity. omega. if_tac. omega. reflexivity. forward. Qed.

That's certainly quite a chunk! Practically you have to step through the proof in Proof General and see the state evolve to understand anything. Additionally, when in the proof, it would be very useful if subgoals has some sort of description like “show the loop invariant plus not the loop condition results in the loop post-condition” - it's very easy to get lost in the proof. But I do feel that, while it would be a lot of work, I could make progress with VST, while I don't (at the moment) feel with other systems. Prof. Appel recently did a formal verification of the SHA256 from OpenSSL.

However, there is no community around VST that I can find - no mailing list, wiki, etc. The Subversion repo is only accessible via HTTP - you can't clone it from what I can tell. I think I found a (completeness) bug in VST, but the only option would be to try emailing Prof. Appel. (I didn't; I'm probably wrong.)

Still, AutoCorres and especially VST leave me more optimistic than I was at the end of the last post!

Others

I didn't have time to look at everything that deserved attention. Cryptol is one. Cryptol is a tool written in Haskell designed for the specification and implementation of cryptographic code and can call out to SMT solvers to show certain properties. From its internal language it can export implementations to (at least) C.

Next, Frama-C, which I mentioned last time in relation to the Jessie plugin, has other plugins. One that's useful is the value analysis, which can be properly used to eliminate NULL derefs, array overruns etc. In fact, Polar SSL has been fully validated in certain configurations using that. That's certainly very valuable, and you can do it without that multi-page Coq proof! Although such analysis wouldn't have caught the carry bug in Ed25519 nor the reduction bug in Donna, not all code is going to be suitable for formal analysis.

Microsoft also have lots of things. There's Dafny, VCC (Verifier for Concurrent C), Boogie, Havoc, Z3 and others! I didn't look at them because I don't have a Windows box to hand this week (although I see that some are exposed via a web interface too). I was thinking that maybe I'd have time when a Windows box was to hand but, realistically, probably not. So I've updated this post to mention them here. If you are Windows based, you will probably do well to pay attention to them first.