Abstract
The type-theoretic notions of existential abstraction, subtyping, subsumption, and intersection have useful analogues in separation-logic proofs of imperative programs. We have implemented these as an enhancement of the verified software toolchain (VST). VST is an impredicative concurrent separation logic for the C language, implemented in the Coq proof assistant, and proved sound in Coq. For machine-checked functional-correctness verification of software at scale, VST embeds its expressive program logic in dependently typed higher-order logic (CiC). Specifications and proofs in the program logic can leverage the expressiveness of CiC—so users can overcome the abstraction gaps that stand in the way of top-to-bottom verification: gaps between source code verification, compilation, and domain-specific reasoning, and between different analysis techniques or formalisms. Until now, VST has supported the specification of a program as a flat collection of function specifications (in higher-order separation logic)—one proves that each function correctly implements its specification, assuming the specifications of the functions it calls. But what if a function has more than one specification? In this work, we exploit type-theoretic concepts to structure specification interfaces for C code. This brings modularity principles of modern software engineering to concrete program verification. Previous work used representation predicates to enable data abstraction in separation logic. We go further, introducing function-specification subsumption and intersection specifications to organize the multiple specifications that a function is typically associated with. As in type theory, if \(\phi \) is a of \(\psi \), that is \(\phi <:\psi \), then \(x:\phi \) implies \(x:\psi \), meaning that any function satisfying specification \(\phi \) can be used wherever a function satisfying \(\psi \) is demanded. Subsumption incorporates separation-logic framing and parameter adaptation, as well as step-indexing and specifications constructed via mixed-variance functors (needed for C’s function pointers).
Similar content being viewed by others
Notes
Bifunctor function-specs in VST were originally the work of Qinxiang Cao, Robert Dockins, and Aquinas Hobor, but were adapted to the new form of preconditions as part of the present work.
Existentially abstracting over the internal representation predicates would further emphasize the uniformity between and —a detailed treatment of this is beyond the scope of the present article, but is a key ingredient of an abstract component system that we are currently building on top of VST.
For example: in our proof of HMAC-DRBG [37], before VST had function-spec subsumption, we had two different proofs of the function , one with respect to a more concrete specification and one with respect to a more abstract specification . The latter proof was 202 lines of Coq, at line 37 of VST/hmacdrbg/drbg_protocol_proofs.v in commit 3e61d29 of https://github.com/PrincetonUniversity/VST. Now, instead of reproving the function-body a second time, we have a funspec_sub proof that is only 55 lines of Coq (at line 42 of the same file).
Kleymann’s program logic, like ours, uses auxiliary variables (which we call WITH-lists) to relate the precondition to the postcondition. When auxiliary variables are used, one must be able to choose them freely to express this relation between pre and post. Two funspecs for the same function, related by funspec_sub, may have quite different auxiliary variables. This is the parameter adaption aspect of Kleymann’s system, and of ours. Kleymann pointed out that parameter adaption is necessary in order to achieve adaptation completeness, which is the property that if \(\forall c.\models \{P\}c\{Q\} \Rightarrow \models \{P'\}c\{Q'\}\) then one can derive that \(\vdash \{P\}\{Q\}\) implies \(\vdash \{P'\}\{Q'\}\), independent of c.
We give Kleymann’s rule for total correctness here. Kleymann’s partial-correctness adaptation rule cannot guarantee safety. That is: Kleyman’s total-correctness Hoare triple says, “If the start state satisfies P, then the command c will terminate, and will terminate in a state satisfying Q.” Kleymann’s partial-correctness Hoare triple says, “If the start state satisfies P, then if the command c terminates, then the final state satisfies Q.” The problem is that c might crash (or “get stuck” in operational-semantic terms), in which case Kleymann’s partial-correctness Hoare triple is still satisfied. For unsafe languages such as C, that is not a very useful Hoare triple, nor is his partial-correctness adaptation rule useful. VST is a logic for partial correctness, but its Hoare triple means, “If the start satisfies P, then it is safe to execute c (c will not crash); c will either infinite-loop, will safely exit by (e.g.) returning from the function, or will terminate in a state satisfying Q”. This is useful for unsafe languages.
See file veric/semax_lemmas.v in the VST repo.
Bart Jacobs, by e-mail, September 2020.
VeriFast permits the function specification to be attached to the function definition in the .c file or to the function declaration in the .h file. This is a limited form of separating the specification from the implementation.
References
Ahrendt W, Beckert B, Bubel R, Hähnle R, Schmitt PH, Ulbrich M (2016) Deductive software verification-the key book, volume 10001 of lecture notes in computer science. Springer, New York
America P, Rutten J (1989) Solving reflexive domain equations in a category of complete metric spaces. J Comput Syst Sci 39(3):343–375
Appel AW (2015) Verification of a cryptographic primitive: SHA-256. ACM Trans Program Lang Syst 37(2):7:1–7:31
Appel AW, Beringer L, Cao Q, Dodds J (2019) Verifiable C: applying the verified software toolchain to C programs. https://vst.cs.princeton.edu/download/VC.pdf. Accessed 10 Sept 2020
Appel AW, Dockins R, Hobor A, Beringer L, Dodds J, Stewart G, Blazy S, Leroy X (2014) Program logics for certified compilers. Cambridge University Press, Cambridge
Appel AW, Naumann DA (2020) Verified sequential malloc/free. In: Proceedings of the 2020 ACM SIGPLAN international symposium on memory management, pp 48–59
Beringer Lennart (2011) Relational decomposition. In: Interactive theorem proving (LNCS 6898). Springer, Berlin, pp 39–54
Beringer L, Appel AW (2019) Abstraction and subsumption in modular verification of C programs. In: ter Beek Maurice H, Annabelle M, Oliveira JN (eds) Formal methods—the next 30 years—third world congress, FM 2019, proceedings, vol 11800. LNCS. Springer, New York, pp 573–590
Beringer L, Petcher A, Katherine QY, Appel AW (2015) Verified correctness and security of OpenSSL HMAC. In: 24th USENIX Security Symposium. USENIX Assocation, pp 207–221
Cao Q, Beringer L, Gruetter S, Dodds J, Appel AW (2018) VST-Floyd: a separation logic tool to verify correctness of C programs. J Autom Reason 61(1–4):367–422
Chajed T Tassarotti J, Kaashoek MF, Zeldovich N (2019) Verifying concurrent, crash-safe systems with perennial. In: Brecht T, Williamson C (eds) Proceedings of the 27th ACM symposium on operating systems principles, SOSP 2019, Huntsville, ON, Canada, October 27–30, 2019. ACM, pp 243–258
Cohen E, Dahlweid M, Hillebrand MA, Leinenbach D, Moskal M, Santen T, Schulte W, Tobies S (2009) VCC: a practical system for verifying concurrent C. In: Berghofer S, Nipkow T, Urban C, Wenzel M (eds) theorem proving in higher order logics, 22nd international conference, TPHOLs 2009, proceedings, vol 5674. Lecture Notes in Computer Science. Springer, New York, pp 23–42
Gerlach J, Efremov D, Sikatzki T, Brodmann M, Burghardt J, Carben A, Clausecker R, Gu L, Hartig K, Lapawczyk T, Pohl HW, Soto J, Völlinger K (2010) ACSL by example: towards a formally verified standard library, version 21.1.0. https://github.com/fraunhoferfokus/acsl-by-example. Accessed 10 Sept 2020
Harel D, Kozen D, Tiuryn J (2000) Dynamic logic. MIT Press, Cambridge
Jacobs B, Smans J, Philippaerts P, Vogels F, Penninckx W, Piessens F (2011) Verifast: a powerful, sound, predictable, fast verifier for C and Java. In: NASA formal methods symposium. Springer, pp 41–55
Jung R, Jourdan J-H, Krebbers R, Dreyer D (2018) Rustbelt: securing the foundations of the rust programming language. Proc ACM Program Lang 2(POPL):66:1–66:34
Jung R, Krebbers R, Jourdan J-H, Bizjak A, Birkedal L, Dreyer D (2018) Iris from the ground up: a modular foundation for higher-order concurrent separation logic. J Funct Program 28:E20. https://doi.org/10.1017S0956796818000151
Kassios IT (2006) Dynamic frames: support for framing, dependencies and sharing without restrictions. In: Misra J, Nipkow T, Sekerinski E (eds) FM 2006: Formal methods, 14th international symposium on formal methods, Hamilton, Canada, August 21–27, 2006, Proceedings, volume 4085 of Lecture Notes in Computer Science. Springer, pp 268–283
Kirchner F, Kosmatov N, Prevosto V, Signoles J, Yakobowski B (2015) Frama-C: a software analysis perspective. Formal Asp Comput 27(3):573–609
Kleymann T (1999) Hoare logic and auxiliary variables. Formal Asp Comput 11(5):541–566
Koh N., Li Y., Li Y., Xia L-y, Beringer L, Honoré W, Mansky W, Pierce BC, Zdancewic S (2019) From C to interaction trees: specifying, verifying, and testing a networked server. In: Proceedings of the 8th ACM SIGPLAN international conference on certified programs and proofs. ACM, pp 234–248
Leavens GT, Naumann DA (2015) Behavioral subtyping, specification inheritance, and modular reasoning. ACM Trans Program Lang Syst 37(4):13:1–13:88
Rustan K, Leino M (2010) Dafny: an automatic program verifier for functional correctness. In: Clarke EM, Voronkov A (eds) Logic for programming, artificial intelligence, and reasoning—16th international conference, LPAR-16, Dakar, Senegal, April 25–May 1, 2010, Revised Selected Papers, volume 6355 of Lecture Notes in Computer Science. Springer, pp 348–370
Leroy X (2009) Formal verification of a realistic compiler. Commun ACM 52(7):107–115
Liskov B, Wing JM (1994) A behavioral notion of subtyping. ACM Trans Program Lang Syst 16(6):1811–1841
Mansky W, Appel AW, Nogin A (2017) A verified messaging system. In: Proceedings of the 2017 ACM international conference on object oriented programming systems languages & applications, OOPSLA ’17. ACM
Mitchell JC, Plotkin GD (1988) Abstract types have existential type. ACM Trans Program Lang Syst 10(3):470–502
Naumann DA (1999) Deriving sharp rules of adaptation for Hoare logics. Technical Report 9906, Department of Computer Science, Stevens Institute of Technology
Nipkow T (2002) Hoare logics for recursive procedures and unbounded nondeterminism. In: Bradfield JC (ed) Computer science logic, 16th international workshop, CSL 2002, 11th annual conference of the EACSL, proceedings, volume 2471 of Lecture Notes in Computer Science. Springer, pp 103–119
Parkinson MJ, Bierman GM (2005) Separation logic and abstraction. In: 32nd ACM SIGPLAN-SIGACT symposium on principles of programming languages (POPL 2005), pp 247–258
Pierce BC (2002) Types and programming languages. MIT Press, Cambridge
Pierik C, de Boer FS (2005) A proof outline logic for object-oriented programming. Theor Comput Sci 343(3):413–442
Schmitt PH, Ulbrich M, Weiß B (2010) Dynamic frames in java dynamic logic. In: Beckert B, Marché C (eds) formal verification of object-oriented software—international conference, FoVeOOS 2010, Paris, France, June 28–30, 2010, Revised Selected Papers, volume 6528 of Lecture Notes in Computer Science. Springer, pp 138–152
Schmitt PH, Ulbrich M, Weiß B (2010) Dynamic frames in java dynamic logic—formalization and proofs. Technical Report 2010–2011, KIT—Karlsruher Institut für Tchnologie
Wang S, Cao Q, Mohan A, Hobor A (2019) Certifying graph-manipulating C programs via localizations within data structures. PACMPL 3(OOPSLA):17:11–17:130
Xia L, Zakowski Y, He P, Hur C-K, Malecha G, Pierce BC, Zdancewic S (2020) Interaction trees: representing recursive and impure programs in coq. PACMPL 4(POPL):51:1–51:32
Ye KQ, Green M, Sanguansin N, Beringer L, Petcher A, Appel AW (2017) Verified correctness and security of mbedTLS HMAC-DRBG. In: Proceedings of the 2017 ACM SIGSAC conference on computer and communications security (CCS’17). ACM
Acknowledgements
We are grateful to the members of the VST research group projects for their feedback, and we greatly appreciate the comments and suggestions made by the FM’19 program committee and by this journal’s referees.
Author information
Authors and Affiliations
Corresponding author
Additional information
Publisher's Note
Springer Nature remains neutral with regard to jurisdictional claims in published maps and institutional affiliations.
This work was funded by the National Science Foundation under the awards 1005849 (Verified High Performance Data Structure Implementations, Beringer) and 1521602 Expedition in Computing: The Science of Deep Specification, Appel).
Appendix: Fully general funspec_sub
Appendix: Fully general funspec_sub
NDfunspec_sub as introduced in Sect. 5 specializes the “real” subtype relation \(\phi <:\psi \) in two regards: first, it only applies if \(\phi \) and \(\psi \) are of the form, i.e. the types of their WITH-lists (“witnesses”) are trivial bifunctors as they do not contain co- or contravariant occurrences of . Second, it fails to exploit step-indexing and is hence unnecessarily strong. Our full definition is as follows (Definition in ):
We first note that is not a (Coq) osition but an —indeed, step-indexing has nothing interesting to say about pure propositions! That is, \(P\vdash Q\) means, “for all resource-maps s, \(P\,s\) implies \(Q\,s\),” but this can be too strong: means, “for all resource-maps s whose step-index is \(\le \) the current ‘age’, \(P\,s\) implies \(Q\,s\).” Recursive equations of s, of the kind that come up in object-oriented patterns, can tolerate where they cannot tolerate \(\vdash \) [5, Chapter 17].
Second, both funspecs are constructors (mk_funspec \( tsig \, cc \, A \, P \, Q\_~\_\)) as discussed in Sect. 5, but the two final arguments (the proofs that P and Q are super-nonexpansive) are irrelevant for the remainder of the definition and hence anonymous. We also abbreviate the TypeTree-interpreting operator alluded to in Sect. 3, , with \({\mathcal {F}}\).
Third, the definition makes use of the following operators (details on the penultimate two operators can be found in [5], Chapter 16):
In particular, the satisfaction of \(P_2\) implies, only with the “precision” (in the step-indexed sense) at which \(P_2\) is satisfied, that \(Q_1\) implies \(Q_2\).
Finally, note that the definition internally existentially quantifies over yet another , the frame F.
It is straightforward to prove that is reflexive, transitive, and specializes to . To obtain soundness of context subtyping ( ), we Kripke-extend the previous definition of VST’s main semantic judgment . We also refined the definition of the predicate : a stronger version of rule permits the exposed specification f to be a (step-indexed) abstraction of the specification g stored in VST’s resource-instrumented model:
As refers to the memory, this notion is again an . Again, users who don’t have complex object-oriented recursion patterns can avoid the step-indexing by using this non-step-indexed variant,
as the following lemma shows:
As one might expect, both notions are compatible with further subsumption:
With these modifications and auxiliary lemmas in place, we have formally reestablished the soundness proof of VST’s proof rules, justifying all rules given in this paper.
Rights and permissions
About this article
Cite this article
Beringer, L., Appel, A.W. Abstraction and subsumption in modular verification of C programs. Form Methods Syst Des 58, 322–345 (2021). https://doi.org/10.1007/s10703-020-00353-1
Received:
Accepted:
Published:
Issue Date:
DOI: https://doi.org/10.1007/s10703-020-00353-1