Copyright by Mihir Parang Mehta 2021 The Dissertation Committee for Mihir Parang Mehta certifies that this is the approved version of the following dissertation:

Verifying Filesystems for Abstract Separation Logic-based Program Verification in the ACL2 Theorem Prover

Committee:

William R. Cook, Supervisor

James Bornholt

Vladimir Lifschitz

Christopher J. Rossbach

Sol Swords Verifying Filesystems for Abstract Separation Logic-based Program Verification in the ACL2 Theorem Prover

by

Mihir Parang Mehta

DISSERTATION

Presented to the Faculty of the Graduate School of The University of Texas at Austin in Partial Fulfillment of the Requirements for the Degree of

DOCTOR OF PHILOSOPHY

THE UNIVERSITY OF TEXAS AT AUSTIN May 2021 This dissertation is dedicated to my family, who made it happen in more ways than I can count, and to the science that was alluring enough to draw me in for the long haul. Acknowledgments

My journey towards this degree has been singularly enjoyable, but I would never have got this far without so much help from so many.

My dissertation advisor, William Cook, has been the co-author most people dream of having - working with me until midnight, helping me pick the mot juste each time, checking the finer details of my logical formulae and reminding me of the best ways to keep people engaged while presenting a paper. In the semesters when I’ve been his teaching assistant, I’ve had the opportunity to watch him being just as detail-oriented and being open about his struggles with cancer in the interest of reminding his audience to safeguard their own health. He has stuck with me through the hardest of times, and I am grateful.

I thank the other members of my committee - James Bornholt, Vladimir Lifschitz, Chris Rossbach and Sol Swords - for engaging with my work along every possible axis and stepping up whenever I needed their counsel. Special thanks are owed to Matt Kaufmann, whose ability and willingness to improve the ACL2 theorem prover and answer all my questions about it was crucial to my work, to Isil Dillig, Thomas Dillig, and Warren Hunt for believing in my potential as a doctoral student, and to Ruben Gamboa and Grant Passmore for giving me my first opportunity to serve on a program committee. I’m

v also grateful to Alison Norman, Don Batory, Sujay Sanghavi, Constantine Caramanis, Glenn Downing and Ahmed Gheith for the opportunity to learn and grow as a teaching assistant in their courses, and to Shibashis Guha and S. Arun-Kumar for continuing a research collaboration during my doctoral studies that began in my undergraduate days at IIT Delhi.

Over my time as a student at UT Austin and as an intern, I have learned from and enjoyed my downtime with so many of my fellow graduate students and my colleagues in industry. I could not name everyone if I tried, but I must thank Cuong Chau, Jo Ebergen, Amit Goel, Shilpi Goel, Rishab Goyal, Arati Kaushik, Keshav Kini, Nari Krishnamurthy, Sundaravaradan N. Ananthan, Ramesh Peri, Arthur Peters, David Rager, Dhananjay Raju, Ben Selfridge, and Mertcan Temel. It has been amazing discussing formal verification and ACL2, hiking and bicyling all over Texas, playing board games, experimenting with desserts, and just generally learning how to be a researcher with all of you.

My parents Parang Mehta and Priya Mehta, my sister Meha Mehta, and my grandparents Narendra Mehta, Raksha Mehta, Maina Sancheti, and Pushp Chand Sancheti have consistently encouraged me to reach for the big prize, the Ph.D. Their love is the reason I kept going and the reason I eventually got there, and I am thankful.

vi Verifying Filesystems for Abstract Separation Logic-based Program Verification in the ACL2 Theorem Prover

Publication No.

Mihir Parang Mehta, Ph.D. The University of Texas at Austin, 2021

Supervisor: William R. Cook

The field of filesystem verification has been receiving steady attention from researchers from the filesystem and verification worlds, but some aspects remain unexplored. Considering it important to verify an existing filesystem with the property of binary compatibility - i.e. a filesystem which maintains a disk image in a state that can be read by existing implementations of that filesystem - we develop LoFAT, an efficient implementation of FAT32 in the lan- guage of the ACL2 theorem prover. Devising HiFAT, a directory-tree model of FAT32, we prove it to abstract LoFAT. This refinement relationship allows us to quickly prototype a number of filesystem calls in LoFAT, which we later replace with more efficient implementations that retain their correctness properties. We also validate LoFAT by co-simulation against mature implementations of FAT32, namely, the mtools and the implementation of FAT32.

vii Considering it important to support code proofs, i.e. proofs of cor- rectness of programs making use of the filesystem, we develop such proofs for programs interacting with LoFAT. Initially relying on read-over-write proper- ties of LoFAT, we later develop an abstract separation logic layer on top of HiFAT, which abstracts it and therefore abstract LoFAT. This layer, AbsFAT, allows us to concisely represent filesystem states and file operations on them. We show some code proofs that are simplified with the use of AbsFAT.

Finally, we investigate the use of abstract separation logic to model concurrent operations on the filesystem. Choosing to focus on single-core concurrency, we use the standard approach which involves an oracle, i.e. an uninterpreted function that decides which of the waiting threads at any mo- ment gets to execute the next instruction. We demonstrate a proof of code correctness based on this model, and conclude with some ideas for future work.

viii Table of Contents

Acknowledgments v

Abstract vii

List of Tables xi

List of Figures xii

Chapter 1. Introduction 1

Chapter 2. Background 7 2.1 ACL2 ...... 7 2.1.1 Guard Verification ...... 8 2.1.2 Single-threaded Objects ...... 9 2.1.3 Equivalence and Rewriting ...... 9 2.1.4 Logical Story of I/O ...... 11 2.1.5 The FTY Discipline ...... 11 2.2 Program Logic ...... 12 2.3 FAT32 ...... 15

Chapter 3. Abstract Models Leading up to FAT32 19 3.1 Properties Proven for the Abstract Models ...... 21

Chapter 4. HiFAT, LoFAT, and Properties Proven 23 4.1 Termination ...... 25 4.2 Equivalence ...... 27 4.3 Invertibility and Error Codes ...... 29 4.4 LoFAT-HiFAT Correspondence ...... 32 4.5 Correctness of the Specification ...... 35

ix Chapter 5. Abstract Separation Logic for Filesystem Verifica- tion 39 5.1 System calls and Support for Filesystem Clients ...... 41 5.2 Rewriting ...... 43

Chapter 6. Concurrency 52 6.1 State ...... 56 6.2 Operations ...... 57 6.3 Queues ...... 58 6.4 ...... 58 6.5 Limitations ...... 58 6.6 Proof ...... 59

Chapter 7. Evaluation 61 7.1 Co-simulation ...... 61 7.2 POSIX Interface and Tests ...... 62 7.3 Performance ...... 65

Chapter 8. Related work 70 8.1 Interactive Theorem Provers ...... 70 8.2 Non-interactive Theorem Provers ...... 71 8.3 Abstract Reasoning about Filesystems ...... 72 8.4 Other Aspects of Operating System Verification ...... 73

Chapter 9. Conclusion 74

Bibliography 77

x List of Tables

3.1 Abstract models and their features ...... 20

7.1 POSIX syscalls implemented ...... 64 7.2 Timing disk image I/O ...... 68 7.3 Code summary, including data generated using David A. Wheeler’s SLOCCount ...... 69

xi List of Figures

1.1 A conjecture involving mkdir and stat ...... 4

2.1 A FAT32 directory tree ...... 18

3.1 Refinement/reuse relationships between abstract models . . . 20 3.2 l2-wrchs-correctness-1 ...... 22 3.3 l2-rdchs-correctness-1 ...... 22 3.4 l2-read-after-write-1 ...... 22

4.1 Two equivalent directory trees...... 28 4.2 Equivalences ...... 31

5.1 Hoare triples describing the possible behaviours of mkdir ... 40 5.2 Proof tree for the conjecture in Fig. 1.1 ...... 43 5.3 An axiom that means different things in the HiFAT and LoFAT contexts ...... 45 5.4 Reasoning about longer sequences of system calls ...... 50 5.5 Coreutils programs in the co-simulation test suite ...... 51 5.6 mtools programs in the co-simulation test suite ...... 51

6.1 Oracle-based cp. Note, the various operations that go into copy- ing one file are treated as a transaction, and the indexing of queues is not zero-based...... 55

7.1 Syscalls and co-simulation tests ...... 64

xii Chapter 1

Introduction

Filesystems have been a long-standing subject of study in the formal verification community. The community has taken a variety of approaches, using both interactive and non-interactive theorem provers, for the purpose of verifying novel filesystems providing various properties. This dissertation dis- cusses one such effort based on the previously unaddressed problem of verifying an existing filesystem which maintains binary compatibility with existing, ma- ture implementations of the same filesystem. In solving this problem for the FAT32 filesystem, the current work’s focus on maintaining binary compati- bility and validating it through co-simulation testing makes this work novel. Another, related, problem addressed by this dissertation is the task of veri- fying programs which make use of the filesystem, which we facilitate through a separation logic interface to FAT32. This gives us a convenient logical ab- straction for proofs of both iterative and non-iterative programs by turning semantic disjointness properties into syntactic ones and thereby making use of the ACL2 theorem prover’s [31] facilities for term rewriting.

In the initial stage of this verification effort, a series of abstract filesys- tem models, named L1 through L6, has been developed to incrementally build

1 up to FAT32’s full set of features. Next, a pair of filesystem models named HiFAT and LoFAT (referring to high and low levels of abstraction) has been developed to match the operation of FAT32 up to co-simulation, re-using data structures from the abstract models and also re-using the general refinement methodology for proofs of correctness. A refinement relationship has been proven between HiFAT, which is a directory tree representation of the filesys- tem, and LoFAT, which is an in-memory representation of a FAT32 disk image. This refinement relationship has been used to prove the correctness of POSIX

file operations on LoFAT, in terms of the return values, errno values, buffer contents, file descriptors, directory streams and so on by reference to HiFAT which is simpler to reason about.

It is necessary to co-simulate with existing filesystem implementations in order to show binary compatibility; towards this end, LoFAT is made exe- cutable by enabling its state to be read from and written to FAT32 disk images with some care towards optimising I/O operations for quick execution. This makes LoFAT inter-operable with FAT32 implementations, and also reduces the scope for bugs in the non-verified part of the implementation by using only two ACL2 functions for reading and writing disk images. A suite of co-simulation tests is developed in which ACL2 programs, making use of LoFAT, replicate the operation of widely used programs from the Coreutils suite of programs

(which make use of Linux’ FAT32 implementation) as well as the mtools.

The next step has been the infrastructure for code proofs, showing that programs making use of the filesystem are correct individually as well as

2 when they are used together in a script. Separation logic, a standard logical framework for program verification, becomes the basis for expressing the spec- ifications of system calls in a manner that supports these proofs for programs which make many system calls. This development goes to show that correct- ness can also be verified in a more general setting for machine code programs (i.e. all compiled programs) by providing a conventional file-descriptor inter- face to a verified filesystem implementation such as this one and making use of existing work on proofs of machine code programs. While related work makes use of many different alternative approaches towards code proofs, separation logic is a natural choice to be the basis for the present work. It is intuitive as well as easily applicable towards verifying code through term rewriting as implemented in general purpose theorem proving systems.

For these code proofs, we introduce AbsFAT, an axiomatisation of a subset of the POSIX system calls offered by filesystems, specifically FAT32. We build on HiFAT and LoFAT; our axiomatic approach here complements the refinement approach we take with HiFAT and LoFAT. Although refinement remains necessary to keep tractable the problem of verifying the binary com- patible model LoFAT, axiomatisation is needed in order to prove properties about external programs which are not (and should not be) developed with the details of our formalisation in mind, except for the POSIX-like interface that LoFAT offers.

Fig. 1.1 shows a program that calls mkdir and then stat. In this exam- ple, we conjecture a Hoare triple with the precondition True and the postcondi-

3 Figure 1.1: A conjecture involving mkdir and stat tT rueu r := mkdir(path); s := stat(path) tpr Ù 0q Ñ ps Ù 0qu tion pr Ù 0q Ñ ps Ù 0q. This conjecture says that stat succeeds when mkdir succeeds, and proving it requires precise specifications of these system calls both in terms of changes in filesystem state and in terms of return values and success/error conditions. AbsFAT provides specifications of this nature, and ACL2 is able to prove this conjecture automatically through rewriting with AbsFAT. Programmers working at this level of abstraction rely on these return values and success/error conditions, around which they build their program logic; this makes the present work valuable.

Our principal contributions with AbsFAT are

• the development of AbsFAT on the basis of abstract separation logic;

• the formalisation of its connection to the existing models HiFAT and LoFAT, which involves the development of a number of system calls and showing that they are equivalent to the versions of those system calls in HiFAT and in turn in LoFAT;

• the development of a library of lemmas which facilitate a number of Hoare-style code proofs through rewriting, accompanied by examples of such proofs.

4 In the rest of this dissertation, we detail this work, starting with back- ground information on ACL2, program logic and FAT32 (chapter 2). The ab- stract models and the various filesystem features as well as correctness proofs built up in them are described in chapter 3. LoFAT and HiFAT, which are ac- tual models of FAT32, are described in chapter 4 along with their properties. We introduce an oracle model of nondeterminism for reasoning about concur- rency and apply it to reasoning about concurrent programs (chapter 6). We evaluate our work (chapter 7), summarise the related work (chapter 8) and draw conclusions about the expected impact of this work (chapter 9).

Some of the research in this dissertation has been previously published as follows:

• Mihir P. Mehta. Formalising Filesystems in the ACL2 Theorem Prover: an Application to FAT32. In Shilpi Goel and Matt Kaufmann, editors, Proceedings of the 15th International Workshop on the ACL2 Theorem Prover and Its Applications, Austin, Texas, USA, November 5-6, 2018, volume 280 of Electronic Proceedings in Theoretical Computer Science, pages 18–29. Open Publishing Association, 2018

• Mihir P. Mehta and William R. Cook. Binary-Compatible Verification of Filesystems with ACL2. In John Harrison, John O’Leary, and Andrew Tolmach, editors, 10th International Conference on Interactive Theorem Proving (ITP 2019), volume 141 of Leibniz International Proceedings in Informatics (LIPIcs), pages 25:1–25:18, Dagstuhl, Germany, 2019.

5 Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik

• Mihir Parang Mehta and William R Cook. Separation Logic-Based Veri- fication Atop a Binary-Compatible Filesystem Model. In Brazilian Sym- posium on Formal Methods, pages 155–170. Springer, 2020

In each of the above papers, the dissertator contributed the entirety of the code and proofs, as well as the bulk of the accompanying analysis with some guidance from the co-authors in regards to organisation and presentation of results. The ideas and results in these papers are synthesised into the dis- sertation, alongside some new research on concurrency described in chapter 6.

6 Chapter 2

Background

2.1 ACL2

The ACL2 theorem proving system [45] consists of a language, which is based on a subset of Common Lisp, and a prover which discharges proof obligations expressed in this language.1 ACL2, employing an untyped first- order logic, incorporates many automated strategies for discharging first-order goals while also allowing user control of the proof process at different levels of abstraction. As in mathematics, the proof of a conjecture in ACL2 usually relies on the proof of simpler lemmas (optionally stored as rules). Most often, these lemmas are rewrite rules for rewriting a certain type of term under certain hypotheses; however, other types of rules exist, such as linear rules for arithmetic reasoning.

Prior work on program verification with ACL2 has highlighted its li- brary support for automatic proofs about straight-line code without loops [21]. However, it also has powerful support for mathematical induction while reason- ing about recursive functions; prior work [51] has also demonstrated ACL2’s

1In the literature, the term ACL2 is sometimes used to refer to the language, and some- times used to refer to the prover.

7 support for rewriting-based proofs of separation properties in the context of program verification [51] through the use of proof tactics (i.e. rewrite rules) which act upon a quantifier-free representation of separation predicates.

2.1.1 Guard Verification

A function can optionally have a guard, an arbitrary assertion in terms of its arguments which is required to be true at runtime. ACL2 generates a proof obligation stating that the guards of all functions called within the function body are satisfied when the guard itself is satisfied. The proof of this obligation is optional; when a function is not guard-verified, guards for function calls within the body are instead checked at runtime. Guard-verified functions, in general, execute faster because they are able to skip these runtime checks and because guards can help the compilation process by including constraints on the type of the function’s arguments which allow space to be efficiently allocated for fixed-width integers, strings, and the like. In addition to helping with execution efficiency, guard verification helps correct programming errors early and often leads to the formulation of lemmas which can be reused in later proofs.

The guard mechanism also supports mbe (must be equal) [4], an ACL2 construct which allows the user to locally decouple logical meaning and op- erational behaviour. In a function body, a sub-expression of the form (mbe

:logic term1 :exec term2) will be treated as meaning term1 during reason- ing but will behave as term2 at runtime; this enables optimisation by a choice

8 of term2 which is efficient during execution. This is sound because mbe extends the function’s guard obligation to include the statement that term1 and term2 are equal in their local context when the function’s guard is satisfied.

2.1.2 Single-threaded Objects

In applicative settings, updates to data structures result in the creation of a new copy of the data structure, which can prove expensive in terms of time and memory. In ACL2, this kind of performance penalty is avoided by the use of immutable data structures called single-threaded objects, or stobjs [11]. Stobjs are aggregate structures with scalar and array fields, equipped with the usual applicative semantics. They are, however, restricted syntactically to ensure that only one copy of the stobj can be referenced, which allows ACL2 to implement accesses and updates to scalar and array fields of the stobj in constant time by means of in-place updates to the lone copy of the stobj.

As with all aggregate data structures, proving invariants of algorithms involving stobjs necessitates lemmas about the invariance of stobj fields while updating other fields (akin to frame axioms [60]). Our work devises ACL2 macros to generate these lemmas automatically.

2.1.3 Equivalence and Rewriting

In ACL2, binary predicates can be proved to be equivalence relations. Such an equivalence is treated like first-order equality, in that rules can be formulated to rewrite terms in the context of the given equivalence.

9 We use a few standard techniques for defining and establishing equiv- alences in ACL2’s untyped logic.

• When a subset relation can be defined on objects which are to be assigned to equivalence classes, equivalent objects can be defined to be subsets of each other. Then the proofs of reflexivity, symmetry, and transitivity, which are required for establishing an equivalence relation, arise from the proofs of reflexivity, anti-symmetry and transitivity for the subset relation.

• When a transformation exists between two types, objects of the first type can be defined to be equivalent when they transform to the same object (modulo a previously defined equivalence) of the second type.

• Sometimes an equivalence relation needs to be defined on objects sat- isfying some notion of well-formedness (such as objects which can be transformed to objects of a different type). However, all functions in ACL2 must be total, including equivalence predicates - they cannot be limited to well-formed arguments. In such a case, the predicate can be made a total function by assigning all ill-formed objects to the same equivalence class and assigning no well-formed objects to this class. This renders the claims of reflexivity, symmetry and transitivity trivial once they have been proven for well-formed arguments.

10 2.1.4 Logical Story of I/O

Theorem proving systems generally have interfaces with the operating system which are unverified, because operating system activity is unpredictable and may not return a consistent result on two calls to the same function with the same arguments. This is also the case with ACL2, which provides I/O functionality at various levels of abstraction for programmer convenience. However, ACL2 adheres to a logical story of I/O [16]2, consisting of formal specifications for these I/O functions in terms of their input/output behaviour and errors passed on from the operating system. These formal specifications exist in the ACL2 logic and support proofs about sequences of I/O operations and optimisations thereof.

2.1.5 The FTY Discipline

Any sufficiently large ACL2 proof effort tends to accumulate theorems which are likely to have multiple hypotheses specifying the type or shape of arguments to various functions (such as arguments being natural numbers or lists). The accumulation of such lemmas is apt to slow down both the theorem prover and the user; the theorem prover is obliged to work harder when there is a larger number of hypotheses it must relieve to apply a rule, and the user in turn can expect the process of debugging failed proof attempts to take longer.

The FTY discipline and its associated library [65] mitigate this issue

2This is a term from ACL2 lore, referred to in more recent work as the logical foundation of I/O.

11 by simplifying the process of fixing arguments to a function without adversely affecting execution efficiency. FTY stands for fixtype and as such a fixing function, which coerces a given value to a value satisfying the given type hypothesis, is a fixpoint operator which returns the value unchanged if and only if it already satisfies the hypothesis. By fixing arguments it becomes possible to formulate lemmas with fewer hypotheses or no hypotheses altogether, which speeds up the proof effort. Considerable time and effort has been saved in the present work while proving properties such as read-over-write as a result of the use of FTY.

2.2 Program Logic

Prior work on program verification with ACL2 has highlighted its li- brary support for code proofs based on executable models [21] and its support for rewriting-based proofs of separation properties in the context of program verification [51] through the use of proof tactics (i.e. rewrite rules) which act upon a quantifier-free representation of separation predicates.

Floyd-Hoare logic [19,26], which introduced Hoare triples

tpreconditionu statement tpostconditionu for simple and compound statements comprising a program, forms the basis of program verification. In recent years, the logic has been strengthened in many different program verification domains, and been successfully automated for static analysis both at the programming language level and at the machine

12 code level. In the present work, we are interested in Floyd-Hoare logic as applied to linearly addressed memory for storage of fixed-size elements, such as bytes. Such memory models have been developed to model RAM, which usually provides byte- or word-addressing; we find the idea immediately ap- plicable to filesystems, which operate on various kinds of linearly addressed storage media while abstracting away the details of those media to expose a directory tree interface to the user.

One such extension is separation logic [55, 58], which was initially de- veloped to address certain scalability challenges in reasoning about programs operating on a RAM-like memory model, i.e. the flat heap model. Heaps are mappings from locations (heap cells) to fixed-size data values; they can be composed using the separating conjunction operator ˚. The heap P ˚ Q is defined as the union of the mappings P and Q, along with the assertion that no mappings are common between P and Q which is implicit whenever P ˚ Q appears in a Hoare triple. Yang et al. [67] describe a bargain of sorts: in exchange for using only tight specifications, i.e. Hoare triples tP u stmt tQu in which the precondition P names all the locations modified by stmt, we get a frame rule allowing us to infer from this Hoare triple the new Hoare triple tP ˚ Ru stmt tQ ˚ Ru. This derivation is sound since the formulaP*R contains the assertion that locations inP andR are disjoint from each other; tightness is also preserved in the new Hoare triple.

Structural Separation Logic (SSL) [66], an extension of this theory, replaces the flat heap with a structured heap. Here, a heap cell is allowed to be

13 either a flat heap cell containing a fixed-size value, or a structured heap cell containing a rich value such as a linked list or a sublist thereof. The utility in reasoning about filesystems is immediately obvious, when we consider rich values to include the contents of directories.

In SSL, the notion of a structured heap goes together with the notion of an abstract heap which helps reason about accesses and updates to sub- structures of structured data. Like structured heaps, abstract heaps contain flat heap cells and structured heap cells; in addition, they contain abstract heap cells, pairs of values each consisting of a substructure of some structured data and the path where the substructure fits in the larger structure. When a substructure is placed in an abstract heap cell, at the same time the structure where it fits is changed to replace the substructure with a body address point- ing to this heap cell. This process of creating an abstract heap cell is known as abstract allocation; it serves to create a new cell in the abstract heap and “sep- arate” a substructure. The converse operation is abstract deallocation; it does away with an abstract heap cell by folding its contents back into the larger structure whence they came, in a manner similar to application of functions in lambda calculus. Abstract allocation and abstract deallocation can both be carried out at will depending on the needs of the proof, since neither has any effect on the state of the system. Hoare judgments about sequences of commands often require abstract allocations and deallocations to be written out explicitly, in order to apply a syscall specification. For this purpose, we use Hoare triples of the form tP u id tQu to represent them where id is a vacuous

14 statement (i.e. a no-op).

The path stored in each heap cell is known as a path promise, ensuring that the substructure in the first element is identifiable as to its origin in the substructure. In the context of SSL applied to filesystems, this marks a difference in approach from prior work in specifying filesystems [25,50] which choose to index into the heap by file path, making the path of each file its heap address. While a variable may be deallocated and thus removed from the abstract heap, its path promise cannot change as long as it is in the abstract heap.

2.3 FAT32

FAT32 was initially developed at Microsoft in order to address the ca- pacity constraints of the DOS filesystem, allowing filesystem volumes of the order of tens of terabytes to be created. While newer filesystems have taken its place as the default filesystem in Windows, FAT32 continues to see widespread use in embedded systems and in removable media. Microsoft’s specification for FAT32 [49], which we follow closely in our work, details the layout of data and metadata in a valid FAT32 disk image.

In FAT32, regular files and directory files are together referred to as files. All files are divided into clusters of a fixed size, akin to extents in other filesystems. The size of a cluster is stored in a reserved area at the beginning of the volume, along with other volume-level metadata such as the total number of clusters and the location of the root directory. The clusters themselves are

15 stored in a data region at the end of the volume. Between these two on-disk data structures, the volume has one or more copies of the file allocation table. The redundant copies are intended to safeguard the filesystem against data loss in the event of disk corruption; however, the policy for these copies is left up to the implementation which may choose to update them infrequently or not at all.

The cluster size must be an integer multiple of the sector size, which in turn must be at least 512 bytes; these and other constraints on the fields of the reserved area are detailed in the FAT32 specification.

Directory files are for the most part treated the same way as regular files by the filesystem, but they differ in a metadata attribute, which indicates that the contents of directory files should be treated as sequences of directory entries. Each such directory entry is 32 bytes and contains metadata including name, size, first cluster index, and access times for the corresponding file.

The file allocation table is a table with one entry for each cluster in the data region; it contains a number of linked lists (clusterchains). It maps each cluster index used by a file to either the next cluster index for that file or a special end-of-clusterchain value. 3 This allows the contents of a file to be reconstructed by reading just the first cluster index from the correspond- ing directory entry, reconstructing the clusterchain using the table, and then looking up the contents of these clusters in the data region. Unused clusters

3There is a range of end-of-clusterchain values in the specification, not just one. We support all values in the range.

16 are mapped to 0 in the table; this fact is used for counting and allocating free clusters.

Two pertinent details about FAT32’s data layout are the following.

• Clusterchains are not shared between files; thus, there is no hard linking.

• Each directory in FAT32 is explicitly required to include, in addition to directory entries for regular files and subdirectories, an entry for itself

(.) and its parent (..).

We illustrate the file allocation table and data layout for a small ex- ample directory tree in figure 2.1. Here, /tmp is a subdirectory of the root directory (/). For the purposes of illustration, all regular files and directories in this example are assumed to span one cluster except for /vmlinuz which spans two clusters (3 and 4), and EOC refers to an “end of clusterchain” value. Also, as shown in the figure, the specification requires the first two en- tries in the file allocation table (0 and 1) to be considered reserved, and thus unavailable for clusterchains.

17 Figure 2.1: A FAT32 directory tree

/ vmlinuz initrd.img etc/

crontab fstab

FAT index FAT entry 0 (reserved) 1 (reserved) 2 EOC 3 4 4 EOC 5 EOC 6 EOC 7 EOC 8 EOC 9 0 . . . .

Directory entry in / 0 “vmlinuz”, 3 32 “initrd.img”, 5 64 “etc”, 6 . . . .

Directory entry in /etc/ 0 “crontab”, 7 32 “fstab”, 8 . . . .

18 Chapter 3

Abstract Models Leading up to FAT32

To meet the aim of faithfully modelling FAT32, it is necessary to in- terpret the binary file format and incorporate some of the constraints from the Microsoft specification into the formal development. The best way to ad- dress the complexity of this task is to build filesystem models incrementally, adding filesystem features in each new model and proving equivalence with earlier, simpler models. This approach validates the refinement methodology and yields data structures which can be used in a faithful model of FAT32.

Thus, the abstract models L1 through L6 are constructed incrementally, allowing the reuse of read-over-write proofs for simpler models in more complex models. In the case of models L4 and L6, a refinement relationship without stuttering [1] is shown; for the other models proofs are reused without proving a formal refinement relation. These reuse relationships are summarised in figure 3.1. Much of the code and proof infrastructure is also shared between the abstract models and the concrete models by design. Details of the filesystem features introduced in the abstract models can be seen in table 3.1.

19 Table 3.1: Abstract models and their features

L1 The filesystem is represented as a tree, with leaf nodes for regular files and non-leaf nodes for directories. The contents of regular files are represented as strings stored in the nodes of the tree; the storage available for these is unbounded. L2 A single element of metadata, length, is stored within each regular file. L3 The contents of regular files are divided into blocks of fixed size. These blocks are stored in an external “disk” data structure; the storage for these blocks remains unbounded. L4 The storage available for blocks is now bounded. An allocation vector data structure is introduced to help allocate and garbage- collect blocks. L5 Additional metadata for file ownership and access permissions is stored within each regular file. L6 The allocation vector is replaced by a file allocation table, matching the official FAT specification.

Figure 3.1: Refinement/reuse relationships between abstract models

L1 - tree

L2 - length

L3 - unbounded disk L4 - bounded disk with garbage collection

L5 - permissions L6 - file allocation table

20 3.1 Properties Proven for the Abstract Models

While the present work focuses on code proofs for the programs in the co-simulation suite which make use of LoFAT, the process of setting up code proof infrastructure for the abstract models is instructive and clarifies the methods used for the much more complex LoFAT. Broadly, filesystem opera- tions we offer are classified as either write operations, which do modify the

filesystem, or read operations, which do not. For each of the six models L1-L6, read-over-write properties are proved which show that write operations have their effects made available immediately for reads at the same location, and also that they do not affect reads at other locations. More precisely, the first read-over-write theorem states that immediately following a write of some text at some location, a read of the same length at the same location yields the same text; and the second read-over-write theorem states that after a write of some text at some location, a read at any other location returns exactly what it would have returned before the write.

In L1, our simplest model, the read-over-write properties are proven from scratch. In each subsequent model, the read-over-write properties are proven as corollaries of equivalence proofs which establish the correctness of read and write operations in the respective model with respect to a previous model. A representation of such an equivalence proof can be seen in figures

3.2, 3.3 and 3.4, which respectively show the equivalence proof for l2-wrchs, the equivalence proof for l2-rdchs and the composition of these to obtain the

first read-over-write theorem for model L2.

21 Figure 3.2: l2-wrchs-correctness-1

l2 l21 write l2-to-l1-fs l2-to-l1-fs

l1 l11 write

Figure 3.3: l2-rdchs-correctness-1

l2 text read l2-to-l1-fs read l1

Figure 3.4: l2-read-after-write-1

l2 l21 text write read l2-to-l1-fs l2-to-l1-fs read l1 l11 write

22 Chapter 4

HiFAT, LoFAT, and Properties Proven

A non-trivial proof effort in this work concerns the correctness of the transformations between different FAT32 representations. These transforma- tions are obliged to terminate in a bounded amount of time, be invertible in terms of appropriate equivalence relations, and return the proper error codes. These proofs lead up to the refinement proof showing the correctness of the POSIX system calls implemented for FAT32 (section 7.2).

The model LoFAT is introduced, faithfully modelling the state of a FAT32 disk image. First, a single-threaded object type is defined, recognised by the predicate fat32$c-p 1. Augmenting this predicate with clauses for the various FAT32 constraints yields the predicate lofat-fs-p (listing 4.1), which recognises valid instances of LoFAT. These constraints are a subset of the constraints actually stipulated for FAT32, chosen to be as small as possible while supporting the proof effort. This helps avoid an undue restriction on LoFAT’s ability to co-simulate with FAT32 implementations which may not strictly adhere to the specification.

1To satisfy ACL2’s syntactic requirements, all functions which accept an instance of LoFAT as an argument will name that argument fat32$c, as seen in code listings later in this document

23 It has been argued [44] that the axiomatic verification methodology, wherein specific properties of a system are enumerated and proved, is inad- equate for systems of any significant complexity, which can only be verified through refinement. Much of the related work [7,14,62] opts to prove the cor- rectness of an implemented filesystem through refinement of a specification, demonstrating a de facto consensus on this point. Thus, the FAT32 model Hi- FAT is developed and proved to abstract LoFAT, or in other words, be refined without stuttering [1] by LoFAT. HiFAT instances are directory trees in which the leaf nodes are regular files and the non-leaf nodes are directories. Each node in a directory tree contains the FAT32 directory entry for the correspond- ing file, and the full contents of each regular file are stored as strings within the tree. Further, these trees are subject to FAT32’s constraints — a regular file may be up to 232 ´ 1 bytes long; a directory may contain up to 216 ´ 1 directory entries; and a directory may not contain duplicate directory entries. While HiFAT is specifically a tree model of FAT32, it is clear that the idea of a tree model - which is common to most if not all filesystems - is general enough to be used in a binary compatible verification effort for an arbitrary filesystem, adapting to a directed acyclic graph when a filesystem with hardlinks is to be modelled.

This refinement relationship enables reasoning about file operations in terms of operations on directory trees, while implementing them efficiently in a data format that is very close to the actual structure of a FAT32 disk image. The specific file operations chosen are a subset of the POSIX filesystem

24 application programming interface. This choice simplifies comparison between the results of running filesystem operations on LoFAT and the ’s implementation of FAT32, thus testing the former’s correctness through co- simulation in addition to theorem proving. System calls in the C language, in general, return a numeric value (zero if successful, non-zero otherwise) and set the value of the global variable errno; in addition, some (such as pwrite) change the state of the filesystem, and some (such as pread) return data by means of a pointer. In an applicative context, global variables and pointers are not available; thus in HiFAT and LoFAT, system calls return multiple values, including the new state of the filesystem (if changed), return values and errno values matching those of the C system call, and strings/structs containing the data provided through pointers (if any). Straightforward implementations of a process table and file table, similar to these used in Synergy [9], are included for the use of file descriptor-based system calls such as pwrite and pread.

4.1 Termination

By default, the ACL2 logic requires that for any recursive function definition, a formula must be provable to the effect that all calls terminate. Such a proof is accomplished by defining a function-specific measure, or letting ACL2 guess the measure, and proving that the measure strictly decreases for each recursive call within the function body. However, termination proofs pose a challenge in many applications involving pointer chasing [23, 24]. In the context of FAT32, when transforming a LoFAT instance into an HiFAT

25 instance, pointer chasing is necessary both for regular files and directory files, necessitating some care towards the avoidance of non-terminating computation in both cases without incurring the overhead of general-purpose cycle detection algorithms.

Each file’s directory entry contains the index of its first cluster, and its contents are determined by following its clusterchain in the file allocation table and concatenating together the corresponding clusters. This is subject to potential cycles in the clusterchain, but these can be mitigated because of the FAT32 stipulation of maximum lengths for regular files and directory files. The measure for the recursion becomes the remaining length of the file, which decreases with each cluster visited in the file allocation table.

For directory files the problem is more involved; since the transforma- tion of a directory on disk to a directory tree involves the recursive transfor- mation of all sub-directories, it is possible for a sub-directory cycle to arise.

Consider an ill-formed disk image where the top-level directory etc contains an entry for the sub-directory apt and apt in turn contains a directory entry for etc; in this scenario, it is possible for the algorithm to spin over the fictitious sub-directories /etc/apt/etc, /etc/apt/etc/apt, . . . A loop-stopping criterion is required which accepts all disk images which are free of cycles and returns an error for all disk images with sub-directory cycles. POSIX defines the con- stant PATH_MAX to bound the length of a pathname, but it is inconsistently used by implementations [41]; thus a naive solution based on a maximum di- rectory nesting depth is likely to reject valid disk images. A better way is

26 to examine the filesystem at the granularity of directory entries, noting that these cannot exceed the total space available in the data region. Thus, an argument entry-count is added to lofat-to-hifat-helper (a recursive helper function for the transformation) and designated as the measure, with decre- mentation for each entry counted when making recursive calls. entry-count is instantiated to the maximum number of entries possible in the data region in lofat-to-hifat (the top-level wrapper function); this ensures that all valid filesystem instances are accepted without an error and demonstrates the ex- istence of a cycle in each case where the total possible number of directory entries is exceeded.

4.2 Equivalence

Several useful filesystem correctness properties depend, for their proofs, on a notion of equivalence between two filesystem instances. While defining such a notion of equivalence, it is desirable to leave room for different im- plementation choices for cluster allocation, garbage collection, and other such details. Some constraints which characterise such an equivalence relation fol- low, and are illustrated in figure 4.1.

• Modulo rearrangement, each directory in two equivalent filesystem in- stances should contain the same regular files and, recursively, the same sub-directories. This ensures that looking up the same pathname in both yields the same results.

27 Figure 4.1: Two equivalent directory trees.

(b) An equivalent rearranged direc- (a) A directory tree with a deleted file tory tree with the deleted file removed

/ /

vmlinuz tmp/ initrd.img initrd.img tmp/ vmlinuz

ticket1.txt ticket2.txt ticket2.txt

• Directory entries for the current directory (.) and the parent directory

(..) should be disregarded, since they do not refer to new unique files. The same is true for deleted files’ directory entries.

• Re-allocation of clusters for the contents of a given file without changing the contents should be disregarded.

• Changes to the redundant copies of the file allocation table should be disregarded.

• Changes to volume-level metadata, such the size of a cluster or the total number of clusters in the filesystem, should be taken into account only if they result in the deletion of file data.

Also, to simplify the verification task, creation times, access times, write times, and long names for files are set aside, even though this limits the reasoning which can be carried out about programs which rely on these for their correct operation, such as the incremental compilation system Make [64].

28 At HiFAT, the most abstract level, we meet the above requirements by

first defining a subset relation hifat-subsetp; and then defining the equiv- alence relation hifat-equiv (listing 4.2) in terms of subsets as discussed in section 2.1.3.

At LoFAT, the next lower level of abstraction, we define the equivalence relation lofat-equiv in terms of the transformation between LoFAT and HiFAT, grouping ill-formed LoFAT instances (that is, instances which return a non- zero error code when transformed to HiFAT) into the same equivalence class (listing 4.3) following the approach outlined in section 2.1.3. Finally, we define an equivalence relation for disk images. These are strings, each representing the entire contents of the image. This equivalence relation, EqFAT, groups all ill-formed disk images which cannot be transformed to a valid LoFAT instance into the same equivalence class (listing 4.4).2

4.3 Invertibility and Error Codes

HiFAT instances are directory trees, defined recursively; thus, proofs about HiFAT generally require induction. Many theorems about recursive functions can be automatically proved in ACL2 through inference of induc- tion schemes; however, an induction scheme can also be explicitly designated in order to control the inductive formulation of a theorem. In such an in-

2Both these functions are considered non-executable in ACL2, because they reference two instances of the stobj fat32$c at the same time. They are thus introduced with defund-nx [3] instead of the usual defun and can only be used for reasoning. These functions also use b* [2], an ACL2 extension of the Common Lisp let* with a more flexible syntax for let-bindings.

29 duction scheme, the induction hypothesis can be strengthened or weakened as needed.

Between HiFAT and LoFAT, transformations hifat-to-lofat and lofat-to-hifat are defined, and must be proved to be inverses of each other under the ap- propriate equivalence relations. This is a claim in two parts. Suppose we transform m1, a given HiFAT instance, to m2, a LoFAT instance, and back.

1 Then, the result should be an m1 related to m1 by hifat-equiv. Similarly,

1 transforming a given m2 to m1 and back should result in an m2 related to m2 by lofat-equiv.

The proof of the first part of this claim (as illustrated in figure 4.2a) turns out to also involve error codes; no claims can be made about the in- vertibility of a transformation if it returns an error. Thus, the proof requires a strengthened induction hypothesis to show in tandem that the error code

1 returned by lofat-to-hifat while transforming m2 back to m1 is 0 (signifying no error.) This induction is a complex proof, since it requires an induction scheme to be defined on functions which interpret binary file formats.

The second part of this claim is true by the definition of lofat-equiv and an instantiation of the first claim (figure 4.2b).

It is also necessary to prove the correctness of the transformations be- tween instances of LoFAT and FAT32 disk images (strings). These transforma- tions, lofat-to-string and string-to-lofat, are proved to be mutual inverses under the equivalence relations equal (first-order equality) and EqFAT, respec-

30 Figure 4.2: Equivalences

(a) hifat-to-lofat-inversion is derived as a corollary of an induction proof (sec- tion 4.3).

1 m1 m1 hifat-equiv

m2

(b) hifat-to-lofat-inversion is instantiated in order to derive lofat-to-hifat-inversion.

1 m1 m1 hifat-equiv

1 m2 m2 lofat-equiv (c) Similarly, lofat-to-string-inversion (not shown) is instantiated in order to 1 derive string-to-lofat-inversion. Here, m2 and m2 are LoFAT instances and s and s1 are disk image strings.

1 m2 m2 equal

s s1 eqfat (d) string-to-hifat-inversion is a corollary of lofat-to-hifat-inversion.

m1

1 m2 m2 lofat-equiv

s s1 eqfat

31 tively. As before, one direction of the claim is proved and then instantiated to prove the other by the definition of EqFAT (figure 4.2c).

Equality is known to refine all equivalence relations; thus, equal re-

fines lofat-equiv, and the correctness of the transformations between disk images and HiFAT instances through the intermediate level LoFAT can finally be certified by composing these proofs (figure 4.2d).

4.4 LoFAT-HiFAT Correspondence

Many of the system calls in LoFAT are implemented through three prim- itive operations: lofat-find-file, lofat-remove-file, and lofat-place-file.

For instance, the system call mkdir is implemented as a call of lofat-place-file with an empty directory as an argument. Thus, the proofs of specifications for these system calls reduce to proofs about these three primitives.

More precisely, we prove the HiFAT versions of these system calls, re- spectively hifat-find-file, hifat-remove-file, and hifat-place-file, to have properties as required by the specifications for the system calls. More specifically, these are read-over-write properties, adapted for “writes” to the filesystem which are file creations, file updates and file deletions. In a general form, these properties state:

1. A read from the filesystem following a write at the same location yields that which was written.

2. A read from the filesystem following a write at a different place yields

32 the same result as a read before the write.

Then, by proving the refinement relationship for each of these three primitives in LoFAT, we are able to much more easily prove that LoFAT meets specifications. LoFAT is a linearly addressed data structure, since it emulates the layout of an actual FAT32 disk image; thus, any proofs regarding oper- ations on LoFAT by necessity involve separation properties between different

files. The executable function lofat-to-hifat, which transforms a LoFAT in- stance to a HiFAT instance, checks whether the clusterchains across all the different files in a filesystem are disjoint from each other. This disjointness is a necessary property for a well-formed FAT32 disk image, and therefore a necessary property of a well-formed LoFAT instance. However, such checks on the disjointness of sets of clusterchains can get expensive in a large filesys- tem instance, which affected the performance of an earlier implementation of LoFAT which effects one or more transformations between HiFAT and Lo- FAT for many of the system calls implemented. Therefore, we have developed more efficient implementations of the LoFAT primitives lofat-find-file and lofat-remove-file; verified that each refines the respective HiFAT primitive; and adapted the system calls in LoFAT to use the new primitives.

We provide specifications for the system calls in LoFAT, through which programs making use of the filesystem can be reasoned about. LoFAT functions at one level of remove from the disk image, since it is a single-threaded object; however, it replicates the structure of the disk in memory and each system call it offers is an analogue of the system call of the same name, adapted for the

33 use of programs written in the applicative ACL2 language. Some of the system calls implemented in LoFAT involve file descriptors; as a result file descriptor tables and file tables are implemented as part of LoFAT. This means that, viewing the disk image as a linearly addressable entity and disregarding details about caching, each execution of a program using FAT32’s POSIX interface is mirrored by an execution of an ACL2 program using LoFAT with the same program logic but making use of LoFAT’s system calls; and any proofs carried out on the latter also apply to the former. This correspondence underlies the focus on binary compatibility and efficient execution in the present work.

A significant part of the implementation has been the reworking of the LoFAT primitives lofat-remove-file and lofat-place-file. These prim- itives now guarantee that files not in the path of a file operation will remain unchanged in their placement in the data region, in contrast to the earlier implementation which transformed to HiFAT, performed the operation, and transformed back to HiFAT, effectively reallocating space in the data region for every file in the filesystem. For proving the that these functions refine their

HiFAT equivalents, respectively hifat-place-file and hifat-remove-file, we opted to simplify the specifications of these functions to make them no-change losers3: functions which operate on a data structure making sure to return it unchanged if returning an error code indicating failure. This yielded rewrite rules with fewer hypotheses, which made it simpler to prove the lemmas nec- essary to support our filesystem call specifications.

3This is a term from ACL2 lore, and often used in the ACL2 source code.

34 4.5 Correctness of the Specification

Prior filesystem verification work [7] has shown the proof process to uncover subtle bugs in the specification of a filesystem which would otherwise have remained hidden; this has matched our experience modelling and verify- ing HiFAT and LoFAT. We note some examples of bugs we found in our models in this manner.

• In FAT32, the first two entries in the file allocation table are reserved for volume-level metadata; thus, the size of the file allocation table must ex- ceed the number of available clusters by at least two. Additionally, there may be a number of unused entries at the end of the file allocation table, since it must span an integer number of sectors. These differences led us to place incorrect upper bounds on the root cluster of the filesystem, the first cluster of an arbitrary file, and the length of the file allocation table. These errors were discovered and rectified during the proofs of correctness of our transformations.

• An off-by-one bug caused the directory bit of a directory entry to be wrongly set; this was also identified and rectified in the process of proving the transformations correct.

• In the process of proving correspondence between the error codes re- turned by HiFAT and LoFAT, in three instances bugs were found where the error codes differed. This was corrected by ensuring in all cases that both return the error code returned by Linux.

35 In addition, some bugs in parts of the code which were not immediately verified were found outside of the theorem proving process, by means of co- simulation. One example was the case of a FAT32 filesystem in which the root had no directory entries, which is possible in FAT32 because only directories other than the root are required to have . and .. entries. FAT32 constrains each directory file to have at least one cluster, and this constraint had been omitted from the specification. Over a number of co-simulation tests with the Linux FAT32 implementation, this bug was discovered and fixed.

36 Listing 4.1: lofat-fs-p (defun lofat-fs-p (fat32$c) (and (fat32$cp fat32$c) ;; There must be at least 512 bytes per sector. (>= (bpb_bytspersec fat32$c) *ms-min-bytes-per-sector*) ;; Each cluster must contain a positive integer number of sectors. (>= (bpb_secperclus fat32$c) 1) ;; There is a lower bound and an upper bound to the number of ;; clusters. (>= (count-of-clusters fat32$c) *ms-min-count-of-clusters*) (<= (+ *ms-first-data-cluster* (count-of-clusters fat32$c)) *ms-bad-cluster*) ;; The reserved area must span a positive integer number of sectors. (>= (bpb_rsvdseccnt fat32$c) 1) ;; Zero or more redundant copies of the FAT are allowed. (>= (bpb_numfats fat32$c) 1) ;; The FAT must span a positive integer number of sectors. (>= (bpb_fatsz32 fat32$c) 1) ;; The root cluster must exist in the addressable part of the file ;; allocation table. (>= (fat32-entry-mask (bpb_rootclus fat32$c)) *ms-first-data-cluster*) (< (fat32-entry-mask (bpb_rootclus fat32$c)) (+ *ms-first-data-cluster* (count-of-clusters fat32$c))) (<= (+ (count-of-clusters fat32$c) *ms-first-data-cluster*) (fat-entry-count fat32$c)) ;; The cluster size must be a multiple of the size of a directory ;; entry. (equal (mod (cluster-size fat32$c) *ms-dir-ent-length*) 0) (equal (mod *ms-max-dir-size* (cluster-size fat32$c)) 0) ;; The data region must be an array of clusters of the appropriate ;; length. (stobj-cluster-listp-helper fat32$c (data-region-length fat32$c)) (equal (data-region-length fat32$c) (count-of-clusters fat32$c)) ;; The file allocation table must contain the appropriate number of ;; 4-byte-wide entries. (equal (* 4 (fat-length fat32$c))37 (* (bpb_fatsz32 fat32$c) (bpb_bytspersec fat32$c))))) Listing 4.2: hifat-equiv (defun hifat-equiv (fs1 fs2) (let ((fs1 (hifat-file-alist-fix fs1)) (fs2 (hifat-file-alist-fix fs2))) (and (hifat-subsetp fs1 fs2) (hifat-subsetp fs2 fs1))))

Listing 4.3: lofat-equiv (defund-nx lofat-equiv (fat32$c1 fat32$c2) (b* (((mv fs1 error-code1) (lofat-to-hifat fat32$c1)) (good1 (and (lofat-fs-p fat32$c1) (equal error-code1 0))) ((mv fs2 error-code2) (lofat-to-hifat fat32$c2)) (good2 (and (lofat-fs-p fat32$c2) (equal error-code2 0))) ((unless (and good1 good2)) (and (not good1) (not good2)))) (hifat-equiv fs1 fs2)))

Listing 4.4: EqFAT (defund-nx eqfat (str1 str2) (b* (((mv fat32$c1 error-code1) (string-to-lofat-nx str1)) (good1 (and (stringp str1) (equal error-code1 0))) ((mv fat32$c2 error-code2) (string-to-lofat-nx str2)) (good2 (and (stringp str2) (equal error-code2 0))) ((unless (and good1 good2)) (and (not good1) (not good2)))) (lofat-equiv fat32$c1 fat32$c2)))

38 Chapter 5

Abstract Separation Logic for Filesystem Verification

In AbsFAT, the model for reasoning about filesystem calls in programs, filesystem states are represented as collections of logically separate variables. Maintaining this separation through state changes from applications of sys- tem call specifications, abstract allocations, and abstract deallocations is a substantial part of how the proofs are resolved in the theorem prover. This formal development is structured around these principles:

• To profit from separation logic, properties of the filesystem state are represented as local properties where possible, in a way that allows them to be proved by rewriting without induction.

• Global properties, which pertain to the whole system, have the potential to expand the state space beyond what the theorem prover can resolve in a reasonable time and result in subgoals (intermediate proof obliga- tions) which are hard for the user to comprehend. Thus, these properties are disabled, which keeps the theorem prover from expanding their def- initions. This, in turn, leads to the creation of general and reusable

39 Figure 5.1: Hoare triples describing the possible behaviours of mkdir

tppath ñ P {b{aq ˚ pαP ÞÑ brC ˚ can_createpaqsqu Z r := mkdir(path) tr Ù 0 ˚ αP ÞÑ brC ` arHssu

tppath ñ P {b{aq ˚ pαP ÞÑ brC ` a : ssqu Z r := mkdir(path) tr Ù EEXISTu tppath ñ P {b{aq ˚ pαP ÞÑ brC ` arβssqu Z r := mkdir(path) tr Ù EEXISTu

tppath ñ P {b{aq ˚ pαP ÞÑ b : squ tppath ñ P {aq ˚ ENOENTpP qu rZ := mkdir(path) rZ := mkdir(path) tr Ù ENOTDIRu tr Ù ENOENTu

lemmas to resolve subgoals involving these properties which arise from file operation specifications.

• Also in the interest of reducing state space and helping user comprehen- sion, file operation specifications are kept compact.

• When new global properties are required specifically for FAT32, they are distinguished where possible to simplify possible reuse of the proof infrastructure for a future filesystem formalisation.

• A completion semantics is used, in which the effect of a system call is deterministically evaluated regardless of whether it returns an error code.

40 5.1 System calls and Support for Filesystem Clients

It is illustrative to more closely examine Fig. 1.1 and its proof, which in turn requires us to consider the specification of mkdir itself. This system call has one success condition and several error conditions, as shown in Fig. 5.1, where path ñ P {b{a means that path matches a directory P with subdirectory Z b, which has a component a; brC ˚ can_createpaqs describes a directory b in which a can be created; brC ` a : ss describes a directory b where regular file a already exists with contents s, brC ` arβss describes a directory b where directory a exists with contents β, and b : s describes a regular file b with contents s. In all but the first of these five Hoare triples, mkdir exits returning an error code, as standardised by POSIX which defines the meanings for each one of the errors ENOENT, ENOTDIR, . . . and their applicability to a given system call [34].

The AbsFAT axiomatisation works on frames, collections of logically separate abstract variables, and supports claims such as the following which is likely to arise in the context of any proof involving the system call mkdir:

When a given filesystem state corresponds to a given well- formed separation logic frame, the state after a successful call to mkdir(path) corresponds to a new well-formed frame with all ab- stract variables unrelated to dirname(path) unchanged, and with a new variable α representing the current state of dirname(path) containing a new empty directory named basename(path).

41 This claim, expressed in terms of the Linux utility functions basename [32] and dirname [33], is an example of the benefits of local reasoning. It expresses the new state after an operation as a change to the old state that clearly leaves the remainder of the filesystem unchanged and simplifies reasoning about the next operation on the filesystem in both cases, when this next operation affects the directory tree under dirname(path) and when it does not. Since our system makes filesystem operations deterministic, including at the AbsFAT level, we are able to make the stronger claim:

When a given filesystem state corresponds to a given well- formed separation logic frame, the state after a call to mkdir(path), successful or not, corresponds to a new well-formed frame with all abstract variables unchanged which are logically separate from the contents of basename(path). Further, when dirname(path) exists as a directory and no file exists at path, the frame will have a new variable α representing the current state of dirname(path) containing a new empty directory named basename(path).

The proof of the Fig. 1.1 conjecture splits into a number of cases, as ex- pected from our understanding of the mkdir specification; Fig. 5.2 shows these case splits. While all but one of the cases are vacuous, since the postcondition implication tpr Ù 0q Ñ ps Ù 0qu is vacuously true whenever r is nonzero, the cases still need to be explored for soundness of the proof. The justification for developing the theorem proving infrastructure becomes apparent simply from exploring the number of cases arising in this proof for a trivial sequence of two

42 Figure 5.2: Proof tree for the conjecture in Fig. 1.1

tppath ñ P {aq ˚ ENOENTpP qu tppath ñ P {aq ˚ pαP ÞÑ a : ´qu rZ := mkdir(path) rZ := mkdir(path) tr Ù ENOENTu tr Ù EEXISTu s := stat(path) s := stat(path) tpr Ù 0q Ñ ps Ù 0qu tpr Ù 0q Ñ ps Ù 0qu

tppath ñ P {aq ˚ pαP ÞÑ ar´squ tppath ñ P {aq ˚ ENOENTpP {aqu rZ := mkdir(path) Z r := mkdir(path) tr Ù EEXISTu tr Ù 0u s := stat(path) s := stat(path) tpr Ù 0q Ñ ps Ù 0qu tpr Ù 0q Ñ ps Ù 0qu tT rueu r := mkdir(path); s := stat(path) tpr Ù 0q Ñ ps Ù 0qu operations, which is not conducive to hand proofs.

As one might expect, a complementary conjecture that mkdir leaves the filesystem state unchanged in an error condition, i.e. when pr Ù 0q, is also automatically provable with the help of AbsFAT.

5.2 Rewriting

AbsFAT’s abstract separation-logic based formulation of file operations has a logically straightforward representation in the theorem proving envi- ronment, which connects neatly with the existing HiFAT directory-tree repre- sentation. Thus, a valid filesystem instance can be represented as a frame, an association list mapping variables to unrooted trees in which the root is distinguished from the other variables.

43 The usability of a proof technique depends, to a significant extent, on the degree to which it can be automated by the prover. In ACL2, this is nat- urally the task of the rewriter, which efficiently completes proofs even when they involve large terms of the kind that show up in code proofs. One of the most labourious aspects of theorem proving is the manual instantiation of lemmas which the theorem prover cannot apply through the regular process of rewriting, possibly because of a mismatch between normal forms, so we look for a method to phrase the separation properties in a way that is palatable to the rewriter. We adapt the techniques of Myreen and Kaufmann’s work on code proofs [51] which demonstrated a logical workaround for the difficulties of separation logic proofs in the presence of existential quantifiers. In their work, the recursive predicate separate served to take the place of the con- ventional separating conjunction operator, which by definition introduces the unwanted existential quantifiers. In proofs, the separate predicate under the action of certain rewriting tactics was shown to create those case splits which were necessary to resolve the subgoals and complete the proof. Our formula- tion of separate, namely abs-separate, is similar and helps us avoid existential quantifiers.

In the general setting where zero or more abstract variables exist at body addresses within the frame frame, and each corresponds to an abstract heap cell in the heap, we can recursively define the predicate abs-separate, in

44 Figure 5.3: An axiom that means different things in the HiFAT and LoFAT contexts

1 old ñ P {a ˚ new ñ P 1{b ˚ αP ÞÑ arCs ˚ βP ÞÑ brC1s Z Z rename old new αP ÞÑ φ ˚ βP 1 ÞÑ brC1 ` arCss

Haskell notation:

abs-separate rs “ True

abs-separate ppα, entry αq :: frameq “ pdisjoint frame entry αq ^

pabs-separate frameq

This predicate has an analogous effect to Myreen’s predicate in the flat memory model, creating case splits which help resolve the subgoals of the proof. This holds true even though the filesystem model is more complex with the abstract variables needing to be maintained in a well-formed state in the sense of collapsibility. Collapsibility, which needs to be maintained as an invariant through various file operations, is necessarily a global property; abs-separate facilitates reasoning about it locally and generally minimises the need for non-local lemmas.

A choice, here, is whether abs-separate and abstract allocation/deal- location should be implemented at the LoFAT level, or the HiFAT level. At a first glance, it seems useful to reason about clusterchains in LoFAT and declare them disjoint through the abs-separate predicate; however, these disjointness properties are adequately addressed by the definition of lofat-to-hifat, a

45 function which transforms a LoFAT instance to a HiFAT instance and returns an error for ill-formed LoFAT instances, including those with any overlap be- tween clusterchains. The choice is ultimately determined by a FAT32-specific consideration: a subdirectory is not completely independent of its parent di- rectory, because it needs to have an explicit .. directory entry pointing to its parent. Thus, a naïve implementation over LoFAT would cause inconsis- tencies; one such inconsistency arises in one of the axioms for the system call rename (Fig. 5.3), which addresses the moving of a subdirectory. This axiom implies that the contents of the subdirectory b are unchanged, which is untrue for LoFAT since the .. directory entry is updated from P to P 1{b. This axiom holds true for HiFAT though, which has no . or .. entries since it’s a direc- tory tree model and doesn’t need them. Thus, we opt to implement this logic at the HiFAT level, avoiding inconsistencies such as the above in AbsFAT and generally benefiting from HiFAT’s ease of use for reasoning.

AbsFAT deals with filesystems, instrumented with body addresses and accompanied by heaps which bind each abstract variable in some body address to a partial directory, i.e. a fragment of a directory, with none, some, or all of that directory’s contents. AbsFAT refers to such heaps as frames, assisting the intuition that a frame contains some immediately useful information and some less immediately useful information, which the frame rule of separation logic helps in managing. The well-formedness of such a frame is determined by Gardner et. al’s [20] definition of the one-step collapse relation Ó, which describes a single context application folding an abstract heap cell into the

46 incomplete directory tree holding the corresponding body address, and its transitive closure Ó˚ which describes a number of such context applications. These definitions are existentially quantified, so AbsFAT opts for an alternative formulation of transitive collapse, iterating over abstract heap cells until all are folded into the root variable. This iterative formulation simplifies the task of maintaining well-formedness as an invariant across file operations from a reasoning perspective, and also makes the invariant intuitive: the property being preserved is the ability of the frame to be properly collapsed.

An instrumented filesystem consists of a root directory and an associ- ation list mapping each numbered variable to:

• a partial directory, possibly containing body addresses itself;

• a path promise; and

• a source pointer to an abstract variable (possibly the root itself) con- taining a body address for the given variable.

The well-formedness of such an instrumented filesystem becomes the proposi- tion of whether it can be collapsed without errors; i.e. whether every abstract cell corresponds to a body address in some variable, matching its path promise. As a basic property of our formulation, we prove that a successful collapse as described above yields a HiFAT instance with no duplicate filenames in any directory, under the hypotheses of separation between the different variables in the abs-separate sense and the absence of “dangling” body addresses which lack corresponding abstract cells.

47 The α values are chosen to be natural numbers to simplify the book- keeping involved in the implementation; and disjointness requires all of them to be distinct from each other. Each entryα value is a partial directory. These entries match Gardner et al.’s definition of an unrooted tree1, given by the grammar: ud ::“ φ | a : s | aruds | ud ` ud | x

In this grammar, φ is the empty tree, a : s is a regular file with name a and contents s, aruds is a directory named a and containing unrooted tree ud, ud ` ud is composition, and x is a variable. This definition allows for multiple variables in a directory, as does our code, but in practice we find it useful while reasoning about file operations to avoid having more than one variable in a directory. When this property holds, it becomes possible to check the existence of a file at a given path by examining the contents of at most one abstract variable.

It remains to address one point: how are abstract allocations to be done in a proof? In the proof tree in Fig. 5.2 for instance, the r Ù 0 case assumes the existence of variable α with the appropriate path promise and the appropriate contents; in a proof search the variable needs to be allocated procedurally. A naïve answer would be to collapse the entire frame into a single root variable and then allocate a new variable with the desired path promise, creating the corresponding body address in the root. This is undesirable, limiting if not

1We replace inodes in the original grammar with strings. In FAT32, with no hardlinks, it is easier to just represent the contents of regular files as strings.

48 entirely curtailing the local reasoning we hope for by bringing all variables together.

AbsFAT solves this question by implementing a transformation on the frame of abstract variables, called partial collapse. For a given path, this operation iteratively deallocates every abstract variable with a path promise prefixed by this path, leaving the frame in a state where the contents of the directory at that path are to be found, without holes, in one variable. All other variables are left as is. This is essential to the specification of almost every one of the system calls we consider, since they require the some directory to be brought into a variable for examination, the way α in the mkdir(path) specification brings in the contents of basename(path). For instance:

• In the specification of mkdir(path), partially collapsing the frame on

dirname(path) allows the contents of the parent directory to be seen, which determines whether the system call will succeed (when no file

exists at path) or fail (when a regular or directory file does exist at

path.)

• Similarly, in the specification of stat(path), partially collapsing dirname(path) reveals the contents of the parent directory which determines whether the system call will succeed (when a regular or directory file does exist

at path) or fail.

Using partial collapse necessitates a somewhat complex proof to show that a partially collapsed frame collapses to the same filesystem as it would

49 Figure 5.4: Reasoning about longer sequences of system calls

{ r := mkdir("/tmp/docs") s := mkdir("/tmp/docs/pdf-docs") r Ù 0 ˚ s Ù ´ ˚ t Ù ´ t := stat("/tmp/docs") r Ù 0 ˚ t Ù 0 } have before the partial collapse, modulo rearrangement of files within directo- ries. The payoff for this proof is in the form of clean specifications of system calls, deterministically evaluating the state of the filesystem just like the sys- tem call specifications in HiFAT and LoFAT. The specifications capture the read-over-write properties of the implementations of HiFAT and LoFAT, while avoiding one of the key problems, namely, the general difficulty of getting read- over-write theorems to apply because of various simplifications in the course of a rewriting proof that keep the left-hand side of the rewrite rule from being unifiable with the target.

This leads to another example (Fig. 5.4), which is worked out in ACL2 along the same lines as demonstrated here. This exemplifies the kinds of properties we need to prove across sequences of operations, when sometimes we need to logically disregard the effect of one system call such as the second mkdir in this case.

This example also shows one interesting approach to verifying filesystem properties, in which the precondition for one system call to do the desired thing may be excessively verbose to write out in full, but can be more concisely

50 Figure 5.5: Coreutils programs in the co-simulation test suite

cp, ls, mkdir, mv, rm, rmdir, stat, truncate, unlink, wc

Figure 5.6: mtools programs in the co-simulation test suite

mcopy, mdel, mdeltree, mmd, mmove, mrd, mren

expressed in terms of the return value of a previous system call.

51 Chapter 6

Concurrency

Concurrency in filesystems is an essential component of their perfor- mance. Concurrency, when offered by the filesystem, allows filesystem clients to be written in a way that decouples independent tasks and allows the hard- ware to better schedule these tasks on the available cores, even when there is just one.

Using AbsFAT, we gain the ability to reason about disjoint parts of the filesystem, a necessity in reasoning about concurrent operation. In this chapter, we demonstrate this in a proof of correctness of a concurrent program for copying files with a subset of the functionality of cp(1).

Although ACL2 supports both parallel execution and parallellised rea- soning, we limit ourselves here to modelling filesystem operations as atomic. This allows us to look at each execution of a concurrent program as a sequen- tial execution of the file operations in that program, with the order of those operations specified by an oracle.

An oracle, conceptually, is a black box entity which decides the order in which operations, designated to be concurrent, take place. Oracle-based reasoning about concurrent filesystem operations requires a notion of program

52 execution, in which each operation has a certain well-defined effect on the state no matter where it is executed in relation to other operations. Accordingly, we build an interpreter for LoFAT, intended to interpret and execute sequences of operations. We also build an interpreter for AbsFAT, and inductively prove that executions of these two interpreters on the same list remain in correspondence throughout, as a corollary of LoFAT being a refinement of AbsFAT.

Formulating a concurrent cp-like program, which separates the copy- ing of each command-line argument into a concurrent task, leads us towards certain design questions:

• What subset of the features of a conventional processor need to be mod- elled?

• What subset of the conventional synchronisation primitives need to be modelled?

• How is thread-local storage and shared state (both registers and memory) to be modelled?

In this chapter, we propose answers to these questions and validate them by formulating the code proof we develop for the cp-like program.

Design decisions for the concurrency model:

1. An auxiliary state variable, fat-st, is used to keep track of variables changed during the execution of a program. While this state variable

53 can be made more detailed for more complex proofs, even going as far as to model a register file if needed, at a minimum it needs to model a few variables representing the arguments, return values and errno values from filesystem calls.

2. The operations executed by the interpreter should include the system calls supported by the filesystem, as well as a small number of opera- tions to get and set state variables using immediate operands. This is necessary since a file operation, in general, requires a number of input variables (buffers, tables, and so on) which need to be set up before the operation.

3. The actual execution of concurrent programs should involve the notion of queues, i.e. threads of atomic operations to be executed in turn, and the scheduling of these queues based on oracles modelled as lists of natural numbers which indicate the next queue to be scheduled at each point.

4. The concurrency control should be centred around transactions. Prece- dent for this has been set in previous work about transaction-enabled operating system calls in TxOS [56] and transaction-enabled filesystem calls in TxFS [27].

Now, consider the execution of the following command:

cp /etc/{crontab,fstab,hosts} /tmp

54 Figure 6.1: Oracle-based cp. Note, the various operations that go into copying one file are treated as a transaction, and the indexing of queues is not zero- based.

/

etc/ tmp/

crontab fstab hosts α α Oracle: 2 3 1 Queues: crontab fstab hosts /

etc/ tmp/ α

crontab fstab hosts α fstab Oracle: 3 1 Queues: crontab hosts /

etc/ tmp/ α

crontab fstab hosts α fstab hosts Oracle: 1 Queues: crontab /

etc/ tmp/ α

crontab fstab hosts α fstab hosts crontab Oracle: Queues:

55 Figure 6.1 illustrates how all queues are cleared, eventually, in an order decided by the oracle. Intuitively, this should result in the same filesystem regardless of the this order.

Our model of this concurrent execution in ACL2 is sufficient to au- tomatically prove that this holds true when tested with two or three named files. However, a more general property for arbitrary numbers of files is also sketched out in ACL2, with some assumptions. Note, under this model, the concurrency control results in a completely serialised set of operations, with none of the filesystem calls actually overlapping. This example and its proof also illustrate the concurrency model’s paradigm of the operations in a trans- action getting executed together, without interruption. This allows cp to copy each file independently, regardless of the interleaving order decided by the oracle.

Having proved the correspondence between AbsFAT and LoFAT execu- tions of sets of operations, we will focus on AbsFAT in the rest of this discussion.

6.1 State

The state of the filesystem consists of an AbsFAT frame along with an aggregate data structure, fat-st, representing the various arguments and return values from filesystem calls. Since it is an aggregate data structure, we define it as a product data type through the FTY library (subsection 2.1.5).

This serves the purpose of standardising filesystem calls with different

56 Listing 6.1: fat-st (fty::defprod fat-st ((fd natp :default 0) (buf stringp :default "") (offset natp :default 0) (count natp :default 0) (retval integerp :default 0) (errno natp :default 0) (path fat32-filename-list-p :default nil) (stat struct-stat-p :default (make-struct-stat)) (statfs struct-statfs-p :default (make-struct-statfs)) (dirp integerp :default 0) (fd-table fd-table-p :default nil) (file-table file-table-p :default nil) (dir-stream-table dir-stream-table-p :default nil))) function signatures and different operations on buffers, file descriptors, direc- tory streams and the errno variable.

6.2 Operations

The operations include most of the filesystem calls discussed previously, as well as a number of operations with immediate operands for setting various fields in the state ahead of a system call making use of those fields. So, a filesystem call such as pread, which takes a path argument, can be preceded by an operation which sets the path appropriately.

57 6.3 Queues

We consider each concurrent program operating on the filesystem to, in terms of reasoning, set up an arbitrary number of queues as decided by the user program. Each queue contains a number of operations which are executed in order while being arbitrarily interleaved with other queues as instructed by the oracle. Execution comes to an end when the queues are all empty.

6.4 Scheduling

The scheduling of operations respects transactions: sequences of oper- ations which must not be interleaved with operations from other queues.

6.5 Limitations

This model of program execution has a few limitations: control flow operations such as branching and looping are not supported, and there is no notion of thread-local storage which would allow a queue to keep some of its state around between successive operations scheduled by the oracle. As a result, there are some ad hoc aspects to the modelling of cp, including the direct generation of queues for scheduling, instead of them being generated through the interpretation of loop constructs in the program. We contend, however, that these aspects do not take away very much from the demonstration of concurrent program reasoning aided by AbsFAT. Further, we contend that the proof process will be fundamentally similar even as the model becomes more

58 realistic in terms of incorporating language features for looping control flow, and as the model incorporates concurrency constructs more complex than the transactions discussed in this chapter.

6.6 Proof

At the top level, the proof of correctness of cp establishes the statement:

Under certain hypotheses, the program reaches equivalent states after executing the cp program until completion with the same ar- guments under two arbitrary oracles.

Of course, in this case our equivalence notion for the states must dis- regard some elements of the state, namely, all variables in the fat-st data structure, and the order of the files within the destination directory. The no- tion of disregarding ordering of files has been previously discussed (hifat-equiv, chapter 4) and it is this notion we use here. We benefit here from previously proved properties about frames collapsing to equivalent directory trees if their respective abstract variables are bound to equivalent directory trees, and a separation logic representation of pwrite that leaves all abstract variables un- changed except for the one containing the destination directory. We become able to focus on this variable, as it changes in both executions of cp under the two oracles, and proceed to reason about each copy of this variable being a subset of the other. This, once established, gives us their equivalence by definition, and in turn the equivalence of both frames.

59 It is self-evident that the proof of this subset relationship will necessar- ily be inductive on the number of files copied at each point in the execution of the cp program. Perhaps less self-evident but nonetheless important is that the induction step of this claim also involves claims which are themselves induc- tive; this is in addition to several other lemmas needed for the proof which are also inductive, albeit much simpler in comparison. ACL2 facilitates quick es- tablishment of these inductive lemmas, and in certain circumstances succeeds in proving them without user intervention to complete a proof with multiple levels of induction.

In future work, we hope to move in two directions:

• Making our control flow and concurrency primitives at least a little like what one would find in unmodified assembly code, which is ultimately the target of program verification in general.

• Adopting true parallelism in the execution of concurrent programs, per- haps following modifications to ACL2’s parallel variant ACL2p [57], to support parallel updates to the fields of a single-threaded object such as a LoFAT instance.

60 Chapter 7

Evaluation

7.1 Co-simulation

Co-simulation is a necessary component of formal verification efforts when binary compatibility is the aim, in order to validate the correspondence of the verified model with the software/hardware system in question [22]. A challenge, from the perspective of co-simulation as well as from the perspective of reducing the risk of bugs in unverified code, is the choice of an interface to the operating system. We develop our co-simulation tests as reads and writes on disk images; thus, the potential for bugs outside the verified part of the implementation is confined to specification and implementation errors in ACL2’s built-in I/O operations (and indeed, one such bug was found and rectified during this development [30]).

Among existing FAT32 implementations, we have chosen to co-simulate with the Linux kernel implementation of FAT32 (as mediated by the GNU

Coreutils) and the mtools [42]. The mtools perform various operations such as copying and deletion of files on a given FAT32 disk image or block device, which makes co-simulation relatively straightforward. Co-simulation with the Coreutils involves more steps since they are agnostic towards the underlying

61 filesystem; each test proceeds by mounting a disk image, running the program in question, and unmounting. This co-simulation setup checks the correct- ness of file operations, without changing filesystem state, in the two following scenarios (which are not mutually exclusive).

1. File operations which retrieve data from the filesystem, such as pread [36], result in output which must be compared to that of the canonical FAT32

implementation. The program diff [18] effects this comparison.

2. File operations which modify the state of the filesystem, such as pwrite [37], result in a modification to the disk image. The modified disk image must then be compared to a disk image modified by the canonical FAT32 im- plementation; this is done by an ACL2 program which checks whether EqFAT holds for the two images.

7.2 POSIX Interface and Tests

Table 7.1 summarises the subset of the POSIX system calls which have been implemented. The Linux convention is for system calls to return an error code, which is zero if and only if no error occurred, and set the global variable errno; together, these allow an application program making a system call to include error-handling code based on whether an error arose and why. In the ACL2 setting, where there are no global variables, FAT32 system calls maintain the convention by including the “return value” and errno value in the values they return. This matches the Linux implementation of FAT32; thus,

62 for example, when rmdir is called on a non-empty directory, the filesystem instance is returned unmodified along with a non-zero “return value” and an errno value of EEXIST, as specified in the POSIX manual page for rmdir [38].

File descriptors, for operations such as pread and pwrite, are provided through a straightforward implementation of a file table and a file descriptor table, similar to Synergy’s [9] implementation; however, the interaction of multiple processes with the filesystem is not yet supported.

This subset suffices for writing and testing ACL2 programs which co- simulate a number of programs from the Coreutils suite (figure 7.1a) and from the mtools (figure 7.1b). The co-simulation test suite also includes a basic sanity check which compares the output of the program mkfs.fat -v, which creates a FAT32 disk image and prints a textual summary of volume-level metadata [28], with the output of an ACL2 program which reports the same metadata.

Each system call, except for statfs [39], is implemented through the Lo-

FAT primitives lofat-find-file, lofat-place-file, and lofat-remove-file; and the LoFAT version is proved to refine the HiFAT version as a consequence of the refinement between the HiFAT and LoFAT primitives.

Co-simulation tests almost always require more than one system call on a given disk image. When this happens, contiguous sequences of operations on the HiFAT instance are carried out while eliding back and forth transfor- mations between HiFAT and LoFAT until the moment of writing back to disk.

This elision is sound, as shown by the theorem lofat-to-hifat-inversion

63 Table 7.1: POSIX syscalls implemented

Syscall close closedir lstat mkdir mknod open opendir pread pwrite readdir rename rmdir statfs truncate unlink

Figure 7.1: Syscalls and co-simulation tests

(a) Coreutils programs co-simulated

Program (b) mtools programs co-simulated cp Program ls mcopy mkdir mdel mv mdeltree rm mmd rmdir mmove stat mrd truncate mren unlink wc

64 (figure 4.2b), and places HiFAT in a role similar to that of a cache.

statfs [39] is an exception and must be implemented at the LoFAT level, since it reports volume-level metadata, such as the total space and free space in the filesystem, which is abstracted away in HiFAT. This also limits the extent to which statfs, and programs which use it such as stat (more precisely, stat

-f/stat --file-system), can be incorporated into co-simulation tests, because volume-level metadata can differ between filesystems which are identical in terms of the files contained. For instance, the directory tree in figure 4.1a contains the same files and directories as the tree in figure 4.1b but may still occupy more space on disk, because the directory entry for the deleted file

/tmp/ticket1.txt still exists and may cause the contents of the directory /tmp to occupy an additional cluster.

These tests are reasonably efficient following our efforts to make our disk image operations quick and verify the guards of all the system calls.

7.3 Performance

This implementation of FAT32 loads up ACL2 in order to execute the model, which necessarily imposes a lower bound on the time taken for a co- simulation test with a program. However, reasonably quick co-simulation is essential to achieving breadth as well as depth in the co-simulation coverage; thus, optimisations become an important part of the modelling effort. The following two design choices are significant.

65 1. LoFAT is implemented as a stobj, even though this complicates syntax and reasoning, in order to avoid the performance penalties associated with creating and destroying large immutable data structures each time a single element in the in-memory FAT32 representation is modified. Transformations to and from HiFAT would have been prohibitive in co- simulation tests without a guard-verified stobj implementation of LoFAT.

2. String representations of data are chosen over byte-list or character-list representations wherever possible. While lists are simpler to reason about, it makes a difference to be able to use the efficient implemen-

tations of the built-in string operations concatenate (string concate-

nation), subseq (substring extraction) and so on while extracting and reconstructing file contents and working with disk images. In addition,

read-file-into-string [6], a recent addition to ACL2, provides a fast

mmap-based [35] alternative to ACL2’s character-oriented I/O operations for the use case of reading information from a FAT32 disk image and populating the fields of the LoFAT instance. Thus, by choosing to work with a string representation for disk images, and by choosing to repre- sent the contents of the data region as an array of cluster-sized strings (section 2.3) to take full advantage of the atomicity of clusters in FAT32, performance penalties associated with conversions between strings and lists are avoided.

Within the parameters of this design, two optimisations are made pos-

66 sible by ACL2’s logical story for I/O operations. Both of these avoid the con- struction of intermediate string representations while transforming between disk images and LoFAT instances, in order to reduce the associated overheads, while retaining the abstraction of the disk image as a string. Specifically, while writing back to disk, the explicit construction of a data region string would involve an expensive concatenation of all the clusters; this is omitted by instead writing back all the clusters in sequential order. Similarly, while reading a disk image, the population of the data region after having read the disk image would involve multiple subseq operations for extracting the clus- ters, with significant memory allocation overhead; this is avoided by instead calling read-file-into-string multiple times with the appropriate offsets to read the pertinent clusters from the disk image directly into the data region of the LoFAT instance. For both these optimisations, mbe is used (section 2.1.1) to show that the optimised ACL2 code has the same effect. This is in keeping with the refinement style of proof used throughout this work: when a con- ceptually simple sequence of I/O operations is replaced with a more complex sequence, the simpler sequence is, in a sense, a specification which is refined. This is also how the model development remains tractable as it evolves: while replacing an earlier implementation, in which disk image strings were explic- itly handled, with the optimised one, the co-simulation test suite showed the absence of regressions but the proof that both implementations work the same way enabled much greater confidence.

As a result of these design choices and optimisations, co-simulations

67 Table 7.2: Timing disk image I/O

Disk image size Read time Write time 128 MB 2.48 s 4.14 s 256 MB 3.58 s 7.91 s 512 MB 7.52 s 15.46 s 1024 MB 15.92 s 24.87 s involving relatively large disk images become possible. For comparison, the in-memory sparse filesystem tmpfs [63] usually mounts volumes of size 1 GB to 10 GB on a standard consumer laptop; we have been able to run tests involving disk images of size 1 GB in the same environment. Table 7.2 lists some timing results for such tests in terms of reading and writing FAT32 disk images, and table 7.3 summarises statistics pertaining to the magnitude of the modelling effort.

The proof development for supporting the filesystem proofs demon- strated in the present work is necessarily complex, since AbsFAT needs to abstract HiFAT and in turn LoFAT without loss of information. Examining all the code supporting the various models and the various proofs, we count 62,674 lines of code across 427 function definitions (a small number of which are induction schemes or other kinds of functions intended only for reasoning and not for execution) and 1143 theorems and lemmas. It remains of interest to check if ACL2’s features for automatically generating lemmas [5], already used in LoFAT, could be brought to bear on some of these.

68 Table 7.3: Code summary, including data generated using David A. Wheeler’s SLOCCount

Lines of code (models and proofs) 61,856 Lines of code (co-simulation) 818 Co-simulation tests 29

69 Chapter 8

Related work

Filesystem verification research has largely followed a pattern of syn- thesising a new filesystem based on a specification chosen for its ease in prov- ing properties of interest, rather than faithfulness to an existing filesystem. Our work, in contrast, follows the FAT32 specification closely in the pursuit binary compatibility. The related work includes abstract logical reasoning about filesystems and file operations, as well as research on concrete proofs about filesystem properties with the use of both interactive and non-interactive theorem provers; in this section each of these is examined in turn.

8.1 Interactive Theorem Provers

An early effort in the filesystem verification domain was by Bevier and Cohen [9], who specified the Synergy filesystem and created an executable model of the same in ACL2. They chose to work at the abstraction level of pro- cesses and file descriptors, and certified their model to preserve well-formedness of their data structures through their various file operations. However, they did not attempt to prove read-over-write properties or crash consistency, and some of the real-world complexity of path resolution was elided by their de-

70 sign choice of maintaining a map from full pathnames to file contents. Later, Klein et al. with the SeL4 project [40] used Isabelle/HOL [53] to verify a microkernel; while their microkernel design excluded file operations in order to keep their trusted computing base small, it did serve as a precursor to their more recent COGENT project [7]. Here the authors took a novel ap- proach, building a verifying compiler to translate a filesystem specification in a domain-specific language of their making to C-language code, accompanied by a refinement-based proof of the correctness of this translation. Elsewhere, the SibylFS project [59], again using Isabelle/HOL, provided an executable specification for filesystems at a level of abstraction that could function across multiple operating systems including OSX and Unix.

The Coq prover [8] has been used for the development of FSCQ [14], a state-of-the art filesystem built to have high performance and formally verified crash consistency properties. FSCQ has had two successors, also using Coq:

DFSCQ [13], which formally specifies the fsync and fdatasync file operations, and SFSCQ [29] which proves two-safety confidentiality properties in terms of data noninterference.

8.2 Non-interactive Theorem Provers

Non-interactive theorem provers such as Z3 [17] have also been used to analyze filesystem models. Hyperkernel [52] is a recent effort which simplifies the xv6 [15] microkernel until the point where Z3 can verify its various prop- erties with its SMT solving techniques. However, towards this end, all system

71 calls in Hyperkernel are replaced with analogs which can terminate in constant time; while this approach is theoretically sound, it increases the chances of dis- crepancies between the model and the implementation which may diminish the utility of the proofs or even render them moot. A stronger effort in the same domain is Yggdrasil [62], which focuses on verifying a filesystem by proving refinement of a simpler specification through symbolic execution in Z3. While Yggdrasil represents substantial progress in terms of the number of filesystem calls supported, crash consistency guarantees provided, and its capability to self-host its source code, it is still subject to the limits of SMT solving which prevents certain filesystem features from being modelled.

8.3 Abstract Reasoning about Filesystems

The work of Gardner et al. on specifying preconditions and postcon- ditions of POSIX filesystem calls [20] has been extended into a theory of con- current operation of filesystems [54] Both these papers are based on structural separation logic [66], a variant of separation logic augmented with reasoning capabilities for data structures such as trees and lists. This work, however, has not attempted to deliver an executable filesystem upon which programs could be run.

Bornholt et al. developed a bounded model checking-based notion of litmus tests [10] for specifying and checking crash-consistency properties for the use of application developers. The present work, in contrast, supports the proofs of arbitrary properties on a binary compatible model of FAT32 in

72 relation to actual executions of programs based on the model, including deep properties which may not be amenable to model checking.

8.4 Other Aspects of Operating System Verification

Two tangentially related systems with similar aims to AbsFAT are the work of Koh et al. [43] towards verifying the operating system components involved in a networked server, and the work of Chajed et al. [12] on Argosy, a verified system for stacking storage layers. While these systems are executable, they do not offer the sort of support for constructing and simplifying proofs about filesystem clients that AbsFAT does.

73 Chapter 9

Conclusion

A byte-level examination of the specification and existing implementa- tions of a filesystem is a necessary part of a verification effort for it to enable reasoning about the behaviour of programs which interact with the filesys- tem. The recursive definition of the directory tree is central to the study of filesystems; thus, induction is also central to the analysis. Defining and using notions of equivalence between directory trees which disregard implementa- tion details is essential for demonstrating that our FAT32 model and existing FAT32 implementations operate the same way. The logical decoupling enabled by mbe helps keep formal developments involving binary file formats tractable as they evolve through various optimisations, including optimisations based on the logical story of I/O.

The present work shows that an approach based on these principles can support reasonably good performance possible for a disk-image manipu- lation methodology of verified filesystem implementation, which is sufficient for validating existing filesystem implementations by means of extensive co- simulation testing. Further, properties of filesystem-interacting programs are somewhat amenable to proof automation but often require a more user-guided

74 process, particularly for programs involving recursion or iteration. This verifi- cation technique is capable of meeting all these needs and supporting concise proofs.

AbsFAT, a separation logic model, is demonstrated to faithfully describe the different system calls in LoFAT, which is in turn a faithful executable model of FAT32. AbsFAT’s formal descriptions of these system calls support proofs of correctness for programs which interact with the filesystem; Through exam- ples, we demonstrate the kinds of Hoare triples that arise in this verification context and how ACL2 completes these proofs via rewriting, guided by Ab- sFAT’s separation logic formulation. This application of abstract separation logic to an executable filesystem model is novel and shows several general prin- ciples which are applicable to the verification of filesystems other than FAT32 and the programs which make use of them.

An oracle-based concurrency model illustrates the kinds of reasoning about concurrent filesystem clients that is possible with AbsFAT under the assumption of serialisability.

An informal survey of filesystem-related bug reports yielded the inter- esting example of Shareaza, a file sharing application [61]. A bug in Shareaza caused an infinite loop when files larger than 4 GB were to be saved to a FAT32-formatted partition. Such an operation is disallowed by the published FAT32 specification in the interests of keeping clusterchains to a manageable length and the error code EFBIG is designated for filesystem implementations to indicate an error of this nature. This sort of bug, which could be mitigated

75 by attention to return values and error codes, is our motivation for building the precise filesystem models this dissertation discusses.

We hope this work will steer future work on filesystem verification to- wards a greater focus on binary compatibility, support of code proofs, and the use of abstract separation logic for compact representations of filesystem calls. ACL2’s abilities to simplify induction proofs and separation-based rea- soning, as discussed in this dissertation and highlighted in the discussion of concurrency, will also hopefully be replicated in the theorem provers of the future.

76 Bibliography

[1] Martín Abadi and Leslie Lamport. The existence of refinement mappings. Theoretical Computer Science, 82(2):253–284, 1991.

[2] ACL2 Community. ACL2 Documentation for B*. See URL http://www.

cs.utexas.edu/users/moore/acl2/manuals/current/manual/?topic=ACL2_

___B_A2.

[3] ACL2 Community. ACL2 Documentation for DEFUND-NX. See URL

http://www.cs.utexas.edu/users/moore/acl2/current/manual/index.html?

topic=ACL2____DEFUND-NX.

[4] ACL2 Community. ACL2 Documentation for MBE. See URL http://

www.cs.utexas.edu/users/moore/acl2/current/manual/index.html?topic=

ACL2____MBE.

[5] ACL2 Community. ACL2 Documentation for MBE. See URL http://

www.cs.utexas.edu/users/moore/acl2/current/manual/index.html?topic=

ACL2____MAKE-EVENT.

[6] ACL2 Community. ACL2 Documentation for READ-FILE-INTO-STRING.

See URL http://www.cs.utexas.edu/users/moore/acl2/current/manual/

index.html?topic=ACL2____READ-FILE-INTO-STRING.

77 [7] Sidney Amani, Alex Hixon, Zilin Chen, Christine Rizkallah, Peter Chubb, Liam O’Connor, Joel Beeren, Yutaka Nagashima, Japheth Lim, Thomas Sewell, et al. Cogent: Verifying high-assurance file system implementa- tions. ACM SIGPLAN Notices, 51(4):175–188, 2016.

[8] Yves Bertot and Pierre Castéran. Interactive Theorem Proving and Pro- gram Development: Coq’Art: The Calculus of Inductive Constructions. Springer Berlin Heidelberg, Berlin, Heidelberg, 2004.

[9] William R. Bevier and Richard M. Cohen. An executable model of the Synergy file system. Technical report, Technical Report 121, Computa- tional Logic, Inc, 1996.

[10] James Bornholt, Antoine Kaufmann, Jialin Li, Arvind Krishnamurthy, Emina Torlak, and Xi Wang. Specifying and Checking File System Crash- Consistency Models. SIGARCH Comput. Archit. News, 44(2):83–98, March 2016.

[11] Robert S. Boyer and J S. Moore. Single-Threaded Objects in ACL2. In Shriram Krishnamurthi and C. R. Ramakrishnan, editors, Practical Aspects of Declarative Languages, pages 9–27, Berlin, Heidelberg, 2002. Springer Berlin Heidelberg.

[12] Tej Chajed, Joseph Tassarotti, M. Frans Kaashoek, and Nickolai Zel- dovich. Argosy: Verifying Layered Storage Systems with Recovery Re- finement. In Proceedings of the 40th ACM SIGPLAN Conference on

78 Programming Language Design and Implementation, PLDI 2019, pages 1054–1068, New York, NY, USA, 2019. ACM.

[13] Haogang Chen, Tej Chajed, Alex Konradi, Stephanie Wang, Atalay İleri, Adam Chlipala, M. Frans Kaashoek, and Nickolai Zeldovich. Verifying a High-performance Crash-safe File System Using a Tree Specification. In Proceedings of the 26th Symposium on Operating Systems Principles, SOSP ’17, pages 270–286, New York, NY, USA, 2017. ACM.

[14] Haogang Chen, Daniel Ziegler, Tej Chajed, Adam Chlipala, M. Frans Kaashoek, and Nickolai Zeldovich. Using Crash Hoare Logic for Certi- fying the FSCQ File System. In Proceedings of the 25th Symposium on Operating Systems Principles, SOSP ’15, pages 18–37, New York, NY, USA, 2015. ACM.

[15] Russ Cox, M. Frans Kaashoek, and Robert T. Morris. Xv6, a simple Unix-like teaching operating system, 2016.

[16] Jared Davis. Reasoning about ACL2 file input. In Proceedings of the sixth international workshop on the ACL2 theorem prover and its appli-

cations, pages 117–126. ACM, 2006.

[17] Leonardo de Moura and Nikolaj Bjørner. Z3: An Efficient SMT Solver. In C. R. Ramakrishnan and Jakob Rehof, editors, Tools and Algorithms for the Construction and Analysis of Systems, pages 337–340, Berlin, Hei- delberg, 2008. Springer Berlin Heidelberg.

79 [18] Paul Eggert, Mike Haertel, David Hayes, , and Len Tower. diff (1)-Linux manual page, accessed: 07 Sep 2018.

[19] Robert W. Floyd. Assigning meanings to programs. Proceedings of the American Mathematical Society Symposia on Applied Mathematics, 6:19–31, 1967.

[20] Philippa Gardner, Gian Ntzik, and Adam Wright. Local Reasoning for the POSIX File System. In Zhong Shao, editor, Programming Languages and Systems, pages 169–188, Berlin, Heidelberg, 2014. Springer Berlin Heidelberg.

[21] Shilpi Goel. Formal verification of application and system programs based on a validated x86 ISA model. PhD thesis, Department of Computer Science, The University of Texas at Austin, 2016.

[22] Shilpi Goel, Warren A. Hunt Jr., Matt Kaufmann, and Soumava Ghosh. Simulation and formal verification of x86 machine-code programs that make system calls. In Formal Methods in Computer-Aided Design (FM- CAD), 2014, pages 91–98. IEEE, 2014.

[23] David Greve. Address enumeration and reasoning over linear address spaces. In 5th International Workshop on the ACL2 Theorem Prover and Its Applications (ACL2 2004), Austin, TX, 2004.

[24] David Greve and Matt Wilding. Dynamic datastructures in ACL2: A challenge, 2002.

80 [25] Wim H. Hesselink and M.I. Lali. Formalizing a Hierarchical File Sys- tem. Electronic Notes in Theoretical Computer Science, 259:67 – 85, 2009. Proceedings of the 14th BCS-FACS Refinement Workshop (RE- FINE 2009).

[26] C.A.R. Hoare. An axiomatic basis for computer programming. Commu- nications of the ACM, 12(10):576–580, 1969.

[27] Yige Hu, Zhiting Zhu, Ian Neal, Youngjin Kwon, Tianyu Cheng, Vijay Chidambaram, and Emmett Witchel. TxFS: Leveraging file-system crash consistency to provide ACID transactions. ACM Transactions on Storage (TOS), 15(2):1–20, 2019.

[28] Dave Hudson, Peter Anvin, and Roman Hodek. mkfs.fat (8)-Linux man- ual page, accessed: 09 Jul 2018.

[29] Atalay Ileri, Tej Chajed, Adam Chlipala, Frans Kaashoek, and Nickolai Zeldovich. Proving confidentiality in a file system using DiskSec. In 13th USENIX Symposium on Operating Systems Design and Implemen-

tation (OSDI 18), pages 323–338, Carlsbad, CA, October 2018. USENIX Association.

[30] Matt Kaufmann. Fixed read-file-into-string bug. (commit message), Jul 2018.

[31] Matt Kaufmann, Panagiotis Manolios, and J S. Moore. Computer-aided reasoning: an approach. Kluwer Academic Publishers, 2000.

81 [32] Michael Kerrisk. basename (3)-Linux manual page, accessed: 01 Aug 2020.

[33] Michael Kerrisk. dirname (3)-Linux manual page, accessed: 01 Aug 2020.

[34] Michael Kerrisk. errno (3)-Linux manual page, accessed: 01 Oct 2019.

[35] Michael Kerrisk. mmap (2)-Linux manual page, accessed: 09 Dec 2018.

[36] Michael Kerrisk. pread (2)-Linux manual page, accessed: 09 Jul 2018.

[37] Michael Kerrisk. pwrite (2)-Linux manual page, accessed: 09 Jul 2018.

[38] Michael Kerrisk. rmdir (2)-Linux manual page, accessed: 17 Mar 2019.

[39] Michael Kerrisk. statfs (2)-Linux manual page, accessed: 09 Dec 2018.

[40] Gerwin Klein, Kevin Elphinstone, Gernot Heiser, June Andronick, David Cock, Philip Derrin, Dhammika Elkaduwe, Kai Engelhardt, Rafal Kolan- ski, Michael Norrish, Thomas Sewell, Harvey Tuch, and Simon Winwood. seL4: Formal Verification of an OS Kernel. In Proceedings of the ACM SIGOPS 22Nd Symposium on Operating Systems Principles, SOSP ’09, pages 207–220, New York, NY, USA, 2009. ACM.

[41] Evan Klitzke. PATH_MAX Is Tricky, Apr 2017.

[42] Alain Knaff. mtools (1)-Linux manual page, accessed: 09 Dec 2018.

[43] Nicolas Koh, Yao Li, Yishuai Li, Li-yao Xia, Lennart Beringer, Wolf Hon- oré, William Mansky, Benjamin C. Pierce, and Steve Zdancewic. From

82 C to Interaction Trees: Specifying, Verifying, and Testing a Networked Server. In Proceedings of the 8th ACM SIGPLAN International Confer- ence on Certified Programs and Proofs, CPP 2019, pages 234–248, New York, NY, USA, 2019. ACM.

[44] Leslie Lamport. Verification and specification of concurrent programs. In J. W. de Bakker, W. P. de Roever, and G. Rozenberg, editors, A Decade of Concurrency Reflections and Perspectives, pages 347–374, Berlin, Hei- delberg, 1994. Springer Berlin Heidelberg.

[45] Matt Kaufmann, Panagiotis Manolios, and J S. Moore. Computer-Aided Reasoning: An Approach. Kluwer Academic Publishers, Boston, MA, June 2000.

[46] Mihir P. Mehta. Formalising Filesystems in the ACL2 Theorem Prover: an Application to FAT32. In Shilpi Goel and Matt Kaufmann, editors, Proceedings of the 15th International Workshop on the ACL2 Theorem Prover and Its Applications, Austin, Texas, USA, November 5-6, 2018, volume 280 of Electronic Proceedings in Theoretical Computer Science, pages 18–29. Open Publishing Association, 2018.

[47] Mihir P. Mehta and William R. Cook. Binary-Compatible Verifica- tion of Filesystems with ACL2. In John Harrison, John O’Leary, and Andrew Tolmach, editors, 10th International Conference on Interactive Theorem Proving (ITP 2019), volume 141 of Leibniz International Pro-

83 ceedings in Informatics (LIPIcs), pages 25:1–25:18, Dagstuhl, Germany, 2019. Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik.

[48] Mihir Parang Mehta and William R Cook. Separation Logic-Based Veri- fication Atop a Binary-Compatible Filesystem Model. In Brazilian Sym- posium on Formal Methods, pages 155–170. Springer, 2020.

[49] Microsoft. Microsoft Extensible Firmware Initiative FAT32 File System Specification, Dec 2000.

[50] C. Morgan and B. Sufrin. Specification of the UNIX Filing System. IEEE Transactions on Software Engineering, SE-10(2):128–142, March 1984.

[51] Magnus O. Myreen. Separation Logic Adapted for Proofs by Rewriting. In Matt Kaufmann and Lawrence C. Paulson, editors, Interactive The- orem Proving, pages 485–489, Berlin, Heidelberg, 2010. Springer Berlin Heidelberg.

[52] Luke Nelson, Helgi Sigurbjarnarson, Kaiyuan Zhang, Dylan Johnson, James Bornholt, Emina Torlak, and Xi Wang. Hyperkernel: Push-Button Verification of an OS Kernel. In Proceedings of the 26th Symposium on Operating Systems Principles, SOSP’17, pages 252–269, New York, NY, USA, 2017. ACM.

[53] Tobias Nipkow, Markus Wenzel, and Lawrence C. Paulson. Isabelle/HOL: A Proof Assistant for Higher-Order Logic, volume 2283. Springer Berlin

84 Heidelberg, Berlin, Heidelberg, 2002.

[54] Gian Ntzik, Pedro da Rocha Pinto, Julian Sutherland, and Philippa Gard- ner. A Concurrent Specification of POSIX File Systems. In Todd Mill- stein, editor, 32nd European Conference on Object-Oriented Programming (ECOOP 2018), volume 109 of Leibniz International Proceedings in In- formatics (LIPIcs), pages 4:1–4:28, Dagstuhl, Germany, 2018. Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik.

[55] Peter W. O’Hearn, John C. Reynolds, and Hongseok Yang. Local Rea- soning About Programs That Alter Data Structures. In Proceedings of the 15th International Workshop on Computer Science Logic, CSL ’01, pages 1–19, London, UK, 2001. Springer-Verlag.

[56] Donald E Porter, Owen S Hofmann, Christopher J Rossbach, Alexan- der Benn, and Emmett Witchel. Operating system transactions. In Proceedings of the ACM SIGOPS 22nd symposium on Operating systems

principles, pages 161–176, 2009.

[57] David L. Rager, Warren A. Hunt, and Matt Kaufmann. A Parallelized Theorem Prover for a Logic with Parallel Execution. In Sandrine Blazy, Christine Paulin-Mohring, and David Pichardie, editors, Interactive The- orem Proving, pages 435–450, Berlin, Heidelberg, 2013. Springer Berlin Heidelberg.

[58] John C. Reynolds. Separation Logic: A Logic for Shared Mutable Data Structures. In Proceedings of the 17th Annual IEEE Symposium on Logic

85 in Computer Science, LICS ’02, pages 55–74, Washington, DC, USA, 2002. IEEE Computer Society.

[59] Tom Ridge, David Sheets, Thomas Tuerk, Andrea Giugliano, Anil Mad- havapeddy, and Peter Sewell. SibylFS: Formal Specification and Oracle- based Testing for POSIX and Real-world File Systems. In Proceedings of the 25th Symposium on Operating Systems Principles, SOSP ’15, pages 38–53, New York, NY, USA, 2015. ACM.

[60] Murray Shanahan. The Frame Problem. In Edward N. Zalta, editor, The Stanford Encyclopedia of Philosophy. Metaphysics Research Lab, Stanford University, 2016.

[61] Bug: file-system file-size limit handling, 2011.

[62] Helgi Sigurbjarnarson, James Bornholt, Emina Torlak, and Xi Wang. Push-Button Verification of File Systems via Crash Refinement. In 12th USENIX Symposium on Operating Systems Design and Implementation

(OSDI 16), pages 1–16, Savannah, GA, November 2016. USENIX Asso- ciation.

[63] Peter Snyder. tmpfs: A virtual memory file system. In In Proceedings of the Autumn 1990 European UNIX Users’ Group Conference, pages 241– 248, 1990.

[64] Richard M. Stallman. GNU Make, 1988.

86 [65] Sol Swords and Jared Davis. Fix Your Types. In Matt Kaufmann and David L. Rager, editors, Proceedings Thirteenth International Workshop on the ACL2 Theorem Prover and Its Applications, Austin, Texas, USA, 1-2 October 2015, volume 192 of Electronic Proceedings in Theoretical Computer Science, pages 3–16. Open Publishing Association, 2015.

[66] Adam Wright. Structural separation logic. PhD thesis, Imperial College London, 2013.

[67] Hongseok Yang and Peter O’Hearn. A semantic basis for local reason- ing. In International Conference on Foundations of Software Science and Computation Structures, pages 402–416. Springer, 2002.

87