Skip to main content
Log in

Abstraction and subsumption in modular verification of C programs

  • Published:
Formal Methods in System Design Aims and scope Submit manuscript

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).

This is a preview of subscription content, log in via an institution to check access.

Access this article

Price excludes VAT (USA)
Tax calculation will be finalised during checkout.

Instant access to the full article PDF.

Fig. 1
Fig. 2
Fig. 3
Fig. 4
Fig. 5

Similar content being viewed by others

Notes

  1. 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.

  2. 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.

  3. 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).

  4. 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.

  5. 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.

  6. See file veric/semax_lemmas.v in the VST repo.

  7. Bart Jacobs, by e-mail, September 2020.

  8. 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

  1. 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

    Book  Google Scholar 

  2. America P, Rutten J (1989) Solving reflexive domain equations in a category of complete metric spaces. J Comput Syst Sci 39(3):343–375

    Article  MathSciNet  Google Scholar 

  3. Appel AW (2015) Verification of a cryptographic primitive: SHA-256. ACM Trans Program Lang Syst 37(2):7:1–7:31

    Article  Google Scholar 

  4. 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

  5. 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

    Book  Google Scholar 

  6. Appel AW, Naumann DA (2020) Verified sequential malloc/free. In: Proceedings of the 2020 ACM SIGPLAN international symposium on memory management, pp 48–59

  7. Beringer Lennart (2011) Relational decomposition. In: Interactive theorem proving (LNCS 6898). Springer, Berlin, pp 39–54

  8. 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

    Google Scholar 

  9. 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

  10. 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

    Article  MathSciNet  Google Scholar 

  11. 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

  12. 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

    Google Scholar 

  13. 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

  14. Harel D, Kozen D, Tiuryn J (2000) Dynamic logic. MIT Press, Cambridge

    Book  Google Scholar 

  15. 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

  16. 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

    Article  Google Scholar 

  17. 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

  18. 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

  19. Kirchner F, Kosmatov N, Prevosto V, Signoles J, Yakobowski B (2015) Frama-C: a software analysis perspective. Formal Asp Comput 27(3):573–609

    Article  MathSciNet  Google Scholar 

  20. Kleymann T (1999) Hoare logic and auxiliary variables. Formal Asp Comput 11(5):541–566

    Article  Google Scholar 

  21. 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

  22. Leavens GT, Naumann DA (2015) Behavioral subtyping, specification inheritance, and modular reasoning. ACM Trans Program Lang Syst 37(4):13:1–13:88

    Article  Google Scholar 

  23. 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

  24. Leroy X (2009) Formal verification of a realistic compiler. Commun ACM 52(7):107–115

    Article  Google Scholar 

  25. Liskov B, Wing JM (1994) A behavioral notion of subtyping. ACM Trans Program Lang Syst 16(6):1811–1841

    Article  Google Scholar 

  26. 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

  27. Mitchell JC, Plotkin GD (1988) Abstract types have existential type. ACM Trans Program Lang Syst 10(3):470–502

    Article  Google Scholar 

  28. Naumann DA (1999) Deriving sharp rules of adaptation for Hoare logics. Technical Report 9906, Department of Computer Science, Stevens Institute of Technology

  29. 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

  30. 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

  31. Pierce BC (2002) Types and programming languages. MIT Press, Cambridge

    MATH  Google Scholar 

  32. Pierik C, de Boer FS (2005) A proof outline logic for object-oriented programming. Theor Comput Sci 343(3):413–442

    Article  MathSciNet  Google Scholar 

  33. 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

  34. 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

  35. 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

    Google Scholar 

  36. 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

    Google Scholar 

  37. 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

Download references

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

Authors

Corresponding author

Correspondence to Lennart Beringer.

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 ):

figure ik

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):

figure iq

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:

figure iy

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,

figure jb

as the following lemma shows:

figure jc

As one might expect, both notions are compatible with further subsumption:

figure jd

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

Reprints and permissions

About this article

Check for updates. Verify currency and authenticity via CrossMark

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

Download citation

  • Received:

  • Accepted:

  • Published:

  • Issue Date:

  • DOI: https://doi.org/10.1007/s10703-020-00353-1

Keywords

Navigation