loader
Generating audio...

arxiv

Paper 2503.00404

SecRef*: Securely Sharing Mutable References Between Verified and Unverified Code in F*

Authors: Cezar-Constantin Andrici, Danel Ahman, Catalin Hritcu, Ruxandra Icleanu, Guido Martínez, Exequiel Rivas, Théo Winterhalter

Published: 2025-03-01

Abstract:

We introduce SecRef*, a secure compilation framework protecting stateful programs verified in F* against linked unverified code, with which the program dynamically shares ML-style mutable references. To ease program verification in this setting, we propose a way of tracking which references are shareable with the unverified code, and which ones are not shareable and whose contents are thus guaranteed to be unchanged after calling into unverified code. This universal property of non-shareable references is exposed in the interface on which the verified program can rely when calling into unverified code. The remaining refinement types and pre- and post-conditions that the verified code expects from the unverified code are converted into dynamic checks about the shared references by using higher-order contracts. We prove formally in F* that this strategy ensures sound and secure interoperability with unverified code. Since SecRef* is built on top of the Monotonic State effect of F*, these proofs rely on the first monadic representation for this effect, which is a contribution of our work that can be of independent interest. Finally, we use SecRef* to build a simple cooperative multi-threading scheduler that is verified and that securely interacts with unverified threads.

Paper Content:
Page 1: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★ CEZAR-CONSTANTIN ANDRICI, MPI-SP, Germany DANEL AHMAN, University of Tartu, Estonia CĂTĂLIN HRIT ,CU, MPI-SP, Germany RUXANDRA ICLEANU, University of Edinburgh, UK GUIDO MARTÍNEZ, Microsoft Research, USA EXEQUIEL RIVAS, Tallinn University of Technology, Estonia THÉO WINTERHALTER, Inria Saclay, France We introduce SecRef★, a secure compilation framework protecting stateful programs verified in F★against linked unverified code, with which the program dynamically shares ML-style mutable references. To ease program verification in this setting, we propose a way of tracking which references are shareable with the unverified code, and which ones are not shareable and whose contents are thus guaranteed to be unchanged after calling into unverified code. This universal property of non-shareable references is exposed in the interface on which the verified program can rely when calling into unverified code. The remaining refinement types and pre- and post-conditions that the verified code expects from the unverified code are converted into dynamic checks about the shared references by using higher-order contracts. We prove formally in F★that this strategy ensures sound and secure interoperability with unverified code. Since SecRef★is built on top of the Monotonic State effect of F★, these proofs rely on the first monadic representation for this effect, which is a contribution of our work that can be of independent interest. Finally, we use SecRef★to build a simple cooperative multi-threading scheduler that is verified and that securely interacts with unverified threads. 1 INTRODUCTION Verifying stateful programs using proof-oriented languages like Rocq, Lean, Isabelle/HOL, Dafny, and F★is being applied to increasingly realistic projects [Appel 2016; Arasu et al. 2023; Bhargavan et al. 2021; Delignat-Lavaud et al. 2017; Gu et al. 2016; Hance et al. 2020; Ho et al. 2022; Klein et al. 2010; Leroy 2009; Li et al. 2022; Murray et al. 2013; Ramananandro et al. 2019; Zakowski et al. 2021]. In such projects, one notices that verified code calls into, or is called by, unverified code. These calls are necessary to integrate verified code with existing systems, but usually they have an assumed specification, making them a potential source of unsoundness and security vulnerabilities, since the unverified code can inadvertently or maliciously break the internal invariants of the verified code. While this is a general problem in any proof-oriented language, in this work we work with F★ and represent stateful programs using a monadic effect called Monotonic State [Ahman et al. 2018], instantiated to obtain first-order ML-style mutable references, which are dynamically allocated and unforgeable, which cannot be deallocated, and whose type cannot be changed. Monotonic State has facilitated the verification of large projects that were later integrated into Firefox, Linux, and Windows [Protzenko et al. 2020; Swamy et al. 2022; Zinzindohoué et al. 2017], yet it offers no guarantees that linking a verified program with unverified code is sound or secure. Authors’ addresses: Cezar-Constantin Andrici, MPI-SP, Bochum, Germany, cezar.andrici@mpi-sp.org ; Danel Ahman, University of Tartu, Estonia, danel.ahman@ut.ee ; Cătălin Hri t,cu, MPI-SP, Bochum, Germany, catalin.hritcu@mpi- sp.org ; Ruxandra Icleanu, University of Edinburgh, UK, r.icleanu@gmail.com ; Guido Martínez, Microsoft Research, Redmond, WA, USA, guimartinez@microsoft.com ; Exequiel Rivas, Tallinn University of Technology, Estonia, exequiel. rivas@ttu.ee ; Théo Winterhalter, Inria Saclay, France, theo.winterhalter@inria.fr . This work is licensed under a Creative Commons Attribution 4.0 International License.arXiv:2503.00404v1 [cs.PL] 1 Mar 2025 Page 2: 2 Andrici et al. The most challenging scenario for ensuring soundness involves dynamic sharing of dynamically allocated mutable references with unverified code. This is so because sharing a reference is a transitive property: the contents of the reference also gets shared, including any references reachable from it. Sharing is also permanent: once shared, a reference is shared forever as it can be stashed away by unverified code, and writes to a shared reference make the newly written values shared as well. It is thus challenging to track which references are shared and which ones are not, so that one could still apply static verification. Let’s look at a simple example that illustrates these points: 1let prog (lib: (ref(ref int )→cb:(unit→unit))) = 2let secret :ref int =alloc 42in 3let r :ref(ref int ) =alloc (alloc 0)in 4let cb =lib r in 5r:=alloc 1; 6cb(); 7assert (!secret == 42)It consists of a program that takes as argument an unverified library. The library gets as input a reference to a reference to an integer, and re- turns a callback cbto the program. The program allocates a reference r(line 3) and passes it to the library (line 4). The library can store rand the reference rpoints to, and read and modify them in later callbacks. Thus, after the first call into the library (line 4), the program and the library share both rand the reference rpoints to. Between the initial call into the library and the call into the callback cb(line 6), the program modifies rpointing it to a newly allocated reference (line 5), and thus this new reference also becomes shared, even if not passed to cbdirectly—the sharing happens by updating an already shared reference. So in this example, a total of three references allocated by the program are shared with the library, even if only one reference was explicitly passed. Here is an adversarial library that writes to all shared references every time the callback is invoked: let lib r =let g :ref(list(ref int )) =alloc []in (𝜆()→g:= !r:: !g;(∗∗saves where 𝑟points to each time the callback is called ∗∗) iter(𝜆r'→r':= 0) ! g;(∗∗writes to all these shared integer references ∗∗) r:=alloc 0)(∗∗points𝑟to a new location ∗∗) To be able to verify a stateful program that calls into unverified code, for each such call one has to specify which of the program’s references get modified, also called the footprint of a call. If one cannot determine such a footprint, then in order to ensure soundness, one is forced to conservatively assume that the call can modify allreferences, which makes verification very challenging, because one loses almost all properties of references. For instance, in prog, we would lose that secret equals 42, and we would not be able to prove the assertion on line 7. Yet, as explained above, because references are dynamically allocated, and because of the transitive and persistent nature of their dynamic sharing, it is hard to determine which references are shared and which ones not. To our knowledge, no existing technique allows for the sound verification of stateful programs that can be linked with arbitrary unverified code while sharing dynamically allocated mutable references with it. A simpler case considered by Georges et al. [2024] (also imaginable for other separation logic work [Dreyer et al. 2012; Reynolds 2002]) is to have no passing of references to unverified code (i.e., no dynamic sharing), but this cannot always be ensured, because existing code cannot always be clearly separated to avoid passing references. While one could consider rewriting the code to avoid reference passing by wrapping them in closures [Sammler et al. 2020; Swasey et al. 2017], or in some cases to only pass references at abstract types, this would involve refactoring the unverified code to use a different interface. Other techniques allow passing references by checking and restoring the properties of the not passed references after a call into unverified code [Agten Page 3: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★3 et al. 2015; Georges et al. 2024], but this is done dynamically and adds an extra cost—for prog above, the contents of secret would be checked/restored twice, once for each call into the unverified code. The solution we propose in this paper allows the sound verification of stateful programs even if the verified program is linked with unverified code and references are dynamically shared. Our solution builds on the idea that being shareable is a monotonic property of references, meaning that it can be treated as a logical invariant once it is established. Instead of tracking precisely which references cross the verified-unverified boundary, we overapproximate by adding a computationally irrelevant labeling mechanism in which fresh references are labeled as private , and that allows the user to dynamically label a reference as shareable , which is permanent. Our overapproximation is sound since we statically verify that only references that are labeled as shareable can cross into unverified code. In our example, this requires the user to add the following calls to label_shareable : 1let safe_prog lib = 2let secret =alloc 42in 3let r =alloc (alloc 0)inlabel_shareable (!r); label_shareable r; 4let cb =lib r in 5let v =alloc 1inlabel_shareable v; r :=v; 6cb(); 7assert (!secret == 42) We also maintain a global invariant on the heap stating that shareable references always point only to other shareable references—preventing one from accidentally pointing a shareable reference to a not shareable reference (e.g., the original r:=alloc 1would not be allowed on line 5 because the fresh reference returned by alloc 1is labeled private ). The labels simplify specifying what the unverified code can do—it can modify only shareable references—which allows preserving the properties of private references. Using this solution, one is able to prove the assertion in the example above, since secret is labeled private . This property is guaranteed even when linked with unverified code. We have built a secure compilation framework, SecRef★, around the main ideas above and inspired by previous work for the IO effect [Andrici et al. 2024]. Similarly to this work, we shallowly embed the source and target language in F★, in our case on top of Labeled References. The only side effect our languages have is state, and they both have first-order ML-style mutable references. The difference is that source programs have concrete refinement types and pre- and post-conditions in their interface, while target programs do not. Part of SecRef★is dedicated to statically verifying stateful source programs like the one above against rich interfaces assumed about linked unverified target code that include refinement types as well as pre- and post-conditions. These interfaces expose an universal property of unverified code, which cannot modify private references. However, to establish any logical predicates on shareable references, which can cross the verified-unverified boundary, SecRef★has to add dynamic checks, which it does using higher-order contracts. This paper makes the following contributions: ▶We introduce Labeled References, a computationally irrelevant labeling mechanism built on top of Monotonic State [Ahman et al. 2018] that allows one to verify stateful programs that share ML- style mutable references with unverified code. The labeling mechanism tracks which references are shareable with the unverified code and which ones are not. The labels simplify specifying which references can be modified by a call into unverified code—i.e., only shareable references can be modified. Our solution allows one to pass between the verified and the unverified code mutable data structures such as linked lists and callbacks that encapsulate and modify references. ▶We build a secure compiler that takes a program verified using Labeled References and protects it so that it can be securely linked with unverified code, which can only modify shareable Page 4: 4 Andrici et al. references. The compiler converts some of the specifications at the boundary between verified and unverified code into dynamic checks by adding higher-order contracts, and we verified in F★the soundness of this enforcement with respect to the specification of the program. ▶We also provide a machine-checked proof in F★that a stateful program verified using Labeled References, then compiled with SecRef★, can be securely linked with arbitrary unverified code. For this we prove Robust Relational Hyperproperty Preservation (RrHP), which is the strongest secure compilation criterion of Abate et al. [2019], and in particular stronger than full abstraction. ▶We give the first monadic representation of Monotonic State by using a free monad, which overcomes a limitation in the original work of Ahman et al. [2018]. We also provide a machine- checked soundness proof in F★for Monotonic State in a different style than the original paper proof of Ahman et al. [2018]. While we expect the monadic representation of Monotonic State to be generally useful, even beyond this paper, it is also crucial for our mechanized proofs above. ▶Finally, we put our framework into practice by developing a case study inspired by a simple cooperative multi-threading model. In this case study, we showcase that we can statically verify a simple scheduler even when it interacts with and manages unverified threads. Outline. §2 starts with the key ideas of SecRef★. §3 presents our monadic representation and soundness proof for Monotonic State. §4 builds Labeled References on top of Monotonic State. §5 then presents our higher-order contracts. §6 shows how all pieces of SecRef★come together and how we prove that SecRef★satisfies soundness and RrHP. §7 presents the cooperative multi- threading case study. Finally, §8 discusses related work and §9 conclusions and future work. We believe SecRef★is a significant step towards building a secure compiler from F★to OCaml. Artifact. This paper comes with an F★artifact that includes the full implementation of SecRef★, the machine-checked proofs, and examples. Some of the examples can be extracted to native OCaml code (without any monadic representation) via an extension to F★’s extraction rules (Appendix A). 2 KEY IDEAS OF SECREF★ In this section, §2.1 introduces the running example of an autograder sharing references with unverified code, and showing how to verify it. §2.2 then explains how we represent unverified code and §2.3 how we add higher-order contracts. Lastly, beyond private and shareable , §2.4 introduces a third label, encapsulated , for references that are not shareable with the unverified code, but that can be indirectly updated by verified callbacks that are passed to the unverified code. 1type linked_list (a:Type ) = |LLNil→linked_list a |LLCons :a→ref(linked_list a )→linked_list a 2type student_hw =ll:ref(linked_list int )→LR(either unit err ) 3 ( requires (𝜆h0→witnessed (is_shareable ll ))) 4 ( ensures (𝜆h0r h1→modif_only_shareable h 0h1∧same_labels h 0h1∧ 5 ( Inr?r∨(sorted ll h 1∧no_cycles ll h 1∧same_values ll h 0h1)))) 6let autograder (test:list int ) (hw:student_hw ) (grade :mref (option int )grade_preorder ) 7 : LR unit (requires (𝜆h0→is_private h 0grade∧None ? (sel h 0grade )) 8 ( ensures (𝜆h0()h1→Some ? (sel h 1grade )∧ 9 same_labels h 0h1∧modif_shareable_and !{grade }h0h1)) = 10 let ll =create_llist test in 11 label_llist_as_shareable ll ;witness (is_shareable ll ); 12 let res =hw ll in gr :=Some (determine_grade res ) Fig. 1. The illustrative example of a verified autograder. Types are simplified. Page 5: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★5 2.1 Static verification using Labeled References In Figure 1 we show how to use Labeled References to verify an autograder.1The autograder takes as argument a function submitted by a student to solve the homework and an initially empty (None ) reference where the grade will be stored (line 6). The function provided by the student is supposed to sort a linked list in place. The autograder creates a shareable linked list, executes the homework on it, checks the result, and sets the grade (lines 10-12). The main property verified for the autograder is that the grade stays private and that it gets set to some value (lines 8-9). Naturally, the homework is unverified since it is written by a student. Thus, since the autograder calls the unverified homework, to be able to verify it, one has to assume a specification for the behavior of the homework. SecRef★then ensures that the homework satisfies the specification. Soundly specifying the behavior of unverified code is one of the main contributions of this paper, and we explain this below for student_hw (lines 2-5in Figure 1), illustrating the assumed specification: (1)The pre-condition requires that the linked list is labeled as shareable, statically guaranteeing that the verified autograder passes only shareable references to the homework (line 3). (2)The post-condition ensures that only shareable references have been modified, and exist- ing references have the same label (line 4). This is a universal property of all unverified computations, which work only with shareable references (§2.2). (3)The post-condition also ensures that if the computation succeeds (by returning Inl), the linked list is sorted, without cycles, and contains the same values (line 5). This is the part of the specification that gets enforced dynamically using higher-order contracts (§2.3). The way we specify the footprint in Figure 1 in the post-condition of the student_hw (item (2) above, line 4) is novel. We specify it as the set of references that are labeled as shareable (using the predicate modif_only_shareable ). The usual way of specifying it as the set of references that are in the linked list [Ahman et al. 2018; Leino 2010; Reynolds 2002] does not scale in our setting, where we also need to specify higher-order unverified code. Imagine that the unverified function is in fact a result of a bigger piece of unverified code and that another liked list was already shared with it (as in the example from the introduction). This means that the unverified function can also modify that linked list, thus, to soundly specify its behavior one has to add to the footprint the references in that linked list too. Moreover, references that were shared through subsequent updates to the first linked list also have to be added. Using our labeling mechanism tames the complexity of globally tracking the shared references and allows stating the footprint in a simple way. The assumption that unverified code modifies only shareable references is true if (1) the unverified code cannot forge references (which we assume), (2) we treat the references allocated by the unverified code as shareable references (which we do and explain in §2.2), and (3) the verified code shares only shareable references with the unverified code. To ensure this last point we added the pre-condition for calling the unverified code that the passed reference has to be shareable (item (1), line 3). This pre-condition in combination with a global invariant on the heap enforces that the explicitly passed reference is shareable, and that all references that are reachable from the reference 1F★syntax ˜citesciostar is similar to OCaml ( val,let,match , etc). Binding occurrences btake the form x:tor#x:tfor an implicit argument. Lambda abstractions are written 𝜆b1...b𝑛→t, whereas b1→...→b𝑛→Cdenotes a curried function type with result C, a computation type describing the effect, result, and specification of the function. Contiguous binders of the same type may be written (v1...v𝑛:t). Refinement types are written b{t}(e.g., x:int{x≥0}represents natural numbers). Dependent pairs are written as x:t1&t2. For non-dependent function types, we omit the name in the argument binding, e.g., type #a:Type→(#m#n:nat)→vec a m→vec a n→vec a (m+n)represents the type of the append function on vectors, where both unnamed explicit arguments and the return type depend on the implicit arguments. A type-class constraint {|d:c t1..tn|}is a special kind of implicit argument, solved by a tactic during elaboration. Type0 is the lowest universe of F★; we also use it to write propositions, including ⊤(True) and⊥(False). We generally omit universe annotations. The type either t 1t2has two constructors, Inl:(#t1#t2:Type )→t1→either t 1t2andInr:(#t1#t2:Type )→t2→either t 1t2. Expression Inr?xtests whether xis of the shape Inr y . Binary functions can be made infix by using backticks: x`op`ystands for op x y . Page 6: 6 Andrici et al. are shareable too. The global invariant on the heap ensures that a shareable reference can point only to other shareable references. This means that the SecRef★user has to manually track only the label of the root reference of a complex data structure (e.g., the head of a linked list). The global invariant is necessary to prevent accidental sharing by subsequent writes to shareable references (e.g., the example from the introduction). The user never has to prove directly the preservation of the global invariant though, because the operations to manipulate references provided by Labeled References take care of that. The only extra proof burden appears when labeling a reference as shareable or writing to a shareable reference, one has to prove some conditions ensuring that the affected reference will point only to shareable references after the operation. The witnessed token used in the pre-condition (line 3) is produced by the witness operation of Monotonic State, which provides two such operations: (1) witness , allowing one to convert stable heap predicates into heap-independent ones, and (2) recall , allowing one to recover an already wit- nessed predicate for any future heap. For example, being shareable is a stable property with respect to our preorder that shareable references stay shareable. Thus if one knows that a reference ris share- able in a heap h(i.e., is_shareable r h ), one can witness this and get a token witnessed (is_shareable r ), which can be recalled later, say when the heap is h', to prove that is_shareable r h '.2 To be able to statically verify the specifications of the autograder (lines 7-9) we reiterate that giving a specification to the unverified function is necessary. Since the autograder calls into unverified code, it inherits the property that it can modify shareable references, thus, its footprint is defined as the union of all shareable references and the singleton set of references containing only the grade (line 9). The autograder’s code stores the grade inside a monotonic reference (line 6), a feature of Monotonic State (denoted using mref), that has a preorder on its stored value, which is enforced statically. For the grade, the preorder ensures that the grade cannot be modified once it is set. To be able to set the grade (on line 12), however, one has to know that the grade was not set by calling into the homework, which we know because the footprint of the call states that it modifies only shareable references, and we know that the grade was private before the call. In SecRef★, one cannot share a monotonic reference with the unverified code because there would be no way to guarantee the preorder of the reference, but the verified code can use monotonic references. One can share only normal references, denoted by ref, as long as they are labeled as shareable. Normal references are just monotonic references with the trivial preorder. 2.2 Representation for unverified code We give a representation for unverified code as a shallow embedding in F★satisfying two constraints: first, obviously, it has to be a representation for unverified code (i.e., it requires only trivial logical reasoning), and second, we need to be able to show that any piece of unverified code using this representation satisfies the universal property that it modifies only shareable references. We found a representation that satisfies both these constraints and that is also unaware of the labeling mechanism, by using the effect MST. The representation uses abstract predicates as well as a version of the reference-manipulating operations that mention only these abstract predicates in their specifications. The intuition why this is an appropriate representation for unverified code in F★ is that one can sequence the new operations freely, because the operations require and ensure only the abstract predicates, so one only has to carry the abstract predicates around from post-conditions to pre-conditions, but one never has to do nontrivial logical reasoning. The type of the new reference-manipulating operations is presented in Figure 2 and they use three abstract predicates in a way that allows the operations to still be freely sequenced: (1) a global invariant on the heap (denoted by I), required and guaranteed by all operations; (2) a predicate 2For readers familiar with Separation Logic, this is similar to invariants and their introduction and elimination rules. Page 7: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★7 type poly_alloc_t (I:heap→Type0 ) (𝜑:ref𝛼→Type0 ) (≼:preorder heap ) = init:𝛼→MST (ref𝛼) (𝜆h0→I h0∧every mref𝜑init) (𝜆h0r h1→h0≼h1∧I h1∧𝜑r) type poly_read_t (I:heap→Type0 ) (𝜑:ref𝛼→Type0 ) (≼:preorder heap ) = r:ref𝛼→MST𝛼(requires (𝜆h0→I h0∧𝜑r)) (ensures (𝜆h0v h1→h0≼h1∧I h1∧every mref𝜑v)) type poly_write_t (I:heap→Type0 ) (𝜑:ref𝛼→Type0 ) (≼:preorder heap ) = r:ref𝛼→v:𝛼→MST unit (𝜆h0→I h0∧𝜑r∧every mref𝜑v) (𝜆h0_ h1→h0≼h1∧I h1) Fig. 2. Types of the reference-manipulating operations polymorphic in I,𝜑and≼. on references (denoted by 𝜑) constraining references stored in the heap by write and alloc, and also ensured of freshly allocated references returned by alloc and of references gotten from the heap by read; (3) a preorder on heaps (denoted by ≼) and a guarantee that for all our operations the heap evolves according to this preorder. In Figure 2 we also use the every mrefcombinator that takes a predicate on references (in our case 𝜑) and a value of an inductive type, then recursively decomposes the value by pattern matching. For each reference, it applies the predicate, ensuring that all reachable references satisfy the given predicate. We used this representation for instance to write the following unverified homework that sorts a linked list, which takes as argument the three operations and combines them freely.3 type target_student_hw =I:(heap→Type0 )→𝜑:(ref𝛼→Type0 )→≼:preorder heap→ (∗set of operations that use the abstract predicates ∗) alloc :poly_alloc_tI𝜑(≼)→read :poly_read_tI𝜑(≼)→write :poly_write_tI𝜑(≼)→ (∗the actual type of the homework ∗) ll:ref(linked_list int )→ MST (either unit err ) (requires (𝜆h0→I h0∧𝜑ll)) (ensures (𝜆h0r h1→h0≼h1∧I h1∧every mref𝜑r)) let unverified_student_hw :target_student_hw =𝜆my_alloc my_read my_write → let rec sort (ll:ref(linked_list int )) = match my_read ll with |LLNil→() |LLCons x tl→begin sort tl ; match my_read tl with |LLNil→() |LLCons x 'tl'→if x≤x'then ()else( let new_tl =my_alloc (LLCons x tl ')in sort new_tl ; my_write ll (LLCons x 'new_tl )) end in sort The abstract predicates are also used for specifying the homework in the target language, and we explain how F★shows that the homework satisfies its post-condition, even if it is abstract. The first conjunct of the post-condition is about the preorder on heaps ≼, which is satisfied because all our 3 operations ensure that the heap evolves according to ≼, which is a reflexive and transitive relation. The second conjunct about global invariant Iis satisfied because it is required in the pre-condition oftarget_student_hw , and then every operation requires and ensures this invariant. The third and last conjunct states that any reference that is returned has to satisfy predicate 𝜑. This conjunct is satisfied because a reference can be allocated (which ensures 𝜑), read from the heap (which ensures 𝜑), or is received from the outside, which is also guaranteed because of the pre-condition requiring 3We elided the proof of termination, which is needed since for simplicity our work assumes that state is the only side-effect. Page 8: 8 Andrici et al. that the input linked list satisfies 𝜑. The pre-condition and the post-condition of target_student_hw ensure that every reference that crosses the verified-unverified boundary satisfies the predicate 𝜑. We therefore claim that this is an appropriate representation for stateful unverified code shal- lowly embedded in F★. To make it even more apparent, in the artifact we also have a syntactic representation, a small deeply embedded lambda calculus with first-order ML-style references, and we show a total (back)translation function in F★from any well-typed syntactic lambda-calculus expression to the shallowly embedded representation with abstract predicates. Now time for some magic: we show that we can instantiate the types we give to unverified code, so that this code gets closer to something that can be safely linked with verified code. We illustrate this instantiation step by step for target_student_hw above. Instantiating the abstract invariant, I, lifts all the functions to the effect LR, which is just a synonym for the effect MST with an extra global invariant, ensuring that shareable points to shareable, so after instantiation we have the same effect as in the type student_hw from Figure 1. We instantiate 𝜑with𝜆r→witnessed (is_shareable r ), which becomes the same pre-condition as in type student_hw . Then, we instantiate the preorder ≼, with 𝜆h0h1→modif_only_shareable h 0h1∧same_labels h 0h1, which means we now have as post-condition the universal property that unverified code modifies only shareable references, which is also the first part of the post-condition of student_hw . So unverified code is instantiated during linking to obtain a type with concrete pre- and post-conditions. This is a first step towards bridging the gap between verified and unverified code, and in §2.3 we show how we additionally use higher-order contracts during the compilation of the verified program to fully bridge this gap. But first, let us still mention that instantiating the 3 operations from Figure 2 is straightforward. We instantiate allocation by allocating a reference and then labeling it as shareable. And we instantiate read and write using the default operations of Labeled References, since, due to the new specs, they are now specialized for shareable references. For readan interesting interaction happens between every mref𝜑rand the global invariant to show that whatever was read from the heap is also shareable. The every mrefcombinator stops when reaching a reference because once it was established that a reference is shareable, the global invariant offers the guarantee that all references reachable form this reference are also shareable. 2.3 Higher-order contracts in SecRef★ After the instantiation, an unverified computation of type unverified_student_hw almost has the type student_hw . The only missing piece is a part of the post-condition (item 3 in §2.1), which states that after the homework returns, if the run was successful, then the linked list is sorted, has no cycles and contains the same values as before. This part is enforced by SecRef★using higher-order contracts [Findler and Felleisen 2002], which are used to enforce only properties of shareable references or values passed when unverified code gives control to the verified code. Since the verified code has no insights into what the unverified code does with the shareable references and with which values it gives them, no logical predicates can be established about them. Therefore, it is the choice of the user if they want to strengthen the types and use higher-order contracts, or if they want to manually establish the logical predicates by adding dynamic checks. The advantage of strengthening the specification is that if one would instead link with some verified program, then there would be no need for the dynamic checks, and for unverified code, one would use SecRef★. We implement the idea of Stateful Contracts [Findler and Felleisen 2002] using wrapping and by implementing two dual functions export and import :export takes existing specs and converts them into dynamic checks (e.g., converts pre-conditions of arrows and refinements on the arguments to dynamic checks), while import adds dynamic checks to have more specs (e.g., adds dynamic checks to strengthen the post-conditions of arrows and to add refinements on the result). The functions export and import are defined in terms of each other to support higher-order functions. Page 9: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★9 In SecRef★, when the verified program has initial control, the import function is used during both compilation and back-translation (the dual of compilation, used for the secure compilation proof in §6). During compilation import is used to add dynamic checks to the verified-unverified boundary of the compiled program to protect it. During back-translation import is used to add dynamic checks to the unverified code so that it can be typed as having the necessary specification. In the dual setting, when the unverified code has the initial control, the export function is used. Our implementation of higher-order contracts is verified not to modify the heap in any way (it only reads from the heap). SecRef★also verifies if the dynamic checks imply the specification that has to be enforced. Since F★specifications are not directly executable, SecRef★leverages type classes to automatically infer the check. If this fails though, the user is asked to manually provide the check and prove that the check implies the specification, which benefits from SMT automation. 2.4 Encapsulated references We want to enable the verified program to mutate private references inside the callbacks it passes to the unverified code. Such references are not shared with the unverified code, but are modified indirectly by the callback calls the unverified code makes. To enable this use case, we introduce a third label, encapsulated . Only references labeled as private can become encapsulated, and they remain encapsulated forever. Since encapsulated references are used only in verified code, there is no need to add a constraint that limits to which kind of references they point. Encapsulated references are only directly modified by the verified program, but an unknown number of times, since the unverified code can call back as many times as it likes. The callback can also be called at later times, since it can be stashed away. The advantage of encapsulated references is that one knows how they get modified, thus, one can encapsulate monotonic references to take advantage of a preorder, and/or use refinement types and/or dependent types for the value stored. Encapsulated references are especially useful when the unverified code has initial control. The unverified code can call multiple times the verified program, but in a first-order setting the calls are independent and any reference allocated by the program gets lost when it gives control back to the unverified code. If the program is higher-order though, it can have callbacks with persistent state by using encapsulated references. We illustrate this case in Figure 3 using the example of a pseudo-number generator that has an initialization phase where it gets a seed as an argument and returns a callback that generates a number on each call. To generate a new number for every call to the callback, the program allocates an encapsulated reference, counter , with which it keeps track of how many times it has been called. This is a monotonic reference with the preorder that it can only increase. Since the generator does not make any assumption about the unverified code (the pre-conditions are trivial), SecRef★does not add any dynamic checks during compilation. 1val generate_nr :int→int→int(∗a pure computation ∗) 2let post =𝜆h0_ h1→modif_shareable_and_encaps h 0h1∧same_labels h 0h1 3let prng (seed:int) 4 : LR(unit→LR int (requires (𝜆_→⊤ )) (ensures post )) (requires (𝜆_→⊤ )) (ensures post ) = 5 let counter :mref int (≤) =alloc 0in label_encapsulated counter ; 6 (𝜆()→counter := !counter + 1;generate_nr seed (!counter )) Fig. 3. The illustrative example of a pseudo-number generator This last example also reveals that the presentation in Figure 1 and §2.2 is simplified to improve readability, and in fact, SecRef★uses the more general predicate modif_shareable_and_encaps (instead of the more specific modif_only_shareable ) as a post-condition for unverified code. Page 10: 10 Andrici et al. 3 MONOTONIC STATE REPRESENTED USING A DIJKSTRA MONAD We first introduce the Monotonic State monadic effect MST on which Labeled References builds. We present the interface of Monotonic State as in the original work of Ahman et al. [2018], after which we present our contribution involving defining MST as a Dijkstra monad with a free-monad-based representation (§3.2), and finally we present a mechanized soundness proof for MST (§3.3). 3.1 The interface of Monotonic State Monotonic State is an F★effect characterizing stateful computations whose state evolves according to a given preorder, with additional features to make specifying and verifying such computations more convenient. In particular, given some predicate on states that is stable for any state changes respecting the preorder, one can witness that the predicate holds in one part of a program, resulting in a state-independent token witnessing this action, and then use this token to recall that the predicate still holds at arbitrary later program points. Ahman et al. [2018] considered a general Monotonic State notion, in which the effect is param- eterized by an arbitrary type of states and a preorder on them. In this paper, we pick as states monotonic heaps (type heap) provided by F★, with the preorder ⪯heaps enforcing that allocated references stay allocated forever, and monotonic references preserve their preorders. With this, we obtain our MST effect, which models first-order4ML-style mutable references. The interface of MST, presented in Figure 4, hides the concrete representation of the heap and references, and the implementation of the operations that manipulate them. The interface provides proof that the operations preserve the preorder through a series of lemmas (which we elide). The basic reference type is that of monotonic references, mref a rel , which have individual pre- orders enforced by the preorder on the heaps ⪯. ML-style references ref a are a special case for the trivial preorder that relates all the a-values. Behind the interface, the references have concrete addresses which are hidden from the user to ensure their unforgeability. For reasoning, however, the interface exposes a function addr_of that returns an erased , computationally irrelevant value that can only be used in specifications, but not branched on in programs, ensuring the abstraction boundary is respected. Labeled References uses addr_of in the encoding of the labeling mechanism. The MST effect comes with operations for reading from, writing to, and allocating fresh references. The pre-condition of write requires the new value to be related to the value of the reference in the previous heap. This ensures that the reference evolves according to the preorder of the reference. The write and alloc operations both contain a modifies clause in their post-conditions, which is the usual way of specifying that only specific references are allowed to be modified (the footprint of the call, no labels yet).Specifically, for a set sof addresses, modifies s h 0h1holds if any references contained in h0and not contained in sremain unmodified. Above, !{r}is F★’s syntax for the singleton set containing the address of r. Notice that the pre-conditions of readand write require the reference to be contained, yet for simplicity, we often elide this detail from the other sections of the paper. 3.2 MST as a Dijkstra monad with a free-monad-based representation In the work of Ahman et al. [2018], the MST effect and the operations above were simply assumed and not given definitions within F★. We improve on this by giving a free-monad-based representation forMST in F★, from which it can be defined as a Dijkstra monad [Maillard et al. 2019]. We recall that a Dijkstra monad is a monad-like structure indexed by specifications, which themselves often come from some monad, called a specification monad. For a Dijkstra monad D, the typeD𝛼𝑤classifies computations that return 𝛼-typed values and satisfy a specification 4Providing a monadic representation that allows storing effectful functions given F★’s predicative, countable hierarchy of universes is an open research problem, further detailed in §3.2 and §9. Page 11: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★11 val heap :Type0 val mref :a:Type0→rel:preorder a→Type0 type ref (a:Type0 ) =mref a (trivial_preorder a ) val contains : #a:Type0→#rel:preorder a→heap→mref a rel→Type0 val sel : #a:Type0→#rel:preorder a→h:heap→r:mref a rel {h`contains `r}→a val heap_write : #a:_→#rel:_→h:heap→r:mref a rel {h`contains `r}→x:a{sel h r `rel`x}→heap val heap_alloc : #a:Type0→rel:preorder a→heap→a→mref a rel &heap val addr_of : #a:Type0→#rel:preorder a→mref a rel→erased pos val next_addr :heap→erased pos let(⪯)h0h1=∀a rel (r:mref a rel ).h0`contains `r=⇒(h1`contains `r∧(sel h0 r ) `rel` (sel h1 r )) val alloc : #a:Type→#rel:preorder a→init:a→ MST (mref a rel ) (requires (𝜆h0→⊤ )) (ensures (𝜆h0r h1→h0⪯h1∧fresh r h 0h1∧modifies !{}h0h1∧ addr_of r ==next_addr h 0)) val read : #a:_→#rel:_→r:mref a rel→MST a (requires (𝜆h0→h0`contains `r)) (ensures (𝜆h0v h1→h0==h1∧v==sel h 1r)) val write : #a:_→#rel:_→r:mref a rel→v:a→ MST unit (requires (𝜆h0→h0`contains `r∧(sel h 0r) `rel`v)) (ensures (𝜆h0()h1→h0⪯h1∧modifies !{r}h0h1∧equal_dom h 0h1)) val witnessed :p:(heap→Type0 )→Type0 let stable_heap_predicate =p:(heap→Type0 ){∀h0h1.p h0∧h0⪯h1=⇒p h1} val witness :p:stable_heap_predicate →MST unit (requires (𝜆h0→p h0)) (ensures (𝜆h0()h1→h0==h1∧witnessed p )) val recall :p:stable_heap_predicate →MST unit (requires (𝜆_→witnessed p )) (ensures (𝜆h0()h1→h0==h1∧p h1)) Fig. 4. Interface of Monotonic State 𝑤:𝑊 𝛼, where𝑊is an aforementioned specification monad. A common way to define a Dijkstra monadD[Maillard et al. 2019] is to identify (i) a monad 𝑀to represent computations classified byD, (ii) an ordered monad 𝑊that models specifications for the computations in 𝑀, where the order formalizes when one specification implies another, and (iii) a monad morphism 𝜃:𝑀→𝑊 that infers for every computation its “most precise” specification. The Dijkstra monad Dis then defined asD𝛼𝑤=𝑚:𝑀𝛼{𝜃(𝑚)⊑𝑤}, formalizing the idea that D𝛼𝑤classifies computations 𝑚:𝑀𝛼 that satisfy the given specification 𝑤. Below we work out such a Dijkstra monad for MST. The computational monad. We begin by defining the computational monad as a free monad:5 type mst_repr (a:Type u #a) :Type u #(max 1a) = |Return :a→mst_repr a |Read : #b:Type0→#rel:preorder b→r:mref b rel→k:(b→mst_repr a )→mst_repr a |Write : #b:Type0→#rel:preorder b→r:mref b rel→v:b→k:mst_repr a→mst_repr a |Alloc : #b:Type0→#rel:preorder b→init:b→k:(mref b rel→mst_repr a )→mst_repr a |Witness :p:stable_heap_predicate →k:(unit→mst_repr a )→mst_repr a |Recall :p:stable_heap_predicate →k:(unit→mst_repr a )→mst_repr a As is standard with free monads, elements of mst_repr denote computations modelled as trees whose nodes are operation calls and whose leaves contain return values. Observe that the continuations of Witness and Recall accept simply unit-typed values—at the level of the computational representation 5We omit a constructor that is needed in F★to account for partiality coming from pre-conditions [Winterhalter et al. 2022]. Page 12: 12 Andrici et al. nowitnessed p tokens are passed to the continuation, they only appear and are passed around in the logical specifications. The return and bind operations of this free monad are standard. Let us also discuss why we cannot store higher-order effectful functions. Notice that our monad transforms a Type u #ainto a Type u #(max 1a), this is because the operations Read,Write , and Alloc quantify over Type0 . Thus, it increases the universe level, which makes functions represented using this monad unstorable in our references. There is no known monadic representation that allows storing effectful functions given F★’s predicative, countable hierarchy of universes. We support references that point to other references but cannot point to effectful functions. The specification monad. For specifications, we use the monad of weakest pre-conditions: let mst_w (a:Type ) =wp:((a→heap→Type0 )→(heap→Type0 )){ ∀post post '. (∀v h1.post v h 1=⇒post 'v h1)=⇒(∀h0.wp post h 0=⇒wp post 'h0)} A specification wp:mst_w a is a function that transforms any post-condition post:a→heap→Type0 over return values and final heaps into a pre-condition wp post :heap→Type0 over initial heaps. The refinement type restricts us to monotonic predicate transformers. The unit of this monad requires the post-condition to hold of the initial state, as mst_w_return v post h 0=post v h 0, and the bind operation composes two predicate transformers, as mst_w_bind wp wp 'post h 0=wp(𝜆v h1→wp 'v post h 1)h0. The order⊑onmst_w is defined so that wp⊑wp 'holds when∀post h 0.wp 'post h 0=⇒wp post h 0. The monad morphism and the Dijkstra monad. As the final ingredient for defining a Dijkstra monad, we define a monad morphism 𝜃:mst_repr𝛼→mst_w𝛼by recursion on the tree structure of mst_repr𝛼. For illustration, the cases for return ,witness , and recall are given as follows. let rec𝜃m=match m with |Return v→mst_w_return v |Read r k→... |Write r v k→... |Alloc init k→... |Witness p k→mst_w_bind (𝜆post h→p h∧(witnessed p =⇒post ()h)) (𝜆v→𝜃(k r)) |Recall p k→mst_w_bind (𝜆post h→witnessed p∧(p h=⇒post ()h)) (𝜆v→𝜃(k r)) The unit of mst_repr is mapped to the unit of mst_w , and for the operations the bind of mst_w is used to compose the specificational interpretation of operations with the recursive application of𝜃to their continuations. For instance, the mst_w interpretation of Witness matches exactly the specification of witness from §3.1 using the correspondence between pre- and post-conditions and predicate transformers [Maillard et al. 2019]. The same pattern repeats for all the other operations. The Dijkstra monad is defined by refining mst_repr computations using mst_w predicates via 𝜃: type mst_dm (a:Type ) (wp:mst_w a ) =m:mst_repr a {𝜃m⊑wp} The layered effects. So far mst_dm is just an ordinary type in F★. In order to turn it into the MST effect discussed earlier to support direct-style programming (where monadic returns and binds are implicit), we use a mechanism to extend F★with new effects [Rastogi et al. 2021]. effect MSTATE (a:Type ) (w:mst_w a )with {repr =mst_dm ;return =mst_dm_return ; ... } effect MST (a:Type ) (pre:heap→Type0 ) (post:heap→a→heap→Type0 ) = MSTATE a (𝜆p h0→pre h 0∧(∀v h1.h0≼h1∧post h 0v h1=⇒p v h 1)) The last effect, MST, is derived as a pre- and post-condition based effect synonym [Maillard et al. 2019], and is the one we discussed in §3.1. In F★we mark MSTATE (and thus also the derived effects) total,reifiable , and reflectable . Since our monadic representation is based on an inductive type, we indicate to F★that our effect is total. Totality means that any programs that typecheck against these effects are guaranteed to terminate, and vice versa, any programs we write in these effects have to be proven to terminate. Reification allows us to reveal the underlying free-monad-based Page 13: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★13 representation of these effects in proofs, and reflection allows us to reflect the free monad operation calls to operations in these effects, with the specification of the reflected code determined by 𝜃. 3.3 Soundness of the free-monad-based Dijkstra monad for Monotonic State In the work of Ahman et al. [2018], the MST effect was simply axiomatized in F★, with its computa- tional interpretation defined and the soundness of its axiomatization proved, both on paper. Above we defined such a computational interpretation in F★and now give a soundness proof that the specifications computed with 𝜃from the free monad representation are computationally satisfied. A soundness proof is desirable because choosing a specification for the free monad operations in 𝜃 is not necessarily the same as being sound with respect to the intended computational behavior. At the core of our soundness proof is the idea that the witnessed tokens are merely a convenience for communicating logical information from one part of a program to another one, and any program that is provable using witnessed tokens ought to also be provable without them. To able to vary the relevance of witnessed tokens, we consider a variant of 𝜃, written𝜃w, which is parametric in the tokens w: (heap→Type0 )→Type0 used in the specifications of Witness and Recall , but is otherwise defined like 𝜃. We also define a predicate witnessed_before that captures which stable heap predicates are recalled in a computation, but were not witnessed in the same computation. In particular, when we can prove empty `witnessed_before `m, then any witnessed p token recalled in m:mst_repr𝛼had to originate from an earlier call to witness p inm, which is a property of closed programs. let rec witnessed_before (preds :set stable_heap_predicate ) (m:mst_repr𝛼) :Type0 = match m with |Return _→⊤ |Read r k→... |Write r v k→... |Alloc init k→∀r.preds `witnessed_before ` (k r) |Witness pred k→(union preds (singleton pred )) `witnessed_before ` (k()) |Recall pred k→pred `mem `preds∧preds `witnessed_before ` (k()) We define the state-passing semantics run_mst for a closed program m. We refine mas being verified to satisfy a specification wp, but we instantiate the wparameter of 𝜃wwith the trivial tokens witnessed_trivial p =⊤, modelling that witnessing and recalling are computationally irrelevant. Using refinement types, we require that the weakest pre-condition wp post holds of the initial heap, and we ensure that the post-condition postholds of the return value and the final heap. During the recursive traversal of m, the heap is augmented with a set of predicates witnessed during execution and with a refinement type ensuring that these predicates hold for the given heap. The refinement holds as the heap evolves, since the operations that manipulate the heap preserve the preorder for which the witnessed predicates are stable. During the traversal, the refinement with witnessed_before ensures that any predicates witnessed are contained in the set of predicates of the augmented heap. type heap_w_preds =h:heap &preds :(set stable_heap_predicate ){∀pred.pred `mem `preds =⇒pred h } let rec run_mst_with_preds (wp:state_wp𝛼) (m:mst_repr𝛼{𝜃witnessed_trivial m⊑wp}) (post:(𝛼→heap→Type0 )) (hp0:heap_w_preds {wp post (fst hp 0)∧(snd hp 0) `witnessed_before `m}) : (r:𝛼&hp1:heap_w_preds {post r (fst hp 1)}) =match m with |Return v→(v,hp0) |Read r k→... |Write r v k→... |Alloc #b#rel init k→let(r,h1) =heap_alloc rel (fst hp 0)init in run_mst_with_preds (𝜃witnessed_trivial (k r)) (k r)post (h1,snd hp 0) |Witness p k→let lp = (snd hp 0) `union ` (singleton p )in run_mst_with_preds (𝜃witnessed_trivial (k())) (k())post (fst hp 0,lp) |Recall p k→run_mst_with_preds (𝜃witnessed_trivial (k())) (k())post hp 0 let run_mst (wp:state_wp𝛼) (m:mst_repr𝛼{𝜃witnessed_trivial m⊑wp∧empty `witnessed_before `m}) (post:(𝛼→heap→Type0 )) :h0:heap {wp post h 0}→r:𝛼&h1:heap {post r h 1} Page 14: 14 Andrici et al. =𝜆h0→let(r,hp1) =run_mst_with_preds wp m post (h0,empty )in(r,fst hp 1) Using run_mst , we define the soundness theorem which states that if a closed program mis well-typed against a specification that uses abstract witnessed tokens (modelled using universal quantification), then for any post-condition postand initial heap h0, if the weakest pre-condition wp post holds in h0, the post-condition holds for the return value vin the final heap h1. let soundness (m:mst_repr𝛼) (wp:state_wp𝛼) :Lemma (requires ((∀w.𝜃wm⊑wp)∧empty `witnessed_before `m)) (ensures (∀post h 0.wp post h 0=⇒(let(v,h1) =run_mst wp m post h 0in post v h 1))) = () 4 LABELED REFERENCES We now explain how we build our reference labeling mechanism on top of Monotonic State. 4.1 The labels As illustrated in §2, in SecRef★we work with three labels for references: type label = |Private |Shareable |Encapsulated When a reference is allocated, it starts labeled with private . The user can then relabel the refer- ence either with shareable orencapsulated . Labeling a reference with shareable orencapsulated is permanent. We ensure this using the following preorder on reference labels: let≼lab:preorder label =𝜆l l'→match l ,l'with |Private ,_|Shareable ,Shareable |Encapsulated ,Encapsulated→⊤ |_,_→⊥ To track references’ labels and how they evolve in programs, we assume a ghost top-level monotonic reference that stores a map from the addresses of references to their labels type label_map_t =erased (mref (pos→erased label ) (𝜆m0m1→∀r. (m0r)≼lab(m1r))) assume val label_map :label_map_t The heap relation⪯defined in Figure 4 from §3 ensures that label_map can only evolve according to≼lab, which in turn is what makes being shareable orencapsulated a stable property of references: let is_shareable :mref_stable_heap_predicate =𝜆#a#rel(r:mref a rel )h→ h`contains `label_map (∗necessary to show stability ∗)∧Shareable ? ((sel h label_map ) (addr_of r )) let is_encaps :mref_stable_heap_predicate =𝜆#a#rel(r:mref a rel )h→ h`contains `label_map (∗necessary to show stability ∗)∧Encapsulated ? ((sel h label_map ) (addr_of r )) where mref_stable_heap_predicate is amref-parameterised stable heap predicate. In contrast, as private references can get relabeled with other labels, being private isnota stable property of the heap: let is_private_addr :pos→heap→Type0 =𝜆r h→Private ? ((sel h label_map )r) let is_private :mref_heap_predicate =𝜆#a#rel(r:mref a rel )h→is_private_addr (addr_of r )h We mark the contents of the map and the map itself as erased , so as to on the one hand ensure that labels can only be used in specifications and programs cannot branch on them, and on the other hand, to avoid passing label_map to computationally relevant operations that expect non-erased arguments. The latter allows label_map to be erased in extracted code (as erased is extracted to unit). Finally, we find it useful to define variants of the modifies clause discussed in §3 to specify that the footprint of a computation modifies only shareable orencapsulated , or both kinds of references: Page 15: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★15 let modif_only_shareable_and_encaps (h0:heap ) (h1:heap ) :Type0 =∀a rel (r:mref a rel ). (¬(eq_addrs r label_map )∧¬(is_shareable r h 0∨is_encaps r h 0))=⇒sel h 0r==sel h 1r let modif_shareable_and (h0:heap ) (h1:heap ) (s:set nat ) :Type0 =∀a rel (r:mref a rel ). (¬(eq_addrs r label_map )∧¬(is_shareable r h 0∨(addr_of r ) `mem `s))=⇒sel h 0r==sel h 1r 4.2 The global invariant for shareable references We introduced our global invariant on the heap, that shareable references can only point to shareable references, in §2.1. To formally state it in §4.3, we want to quantify over all references of all types that are labeled as shareable and check that the stored value contains only shareable references. A convenient way to state the invariant is using a deep embedding of the types a shareable reference can store values of. These are either base types, sums or products of them, other references storing values of such types, and data structures built from them, collectively known in the literature as full ground types [Kammar et al. 2017; Murawski and Tzevelekos 2018]. The embedding is given by type full_ground_typ = |SUnit |SInt |SBool |SSum :full_ground_typ→full_ground_typ→full_ground_typ |SPair :full_ground_typ→full_ground_typ→full_ground_typ |SRef :full_ground_typ→full_ground_typ |SLList :full_ground_typ→full_ground_typ and comes with the evident recursive inclusion to_Type :full_ground_typ→Type0 into Type0 types. It is important to emphasize that this deep embedding is only used for stating the global invariant about shareable references and to help with proofs about its preservation, but it does not limit the capacity of the heap to store values of more general types in universe Type0 (recall Figure 4). Only certain operations (e.g., label_shareable in §4.3) need their type arguments to be given as full_ground_typ instead of Type0 , so as to help F★with proving that the global invariant is preserved. To check that a stored value contains only shareable references, we use the deep embedding to define a predicate forall_refs which checks that every reference in a given value of full_ground_typ type satisfies a predicate. We then lift this predicate to properties of references with respect to heaps. As these predicates are parametric in pred, we can instantiate them with different predicates. let rec forall_refs (pred:mref_predicate ) (#t:full_ground_typ ) (x:to_Type t ) :Type0 = let rcall #t x=forall_refs pred #t x in match t with |SUnit |SInt |SBool→⊤ |SSum t 1t2→match x with |Inl x '→rcall x '|Inr x '→rcall x ' |SPair t 1t2→rcall (fst x)∧rcall (snd x ) |SRef t '→pred x |SLList t '→match x with |LLNil→⊤ |LLCons v xsref→rcall v∧pred xsref let forall_refs_heap (pred:mref_heap_predicate )h x=forall_refs (𝜆r→pred r h )x Finally, to be able to define the invariant, we transitively close these predicates on a heap: let trans_forall_refs_heap (h:heap ) (pred:mref_stable_heap_predicate ) = ∀(t:full_ground_typ ) (r:ref(to_Type t )).h`contains `r∧pred r h =⇒forall_refs_heap pred h (sel h r ) This general version we apply to is_shareable and contains predicates, e.g., so as to specify that every shareable reference rpoints to a value (sel h r) that contains only shareable references. Page 16: 16 Andrici et al. 4.3 The LReffect We define the Labeled References effect LRon top of the Monotonic State effect MST by restricting MST computations with a global invariant that enforces the correct usage of shareable references and the ghost state storing reference labels. The invariant is defined as a conjunction of conditions: let contains_pred #a#rel(r:mref a rel )h=h`contains `r let lr_inv (h:heap ) :Type0 = trans_forall_refs_heap h contains_pred ∧(∗contained references point to contained references ∗) trans_forall_refs_heap h is_shareable ∧(∗shareable references point to shareable references ∗) h`contains `label_map∧(∗the labeling map is contained in the heap ∗) is_private (label_map )h∧(∗the labeling map remains private ∗) (∀r.r≥next_addr h =⇒is_private_addr r h )(∗all yet to be allocated references are labeled as private ∗) The last condition ensures that any freshly allocated reference is private by default and will only become shareable by explicitly labeling it so, allowing for fine-grained control over which references can cross the verified-unverified code boundary. This also applies to encapsulated references. The LReffect is then defined on top of MST by packaging the invariant both into pre- and post- conditions. By being an effect synonym as opposed to a new effect, we can seamlessly coerce any MST computation into LR, as long as it satisfies the invariant, something that is very helpful for linking verified code with unverified code (see §2.2). In detail, LRis defined in F★as follows: effect LR (a:Type )pre post =MST a (requires (𝜆h0→lr_inv h 0∧pre h 0)) (ensures (𝜆h0r h1→lr_inv h 1∧post h 0r h1)) By providing the MST operations we discussed in §3 with suitable stronger pre- and post- conditions, we can lift them to the LReffect. For allocation, the LRoperation lr_alloc #a#rel init addi- tionally requires that if the type ahappens to be a full_ground_typ , then every reference contained in the initial value inithas to be contained in the heap. In its post-condition, lr_alloc additionally guarantees that the freshly allocated reference is labeled private and no other references become shareable during its allocation. Modulo the use of lemmas proving that allocation of references preserves lr_inv ,lr_alloc is defined simply as alloc, with the type val lr_alloc #a(#rel:preorder a ) (init:a) :LR(mref a rel ) (requires (𝜆h0→∀t.to_Type t ==a=⇒forall_refs_heap contains_pred h 0#t init )) (ensures (𝜆h0r h1→alloc_post #a#rel init h 0r h1∧is_private r h 1∧becomes_shareable !{}h0h1)) The specification of LR’slr_read operation remains the same as for MST’sread (see Figure 4). For lr_write #a#rel r v , we strengthen the pre-condition by additionally requiring that the reference is different from the ghost state holding the labeling map, and that if ahappens to be a full_ground_typ , then every reference contained in vhas to be contained in the heap, and that if rhappens to be a shareable reference, then every reference contained in vhas to be shareable as well. In return, the post-condition additionally guarantees that no new references become shareable while writing into r. As with lr_alloc , modulo the use of some lemmas, lr_write is defined as write . val lr_write #a(#rel:preorder a ) (r:mref a rel ) (v:a) :LR unit (requires (𝜆h0→write_pre #a#rel r v∧¬(eq_addrs r label_map )∧ (∀t.to_Type t ==a=⇒forall_refs_heap contains_pred h 0#t v∧ (is_shareable r h 0=⇒forall_refs_heap is_shareable h 0#t v)))) (ensures (𝜆h0()h1→write_post r v h 0()h1∧becomes_shareable !{}h0h1)) Page 17: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★17 Similarly to reading from a reference, witnessing and recalling stable predicates also retain the exact pre- and post-conditions as listed in Figure 4, as they also keep the whole heap unchanged. In programs and in particular in the examples in the rest of the paper, programmers can use lr_alloc ,lr_read , and lr_write via the usual ML-style syntax, as alloc,!, and :=respectively. In addition to the operations lifted from the MST effect, the LReffect comes with two important additional operations, which allow private references to be relabeled as shareable orencapsulated . val label_shareable (#t:full_ground_typ ) (r:ref(to_Type t )) :LR unit (requires (𝜆h0→h0`contains `r∧is_private r h 0∧forall_refs_heap is_shareable h 0(sel h 0r))) (ensures (𝜆h0()h1→equal_dom h 0h1∧modifies !{label_map }h0h1∧ is_private (label_map )h1∧becomes_shareable !{sr}h0h1)) val label_encapsulated (#a:Type ) (#rel:preorder a ) (r:mref a rel ) :LR unit (requires (𝜆h0→h0`contains `r∧is_private r h 0∧¬(eq_addrs r label_map ))) (ensures (𝜆h0()h1→equal_dom h 0h1∧modifies !{label_map }h0h1∧ is_private (label_map )h1∧becomes_encapsulated !{r}h0h1)) Both operations can be called with private references contained in the heap, with label_shareable re- quiring that the reference rhas to store full_ground_typ -values (this helps to prove that label_shareable preserves the global invariant, as noted in §4.2), that rhas a trivial preorder (via the type refof non-monotonic references, see Figure 4), and that any references rpoints to have to have already been labeled as shareable . This last point means that when labeling complex data structures as shareable (e.g., linked lists), we have to first apply label_shareable at the end of chains of references. The label_encapsulated operation on the other hand applies to private monotonic references with arbitrary preorders, which in turn necessitates the explicit comparison with label_map —something that follows implicitly for label_shareable due to the preorders of rand label_map being different. These operations are defined by appropriately modifying the ghost state storing the labeling. 5 HIGHER-ORDER CONTRACTS As explained in §2.3, SecRef★uses higher-order contracts at the boundary between verified and unverified code to ensure properties about returned or passed shareable references that cannot be statically determined. The higher-order contracts are built around two functions, import and export , that map from intermediate types to source types, and vice versa. We first introduce intermediate types in §5.1, and then explain in §5.2 how the export and import functions are defined. 5.1 The polymorphic and intermediate interfaces The verified program and the unverified code communicate through an interface, which is different in the source language compared to the target language. To represent the interface of unverified code as presented in §2.2, we define in Figure 5 the type class poly_iface , parameterised by three abstract predicates ( threep ). The types that can appear on the interface of unverified code are thus determined by the instances we provide for the poly_iface class. We provide instances for the base types unit,intand bool, and for pairs, sums, option, list and arrow types, as well as for references and for linked lists. In Figure 5, we show the most interesting instances. Notice that the instances for references and linked lists take a full ground type (§4.2). All types that have a representation as a full ground type can be characterized by poly_iface class. Also notice that poly_iface applies the ideas from §2.2 so that it can be used to describe higher-order interfaces as well. Having defined polymorphic interfaces necessary to represent unverified code, we define in- termediate interfaces as polymorphic interfaces instantiated with some concrete predicates, and intermediate types as instances of such interfaces. Because we represent unverified code using Page 18: 18 Andrici et al. type threep =I:(heap→Type0 )∗𝜑:(#a:_→#rel:_→mref a rel )∗≼:(preorder heap ) class poly_iface (preds :threep ) (t:Type ) = { wt:every_mref_tc t } instance poly_iface_int (preds :threep ) :poly_iface preds int = {wt=every_mref_tc_int } instance poly_iface_sum (preds :threep )t1 t2 {|poly_iface preds t1 |} {|poly_iface preds t2 |} :poly_iface preds (either t1 t2 ) = { wt=every_mref_tc_sum t1 t2 } instance poly_iface_ref (preds :threep ) (t_emb :full_ground_typ ) :poly_iface preds (ref(to_Type t_emb )) = {wt=every_mref_tc_ref t1 #(full_ground_typ_is_every_mref_tc t_emb ) } instance poly_iface_arrow ((I,𝜑,≼):threep )t1 t2 {|c1:poly_iface preds t1 |} {|c2:poly_iface preds t2 |} :poly_iface preds (x:t1→MST t2 (requires (𝜆h0→I h0∧c1.wt.every mrefx𝜑)) (ensures (𝜆h0r h1→I h1∧h0≼h1∧c2.wt.every mref𝜑r))) = {wt=every_mref_tc_arrow t1 t2 _ _ } Fig. 5. Showing the polymorphic interface implemented with type class poly_iface. polymorphism (§2.2), our higher-order contracts have to be enforced on functions under polymor- phism as well [Andrici et al. 2024], which means that our contracts do not know what they enforce until the very end, when the type is instantiated and the actual checks are provided. 5.2 Exporting and importing The functions export andimport are implemented as fields of two type classes, named exportable_from and importable_to . For instance, exportable_from styp carries an intermediate type ityptogether with a function styp→itypthat is shown to preserve the every mrefcombinator. Its dual, importable_to styp is similar, but it has to account for potential breaches of contract by allowing import to fail with an error. We also provide a safe_importable_to class for types for which importing always succeeds. To be able to export/import functions that are polymorphic in the three predicates ( threep ), the type classes are parameterised with some implementation of the three predicates. The specs_of_styp is just an inductive type that semi-syntactically represents the pre- and post-condition that appear on type styp, structured as a tree. It allows us to be able to provide the contracts at importing/exporting time, when the three predicates become concrete. The functions export andimport take the contracts as a tree of type hoc_tree specs_of_styp , which has the same structure as specs_of_typ , and its type guarantees that each pre- and post-condition that has to be enforced has a contract. class exportable_from (preds :threep ) (styp:Type ) (specs_of_styp :spec_tree preds ) = { c_styp :every_mref_tc styp ; ityp :Type ;c_ityp :poly_iface preds ityp ; export :styp→hoc_tree specs_of_styp →ityp; export_preserves_ 𝜑:x:styp→𝜑:_→hocs :_→Lemma (every mref𝜑x) (every mref𝜑(export x hocs )) } class importable_to (preds :threep ) (styp:Type ) (specs_of_styp :spec_tree preds ) = { c_styp :every_mref_tc styp ; ityp :Type ;c_ityp :poly_iface preds ityp ; import :ityp→hoc_tree specs_of_styp →either styp err ; import_preserves_ 𝜑:x:ityp→𝜑:_→hocs :_→Lemma (every mref𝜑x) (every mref𝜑(import x hocs )) } For intermediate types (and thus already in the source language), these type classes are trivially instantiated with identities, and for operators such as option , one proceeds recursively. For instance, theexport function for option a when a is itself exportable will map theexport function for a. What is more interesting is importing and exporting functions that may have pre- and post-conditions. Exporting functions. We provide a general instance to export a function fof source type a→MST (either b err )pre post to a function of the intermediate type a'→MST (either b 'err)pre 'post ', Page 19: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★19 where a'and b'are the intermediate counterparts of aand b, and pre 'and post 'are the pre- and post- condition that appear in poly_iface_arrow in Figure 5. For this, we need an import function ( a'→a) for type aand an export function ( b→b') for type b, which we then compose with f. To be able to callf, we have to ensure that preholds, which we cannot do in general without a dynamic check, thus, the instance requires the existence of a check function which approximates the pre-condition: check :x:a→MST (either unit err ) (requires (pre 'x)) (ensures (𝜆h0r h1→h0==h1∧(Inl?r=⇒pre x h 0))) which either returns an error or succeeds by enforcing pre. Notice that check may safely assume pre ' as it holds when it is called. Finally, one has to ensure that post 'holds, for which we require that the original post-condition of fsatisfies the following property: ∀x h0r h1.post x h 0r h1=⇒post 'h0r h1. One detail that we omitted is that pre 'and post 'are polymorphic in the type of xand r, compared to preand postthat use the types aand b. Therefore, because we call import and export , we also have to prove that pre 'x=⇒pre '(import x )and post 'h0r h1=⇒post 'h0(export r )h1, which we do by using the two lemmas included in the type classes, export_preserves_ 𝜑and import_preserves_ 𝜑. Importing functions. Similarly, we can import a general function fof the intermediate type x:a'→MST (either b 'err)pre 'post 'to the source type x:a→MST (either b err )pre post . Because the type already accounts for errors, we use the safe_importable_from class to avoid duplicating errors. This time we export the input from atoa', call the function, and import the output from b'tob. To be able to call the function f, we need to enforce pre 'using pre, hence we only provide an instance under the assumption ∀x h0.pre x h 0=⇒pre 'x h0. Similarly, once the function is run, we only know that post 'is verified, so we also require ∀x h0e h1.pre x h 0∧post 'h0(Inr e )h1=⇒post x h 0(Inr e )h1, which ensures we do preserve the post-condition of the error case. Often this will be discharged automatically by having the post-condition trivially true for errors. The more interesting part is when the function succeeds and we need to verify the post-condition. For this, the instance also requires a stateful contract [Findler and Felleisen 2002], see select_check [Andrici et al. 2024] below: type cb_check a b {|every_mref_tc b |}pre post x eh = r:b→MST (either unit err ) (𝜆h1→post 'eh r h 1) (𝜆h1rck h 1'→h1==h1'∧(Inl?rck=⇒post x eh r h 1)) select_check :x:a→MST (eh:erased heap {pre x eh } &cb_check a (either b err )pre post x eh ) (requires (pre x )) (ensures (𝜆h0 r h1→reveal (dfst r ) == h0∧h0==h1)) A stateful contract is first run before calling fto read from the heap information necessary to enforce the post-condition, which it can store in the closure, and then the callback of the contract is run after freturns to verify the post-condition post, or return an error otherwise. The same_values predicate from the autograder example (Figure 1) is enforced by SecRef★using a stateful contract. 6 SECREF★: FORMALLY SECURE COMPILATION FRAMEWORK In this section we present the formalization of SecRef★(Figure 6). The formalization is mechanized in F★and instantiates the compiler model presented by Abate et al. [2019] with source and target languages that are shallowly embedded in the style of Andrici et al. [2024]. We start by presenting the source (§6.1) and target language (§6.2). We continue by giving a semantics to them (§6.3). Then, we present compilation and back-translation (§6.4), and how we prove soundness and RrHP (§6.5). For all of these, we first present the setting when the verified program has initial control, however, SecRef★is verified to be secure also when the unverified code has initial control (also §6.5). 6.1 Source Language We make a clear split between the verified program and the code it is linked with. In the source language, we refer to the former as a partial source program, and the latter as a source context. The Page 20: 20 Andrici et al. letI𝑐=lr_inv (∗from §4.3. The three concrete predicates, defined as in §2.2.∗) let𝜑𝑐=𝜆r→witnessed (is_contained r )∧witnessed (is_shareable r ) let≼𝑐=𝜆h0h1→modif_shareable_and_encaps h 0h1∧same_labels h 0h1 type interface𝑆= { ctype :threep→Type ; specs_of_ctype :threep→spec_tree threep ; hocs :hoc_tree (specs_of_ctype (I𝑐,𝜑𝑐,≼𝑐)); imp_ctype :preds :threep→safe_importable_to preds (ctype preds ) (specs_of_ctype preds ); 𝜓:heap→int→heap→Type0 } type ctx𝑆I𝑆=I𝑆.ctype (I𝑐,𝜑𝑐,≼𝑐) type prog𝑆I𝑆=I𝑆.ctype (I𝑐,𝜑𝑐,≼𝑐)→LR int⊤I𝑆.𝜓 type whole𝑆=𝜓:post_cond & (unit→LR int⊤𝜓) let link𝑆#I𝑆(P:prog𝑆I𝑆) (C:ctx𝑆I𝑆) = (| I𝑆.𝜓,𝜆()→P C|)(∗∗denoted by𝐶[𝑃]∗∗) type interface𝑇= { ctype :threep→Type ; interm_ctype :preds :threep→poly_iface preds (ctype preds ); } type ctx𝑇I𝑇=I:_→𝜑:_→≼:_→ poly_alloc_tI𝜑(≼)→poly_read_tI𝜑(≼)→poly_write_tI𝜑(≼)→I𝑇.ctype (I,𝜑,≼) type prog𝑇I𝑇=I𝑇.ctype (I𝑐,𝜑𝑐,≼𝑐)→LR int⊤⊤ type whole𝑇=unit→LR int⊤⊤ let link𝑇#I𝑇(P:prog𝑇I𝑇) (C:ctx𝑇I𝑇) = 𝜆()→P(CI𝑐𝜑𝑐(≼𝑐)ctx_alloc ctx_read ctx_write )(∗∗denoted by𝐶[𝑃]∗∗) let compile_interface (I𝑆:interface𝑆) :interface𝑇= {(∗∗denoted by𝐼𝑆↓∗∗) ctype = (𝜆preds→(I𝑆.imp_ctype preds ).ityp); interm_ctype = (𝜆preds→(I𝑆.imp_ctype preds ).c_ityp ); } let compile_prog #I𝑆(P:prog𝑆I𝑆) :prog𝑇(I𝑆↓) =(∗∗denoted by𝑃↓∗∗) 𝜆C→P((I𝑆.imp_ctype (I𝑐,𝜑𝑐,≼𝑐)).safe_import C I𝑆.hocs ) let back_translate_ctx #I𝑆(C:ctx𝑇I𝑆↓) :ctx𝑆I𝑆=(∗∗denoted by𝐶𝑇↑∗∗) (I𝑆.imp_ctype (I𝑐,𝜑𝑐,≼𝑐)).safe_import (C(I𝑐,𝜑𝑐,≼𝑐))I𝑆.hocs Fig. 6. Secure compilation framework. Idealized mathematical notation. Type threep is presented in Figure 5. Types poly_alloc_t, poly_read_t, and poly_write_t are presented in Figure 2. latter is also a verified piece of code, but for secure compilation we think of it as a target context (aka unverified) that was back-translated (the dual of compilation) to a source context (aka verified). The partial source program and context communicate through a higher-order interface of type interface𝑆, which contains a type ( ctype ), a type class constraint on this type ( imp_ctype ), and a post-condition 𝜓. As we want the type ctype to be polymorphic in the three predicates in our source language, so as to obtain a polymorphic type in the target language, we include the fields specs_of_ctype andhocs to be able to apply higher-order contracts to a polymorphic type as explained in §5.2. The representation of the partial source programs ( prog𝑆) is a computation in the monadic Page 21: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★21 effect LRthat takes as argument a value of type ctype instantiated with the concrete predicates, returns an integer, and ensures the post-condition 𝜓. The representation of source contexts ( ctx𝑆) is just a value of type ctype instantiated with the concrete predicates. Source linking ( link𝑆) is then defined just as function application. A whole source program is a dependent pair between a post- condition and a computation in effect LRfrom unit to integer that guarantees the post-condition. The type class constraint imp_ctype guarantees that the specification on the interface can be soundly enforced by SecRef★. The assumptions made about the unverified code appear on this source interface (e.g., the assumptions the autograder makes about the homework in Figure 1), and imp_ctype ensures that they are one of the two types of assumptions that SecRef★supports (§2.1). 6.2 Target language The formalization of the target language is similar to the source language, with two notable differences: the representation of partial target programs ( prog𝑇) does not require the computation to be verified with respect to a post-condition, since it has the trivial post-condition; and target contexts ( ctx𝑇) use the representation from §2.2. The type ctype in the target interface (of type interface𝑇), now is constrained with the poly_iface type class from §5.1. A partial target program (see prog𝑇) expects the context to satisfy the concrete specification it assumes about the context: to only modify shareable and encapsulated references. Therefore, linking is done at the types of the target language, but after linking, the communication between the program and the context happens at an intermediate type that contains specifications [Andrici et al. 2024]. This can be seen in the definition of target linking ( link𝑇), it instantiates the target context with the concrete predicates I𝑐,𝜑𝑐, and≼𝑐, thus strengthening the type of the context. During linking, the concrete implementations of the operations alloc,read, and write are provided. As explained in §2.2, their definitions are straightforward (specifications and proofs elided): let ctx_alloc init =let r =lr_alloc init in label_shareable r ;r/let ctx_read =lr_read /let ctx_write =lr_write The representation of target contexts is a reasonable representation for unverified code—logical reasoning is not necessary to prove the pre- and post-conditions that use the abstract predicates. As noted in §2.2, we deeply embed a total simply typed language with first-order ML-style references, and provide a translation from any typed expression in this language to a context of type ctype to the representation of target contexts ctx𝑇. The translation is defined structurally recursively on the typing derivations. Formalizing this translation revealed multiple edge cases in higher-order settings, and also the fact that monotonic references cannot be shared with unverified code. This gives us sufficient confidence that our representation is reasonable, in addition to multiple examples we implemented using this representation in the artifact (e.g., the homework in §2.2). 6.3 Semantics Our source and target languages are shallowly embedded. Since whole source and target programs are computations in the LReffect, we can define the same semantics for both. We define this semantics as predicates over the initial heap, the final result (an integer), and the final heap. type state_sem =heap→state→heap→Type0 let(⊆) (s1s2:state_sem ) =∀h0r h1.s1h0r h1=⇒s2h0r h1 To define the semantics of whole programs, we must first reveal the monadic representation of LR using the reify construct provided by F★[Rastogi et al. 2021]. We define the semantics function beh by adapting the monad morphism 𝜃from §3.2 [Andrici et al. 2024; Maillard et al. 2019]. let beh_mst (m:mst_repr int ) :state_sem =𝜆h0r h1→∀p.𝜃m p h 0=⇒p r h 1 Page 22: 22 Andrici et al. let beh (W:unit→LR int⊤⊤) =beh_mst (reify (W())) Since we use 𝜃for both the Dijkstra monad and our semantics function, we can easily turn the intrinsic property that a whole source program satisfies a post-condition 𝜓into an extrinsic property: Theorem 6.1 (Soundness of whole source programs). ∀(𝜓,𝑊):whole𝑆.beh(𝑊)⊆𝜓 Proof sketch. For a whole source program, we know its underneath monadic representation ism:(mst_repr int ){𝜃m⊑(𝜆p h0→∀r h1.𝜓h0r h1=⇒p r h 1)}(§3.2), which implies that beh m⊆𝜓.□ The proof is so straightforward because the type of a whole source program contains an intrinsic specification. We just lift the specification to an extrinsic property of the whole program. Intuitively, the proof that the program satisfies the specification was already done modularly by F★typing. The semantics function is written in the same language as the code it reasons about, meaning that the semantics a program is given depends on F★’s consistency. Therefore, as part of our Trusted Computing Base (TCB) we include the following F★features that SecRef★relies on: F★’s type system, the effect mechanism (which has a paper proof), the axiomatized Ghost effect, and the abstractions provided by modules. We also assume that assume is not used by the program and context. 6.4 Compilation + Target Linking = Back-translation + Source Linking We define the compilation of partial programs ( compile_prog in Figure 6, denoted as 𝑃↓) as wrapping the partial source program in a new function of type prog𝑇. The partial target program takes as argument an instantiated target context whose specification states that it modifies only shareable references. Therefore, to enforce the other assumptions made by the partial source program, SecRef★ adds to the compiled program the higher-order contracts using safe_import . We now introduce the back-translation (the dual of compilation, denoted by 𝐶↑). It is used in the proofs of soundness and Robust Relational Hyperproperty Preservation (§6.5). Back-translation takes a target context and produces a source context. We define it as instantiating the context and then adding the higher-order contracts. One immediately observes that this is exactly what happens during compilation plus target linking, which can be proved as the next equality. Theorem 6.2 (Syntactic inversion law). ∀𝐼𝑆.∀𝐶𝑇:ctx𝑇𝐼𝑆↓.∀𝑃:prog𝑆𝐼𝑆.𝐶𝑇[𝑃↓]=𝐶𝑇↑[𝑃] Proof sketch. Follows by unfolding definitions. □ Theorem 6.2 is one of the main contributions of Andrici et al. [2024], which we also manage to successfully prove in our setting. The key to such a strong result is the carefully designed compilation model. We build on the model presented by Andrici et al. [2024] for IO programs and show how to build a secure compilation framework for stateful programs. The novel additions include dealing with references that cross the verified-unverified boundary by taking advantage of the monotonicity of LR, like𝜑in the representation for unverified code and how 𝜑𝑐uses witnessed . 6.5 Soundness and Robust Relational Hyperproperty Preservation (RrHP) Andrici et al. [2024] show how to use Theorem 6.2 to prove soundness and Robust Relational Hyperproperty Preservation in their setting for IO. We prove the criteria for our setting with state by following their proofs. In particular, SecRef★allows one to statically verify that a partial program satisfies a post-condition 𝜓. We prove a soundness theorem that guarantees that compiling the partial program and then linking it with arbitrary unverified code produces a whole target program that also satisfies the post-condition 𝜓(when the partial program has initial control). Theorem 6.3 (Soundness). ∀𝐼𝑆.∀𝑃:prog𝑆𝐼𝑆.∀𝐶𝑇:ctx𝑇𝐼𝑆↓.beh(𝐶𝑇[𝑃↓])⊆𝐼𝑆.𝜓 Page 23: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★23 Proof sketch. We rewrite the goal with Theorem 6.2 to 𝐶𝑇[𝑃↓]=𝐶𝑇↑[𝑃]. Now,𝐶𝑇↑[𝑃] builds a whole source program and we can apply Theorem 6.1. □ We prove that SecRef★robustly preserves relational hyperproperties. Abate et al. [2019] have this as their strongest secure compilation criterion, and they show it is stronger than full abstraction. Theorem 6.4 (Robust Relational Hyperproperty Preservation (RrHP)). ∀𝐼𝑆.∀𝐶𝑇:ctx𝑇𝐼𝑆↓.∃𝐶𝑆:ctx𝑆𝐼𝑆.∀𝑃:prog𝑆𝐼𝑆.beh(𝐶𝑇[𝑃↓])=beh(𝐶𝑆[𝑃]) Proof sketch. The proof follows by back-translating the target context ( 𝐶𝑇↑) and using it to instantiate the existential of a source context, after which we conclude with Theorem 6.2. □ Finally, we have proved that SecRef★also satisfies RrHP (Theorem 6.4) in the dual setting: when the context has initial control. We mention it briefly because the proofs are similar, since Theorem 6.2 again holds. In this case, instead of soundness, we proved the dual of soundness [Andrici et al. 2024], a less interesting criterion stating that the resulting whole target program modifies only shareable references. We also proved RrHP in the setting where target contexts are represented with typed syntactic expressions [Andrici et al. 2024] of the deeply-embedded language mentioned in §2.2, a case in which target linking and back-translation first do the extra step of translating the expression into the representation for target contexts ( ctx𝑇). The proofs can be found in the artifact. 7 CASE STUDY: SIMPLE STATEFUL COOPERATIVE MULTI-THREADING To test our framework further, we verify a simple stateful cooperative multi-threading case study. In this case study, we are particularly interested in showcasing how scheduling properties can be verified, even when the verified scheduler is orchestrating tasks that are themselves unverified. Our model of cooperative multi-threading is inspired by the work of Dolan et al. [2017] in the context of OCaml, where effect handlers are used to program concurrent systems. In this model, running threads are represented as computation trees. A computation tree is either a final value or a computation yielding control to the next task. When yielding control, the continuation is a stateful computation that returns a new computation tree. We can model these trees as follows: type atree (#t:full_ground_typ ) (r:ref(to_Type t )) (a:Type0 ) = |Return :a→atree r a |Yield :continuation r a→atree r a and continuation (#t:full_ground_typ ) (r:ref(to_Type t ))a= unit→LR(atree r a ) (requires (𝜆h0→is_shareable r h 0)) (ensures (𝜆h0_ h1→h0≼𝑐h1) These trees share a global reference r, which can be accessed by any of the involved stateful computations. For the continuations, we assume that they represent unverified code, and thus they have a corresponding specification: the pre-condition requires the reference rto be shareable , while the post-condition ensures that the unverified code only modifies shareable (orencapsulated ) references using the relation ≼𝑐from Figure 5. As before, we elide the reference containment requirements from pre- and post-conditions, as well as fuel parameters used for proving termination. We model the scheduler state using a private reference, which stores (i) an execution history (a list of thread IDs), (ii) the ID of the next thread to be executed, and (iii) a count of the number of finished threads (to which we refer as inactive ). We refine the scheduler state by adding a fairness property that the execution history needs to satisfy, and also ensure that both the next thread and inactive counts are within valid ranges. The argument kis used to keep track of the number of threads that was passed to the scheduler on initialization. In detail, we define it as follows: Page 24: 24 Andrici et al. type count_st k =hist:(list int ) &next:int&inact :int{fairness k hist next ∧0≤next <k∧0≤inact≤k} let counter_preorder k :preorder (count_st k ) =𝜆v v '→v.1`prefix_of `v'.1 let counter_t k =mref (count_st k ) (counter_preorder k ) The scheduler is implemented as a structurally recursive function with the following type: val scheduler #t(r:ref(to_Type t )) (tasks :list(continuation r unit )) (counter :counter_t (length tasks )) :LR unit (requires (𝜆h0→is_private counter h 0∧is_shareable r h 0)) (ensures (𝜆h0_ h1→modif_shareable_encaps_and h 0h1!{counter }∧gets_shared empty h 0h1)) It takes a list of tasks to be executed (represented as unit-typed continuations), the global shared reference r, and the reference to the current state of the scheduler (which is required to be private in the pre-condition). On each iteration, the scheduler picks the next task to be run, runs it, and acts depending on whether the outcome was a final value or a computation yielding control. We are guaranteed that at each step we run a next task from the given task list and only shared references are modified. This invariant allows us to verify the fairness property of the scheduler’s state. In comparison to the post-condition of unverified code, notice that the post-condition for the scheduler ensures that no labels are modified, but we do not claim to have only modified shared and encapsulated variables: the scheduler state gets updated as the scheduler runs the tasks. Building on the scheduler implementation, we can write a function to run a list of computations. let run #t(v:to_Type t ) (tasks :list(r:ref(to_Type t )→continuation r unit ){length tasks > 0}) : LR(to_Type t ) (requires (𝜆h0→forall_refs_heap contains_pred h0 v ∧forall_refs_heap is_shared h0 v )) (ensures (𝜆h0_ h1→h0≼𝑐h1)) = let counter :counter_t (length tasks ) =alloc (| [], 0, 0 |) in let r =alloc v in label_shareable r ; let tasks =map (𝜆(f: (s:ref(to_Type t ))→continuation s unit )→f r)tasks in scheduler r tasks counter ; !r Here we allocate two references: one that is shared by the different tasks (and thus it is labeled shareable ) and one that is used to store the scheduler’s state (and which is kept private ). We then run the computations using the scheduler and in the end return the final value of the shared reference. The scheduler’s allocated state is the initial one: empty history, the next task is the first one in the task list, and the number of inactive tasks is zero. As a final remark, we note that the function run can also be called by unverified code to cooperatively execute other unverified code. Importantly, runhas the exact same post-condition as unverified code (as used in the continuations). 8 RELATED WORK We already mentioned in the introduction a list of techniques that allow verification of a stateful program, however, they do not support sound verification of a program that can then be linked with arbitrary unverified code with which it dynamically shares mutable references. Secure Compilation. The approaches of Agten et al. [2015] and Strydonck et al. [2021] support secure compilation of formally verified stateful programs against unverified contexts. Agten et al. [2015] securely compile modules verified with separation logic [Reynolds 2002], but written in the C language, which normally lacks memory safety. To overcome that, they add runtime checks at the boundary between verified and unverified code, and also use two static notions of memory: local, which is never shared with the unverified code, and heap memory, which the unverified code can read and write arbitrarily because of the lack of memory safety. The local memory is Page 25: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★25 protected using hardware enclaves, and if one wants to share data from local memory, one has to copy it to the heap memory. To protect the heap memory when calling into the unverified code, they first hash the complement of the assumed footprint of the call, and when control is returned, they recompute the hash and compare it, to make sure that the unverified code did not change heap locations outside the footprint. What we do is different because we work with memory safe languages with ML-style references, which act like capabilities. We show that if the source and target language have capabilities, one can avoid these expensive hash computations by getting the footprint right. Moreover, we have only one notion of references, which can be kept private or be dynamically shared with the unverified code by using the labeling mechanism. Strydonck et al. [2021] compile stateful code verified with separation logic to a target language with hardware-enforced linear capabilities, which cannot be duplicated at runtime. The main idea is to require the unverified context to return the linear capabilities back so that the verified code can infer that the context cannot have stored them. Linear capabilities are, however, an unconventional hardware feature that is challenging to efficiently implement in practice [Georges et al. 2021]. In contrast to their solution, SecRef★provides soundness and security in a target language with ML-style mutable references, which can be implemented using standard capabilities [Watson et al. 2020], but this required us to overcome the challenge of unverified contexts that stash references. Gradual verification is a combination of static and dynamic verification proposed by Bader et al. [2018]. For assertions that cannot be proven statically using the existing code annotations, one can opt to let the tool insert dynamic checks that dynamically enforce the assertion. DiVincenzo et al. [2025] introduced Gradual C0 to do gradual verification for stateful programs. Gradual C0 requires analyzing the unverified code, while our DSL does not. Gradual C0 requires the unverified code to determine what checks to add, and it can be many, while SecRef★avoids adding checks to preserve logical invariants about private references. SecRef★adds higher-order contracts to enforce pre- and post-conditions at the boundary of verified-unverified code. In the absence of unverified code, the dynamic checks are necessary to be able to establish logical predicates about shareable references. As future work, one could try to extend SecRef★so that it statically analyzes unverified code and discards the contracts using soft contract verification [Nguyen et al. 2014, 2018]. 9 CONCLUSIONS AND FUTURE WORK In this paper we introduce SecRef★, a secure compilation framework that allows for the sound verification of stateful programs that can be compiled and then linked with arbitrary unverified code, importantly allowing dynamic sharing of mutable references. SecRef★solves a problem that, to our knowledge, exists in all proof-oriented programming languages (Rocq, Lean, Isabelle/HOL, Dafny, F★, etc). We present our contributions in the context of F★, but we believe many of the ideas could be applied to other proof-oriented programming languages as well, especially to the dependently-typed ones. Our monadic representation for Monotonic State, which uses a Dijkstra monad, can be ported to other dependently-typed languages (e.g., Dijkstra monads also have been implemented in Rocq [Maillard et al. 2019; Silver and Zdancewic 2021]). Once Monotonic State is ported, we believe one should be able to port the other contributions we present in this paper too: Labeled References, the higher-order contracts, the model for secure compilation, and the soundness and RrHP proofs. Dijkstra monads work well in F★because of SMT automation and the effect mechanism of F★, but they are not the standard means to perform verification in other proof-oriented programming languages. Dijkstra monads are however just a way to reason about monadic computations, thus one could instead use other frameworks built to reason about monadic computations [Letan and Régis-Gianas 2020; Maillard et al. 2020; Xia et al. 2020; Yoon et al. 2022]. Page 26: 26 Andrici et al. We believe that SecRef★is a significant step towards the long-term goal of building a secure compiler from F★to OCaml. We see four orthogonal open problems that need to be solved to get closer to this goal: (1) Modeling higher-order store for effectful functions around F★’s predicative, countable hierarchy of universes. Koronkevich and Bowman [2024] propose an acyclic higher-order store, but this would not be sufficient to model realistic attackers written in OCaml (e.g., no support for Landin’s Knot [Landin 1964]). (2) Modeling more of the effects of OCaml by extending the repre- sentation of the monadic effect, and then figuring out how this impacts the soundness and security guaranteed by SecRef★. While this was already studied separately for the IO effect [Andrici et al. 2024], how other effects would impact soundness and security is an open question. (3) End-to-end verified compilation to OCaml . While SecRef★targets a language shallowly embedded in F★, at least two more steps are necessary to target OCaml, both open research problems: verified quoting (to go from the shallow embedding to a deep embedding), and verified erasure specialized for a Dijkstra monad. For verified erasure, Forster et al. [2024] present a verified correct erasure from Rocq to Malfunction (one of the intermediate languages of OCaml), which we hope can be specialized to computations represented using monads and extended to verify security. Their proof relies on the formalization of Rocq [Sozeau et al. 2020], which means that before one is able to reproduce and extend their proof, one would first have to give a formalization to F★. (4) Encoding Labeled References on top of Separation Logic , yet how to do Separation Logic in F★well is itself a topic of ongoing research [Fromherz et al. 2021; Swamy et al. 2020, 2024]. ACKNOWLEDGMENTS This work was in part supported by the European Research Council under ERC Starting Grant SECOMP (715753), by the German Federal Ministry of Education and Research BMBF (grant 16KISK038, project 6GEM), and by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) as part of the Excellence Strategy of the German Federal and State Governments – EXC 2092 CASA - 390781972. Exequiel Rivas was supported by the Estonian Research Council starting grant PSG749. Danel Ahman was supported by the Estonian Research Council grant PRG2764. A RUNNING SECREF★PROGRAMS BY EXTRACTING TO OCAML As the MST effect is marked reifiable , F★can automatically extract effectful programs to OCaml by using the reified representation of the program (i.e. the free monad is §3.2). However, we wanted native extraction in order to be more efficient and more realistic. To extract our custom effects to native operations, we make use of existing support in F★to extend its extraction rules. Essentially, we add rules that recognize the operations of the MST effect (alloc,read,write ) and translate them directly to their native OCaml counterparts, bypassing the monadic representation. This is implemented in a standalone F★module that uses compiler-internal APIs in order to register the new rules. This module is later built into a dynamic object (an OCaml cmxs file) and loaded dynamically by F★when extraction is to be performed. The original monadic representation and its operations are marked with the ‘ noextract ’ qualifier, to make sure they do not appear in extracted code, and only the primitive operations are used. It was necessary to write a small patch to F★to allow this. The current F★rejects trying to use native extraction rules for reifiable effects. Our patch allow this, and uses the native representation for extraction purposes. The patch applies cleanly over the latest F★release (v2025.02.17) and is included in the supplementary material. B ADDITIONAL EXAMPLE: GUESSING GAME To showcase the use of encapsulated references, in Figure 7 we present another example of a verified program that plays the “guess the number I’m thinking of” with an unverified player. The Page 27: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★27 verified program gives to the player a range and a callback that the player can use to guess the number. The verified program saves the guesses of the player using an encapsulated monotonic reference that has a preorder that ensures that no guess is forgotten (line 5). SecRef★does not add any dynamic checks using higher-order contracts during compilation because the post-condition of the player follows from the universal property of unverified code (§2.2). 1type cmp = |LT|GT|EQ 2let post =𝜆h0_ h1→modif_shareable_and_encaps h 0h1∧same_labels h 0h1 3type player_t = (l:int ∗r:int ∗(guess :int→LR cmp (𝜆_→⊤ )post))→LR int (𝜆_→l<r)post 4let play_guess (l pick r :int) (player :player_t ) :LR(bool ∗list int ) (𝜆_→l<pick∧pick <r)post = 5let guesses :mref (list int ) (𝜆l l'→l`prefix_of `l') =alloc []in label_encapsulate guesses ; 6let final_guess = 7 player (𝜆x→guesses :=guesses @ [x];if pick =x then EQ else if pick <x then LT else GT )in 8guesses :=guesses @ [final_guess ]; 9if pick =final_guess then (true, !guesses ) 10 else(false , !guesses ) Fig. 7. The illustrative example of a function that plays "Guess the number I’m thinking of" REFERENCES C. Abate, R. Blanco, D. Garg, C. Hritcu, M. Patrignani, and J. Thibault. Journey beyond full abstraction: Exploring robust property preservation for secure compilation. CSF. 2019. P. Agten, B. Jacobs, and F. Piessens. Sound modular verification of C code executing in an unverified context. POPL . 2015. D. Ahman, C. Fournet, C. Hriţcu, K. Maillard, A. Rastogi, and N. Swamy. Recalling a witness: Foundations and applications of monotonic state. PACMPL , 2(POPL):65:1–65:30, 2018. C. Andrici, Ş. Ciobâcă, C. Hritcu, G. Martínez, E. Rivas, É. Tanter, and T. Winterhalter. Securing verified IO programs against unverified code in F★.Proc. ACM Program. Lang. , 8(POPL):2226–2259, 2024. A. W. Appel. Modular verification for computer security. CSF. 2016. A. Arasu, T. Ramananandro, A. Rastogi, N. Swamy, A. Fromherz, K. Hietala, B. Parno, and R. Ramamurthy. FastVer2: A provably correct monitor for concurrent, key-value stores. CPP. 2023. J. Bader, J. Aldrich, and É. Tanter. Gradual program verification. VMCAI . 2018. K. Bhargavan, A. Bichhawat, Q. H. Do, P. Hosseyni, R. Küsters, G. Schmitz, and T. Würtele. An in-depth symbolic security analysis of the ACME standard. CCS. 2021. A. Delignat-Lavaud, C. Fournet, M. Kohlweiss, J. Protzenko, A. Rastogi, N. Swamy, S. Z. Béguelin, K. Bhargavan, J. Pan, and J. K. Zinzindohoue. Implementing and proving the TLS 1.3 record layer. IEEE S&P . 2017. J. DiVincenzo, I. McCormack, C. Zimmerman, H. Gouni, J. Gorenburg, J.-P. Ramos-Dávila, M. Zhang, J. Sunshine, E. Tanter, and J. Aldrich. Gradual C0: Symbolic execution for gradual verification. ACM Trans. Program. Lang. Syst. , 46(4), 2025. S. Dolan, S. Eliopoulos, D. Hillerström, A. Madhavapeddy, K. C. Sivaramakrishnan, and L. White. Concurrent system programming with effect handlers. In M. Wang and S. Owens, editors, Trends in Functional Programming - 18th International Symposium, TFP 2017, Canterbury, UK, June 19-21, 2017, Revised Selected Papers . 2017. D. Dreyer, G. Neis, and L. Birkedal. The impact of higher-order state and control effects on local relational reasoning. J. Funct. Program. , 22(4-5):477–528, 2012. R. B. Findler and M. Felleisen. Contracts for higher-order functions. ICFP . 2002. Y. Forster, M. Sozeau, and N. Tabareau. Verified extraction from Coq to OCaml. Proc. ACM Program. Lang. , 8(PLDI):52–75, 2024. A. Fromherz, A. Rastogi, N. Swamy, S. Gibson, G. Martínez, D. Merigoux, and T. Ramananandro. Steel: proof-oriented programming in a dependently typed concurrent separation logic. Proc. ACM Program. Lang. , 5(ICFP):1–30, 2021. A. L. Georges, A. Guéneau, T. V. Strydonck, A. Timany, A. Trieu, S. Huyghebaert, D. Devriese, and L. Birkedal. Efficient and provable local capability revocation using uninitialized capabilities. Proc. ACM Program. Lang. , 5(POPL):1–30, 2021. A. L. Georges, A. Guéneau, T. V. Strydonck, A. Timany, A. Trieu, D. Devriese, and L. Birkedal. Cerise: Program verification on a capability machine in the presence of untrusted code. J. ACM , 71(1):3:1–3:59, 2024. R. Gu, Z. Shao, H. Chen, X. N. Wu, J. Kim, V. Sjöberg, and D. Costanzo. CertiKOS: An extensible architecture for building certified concurrent OS kernels. OSDI . 2016. Page 28: 28 Andrici et al. T. Hance, A. Lattuada, C. Hawblitzel, J. Howell, R. Johnson, and B. Parno. Storage systems are distributed systems (so verify them that way!). OSDI , 2020. S. Ho, J. Protzenko, A. Bichhawat, and K. Bhargavan. Noise*: A library of verified high-performance secure channel protocol implementations. IEEE S&P . 2022. O. Kammar, P. B. Levy, S. K. Moss, and S. Staton. A monad for full ground reference cells. In 32nd Annual ACM/IEEE Symposium on Logic in Computer Science, LICS 2017, Reykjavik, Iceland, June 20-23, 2017 . 2017. G. Klein, J. Andronick, K. Elphinstone, G. Heiser, D. A. Cock, P. Derrin, D. Elkaduwe, K. Engelhardt, R. Kolanski, M. Norrish, T. Sewell, H. Tuch, and S. Winwood. seL4: formal verification of an operating-system kernel. CACM , 53(6):107–115, 2010. P. Koronkevich and W. J. Bowman. Type universes as allocation effects. CoRR , abs/2407.06473, 2024. P. J. Landin. The mechanical evaluation of expressions. The Computer Journal , 6(4):308–320, 1964. K. R. M. Leino. Dafny: An automatic program verifier for functional correctness. LPAR . 2010. X. Leroy. Formal verification of a realistic compiler. CACM , 52(7):107–115, 2009. T. Letan and Y. Régis-Gianas. FreeSpec: specifying, verifying, and executing impure computations in Coq. CPP. 2020. J. Li, A. Lattuada, Y. Zhou, J. Cameron, J. Howell, B. Parno, and C. Hawblitzel. Linear types for large-scale systems verification. OOPSLA , 2022. K. Maillard, D. Ahman, R. Atkey, G. Martínez, C. Hriţcu, E. Rivas, and E. Tanter. Dijkstra monads for all. PACMPL , 3(ICFP), 2019. K. Maillard, C. Hriţcu, E. Rivas, and A. Van Muylder. The next 700 relational program logics. PACMPL , 4(POPL):4:1–4:33, 2020. A. S. Murawski and N. Tzevelekos. Algorithmic games for full ground references. Formal Methods Syst. Des. , 52(3):277–314, 2018. T. C. Murray, D. Matichuk, M. Brassil, P. Gammie, T. Bourke, S. Seefried, C. Lewis, X. Gao, and G. Klein. seL4: From general purpose to a proof of information flow enforcement. IEEE S&P . 2013. P. C. Nguyen, S. Tobin-Hochstadt, and D. V. Horn. Soft contract verification. ICFP . 2014. P. C. Nguyen, T. Gilray, S. Tobin-Hochstadt, and D. V. Horn. Soft contract verification for higher-order stateful programs. Proc. ACM Program. Lang. , 2(POPL):51:1–51:30, 2018. J. Protzenko, B. Parno, A. Fromherz, C. Hawblitzel, M. Polubelova, K. Bhargavan, B. Beurdouche, J. Choi, A. Delignat-Lavaud, C. Fournet, N. Kulatova, T. Ramananandro, A. Rastogi, N. Swamy, C. M. Wintersteiger, and S. Z. Béguelin. EverCrypt: A fast, verified, cross-platform cryptographic provider. IEEE S&P . 2020. T. Ramananandro, A. Delignat-Lavaud, C. Fournet, N. Swamy, T. Chajed, N. Kobeissi, and J. Protzenko. EverParse: Verified secure zero-copy parsers for authenticated message formats. USENIX Security . 2019. A. Rastogi, G. Martínez, A. Fromherz, T. Ramananandro, and N. Swamy. Programming and proving with indexed effects, 2021. J. C. Reynolds. Separation logic: A logic for shared mutable data structures. In 17th IEEE Symposium on Logic in Computer Science (LICS 2002), 22-25 July 2002, Copenhagen, Denmark, Proceedings . 2002. M. Sammler, D. Garg, D. Dreyer, and T. Litak. The high-level benefits of low-level sandboxing. Proc. ACM Program. Lang. , 4 (POPL):32:1–32:32, 2020. L. Silver and S. Zdancewic. Dijkstra monads forever: termination-sensitive specifications for interaction trees. Proc. ACM Program. Lang. , 5(POPL):1–28, 2021. M. Sozeau, S. Boulier, Y. Forster, N. Tabareau, and T. Winterhalter. Coq Coq correct! Verification of type checking and erasure for coq, in Coq. Proc. ACM Program. Lang. , 4(POPL):8:1–8:28, 2020. T. V. Strydonck, F. Piessens, and D. Devriese. Linear capabilities for fully abstract compilation of separation-logic-verified code. J. Funct. Program. , 31:e6, 2021. N. Swamy, A. Rastogi, A. Fromherz, D. Merigoux, D. Ahman, and G. Martínez. SteelCore: An extensible concurrent separation logic for effectful dependently typed programs. Proc. ACM Program. Lang. , 4(ICFP), 2020. N. Swamy, T. Ramananandro, A. Rastogi, I. Spiridonova, H. Ni, D. Malloy, J. Vazquez, M. Tang, O. Cardona, and A. Gupta. Hardening attack surfaces with formally proven binary format parsers. In R. Jhala and I. Dillig, editors, PLDI ’22: 43rd ACM SIGPLAN International Conference on Programming Language Design and Implementation, San Diego, CA, USA, June 13 - 17, 2022 . 2022. N. Swamy, A. Rastogi, G. Martínez, M. Frisella, T. Dardinier, G. Ebner, and T. Ramananandro. PulseCore: A dependently typed stratified separation logic, 2024. D. Swasey, D. Garg, and D. Dreyer. Robust and compositional verification of object capability patterns. PACMPL , 1(OOPSLA): 89:1–89:26, 2017. R. N. M. Watson, P. G. Neumann, J. Woodruff, M. Roe, H. Almatary, J. Anderson, J. Baldwin, G. Barnes, D. Chisnall, J. Clarke, B. Davis, L. Eisen, N. W. Filardo, R. Grisenthwaite, A. Joannou, B. Laurie, A. T. Markettos, S. W. Moore, S. J. Murdoch, K. Nienhuis, R. Norton, A. Richardson, P. Rugg, P. Sewell, S. Son, and H. Xia. Capability Hardware Enhanced RISC Instructions: CHERI Instruction-Set Architecture (Version 8). Technical Report UCAM-CL-TR-951, University of Page 29: SecRef★: Securely Sharing Mutable References Between Verified and Unverified Code in F★29 Cambridge, Computer Laboratory, 2020. T. Winterhalter, C.-C. Andrici, C. Hriţcu, K. Maillard, G. Martínez, and E. Rivas. Partial Dijkstra Monads for all. TYPES, 2022. L. Xia, Y. Zakowski, P. He, C. Hur, G. Malecha, B. C. Pierce, and S. Zdancewic. Interaction trees: representing recursive and impure programs in Coq. Proc. ACM Program. Lang. , 4(POPL):51:1–51:32, 2020. I. Yoon, Y. Zakowski, and S. Zdancewic. Formal reasoning about layered monadic interpreters. Proc. ACM Program. Lang. , 6 (ICFP), 2022. Y. Zakowski, C. Beck, I. Yoon, I. Zaichuk, V. Zaliva, and S. Zdancewic. Modular, compositional, and executable formal semantics for LLVM IR. Proc. ACM Program. Lang. , 5(ICFP):1–30, 2021. J.-K. Zinzindohoué, K. Bhargavan, J. Protzenko, and B. Beurdouche. HACL*: A verified modern cryptographic library. CCS. 2017.

---