Verified Linking for Modular Kernel Verification - Institut f¼r

225
Verified Linking for Modular Kernel Verification U N I V E R S I T A S S A R A V I E N S I S Dissertation zur Erlangung des Grades des Doktors der Ingenieurwissenschaften (Dr.-Ing.) der Naturwissenschaftlich-Technischen Fakult¨ aten der Universit¨ at des Saarlandes Thomas In der Rieden [email protected] Saarbr¨ ucken, September 2009

Transcript of Verified Linking for Modular Kernel Verification - Institut f¼r

Page 1: Verified Linking for Modular Kernel Verification - Institut f¼r

Verified Linking forModular Kernel Verification

UN I

V E R S IT AS

SA

R A V I E NS IS

Dissertation

zur Erlangung des Gradesdes Doktors der Ingenieurwissenschaften (Dr.-Ing.)der Naturwissenschaftlich-Technischen Fakultaten

der Universitat des Saarlandes

Thomas In der Rieden

[email protected]

Saarbrucken, September 2009

Page 2: Verified Linking for Modular Kernel Verification - Institut f¼r

Tag des Kolloqiums: 20.11.2009Dekan: Prof. Dr. Joachim Weickert

Vorsitzender des Prufungsausschusses: Prof. Dr. Philipp Slusallek1. Berichterstatter: Prof. Dr. Wolfgang J. Paul2. Berichterstatter: Prof. Dr. Michael Backes

(Vertreter fur Prof. Dr. Manfred Broy)Akademischer Mitarbeiter: Dr. Dirk C. Leinenbach

Page 3: Verified Linking for Modular Kernel Verification - Institut f¼r

Danksagung

But the servant who did things that deserved abeating without knowing it will receive a lightbeating. Much will be required from everyone towhom much has been given. But even more will bedemanded from the one to whom much has beenentrusted.

Luke 12:48

Ich mochte mich an dieser Stelle bei allen bedanken, die letztlich zumGelingen dieser Arbeit beigetragen haben. Da ich mir meiner menschlichenUnzulanglichkeiten bewusst bin, entschuldige ich mich bei denjenigen, die ichvergessen habe, im Voraus.

Mein erstes Dankeschon geht an meine Eltern. Ein jeder, der mich kennt,kann sich eine Vorstellung davon machen, was es bedeutet, mich als Sohn zuhaben—eine Vorstellung die ich sicherlich muhelos ubertroffen habe. Danke furEure nahezu grenzenlose Geduld und Euer zu oft ungerechtfertigtes Vertrauenin mich.

Normalerweise folgt hier der kanonische Dank an den betreuenden Professorund das tolle Thema, das er einem gegeben hat. Ich mochte dieser Traditionnicht folgen. Stattdessen mochte ich ein Dankeschon an einen ausgesprochenmutigen Philanthropen richten (auch wenn er das jetzt sicherlich weit von sichweist), der aus Prinzip nicht den einfachen Weg geht, eine Eigenschaft, die michmit ihm verbindet. Danke Dir, Wolfgang, dass Du etwas gesehen hast, was denmeisten anderen verborgen geblieben ist, und dass Du mir uber all die Jahreein guter (vaterlicher ;)) Freund geworden bist.

Zwei Menschen in meiner Nahe verdanke ich sehr viel, ich weiß nicht, obich ohne sie diese Arbeit zu Ende gebracht hatte. Mark und Dirk, ich dankeEuch fur alle Unterstutzung, die Ihr mir reichlich zu Teil habt werden lassen,technisch und moralisch. Abgesehen davon seid Ihr zwei erstklassige Kollegenund Freunde, ich bin stolz darauf, mit Euch im Projektmanagement zusammengearbeitet haben zu durfen.

In jungen Jahren bereits in die Konzeption und Leitung eines Projekts wieVerisoft eingebunden zu sein, ist ungewohnlich und es bedarf ungewohnlicherMenschen, um dies moglich zu machen. Der leider viel zu fruh verstorbeneBernd Reuse war so ein Mensch. Ich hatte Ihnen Ihre Standardfrage ‘Herr Inder Rieden, was macht Ihre Promotion?’ gerne endlich positiv beantwortet.Vielen Dank fur Ihr Vertrauen und Ihre Visionen.

Es gibt Dinge, auf deren Verlauf man nur bescheidenen Einfluss hat. Das

i

Page 4: Verified Linking for Modular Kernel Verification - Institut f¼r

ii

Gelingen eines Verbundvorhabens wie Verisoft gehort dazu. Dass es ein Erfolggeworden ist, verdanke ich vor allem den vielen Projektmitarbeitern, die hartgearbeitet haben. Euch allen, vor allem denen des Lehrstuhls Paul, ein herzlichesDankeschon.

Zum Schluss mochte ich noch ein paar besonderen Menschen danken: SabineNermerich, meiner ersten und dienstaltesten Assistentin, dafur, dass Sie nie einBlatt vor den Mund nimmt. Meinen ganzen Damen im Cluster Office, Ihr seideine Wucht.

Eyad Alkassar, dessen korperlichen Widerstandsfahigkeit nicht immer ganzmit seiner geistigen und moralischen Integritat korreliert. Ich danke Dir furDeine unzahligen (liebenswurdigen) Angriffspunkte. Bitte beschaftige Dichweiterhin mit barensicheren Mulleimern, das hat Zukunft ;).

Norbert Schirmer, auch Dir ein herzliches Dankeschon. Abgesehen davon,dass ich ohne Dich heute noch ein Beweisbarbar in Isabelle ware, haben michDeine Essgewohnheiten immer wieder zu interessanten Anregungen gefuhrt undDeine Verzuckung beim Bassspiel inspiriert.

Diese Arbeit wurde teilweise im Rahmen der Verisoft und Verisoft XTVorhaben vom Bundesministerium fur Bildung und Forschung (BMBF) unterdem Forderkennzeichen 01 IS C38 gefordert.

This thesis has been partially funded by the German Federal Ministry ofEducation and Research (BMBF) in the framework of the Verisoft and VerisoftXT projects under grant 01 IS C38.

Page 5: Verified Linking for Modular Kernel Verification - Institut f¼r

iii

Abstract

This thesis is split into two parts. In the first one, we will present a formal spec-ification of Communicating Virtual Machines (CVM), a hardware-abstractingprogramming framework for microkernel implementers.

In the second part, we present the sketch of a simulation theorem between theCVM model and the hardware, represented by an integrated processor/devicemodel. For parts of the CVM model, namely the microkernel, certain correctnessproperties are formalized and verified.

This work is part of the Verisoft project. Verisoft aims at the pervasiveformal verification of integrated computer systems. This means that correctnessproperties from the top layers of such systems are transferred down to the lowestlevel, the hardware, where they have to be discharged. Following this procedureguarantees that we have not made too strong assumptions on the top layers.

To specify CVM, we integrate several computational models, for example fordevices, assembly language, and a C-like programming language called C0. Inthe verification part, we also use fundamental results from the Verisoft project,for example a correctness theorem for a non-optimizing C0 compiler.

The major part of the results presented in this thesis has been formalizedand verified in the interactive theorem prover Isabelle/HOL.

Kurzzusammenfassung

Die vorliegende Arbeit befasst sich im ersten Teil mit der formalen Spezifika-tion von Communicating Virtual Machines (CVM), einer von der Hardwareabstrahierenden Programmierumgebung fur Mikrokernel Implementierer.

Im zweiten Teil wird der Entwurf eines Simulationstheorems zwischen demCVM Model und der Hardware, realisiert durch ein integriertes Prozessor-/Geratemodell, prasentiert. Fur einen Teil des CVM Modells, den Mikrokernel,werden diverse Korrektheitseigenschaften formalisiert und verifiziert.

Die vorliegende Arbeit ist im Rahmen des Verisoft Projekts entstanden.Verisoft zielt auf die durchgangige formale Verifikation von integrierten Compu-tersystemen ab. Das bedeutet, dass Korrektheitsaussagen und -eigenschaftenvon den obersten Schichten solcher Systeme bis auf die unterste Schicht derHardware heruntergebrochen und entlastet werden mussen. So ist gewahrleistet,dass keine zu starken Annahmen auf den oberen Ebenen gemacht worden sind.

Zur Spezifikation von CVM werden diverse Berechnungsmodelle integriert,unter anderem fur Gerate, Assembler und eine C-ahnliche Programmiersprachenamens C0. Bei der Verifikation werden ebenfalls grundlegende Ergebnisseaus Verisoft verwendet, beispielsweise ein Korrektheitssatz fur einen nicht-optimierenden C0 Compiler.

Der großte Teil der Resultate, die in dieser Arbeit vorgestellt werden, sind iminteraktiven Theorembeweiser Isabelle/HOL formalisiert und verifiziert worden.

Page 6: Verified Linking for Modular Kernel Verification - Institut f¼r

iv

Extended Abstract

Operating systems are crucial components in nearly every computer system.They provide plenty of services and functionalities, e.g., managing inter-processcommunication, device access, and memory management. Obviously, they playa key role in the reliability of such systems, and in fact, a considerable shareof hacker attacks target operating system vulnerabilities. Thus, proving acomputer system to be safe and secure requires to prove its operating systemto be safe and secure.

We introduce a computational model called CVM (communicating virtualmachines) that formalizes concurrent user processes interacting with a generic(abstract) microkernel and devices. To establish interaction, the abstract kernelfeatures special functions called CVM primitives, which allow to alter processor device configurations, e.g., by copying data from one process to another. Bylinking a CVM implementation to an abstract kernel, we obtain a concretekernel (‘personality’). We describe how a whole framework, featuring virtualmemory support, memory management, system calls, user defined interrupts,etc.—thus providing a trustworthy platform for microkernel programmers—canbe proven correct.

One main result of this thesis are a complete formal definition of thecomputational model for CVM, including all of its primitives. Furthermore,we present a complete definition of abstract linking (to obtain the concretekernel) and its pre-requisites. Last but not least, we elaborate on parts of theoverall CVM implementation correctness theorem: we formalize the correctnessrelation between the abstract and the concrete kernel and present the formalproof that this relation is preserved by certain kernel steps. To a very largeextent, this proof has also been formalized in the interactive theorem proverIsabelle/HOL [NPW02]. The specifications have been fully formalized.

This thesis has been written in the context of the Verisoft project [IP08,Ver03], which aims at formal and pervasive verification of entire computersystems. Pervasive stands for the policy that all correctness properties fromthe abstract upper layers are transferred down through all the intermediatelayers to the actual hardware—given through an instruction set architecturewith devices—, where they are to be discharged using only a very small set ofwell-known axioms.

Due to the nature of such an endeavor, we use results from various sourcesin the Verisoft project: the low-level hardware and its correctness [Tve09],assembler language semantics (partially [Tsy09], partially own work) C0 C-like programming language semantics and compiler correctness [Lei08], stackverification and top-level correctness [AHL+09, Alk09, Tsy09].

The results of this thesis contribute to two sub-projects within Verisoft: in theacademic system, representing a vertical cross section of a real general-purposecomputer system, covering all layers from the gate-level hardware description tocommunicating concurrent programs, a microkernel named VAMOS uses CVM.The second sub-project is dedicated to the development of a representativeautomotive system stack. Here, a real-time operating system named OLOS[Kna08] bases on CVM.

Page 7: Verified Linking for Modular Kernel Verification - Institut f¼r

v

Zusammenfassung

Betriebssysteme gehoren zu den essentiellen Komponenten eines jeden Computer-systems. Sie bieten Mechanismen und Funktionalitaten, die die Kommunikationzwischen Prozessen, den Zugriff auf Gerate, sowie die Speicherverwaltung regeln.Es ist daher offensichtlich, dass die Verlasslichkeit eines solchen Computersy-stems ganz erheblich von der des Betriebssystems abhangt—tatsachlich ist esso, dass ein betrachtlicher Anteil von Hackerangriffen auf Lucken im Betriebssy-stem abzielt. Mochte man daher beweisen, dass ein Computersystem sicher undzuverlassig ist, so muss man diese Eigenschaften auch fur das Betriebssystemzeigen.

Wir fuhren in dieser Arbeit ein Berechnungsmodell namens CVM (commu-nicating virtual machines) ein, wo nebenlaufige Benutzerprozesse mit einemgenerischen (abstrakten) microkernel und Geraten interagieren. Dazu bietet derabstrakte Kernel spezielle Funktionen namens CVM Primitive an, die von denBenutzerprozessen aufgerufen werden konnen um Prozess- oder Geratekonfi-gurationen zu verandern—beispielsweise in dem Daten von einem Prozess zueinem anderen kopiert werden. Indem wir die CVM Implementierung zu einemabstrakten Kernel linken, erhalten wir einen konkreten Kern (‘personality’).Wir beschreiben in der vorliegenden Arbeit, wie ein solches Framework mitUnterstutzung fur virtuellen Speicher, Speicherverwaltung, etc. als korrekt be-wiesen werden kann und somit als verlassliche Plattform fur die Programmierervon Mikrokerneln dienen kann.

Ein Hauptergebnis dieser Dissertation ist die komplette formale Definitiondes CVM Berechnungsmodells, inklusive aller seiner Primitive. Desweiteren stel-len wir eine Definition des abstrakten Linkens (zur Konstruktion des konkretenKerns) vor, sowie der dazugehorigen Vorbedingungen. Dann verwenden wir dieseErgebnisse um einen Teil des Theorems zur CVM Implementierungskorrektheitvorzustellen: wir formalisieren die Korrektheitsrelation zwischen abstraktemund konkreten Kern und prasentieren den Beweis, dass diese Relation vongewohnlichen Kernschritten erhalten wird. Der Beweis ist zum allergroßten Teilim interaktiven Theorembeweiser Isabelle/HOL [NPW02] formalisiert worden,die Spezifikationen liegen vollstandig formal vor.

Diese Dissertation ist im Rahmen des Verisoft Projekts [IP08, Ver03] ent-standen. Verisoft zielt auf die formale und durchgangige Verifikation ganzerComputersysteme ab. Durchgangig heißt dabei, dass all Korrektheitseigenschaf-ten der abstrakten oberen Schichten eines Systems nach unten bis auf dieeigentliche Hardware herunter gebrochen werden, wo sie dann entlastet werdenunter zu Hilfenahme weniger wohlbekannter Axiome.

Wir verwenden Resultate verschiedener Quellen in Verisoft: die Hardware undihre Korrektheit [Tve09], die Semantik der Assemblersprache (teilweise [Tsy09],teilweise eigene Arbeit), die Semantik der C-ahnlichen Programmiersprache C0und Compilerkorrektheit [Lei08], sowie Ergebnisse zur Stackverifikation undihrer Top-level Korrektheit [AHL+09, Alk09, Tsy09].

Die Resultate dieser Arbeit werden in zwei Teilprojekten in Verisoft ver-wendet: im akademischen System, das einen vertikalen Querschnitt eines realenStandard-Computersystems, das alle Ebene umfasst, verwendet eine Mikroker-nel Implementierung namens VAMOS CVM. Das zweite Teilprojekt ist derEntwicklung eines representativen Automotive Systems gewidmet. Hier basiertein Echtzeit-Betriebssystem namens OLOS [Kna08] auf CVM.

Page 8: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 9: Verified Linking for Modular Kernel Verification - Institut f¼r

Contents

1 Introduction 11.1 The Context: The Verisoft Academic System . . . . . . . . . . 31.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.4 Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.4.1 Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91.4.2 Abstract Data Types . . . . . . . . . . . . . . . . . . . . 101.4.3 Partial Functions and Option Type . . . . . . . . . . . . 101.4.4 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2 Related Work 132.1 Operating System Verification . . . . . . . . . . . . . . . . . . . 13

2.1.1 The Early Days . . . . . . . . . . . . . . . . . . . . . . . 132.1.2 Recent Work . . . . . . . . . . . . . . . . . . . . . . . . 15

2.2 Other Related Projects . . . . . . . . . . . . . . . . . . . . . . . 162.3 Verisoft/Verisoft XT Context . . . . . . . . . . . . . . . . . . . 18

2.3.1 Hyper-V . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.3.2 PikeOS . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

I Computational Models 21

3 The C-like Programming Language C0 233.1 An Informal View on C0 . . . . . . . . . . . . . . . . . . . . . . 23

3.1.1 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243.1.2 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . 243.1.3 Statements . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.2 C0 Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273.2.1 Types and Type Name Environment . . . . . . . . . . . 273.2.2 Expressions and Complex Literals . . . . . . . . . . . . 283.2.3 Statements . . . . . . . . . . . . . . . . . . . . . . . . . 303.2.4 Procedure Table . . . . . . . . . . . . . . . . . . . . . . 32

3.3 C0 Small-Step Configurations . . . . . . . . . . . . . . . . . . . 323.3.1 Memory Configurations . . . . . . . . . . . . . . . . . . 323.3.2 Program Rest . . . . . . . . . . . . . . . . . . . . . . . . 363.3.3 C0 Configuration . . . . . . . . . . . . . . . . . . . . . . 36

3.4 Expression Evaluation . . . . . . . . . . . . . . . . . . . . . . . 373.4.1 Address of g-Variables . . . . . . . . . . . . . . . . . . . 37

vii

Page 10: Verified Linking for Modular Kernel Verification - Institut f¼r

viii Contents

3.4.2 Value of a g-Variable . . . . . . . . . . . . . . . . . . . . 383.4.3 Evaluating Expressions . . . . . . . . . . . . . . . . . . 38

3.5 Execution of C0 Programs . . . . . . . . . . . . . . . . . . . . . 453.5.1 Initial Configuration . . . . . . . . . . . . . . . . . . . . 453.5.2 Memory Updates . . . . . . . . . . . . . . . . . . . . . . 473.5.3 C0 Transition Function . . . . . . . . . . . . . . . . . . 47

4 Devices 554.1 Device Configurations . . . . . . . . . . . . . . . . . . . . . . . 554.2 Device Communication . . . . . . . . . . . . . . . . . . . . . . . 564.3 Transition Functions . . . . . . . . . . . . . . . . . . . . . . . . 58

5 VAMP ISA and Assembler 615.1 VAMP ISA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

5.1.1 Instruction Set Architecture . . . . . . . . . . . . . . . . 625.2 Combining ISA and Devices . . . . . . . . . . . . . . . . . . . . 645.3 Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

5.3.1 Abstracting from Bit Vectors . . . . . . . . . . . . . . . 675.3.2 Assembler Transitions . . . . . . . . . . . . . . . . . . . 67

6 Communicating Virtual Machines 696.1 CVM Configuration . . . . . . . . . . . . . . . . . . . . . . . . 706.2 Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

6.2.1 Device Steps . . . . . . . . . . . . . . . . . . . . . . . . 716.2.2 Kernel Steps . . . . . . . . . . . . . . . . . . . . . . . . 726.2.3 User Steps . . . . . . . . . . . . . . . . . . . . . . . . . . 75

6.3 CVM Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . 776.3.1 Auxiliary Functions . . . . . . . . . . . . . . . . . . . . 776.3.2 Process Management . . . . . . . . . . . . . . . . . . . . 806.3.3 Inter-Process Communication . . . . . . . . . . . . . . . 826.3.4 User Processes and Devices . . . . . . . . . . . . . . . . 856.3.5 Dealing with Physical Memory . . . . . . . . . . . . . . 896.3.6 Special Primitives . . . . . . . . . . . . . . . . . . . . . 97

6.4 Initial CVM Configuration and n-Step Transition Function . . 986.5 CVM Implementation and Abstract Linking . . . . . . . . . . . 99

6.5.1 CVM Implementation . . . . . . . . . . . . . . . . . . . 996.5.2 Abstract Linking . . . . . . . . . . . . . . . . . . . . . . 102

II CVM Correctness 107

7 CVM Implementation Correctness 1097.1 Auxiliary Functions . . . . . . . . . . . . . . . . . . . . . . . . 1097.2 Abstraction Relations . . . . . . . . . . . . . . . . . . . . . . . 110

7.2.1 User Process Relation . . . . . . . . . . . . . . . . . . . 1107.2.2 Kernel Relations . . . . . . . . . . . . . . . . . . . . . . 1137.2.3 Device Relation . . . . . . . . . . . . . . . . . . . . . . . 1267.2.4 Interrupt Mask and Current Process . . . . . . . . . . . 1267.2.5 Other Invariants . . . . . . . . . . . . . . . . . . . . . . 1267.2.6 Putting It All Together . . . . . . . . . . . . . . . . . . 127

Page 11: Verified Linking for Modular Kernel Verification - Institut f¼r

Contents ix

7.3 Sketch of a Correctness Theorem . . . . . . . . . . . . . . . . . 128

8 Preservation of Kernel Consistency 1338.1 Auxiliary Lemmas . . . . . . . . . . . . . . . . . . . . . . . . . 1338.2 Weak Validity of C0 Configurations . . . . . . . . . . . . . . . 1378.3 Expression Evaluation in the two Kernels . . . . . . . . . . . . 1398.4 Static Properties . . . . . . . . . . . . . . . . . . . . . . . . . . 1508.5 Dynamic Properties . . . . . . . . . . . . . . . . . . . . . . . . 152

8.5.1 Program Rest Consistency . . . . . . . . . . . . . . . . . 1568.5.2 Symbol Table Consistency . . . . . . . . . . . . . . . . . 1598.5.3 Return Destination Consistency . . . . . . . . . . . . . . 1638.5.4 Heap Invariants . . . . . . . . . . . . . . . . . . . . . . . 1648.5.5 g-Variable Consistency . . . . . . . . . . . . . . . . . . . 166

8.6 Preservation of Kernel Consistency by C0 Kernel Steps . . . . 180

9 Summary 1839.1 Achievements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1839.2 Integrating Our Results . . . . . . . . . . . . . . . . . . . . . . 1859.3 Operating Systems Verification . . . . . . . . . . . . . . . . . . 187

Bibliography 189

Index 201

Page 12: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 13: Verified Linking for Modular Kernel Verification - Institut f¼r

List of Figures

1.1 The Verisoft Academic System . . . . . . . . . . . . . . . . . . . . . . 41.2 Implementation Layers of the Academic System . . . . . . . . . . . . . 51.3 Semantic Layers in Verisoft . . . . . . . . . . . . . . . . . . . . . . . 6

3.1 Statement Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313.2 Flattened Statement Tree . . . . . . . . . . . . . . . . . . . . . . . . 323.3 g-Variable Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . 333.4 Execution of Compound Statements with ‘Real’ Statements . . . . . . 483.5 Execution of Compound Statements with Skip Statements . . . . . . . 49

4.1 Devices Interacting with External Environment and Processor . . . . . 56

6.1 CVM Control Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . 716.2 CVM Memory Map . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

7.1 Mapping Abstract to Concrete Heap Variables . . . . . . . . . . . . . 1167.2 CVM Abstraction Relations . . . . . . . . . . . . . . . . . . . . . . . 127

9.1 Abstract and Concrete Program Rest at Induction Start . . . . . . . . 186

xi

Page 14: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 15: Verified Linking for Modular Kernel Verification - Institut f¼r

List of Tables

3.1 Unary C0 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . 253.2 Binary C0 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . 263.3 Lazy C0 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 263.4 Unary Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293.5 Binary Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293.6 Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . 293.7 Lazy Binary Operators . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.1 Special Purpose Registers . . . . . . . . . . . . . . . . . . . . . . . 63

6.1 VAMP Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776.2 CVM Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

xiii

Page 16: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 17: Verified Linking for Modular Kernel Verification - Institut f¼r

Programming today is a race between softwareengineers stirring to build bigger and betteridiot-proof programs, and the universe trying toproduce bigger and better idiots. So far, theuniverse is winning.

Unknown

Chapter 1

Introduction

Contents1.1 The Context: The Verisoft Academic System . . . . . . . 3

1.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1.3 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

1.4 Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Computer systems are faulty. We all know that: who has never experiencedthe annoyance of a so-called blue screen?

Most of the time, these faults are frustrating, sometimes they are infuriating.We have to redo some of our recent work (of course because we had not savedour files regularly), maybe we have to wait a couple of minutes until we cancontinue.

Obviously, we know how to repair a faulty system: ‘We turn it off, we pullthe plug, we count to ten, we plug it in, and finally we turn it on again.’, saysSir Tony Hoare—and he is right, most of the times this works and looking forthe bug, which caused the whole dilemma, would be just a waste of time.

Unfortunately, we can’t use these repair instructions with all computersystems in the world. Some not, because they don’t have a plug (have you evertried to reset a locked-up mobile phone with an integrated battery?), othersbecause even a second offline would have disastrous consequences—think ofair-crafts, space ships, incubators, pacemakers and so on.

We call such a thing a critical computer system: if human life or health isat stake, we talk of safety-critical systems, if it is about money or data privacy,we call it security-critical.

There is a long, sad track of accidents caused by faulty hard- and software:

• In 1982, the CIA slipped a bug into a piece of a Canadian computersystem. This computer system was known to be obtained by the Sovietsto control the trans-Siberian gas pipeline. The Reagan administration wastrying to stop Western Europe from importing Soviet natural gas. Theresulted event has been reported to be the largest non-nuclear explosionin history [Dav04].

• The Terac-25 [Lev95] was a radiation therapy device that could delivertwo kinds of radiation: either low-intensity electron beam or high-intensity

1

Page 18: Verified Linking for Modular Kernel Verification - Institut f¼r

2 Introduction

X-rays. The latter ones were generated by shooting high-power electronsinto a metal target situated between the electron gun and the patient.Due to a race condition in the operating system of the Therac-25 and thereplacement of the old electromechanical safeties with software control, amaloperation could lead to the X-ray been fired without the metal targetin place. At least five casualties have been reported in the years 1985 to1987, not counting the many seriously injured patients.

• Probably the first computer bug of which the world became widely awareof is the Intel Pentium floating point divide bug from 1993. The Pentiumchip produced faulty results for floating point divisions in a certain range.At the time Intel became aware of the bug, already 3 to 5 million chipshad been sold. Though only a very few people were actually affected bythe impreciseness, the bug ultimately created costs of $475 million forIntel [Jan95].

• On June 4, 1996, Ariane 5 flight 501 crashes 40 seconds after launch.Working code of predecessor Ariane 4 has been reused in Ariane 5. Thebug is in a routine, which converts a 64-bit floating-point number toa 16-bit signed integer. The problem was that the engines poweringAriane 5 are much faster, so the floating-point numbers are larger thanbefore causing an overflow interrupt. Since the soft- and hardware of thebackup system is identical to the primary one, both systems fail. In theconsequence, the rocket is overpowered and starts to disintegrate as theself-destruct system of the launcher is triggered [Boa96]. The damage hasbeen estimated with $500 million.

• In 2000, once again a software bug in radiation therapy claimed at leasteight casualties while leaving another 20 with serious overdoses. Thesoftware allowed to place four shielding blocks to protect healthy tissuefrom radiation. But doctors in Panama wanted to use five blocks anddiscovered a trick to cheat the software by drawing one single large blockwith a hole in the middle. Unfortunately the software, which comes froma U.S. company, gives different answers depending on how the hole isdrawn: in one direction, the correct dose is calculated. but in anotherdirection the dose is twice as much as it should be. All physicians involvedhave been indicted for murder, since they were legally obliged to checkthe computer results by hand.

If we have a look around in the world, there are fortunately not too manyreally evil things happening due to computer errors. Industry is aware of theproblems that arise from faulty systems and has come up with a bunch ofapproaches to minimize the risks:

• One approach is to define the development process and its guidelines asprecisely as possible. This is usually done using natural language and theresults are put together in a so-called standard.

Depending on the application area, a vast number of standards have beenproduced, e.g., the Common Criteria for Information Security Evaluation[The99].

Page 19: Verified Linking for Modular Kernel Verification - Institut f¼r

1.1. The Context: The Verisoft Academic System 3

• Intensive testing is another way to improve the quality of critical systems.Along with an increasing severance in the case of failure, the testingbecomes more exhaustive.

• Redundancy is still the primary choice for highly critical systems. Inavionic and space industry, most core systems come in triplicate, so thatif one system fails, a spare one takes over.

• Formal methods in specifying and verifying individual parts or modulesof critical systems are becoming more and more familiar tools in industry.Here, formal methods means paper-and-pencil proofs or computer-aidedverification (CAV) .

All of the above approaches have flaws. When using natural language,ambiguity comes into play. Besides all of the ‘shoulds’ and ‘coulds’, there isoften plenty of space left for individual interpretation by the reader.

Exhaustive testing is neither an answer. It is helpful for debugging, i.e.,in finding bugs, but it won’t guarantee their absence. You can compare it tolooking for the needle in the haystack: you have found one, two, three, and soon, but who tells you that there are none left? And often enough, test developerand programmer are the same people. So when they develop tests, they have the‘right’ way to handle their software always in mind. Who would have thoughtabout an ingenious trick as the one the Panama doctors had come up with?

Formal methods are useful, no doubt. Mathematical precision allows to avoidthe impreciseness of natural language. Computers check or even lead the proofsthat a piece of software or a hardware component is fault-free. Unfortunately,a system consisting of correct modules is not automatically correct itself: youhave to consider the interfaces and the interplay of the different components:they might be correct, but do they also fit together?

The Verisoft project [IP08, Ver03] pursues a different strategy, namely toformal and pervasive verification of entire computer systems. Here, formalmeans that all proofs are machine-checked or machine-made using computer-aided verification systems. Pervasive stands for the policy that all correctnessproperties from the abstract upper layers are transferred down through all theintermediate layers to the actual hardware, where they are to be dischargedusing only a very small set of well-known axioms.

This procedure guarantees that the assumptions made are not too strong,since they have to be justified against the ‘real world’, the hardware1. Onthe other side we exclude human shortcoming by having the proofs rigorouslymachine-checked.

Hence, the so-verified systems are of extreme quality as required in manyindustrial sectors, such as automotive engineering, security, and medical tech-nology.

1.1 The Context: The Verisoft Academic System

Another goal of Verisoft was the prototypical application of the methodologyto four concrete scenarios, of which three came from the industrial sector. In

1Of course there can still be errors on the lowest level not captured by the proofs, e.g.,physical damage, external influences, etc.

Page 20: Verified Linking for Modular Kernel Verification - Institut f¼r

4 Introduction

Figure 1.1: The Verisoft Academic System

one of these scenarios, a pervasively verified system (the academic system)for writing, signing, and sending emails had to be constructed. As such, theacademic system represents a vertical cross section of a real general-purposecomputer system, covering all layers from the gate-level hardware descriptionto communicating concurrent programs. The properties of the academic systemcorrespond to the required standards for real systems.

The academic system design comprises four layers. At the bottom, we havethe hardware layer which mainly consists of a microprocessor. In order tointeract with the academic system, this processor is realized on a FPGA, whichitself is hooked up to a host PC.

The second lowest level is the one of the system software. Here, we findthe microkernel, on top of which an operating system runs, and the memorymanagement mechanisms.

Above the system software, we find the networking and communication layer.Three protocols are part of the academic system in order to realize networkcommunication and email transfer.

On the topmost layer, the application software is located: a simple emailclient, serving as the user interface, and a cryptographic signature module.

Orthogonally to the system stack, we also have a tool layer, which comprisesthe compiler to translate the high-level language implementations.

The different implementation layers are shown in Fig. 1.2. We will nowdescribe the components of the academic system in bottom-up fashion.

Processor Hardware The hardware platform is given by the VAMP (VerifiedArchitecture Microprocessor) [BJK+06]. The VAMP is a 32-bit RISC CPU

Page 21: Verified Linking for Modular Kernel Verification - Institut f¼r

1.1. The Context: The Verisoft Academic System 5

Applications

SOS

VAMOS

CVM

VAMP DevicesHardware

Software

Machine-dependent

Machine-independent

System ModeUser Mode

System SoftwareUserland

ExternalEnvironment &Other Systems

Figure 1.2: Implementation Layers of the Academic System

with full DLX-instruction set including a set of IEEE-compliant floating pointinstructions, delayed PC, address translation, and support for maskable nestedprecise interrupts.

The VAMP hardware consists of a 5-stage pipeline with an out-of orderTomasulo scheduler with reorder buffer [Kro01]. There are five execution units,the XPU (fix point unit), the MU (Memory Unit), and three FPUs (FloatingPoint Units). Instructions have up to six 32-bit source operands and deliver upto four 32-bit results.

The floating-point units [Jac02] are fully IEEE compliant and support singleand double precision operations as well as denormal numbers and the full set ofIEEE exceptions in hardware. The FPUs are pipelined and the multiplicativeFPU has a cycle in the pipeline structure in order to compute quotients withNewton-Raphson iteration.

The memory interface of the VAMP consists of two MMUs (Memory Man-agement Units) that access instruction and data cache, respectively, which inturn access a physical memory via a bus protocol. The caches [Bey05] supportwrite-back and are kept consistent with snooping.

A verified version of the VAMP with MMUs in the interactive theorem proverPVS [OSR92] can be found in [Dal06], basing on results from [Hil05, DHP05].The results have been repeated in Isabelle/HOL [NPW02] with a much higherdegree of automation by integrating automatic tools [Tve09, TS08, Tve05].

Compiler A compiler has been implemented and formally verified, based ona small-step semantics for C0, our restricted version of C. The implementationof the compiler has been done in C0 itself.

The specification of the C0 compiler in Isabelle/HOL and its implementationin C0 have been completed. There exists a formal proof based on a Hoare logicfor C0 [Sch06, Sch05] that the implementation generates the same assemblycode as the compiler specification [Pet07]. A formal proof that the generatedassembly program simulates the original C0 program has been conducted inIsabelle/HOL and presented in [Lei08].

Page 22: Verified Linking for Modular Kernel Verification - Institut f¼r

6 Introduction

XCalls

C0 Hoare Logic

C0 Big-StepSemantics

C0 Small-StepSemantics

VAMP Assembly

VAMP ISA

VAMP Hardware

+

+

+

Abstract Code Property

Real System Property

transferFigure 1.3: Semantic Layers in Verisoft

Microkernel and CVM Layer The layer of communicating virtual ma-chines (CVM) has been introduced for the first time in [GHLP05]. A refinedand improved version can be found in [IT08].

The CVM layer establishes a hardware-independent programming interfacefor a microkernel and a virtual computation environment for concurrentlyrunning processes. Some parts of CVM must be implemented in assemblersince C0 lacks—for good reason—low-level programming constructs. Themicrokernel, named VAMOS and implemented in C0, is based on the CVMinterface and contains no assembler parts.

This work as well as [Tsy09, ST08] deals with aspects of CVM correctness.More details about VAMOS can be found in [DDB08, DDWS08].

Operating System The kernel calls of the above microkernel are targetedfor the implementation of a simple operating system (SOS) on top of this. Itoffers file I/O, inter-process communication, remote procedure calls and accessto the peripherals as system calls to user processes. A formal specification ofSOS has been presented in [Bog08].

Networking and Communication Protocols To realize network connec-tivity, a C0 implementation of the standard protocols Internet Protocol (IP, RFC791 [Inf81a]) , which provides packet-oriented communication, and TransportControl Protocol (TCP, RFC 793 [Inf81b]), providing reliable connection-basedcommunication, has been created.

To establish email communication, a mail server implementing the SimpleMail Transfer Protocol (SMTP, RFC 2821 [Gro01]) has been implemented. Itprovides the functionality for sending emails via SMTP (sendmail functionality),and it receives emails via SMTP and delivers them to the correct recipient/user

Page 23: Verified Linking for Modular Kernel Verification - Institut f¼r

1.2. Motivation 7

(mail server functionality).

Application Software There are two applications running atop of the simpleoperating system. One is a signature module providing the functionality forcryptographically signing texts (in particular emails) and for checking suchsignatures. It uses an asymmetric signature algorithm (RSA-PSS) with anSHA-1 hash function.

There is a formal specification of the module’s interfaces and of RSA-PSS(including error handling). RSA-PSS and SHA-1 have been implemented in C0,and RSA-PSS has also been formally verified [LWB06].

A simple email client is the top-level application, providing the user interfacefor the whole academic system. It provides functionality for editing, signing,and sending emails, as well as receiving, checking the signature of, and readingemails.

The client, implemented in C0, has been fully formally verified [BHW06].Work on how to formally define security requirements of user interfaces (suchas the email client) has been published in [BB06b, BB06a, BB04].

User interaction is handled via a standard terminal hooked up to the systemby a serial interface . A formal device and programming model for such a devicehas been presented in [AHK+07].

A comprehensive overview of the overall stack verification of the academicsystem from the low-level hardware to the level of CVM can be found in[AHL+09].

1.2 Motivation

In the last forty years, several major projects have been dedicated to operatingsystem verification (see also Chapter 2). Most of the work has been investedin the formal specification of operating systems and kernels. Only a very fewprojects involved mentionable verification efforts on code-level.

L4.verified/seL4 [KEH+09] and KIT [Bev87] are exemptions. The first onefeatures a high-performance microkernel, whose implementation seems to befully formally verified. Since pervasiveness was not one of the project’s goals,the trusted base is pretty large (compiler, hardware, assembler code portions).

On the other hand, KIT has been part of the famous CLI stack, which hasbeen formally and pervasively verified. Here, the kernel functionality measuredby the services it offers is very limited and cannot be compared to a modernmicrokernel.

The CVM model presented in this work aims to cover both aspects: itsfeatures are comparable to those of a second generation microkernel and is atthe same time part of the Verisoft pervasive model stack (cf. Fig. 1.3).

Another important aspect we consider in this thesis is modularization andcompositionality. While KIT was still written in assembler for a particularprocessor, nowadays microkernels are supposed to target several hardwareplatforms. Necessarily, any kernel implementation has to include low-level codeparts. Without proper separation of the hardware-specific code from the otherparts, verification work has to be redone for all target architectures.

For this reason we have encapsulated the hardware-dependent low-levelimplementation. A user of our framework can use his own hardware-independent

Page 24: Verified Linking for Modular Kernel Verification - Institut f¼r

8 Introduction

kernel implementation (abstract kernel). Furthermore we provide a formallinking mechanism, which allows a kernel implementer to merge the code partsand thus obtain a compilable, runnable kernel (concrete kernel).

Implementation correctness for the abstract kernel can then be provenindependently from the rest of the implementation. Since this abstract imple-mentation can be written completely in our high-level programming languageC0, more sophisticated and powerful code verification tools like [Sch06] canbe used. The verification results of the CVM layer can then be used in anassume/guarantee style of reasoning.

A considerable part of the overall correctness theorem for CVM deals withthe correctness relation between the abstract and the concrete kernel. Forstandard abstract kernel steps, i.e., no kernel calls, the preservation of thiscorrectness relation has been proven and will be presented in this work (seeChapter 8). To a very large extent, this proof has been conducted in theinteractive theorem prover Isabelle/HOL [NPW02].

Major parts of the formal theories of Verisoft have been published or are inthe process of publication [HP07]. This work relies on other Verisoft results: thelow-level hardware and its correctness [Tve09], assembler language semantics[Tsy09], C0 C-like programming language semantics and compiler correctness[Lei08], stack verification and top-level correctness [AHL+09].

1.3 Outline

In the rest of this chapter, we will introduce some basic notation. In Chapter 2,we discuss related work.

The remainder of this thesis is split into two parts. The first one is dedicatedto the computational models required to finally define CVM:

• In Chapter 3, we will introduce the C-like programming language C0.We start with the concrete C0 syntax and proceed then to the formalrepresentation of C0 programs. In the end of the chapter, we work outon expression evaluation and the C0 transition function.

• In Chapter 4, a generic framework for devices interacting with a processorand an (not modeled) environment will be introduced.

• In Chapter 5, we will discuss the underlying hardware. The bottom layeris given through the instruction set architecture of the so-called VAMP(Verified Architecture Microprocessor), whose semantics will shortly beintroduced. Subsequently, we will combine this model with the generalizeddevice model from Chapter 4.

The next abstraction layer is specified by the formal assembler semantics,the natural machine model for a system level programmer. The definitionsof its data types and transition functions make up for the rest of thischapter.

• In Chapter 6, we will introduce Communicating Virtual Machines—short:CVM—, a hardware-abstracting computational model for user processesinteracting with each other and devices via a so-called abstract kernel.The mechanisms for communication are established by special functionscalled primitives, whose semantics will be defined subsequently. Finally,

Page 25: Verified Linking for Modular Kernel Verification - Institut f¼r

1.4. Notation 9

we present a concept allowing to build a runnable, compilable and henceconcrete kernel by linking the hardware-dependent parts to it.

In the second part, we will deal with CVM correctness:

• In Chapter 7, we will work out the idea of a CVM correctness theorem,a simulation theorem between the hardware and CVM. Here, we willdefine the necessary abstraction relations between the components andthe hardware. Finally, we will give a sketch of how an overall correctnesstheorem could look like.

• In Chapter 8, we will present part of the correctness proof of CVM. Inparticular, we show how the abstraction relations between the two kernelsis preserved for non-primitive kernel steps.

We conclude in Chapter 9 with a summary and an outlook on future work.

1.4 Notation

In this section, we introduce the notation necessary to understand the rest ofthis thesis.

1.4.1 Basics

We use Σ+ to denote the set of identifiers, for instance variable or type names.N is the set of the natural numbers, while Z stands for the integers. {i, . . . , j}denotes the integer interval from i to j for i < j. The Boolean set {True,False}is abbreviated by B.

Sometimes, we introduce anonymous functions using . For instance, λx, y.x·yis a function that yields the product of two operands.

fn is the n-time application of a function f with fn(x) = f(fn−1(x)), withf1(x) = f(x).

We define a finite sequence with n elements of type t formally by a mappings : [0 : n − 1] → t. For 0 ≤ i ≤ j < n, s[i : j] denotes the sub sequence(s[i], . . . , s[j]). Sometimes, we specify a sub sequence by s[i, l], where 0 ≤ i < ndefines the start index and l with i+ l < n the length of the sub sequence.

In particular, we treat bit vectors as sequences of bits. The type of all bitvectors of length n is denoted by Bn.

Frequently, we will use records in this thesis. We access their componentswith the . operator, that is r.c returns the component c of record r. We denoterecord updates with r[c0 := this, c1 := that , . . . , ci := 42] where r is a recordand c0, . . . , ci are components of it. We assume that components that are notexplicitly mentioned in a record update stay unchanged. On the fly creation ofa record is written [c0 = this, c1 = that , . . . , ci = 42].

Not surprisingly, predicates are functions from an arbitrary domain into theBoolean set. A predicate P holds or is valid for an input x, iff P (x) = True.Shorthand notations for P (x) = True and P (x) = False are P (x) and ¬P (x).

Implications are often given by inference rules . For instance, (A ∧B)⇒ Cis denoted by

A B

C

Page 26: Verified Linking for Modular Kernel Verification - Institut f¼r

10 Introduction

We will sometimes use this notation to define predicates. In these cases, weassume that everything not covered explicitly by inference rules, evaluates toFalse.

In places where their necessity is obvious, we will omit universal and/orexistential quantifiers . We say d divides n, iff there exists an i ∈ Z such thati · d = n and write:

i · d = n i ∈ Zd | n

Often, we will have to deal with pairs. We define fst : t1 × t2 → t1 andsnd : t1 × t2 → t2 that return the first element or the second element of a pairrespectively.

1.4.2 Abstract Data Types

We use abstract terms to define terms in a structured way. The constructionrules are given by so-called constructors. The set of all terms, which can bebuild using the constructors of an abstract data type t is called type t. Thetype t is the set of all terms that can be built by using the constructors of anabstract data type t. Each constructor is a function with a result in the rangeof t.

For instance, an abstract data type for lists with elements of type t couldlook as follows:

t list = Nil| Cons(t, t list)

Here, we have two constructors: Nil : t list for the empty list, and Cons :t× t list → t list for the concatenation of an element and a list.

1.4.3 Partial Functions and Option Type

Isabelle/HOL has no support for partial functions, but there are two ways tosimulate them. First, we can introduce a constant representing the undefinedvalue. In this thesis, we name this constant undef .

Furthermore, there is the so-called option type. Formally, an option type isan abstract data type with two constructors.

For a type t we define the corresponding option type as:

t option = None| Some(t)

Here, None specifies an undefined value and Some(x) a defined value x. Foreach option type t there is a function the : t option → t that converts theargument back to the base type. We define

the(x) =

{y if x = Some(y)undef otherwise

For the rest of this thesis, we use t⊥ for t option and bxc for Some(x) asshorthand notation.

Page 27: Verified Linking for Modular Kernel Verification - Institut f¼r

1.4. Notation 11

1.4.4 Lists

We have seen the abstract data type for lists. It is very cumbersome to use theconstructors explicitly—and lists are vastly used in this thesis. For concisenesswe denote the empty list Nil with [ ] and write h#t for Cons(h, t). We candescribe lists explicitly: for instance, [a, b, c] is short for a#b#c#[ ], which isagain pretty printing for Cons(a,Cons(b,Cons(c,Nil))) .

We can apply a function f to all elements of a list by the map function:map : (t1 → t2)× t1 list → t2 list , which is defined inductively:

map(f, [ ]) = [ ]map(f, h#t) = f(h)#(map(f, t))

The length of a list is equal to the number of its elements:

| [ ] | = 0| h#t | = Suc | t |

We need some more auxiliary functions to facilitate list handling: hd :t list → t returns the first element—the head—of a list, tl : t list → t list thetail of the list, i.e., the list without its head. :

hd([ ]) = undefhd(h#t) = h

tl([ ]) = undeftl(h#t) = t

The append operator @ : t list × t list → t list allows to concatenate twolists of equal types:

[ ]@l = l

(h#t)@l = h#(t@l)

ith(l, i)—or short: l!i—returns the i-th element of list l . Note that the firstelement of a list is its 0-th element. For i ≥| l |, the result is undef . Updatingthe n-th element of a list with some value x is denoted by l[n := x].

The function butlast : t list → t list takes a list and returns this list withoutits last element We define:

butlast([]) = []butlast(xs#x) = xs

In some places, we need to flip a pair, that is the first element becomes thesecond and vice versa. For this purpose, we define flip : (t1 × t2)→ (t2 × t1)and set flip(a, b) = (b, a).

The function replicate : N× t→ t list creates a list with as many copies asspecified by the first argument of the functions second argument, e.g.,

replicate(5, a) = [a, a, a, a, a].

.

Page 28: Verified Linking for Modular Kernel Verification - Institut f¼r

12 Introduction

Frequently, we have to convert lists into sets . In this case, {l} denotes theset derived from a list l.

We define an auxiliary function mapof : (t1 × t2) list × t1 → t2⊥, whichworks as a pattern matching on lists of pairs . mapof takes two arguments: alist of pairs to be searched and a pattern to be compared. The second elementof the first pair in l matching the pattern will be returned. If there is no suchelement, the result is None.

We define formally:

mapof ([], x) = None

mapof ((a, b)#t), x) =

{bbc if a = x

mapof (t, x) otherwise

We define a function filter : (t → B) × (t list) → t list , which filters listsusing a predicate as the filter criterion. For a given predicate P , we definerecursively:

filter(P, [ ]) = [ ]

filter(P, x#xs) =

{x#(filter(P, xs)) if P (x)filter(P, xs) otherwise

.

Page 29: Verified Linking for Modular Kernel Verification - Institut f¼r

I’m not the smartest fellow in the world, but I cansure pick smart colleagues.

Franklin D. Roosevelt

Chapter 2

Related Work

We start with the related work on operating system verification in Sect. 2.1,where older work is treated in Sect. 2.1.1 and more recent work in Sect. 2.1.2.For a brilliant and exhaustive overview on this topic, we strongly recommend[Kle09].

Work, which is not directly dedicated to operating system verification butcontributing to it, is discussed in Sect. 2.2.

We conclude with the past and on-going work related to microkernel andoperating system verification in Verisoft and its successor Verisoft XT (cf.Sect. 2.3).

2.1 Operating System Verification

2.1.1 The Early Days

The first major attempts to specify and verify complete operating systems—PSOS and UCLA Secure Unix—date back to the seventies of the last century.

PSOS—A Provably Secure Operating System The PSOS—provablysecure operating system—project at SRI started in 1973. According to [NF03],PSOS ‘was designed as a useful general-purpose operating system with demon-strable security properties’.

The PSOS design and specification principles relies heavily on hierarchi-cal modeling, where the abstraction objects of each layer were meant to beimplemented by the abstract and primitive objects of lower layers.

This modeling approach was named Hierarchical Development Methodol-ogy (HDM) and had been developed very early in the project, coming alongwith a specification and assertion language SPECIAL [RLNS75, RL77]. UsingSPECIAL, each module in the system had been specified where a layer couldcomprise a number of encapsulated modules. Furthermore, SPECIAL wassupporting mapping and abstraction functions between modules on differentlayers, a feature that could have been used as a base for implementation proofslater on.

The design of PSOS seems to be basically complete, though it remains unclearhow much of it has actually been implemented—considering the fact that newhardware had been involved. According to the project’s final report [NBFR80],

13

Page 30: Verified Linking for Modular Kernel Verification - Institut f¼r

14 Related Work

no code proofs had been accomplished but only ‘some simple illustrative proofswere carried out’. Research on information flow analysis on PSOS went on until1983 [GM82, GM84].

Nevertheless, the PSOS project has a long-lasting and far-reaching impact.The experiences made with HDM have led to the development of SRI’s PrototypeVerification System (PVS) [OSR92]. Furthermore, the hierarchical approach tosystem modeling and proving was inspiring to the famous CLI stack project[Moo89, BHMY89] (cf. Sect. 2.1.1).

The PSOS methodology was used in the Ford Aerospace Kernelized SecureOperating System (KSOS) [BBJ79, MD79, PCH84].

UCLA Secure Unix The UCLA1 Secure Unix had been developed as anoperating system for the DEC PDP-11/45 computer [WKP80]. Though theterm ‘microkernel’ had not been invented at this point in time, this was alreadyvery close to the concepts of modern microkernels, and thus an early attempt inseparating operating system from kernel functionality. The verification effortswere concentrating on the kernel component.

The security proof for the kernel was split into two part. In a first step, afour-level specification, ranging from Pascal code at the bottom to the top-levelsecurity criterion, had to be developed. Then, the verification of the system wascompleted by proving that these different levels of abstractions were consistentwith each other, more precise: that the state machines simulate each other.

The upper three levels were formulated by means of finite state machines.The top-level security criterion was supposed to capture the common notion ofdata security based on capabilities (cf. Sect. 2.1.1): state components are onlyallowed to be modified or referenced if the current process allows for it.

The authors of [WKP80] state, over ninety percent of the UCLA kernelhas been specified—though they think that ‘the task is still too difficult andexpensive for general use’. Yet, the specification work has uncovered alreadysignificant security bugs, which had slipped conventional testing.

An implementation of the kernel in a subset of Pascal has been completed,of which less than twenty percent have been verified. The authors write that‘even accomplishing even this much was quite painful’ and recommend to waitfor more effective machine aids in order to complete the task. The choice wentfor Pascal, since it was both—relatively—suitable for low-level system softwareimplementation and it had a clear formal semantics [HW73].

Furthermore, the authors state that ‘the recommended approach to pro-gram verification—developing the proof before or during software design anddevelopment—is often not practical’ [WKP80, Sect. 4].

Standard Pascal had been extended by the ability to locate variables atspecific memory regions, so that hardware register manipulation could berealized without in-line assembly parts. Code fragments which were referencingor manipulating hardware registers where isolated and put in separate functions.Obviously, the Pascal semantics as described in [HW73] do not capture theireffects. It seems that the pre- and postconditions of these functions have beenadded manually in an axiomatic way.

An interesting observation by the authors is the negligence of hardwarefiniteness, in particular when dealing with arithmetic and the corresponding

1University of California, Los Angeles

Page 31: Verified Linking for Modular Kernel Verification - Institut f¼r

2.1. Operating System Verification 15

translation from high-level languages to lower levels. In Verisoft, the sameinsight led to the introduction of so-called guards in high-level code verification,triggering in the case of an arithmetic overflow.

The overall system performance was pretty poor—an order of magnitudeslower than the standard Bell Unix of those days in some cases. This is due tothe use of a nearly non-optimizing compiler (the people in the project wantedto be able to at least manually check the generated code) and a bad overheadin process switching.

KIT Probably the first kernel deserving to be called formally verified, isKIT—Kernel for Isolated Tasks—from the end of the 1980s [Bev87, Bev88,Bev89a, Bev89b].

Besides process isolation—as implied by the name—KIT offered servicesfor asynchronous I/O device access, single-word message passing and exceptionhandling. There was no support for shared memory or virtual memory in themodern sense, nor did KIT offer dynamic process creation.

KIT has been implemented in an artificial, yet realistic assembly language.The code size is with 620 lines, out of which are 300 lines of actual instructions,is very small. No surprise that the kernel is rather primitive compared tomodern microkernels.

The verification has been conducted in the Boyer-Moore theorem proverNqthm [BKM95], which was the precursor to the well-known ACL2 prover[Moo00]. Similar to UCLA Secure Linux (cf. Sect. 2.1.1), the correctness prooffor KIT relies on the correspondence between finite state machines.

The correspondence proof shows that the kernel implements this abstractioncorrectly, that is no implementation bugs have been introduced. The underlyinghardware is assumed to be fault-free.

The top-level specification seems to be strong enough to guarantee processisolation, a property which is described as ‘that a task’s private state can changeonly when it is active’ [Bev87, p.4].

2.1.2 Recent Work

The decade after KIT was very quiet regarding projects in operating systemverification. Since the dawn of the new millennium, it seems that the topic hasbeen revived.

VFiasco The VFiasco project, short for Verified Fiasco, started in 2001.Fiasco [HH01] is a C++ re-implementation of the high-performance microkernelL4 [Lie95]. The Fiasco kernel is built with the intention to be used in real-time systems, so almost all of its code is pre-emptible, that is interruptibleguaranteeing for short reaction times to external interrupts.

The idea of the project is the direct translation of the programming languageC++ into its semantics in the theorem prover PVS [OSR92], thus avoiding theformalization of C++ syntax there.

VFiasco has not seen any publications since 2005, the last one [HT05] sum-marizing on the C++ verification approach, which has though been continuedin the Robin project on the Nova Hypervisor [Tew07].

Yet, both projects lack any successes in verifying representative portions ofthe implementation.

Page 32: Verified Linking for Modular Kernel Verification - Institut f¼r

16 Related Work

EROS/Coyotos Coyotos [SDNM04] is a secure, microkernel-based operatingsystem that builds on the ideas and experiences of the EROS project [SSF99].EROS (Extremely Reliable Operating System) is a capability-based secondgeneration microkernel succeeding KeyKOS [Har85].

In [SW00], a paper-and-pencil formalization with a proof for the securitymodel have been presented. Yet, this security model was not related to theactual EROS implementation.

This was part of the successor kernel Coyotos. For this purpose, a newimplementation language called BitC [SS06] has been developed, which isboth powerful enough to implement low-level system software and also easesverification efforts. This idea is very appealing, since all common implementationlanguages for kernel (C, C++, assembly) have significant flaws and insecurefeatures.

It seems that the Coyotos project makes rapid progress on the design andimplementation of the kernel. Until now, there has been no publications onverification efforts or on a framework for reasoning on BitC programs.

L4.verified/seL4 The goal of the seL4 (secure embedded L4) project wasthe enhancement of Liedtke’s L4 microkernel conception [Lie95] with attentionto security-relevant embedded systems [Elp04].

According to [EKD+07, KEH+09], the project has been successfully con-cluded by the end of 2007. The result is a C and assembly implementation(8,700 LOC C, 600 LOC assembly) running on the ARM11 hardware platform,offering threads, IPC, memory virtualization and interrupt handling and is ofcommercially competitive performance.

The design approach of seL4 is very appealing, since it combines aspects ofboth OS design and formal verification. The OS design group was using theprogramming language Haskell [Jon03] for rapid prototyping and—enhancedwith a hardware simulated—testing with user applications.

Since Haskell is very close to Isabelle/HOL, it is automatically translatableinto the theorem prover and hence available as a low-level formal design forfurther verification efforts.

Strongly related to seL4 is the L4.verified project, in which a model stackabove the concrete implementation has been developed. The topmost layer isan access control model for seL4 [EKE08], which is refined to an operationalmodel of user-visible kernel operations. The two lower layers are made up bythe Haskell low-level formalization and the actual implementation.

Verification in L4.verified aims at functional correctness, in the sense that‘the implementation always strictly follows our high-level abstract specificationof kernel behavior’. The refinement proof between the bottom two layers isconducted within Verisoft’s Hoare-style verification framework [Sch06].

As mentioned above, hardware, compiler, and assembly code belong to thetrusted base. [KEH+09] states that the verification efforts have been completed.

2.2 Other Related Projects

There are a number of projects, which do not explicitly deal with operatingsystem verification, but are of interest due to their scientific contributions tothe topic.

Page 33: Verified Linking for Modular Kernel Verification - Institut f¼r

2.2. Other Related Projects 17

FLINT The FLINT project has a special interest in developing the infrastruc-ture needed to construct large-scale certified system software. In particular, theenhancement of theorem provers to be more suitable for building large proofsfor system level software is in the focus.

In [FSDG08, FSGD09], the authors present a novel Hoare-logic-like frame-work for certifying low-level system programs involving both hardware interruptsand preemptive threads.

In [NYS07], a verification framework named XCAP is introduced and exem-plarily applied to prove the correctness of context switching code on the Intelx86 architecture.

AAMP7 The AAMP7 microprocessor of Rockwell Collins, Inc., basicallyimplements the properties of a separation kernel in hardware. In order toobtain Common Criteria EAL7 certification [The99] for it, various efforts havebeen undertaken [HSY06]. The separation kernel model used is based on[MWE03, MWE05], the proof work itself is done in ACL2 [Moo00]. A low-levelmodel is closely related to the actual implementation (processor microcode),but there is no formal correspondence proof, though the work done surely goesbeyond the requirements of an EAL7 evaluation.

Specification of an Operating System in Focus In [Spi98], a high-levelabstract model for a generic operating system has been formalized in Focus[BDD+92]. The model covers several main aspects of an operating system,such as processor management, memory management, process cooperation, andmanagement.

Specification of the Mach Kernel The term microkernel is reported firstto be used in the context of the Mach kernel [RBF+89] developed at Carnegie-Mellon in the years from 1985 to 1994.

In [BS93b, BS93a], a formal specification of the kernel is given, though tothe best of our knowledge, no implementation proofs had been conducted.

Linking Unfortunately, there is not much related work on the formalization oflinking (cf. Sect. 6.5.2), in particular not on the level of programming languagessemantics.

As Cardelli says in [Car97]: ‘We suggest that linking and separate compilationshould be seriously taken into account when designing a language and modulesystem. This sentence may seem a truism, but these issues have been surprisinglyunder-emphasized in the technical literature.’

This statement is more than ten years old, but the community still neglectsthis topic carelessly.

Some further related work on compilation management for Standard MLcode fragments can be found in [HPLR94]. Recompilation, which is stronglyconnected to linking and dependency analysis, and its optimization is addressedin [Tic86] and [SA93].

Page 34: Verified Linking for Modular Kernel Verification - Institut f¼r

18 Related Work

2.3 Verisoft/Verisoft XT Context

The development of CVM has been application-driven. In the Verisoft project,two microkernels have been developed, which make use of the low-level CVMimplementation.

In the successor project, Verisoft XT, work on operating system verificationhas been continued. Here, two commercially available products are subject toverification: Microsoft’s virtualization platform Hyper-V [Hyp09] and SYSGO’smicrokernel PikeOS [Pik09].

VAMOS VAMOS (Verified Architecture Microkernel Operating System) is amicrokernel implementation, which is used in Verisoft’s academic system (seeSect. 1.1).

VAMOS provides a process scheduler, an infrastructure for communicationwith hardware devices, and message passing between processes. All softwarelayers are formally specified; refinement relations correlate the adjacent layerssuch that eventually all specification layers can be mapped down to our hardware-processor model. The whole model stack is formalized in the theorem proverIsabelle/HOL.

For considerable parts of the kernel, correctness proofs have been conducted,for instance, fairness and implementation correctness of the scheduler have beenshown [DDW09].

Currently, implementation correctness for the inter-process communicationhas come close to an end. We expect the results to be published in 2010.

OLOS OLOS (OSEKtime-like Operating System) has been implemented asa time-triggered microkernel for the Verisoft automotive project. Considerableparts of OLOS—including the communication layer and bus controller device—have been formally verified in Isabelle/HOL [ABK08, Kna08, BBG+05, IK05],with a special attention to the real-time properties of the system [KP07b,KP07a].

Basing on OLOS, a distributed automotive system has been exemplarily setup, where the single nodes where interconnected via a real-time bus [KS06]. Anapplication, developed together with BMW, has been implemented and formallyverified [BGH+06, BKKS05].

2.3.1 Hyper-V

Hyper-V [Hyp09] is a virtualization platform, which allows to run multipleoperating systems at the same time on Intel’s and AMD’s latest x64 hardware,by providing a transparent interface to the underlying hardware. It is part ofthe Windows Server 2008 distribution. Hyper-V comprises about 100KLOC ofC code and another 5KLOC of assembly code.

Besides the sheer complexity of highly optimized, industrial performancecode, Hyper-V itself runs multithreaded in a concurrent setting and has notbeen implemented with formal verification in mind.

This makes conventional (sequential) code verification not applicable. Anew tool, VCC, had to be developed [CDH+09] along with the correspondingmethodology [CMST09, MMS08, BLW08]. VCC allows to write verificationconditions directly into the program code (of course in a structured way, so that

Page 35: Verified Linking for Modular Kernel Verification - Institut f¼r

2.3. Verisoft/Verisoft XT Context 19

they can easily be removed before compiling), hence assuring the consistency ofspecification and implementation by design.

Due to the secrecy necessary when dealing with commercial products, onlya few publications are available to date dealing with details of the actualverification [LS09].

2.3.2 PikeOS

PikeOS is a microkernel for x86, PowerPC, and ARM hardware platformsto develop embedded systems in which multiple guest operating systems andapplications can run simultaneously in a secure environment. In particular,PikeOS realizes partitioning, such that several operating systems can runisolated on the same processor.

As in the Hyper-V project, verification is done directly on code level usingthe VCC tool. The PikeOS team reports on the on-going work in [BBBB09b,BB09, BBBB09a]

Page 36: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 37: Verified Linking for Modular Kernel Verification - Institut f¼r

Part I

Computational Models

21

Page 38: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 39: Verified Linking for Modular Kernel Verification - Institut f¼r

Software is like entropy. It is difficult to grasp,weighs nothing, and obeys the second law ofthermodynamics; i.e., it always increases.

Norman R. Augustine

Chapter 3

The C-like Programming Language C0

Contents3.1 An Informal View on C0 . . . . . . . . . . . . . . . . . . 23

3.2 C0 Programs . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.3 C0 Small-Step Configurations . . . . . . . . . . . . . . . 32

3.4 Expression Evaluation . . . . . . . . . . . . . . . . . . . . 37

3.5 Execution of C0 Programs . . . . . . . . . . . . . . . . . 45

In this chapter, we are going to introduce the C-like programming languageC0. C0 has been developed in the first Verisoft project and serves as the pro-gramming language for most of the system level software within the project. Asimple, non-optimizing compiler from C0 to VAMP assembly code (cf. Sect. 5.3)has been developed and completely formally verified. These proofs are coveredin detail in [Lei08] and [Pet07].

For the rest of this chapter, we will proceed as follows. In Sect. 3.1, we willintroduce the concrete C0 syntax.

In Sect. 3.2, we describe formally the representation of programs for the C0semantics. C0 configurations keep track of the run-time information during theexecution of such a program . We define these configurations formally in Sect.3.3.

In Sect. 3.4, we treat the evaluation of C0 expressions . Last but notleast, we complete C0 program execution with the formal definition of the C0transition function in Sect. refc0semantics::sect::delta.

The semantics of C0 will be used later on in Chapter 6 to model the behaviorof CVM’s abstract kernel .

3.1 An Informal View on C0

C has been developed in the early seventies of the last century. It has beendeveloped for the implementation of system level software, in particular forkernel implementation.

Throughout the years, several standards for C have been developed andpublished, the latest dating back to 1999 [ISO99]. Based on considerationsregarding formal verification, certain design limitations compared to these

23

Page 40: Verified Linking for Modular Kernel Verification - Institut f¼r

24 The C-like Programming Language C0

standards have been applied to C0. For instance, we do not allow side effectsduring expression evaluation; this includes function calls as part of expressions,making the introduction of a particular C0 function call statement necessary(see Sect. 3.2.3).

C0 features a dedicated type for arrays . Their size has to be fixed atcompile time.

We restrict the use of pointers, which are typed in C0. Pointers must notpoint to functions or local variables . There are no void pointers and pointerarithmetic is forbidden.

We do not allow explicit memory deallocation, like the C free statement.Instead, we are planning to use a garbage collector that frees unreachable heapportions periodically. This garbage collector has already been code verified, butyet not been integrated into the C0 semantics.

Despite all these restrictions, the various efforts in Verisoft have producedevidence for the usability of C0 in system programming. For instance, two oper-ating systems, VAMOS [AHL+09] and OLOS [IK05] have been implemented inC0. In Verisoft’s academic sub-project, a whole system stack including protocol,driver and application implementations has been developed and formally verifiedin large parts.

3.1.1 Types

In C0, we support four basic types:

• Boolean: bool with values in {True,False}

• 32-bit signed integers: int, with values in {−231, . . . , 231 − 1}

• 32-bit unsigned integers: unsigned int, with values in {0, . . . , 232 − 1}

• 8-bit signed integers: char, with values in {−27, . . . , 27 − 1}

We allow arithmetic operations only on 32-bit integers and unsigned integers.In addition, we define so-called aggregate types:

• For a type t, ∗t denotes a (typed) pointer.

• For a type t and n ∈ N, t[n] denotes an array of (fixed) size n of type t.

• For types t0, . . . , ti−1 and names σ0, . . . , σi−1, struct{σ0 : t0, . . . , σi−1 :ti−1} denotes a structure with i components.

Note that we do not support unions or bitfields.

3.1.2 Expressions

Variable names and literals are expressions. For an expression e, (e) is anexpression.

If both e and i are expressions, than the array access e[i] is also an expression.Let σ ∈ Σ+ be a name, and e an expression; then e.σ, that is access of a structurecomponent, is an expression.

Pointer dereferencing ∗e and application of the ‘address-of’ operator &e areexpressions, too.

Page 41: Verified Linking for Modular Kernel Verification - Institut f¼r

3.1. An Informal View on C0 25

Table 3.1: Unary C0 Operators

Operator Meaning Supported Operand Types Result Type

~ bit-wise nega-tion

t ∈ {int , unsigned int} t′ = t

! logical negation t ∈ {bool} t′ = t- unary minus t ∈ {int} t′ = t

int cast to int t ∈ {char , unsigned int , int} t′ = intunsigned cast to

unsigned intt ∈ {char , unsigned int , int} t′ =

unsigned intchar cast to char t ∈ {char , unsigned int , int} t′ = char

t is the type of the operand, t′ is the type of the result

For an expression e and a unary operator ⊕1 from table 3.1, ⊕1e is anexpression. For expressions e1, e2 and a binary operator ⊕2 from Table 3.2,e1 ⊕2 e2 is an expression.

Though pointer arithmetic is prohibited, we allow pointers to sub variablesof aggregate variables.

3.1.3 Statements

Let f denote a function name, e and ei functions, t a type, s and si statements.Then, C0 offers support for the following statements:

• Assignments: e1 = e2. Unlike standard C, C0 allows to assign arrayvalues.

• Dynamic heap memory allocation: e1 = new(t). As mentioned before,there is no support for manual deallocation.

• Function calls: e = f(e0, . . . , en−1). The dedicated function call statementmeets the constraints regarding side effects in expression evaluation.

• Return from function call: return e

• While loops: while e s

• For loops: for (e0 = e1; e2; e3 = e4) s

• If statements: if e s0 or if e s0 else s1

Sequences of statements—separated by semicolons—are allowed. In contrast toC, C0 does not provide support for goto, switch or long jump statements.

Complex Literals

Complex literals comprise literals of aggregate types. They can be used inatomic assignments to non-elementary variables:

• For complex literals ci of type t, {c0, c1, . . . , cn−1} denotes a complexliteral of the array type t[n].

Page 42: Verified Linking for Modular Kernel Verification - Institut f¼r

26 The C-like Programming Language C0

Table 3.2: Binary C0 Operators

Operator Meaning Supported Operand Types Result Type

+ addition t1 = t2 ∈ {int , unsigned int} t′ = t1- subtraction t1 = t2 ∈ {int , unsigned int} t′ = t1* multiplication t1 = t2 ∈ {int , unsigned int} t′ = t1/ division t1 = t2 ∈ {unsigned int} t′ = t1% modulo t1 = t2 ∈ {unsigned int} t′ = t1| bit-wise or t1 = t2 ∈ {int , unsigned int} t′ = t1& bit-wise and t1 = t2 ∈ {int , unsigned int} t′ = t1^ bit-wise exclu-

sive ort1 = t2 ∈ {int , unsigned int} t′ = t1

<< logical leftshift

t1 ∈ {int , unsigned int},t2 ∈ {char , int , unsigned int}

t′ = t1

>> logical rightshift

t1 ∈ {int , unsigned int},t2 ∈ {char , int , unsigned int}

t′ = t1

< less t1 = t2 ∈{char , int , unsigned int}

t′ = bool

<= less or equal t1 = t2 ∈{char , int , unsigned int}

t′ = bool

> greater t1 = t2 ∈{char , int , unsigned int}

t′ = bool

>= greater orequal

t1 = t2 ∈{char , int , unsigned int}

t′ = bool

== equal ceq t′ = bool!= not equal ceq t′ = bool

t1 and t2 are the types of the operands, t′ is the type of the result, the typecondition ceq for equality tests is fulfilled if both types are equal or if one of

them is a pointer and the other the special null pointer type

Table 3.3: Lazy C0 Operators

Operator Meaning Supported Operand Types Result Type

&& logical and t1, t2 ∈ {bool} bool|| logical or t1, t2 ∈ {bool} bool

t1 and t2 are the types of the operands, t′ is the type of the result

Page 43: Verified Linking for Modular Kernel Verification - Institut f¼r

3.2. C0 Programs 27

• For complex literals ci of types ti and component names σi, {.σ0 =c0, .σ1 = c1, . . . , .σn−1 = cn−1} is a complex literal of the structure typestruct{σ0 : t0, σ1 : t1, . . . , σn−1 : tn−1}.

For an expression e and a complex literal c, the assignment e = c is also a C0statement. Note, that complex literals are not allowed within expressions.

In-line Assembly

Low-level system software often addresses parts of the underlying hardware—for instance processor registers— that are not visible in higher programminglanguages. For this purpose we have to extend our C0 language with a statement,which allows to embed assembly code into C0 code.

Let u be a list of assembler instructions; then asm{u} is a C0 statement.The concrete kernel introduced in Sect. 6.5.1 uses in-line assembly code in

its hardware-dependent low-level parts (see [Tsy09]). In [Alk09], a low-leveldevice driver makes use of in-line assembly to address a hard disk device.

3.2 C0 Programs

In this section, we will introduce formally the notion of C0 programs . Such aprogram is given through a type name environment—a mapping of type namesto types—, its functions, and information about its global variables.

3.2.1 Types and Type Name Environment

C0 offers four basic types: BoolT for Booleans, IntT for signed 32-bit inte-gers, UnsgndT for 32-bit unsigned integers, and CharT for 8-bit signed inte-gers. A structure type with n components of types t0, t1, . . . , tn−1 and namesσ0, σ1, . . . , σn−1 is given by StrT ([(σ0, t0), (σ1, t1), . . . , (σn−1, tn−1)]). An arraytype with n elements of type t is defined by ArrT (n, t).

C0 supports fully recursive data types. This makes modeling by abstractdata structures a bit harder, since we have to introduce an additional level ofindirection: pointers do not directly give the type, to which they map, but atype name. Type names are mapped to names via the type name environment.Thus, a pointer to a type name tn is given by PtrT (tn). The type NullT hasonly one element, namely the null pointer literal.

We can now define types formally by the abstract data type below:

ty = BoolT | IntT | UnsgndT | CharT| StrT ((Σ+ × ty) list)| ArrT (N, ty)| PtrT (Σ+) | NullT

Type name environments are defined by tenv : (Σ+ × ty) list.

Definition 3.1 (Elementary Types) A type t is called elementary iff it isnot a structure type or an array type:

?elemT (t) = t ∈ {BoolT , IntT ,UnsgndT ,CharT ,PtrT ,NullT}

Page 44: Verified Linking for Modular Kernel Verification - Institut f¼r

28 The C-like Programming Language C0

Definition 3.2 (Abstract Size of Types) The abstract size of a type t isdefined inductively:

sizeT (t) =

1 if ?elemT (t)n · sizeT (t′) if t = ArrT (n, t′)0 if t = StrT ([])sizeT (t) + sizeT (StrT (clist)) if t = StrT ((σ, t)#clist)

3.2.2 Expressions and Complex Literals

The building blocks of C0 expressions are literals . We define a correspondingabstract data type lit providing a constructor for each basic C0 type plus aliteral for null pointers:

lit = Boolean(B) | Int(Z) | Unsgnd(N) | Char(Z) | NullPointer

Expressions are defined inductively with two base cases:

• literal expressions Lit(l) with a literal l ∈ lit and

• variable accesses Var(σ) to global or local variables with name σ ∈ Σ+.

For expressions e, e1, e2, i, a name σ, and an operator op, the inductive casesof expressions are:

• array access Arr(e, i),

• structure access Str(e, σ),

• addr-of computation AddrOf (e),

• pointer dereferencing Deref (e),

• arithmetic and logical computations UnOp(op, e), BinOp(op, e1, e2), andLazyBinOp(op, e1, e2).

The abstract data type, which we use for modeling C0 expressions in Isabelle,is defined as follows:

expr = Lit(lit)| Var(Σ+)| Arr(expr , expr)| Str(expr ,Σ+)| UnOp(unop, expr)| BinOp(binop, expr , expr)| LazyBinOp(lazybinop, expr , expr)| AddrOf (expr)| Deref (expr)

The list of supported operators is given in Tables 3.4, 3.5, 3.6, and 3.7.

Page 45: Verified Linking for Modular Kernel Verification - Institut f¼r

3.2. C0 Programs 29

Table 3.4: Unary Operators

Operator Meaning

minus unary minusneg bitwise negationnot logical not

toint conversion to Inttounsgnd conversion to Unsgndtochar conversion to Char

Table 3.5: Binary Operators

Operator Meaning

add additionsub subtraction

mult multiplicationdiv divisionmod moduloorb bitwise or

andb bitwise andxorb bitwise exclusive orshift l arithmetic left shiftshift r arithmetic right shift

Table 3.6: Comparison Operators

Operator Meaning

compless lesscomple less or equal

compgreater greatercompge greater or equalcompeq equalcompneq not equal

Table 3.7: Lazy Binary Operators

Operator Meaning

and l logical andor l logical or

Page 46: Verified Linking for Modular Kernel Verification - Institut f¼r

30 The C-like Programming Language C0

Complex Literals

Complex literals are literals for aggregate types, i.e., structures and arrays:

litc = clPrim(lit)| clArr(litc list)| clStr((Σ+ × litc) list)

3.2.3 Statements

stmt = Skip| Comp(stmt , stmt)| Ass(expr , expr , sid)| Assc(expr , litc, sid)| PAlloc(expr ,Σ+, sid)| SCall(Σ+,Σ+, expr list , sid)| Return(expr , sid)| Ifte(expr , stmt , stmt , sid)| Loop(expr , stmt , sid)| Asm(asm list , sid)| XCall(Σ+, expr list , expr list , sid)| ESCall(Σ+,Σ+, expr list , sid)

All statements but Skip and Comp are uniquely tagged with an identifiersid ∈ N. These identifiers are mainly needed for the compiler correctness proofin [Lei08], thus we omit them most of time due to readability.

Skip is the empty statement. We can combine two statements s1 and s2

with the compound statement Comp(s1, s2).In Isabelle, we actually have only one assignment statement; to achieve a

better readability, we use two versions of assignments: one for complex literalsAssc(eleft , lc) and one for expressions Ass(eleft , eright). We allocate new heapobjects of a type with name tn with the allocation statement PAlloc(eleft , tn);the newly created pointer will be assigned to the left-hand expression eleft .

Calling a function with name fn is done via the SCall(fn, vn, [e0, . . . , en−1])statement, with [e0, . . . , en−1] defining a—possibly empty—list of parametersand vn being the variable name, where the return value will be stored. Wereturn from a function call with the Return(e) statement, e being the returnexpression.

With the statement Ifte(e, sthen, selse), we realize conditional program exe-cution. If e evaluates to True, execution continues with sthen, otherwise withselse. Loop statements are modeled by Loop(e, slbody), where slbody is executedas long as e evaluates to True. Note that there is no special statement for forloops, since they can be syntactically transformed into semantically equivalentwhile loops during pre-processing.

The Asm(u) statement allows to embed assembler code in C0 code, where uis a list of VAMP assembler instructions (cf. Chapter 5). Note that the execution

Page 47: Verified Linking for Modular Kernel Verification - Institut f¼r

3.2. C0 Programs 31

Comp

Ifte Loop

AssLoopComp

SCallAssPAlloc

Figure 3.1: Statement Tree

of u is defined by the VAMP assembler semantics, i.e., it is not covered by theC0 semantics.

Some C0 programs are not ’complete’, e.g., the abstract kernel introduced inSect. 6.5.1. These programs declare functions, but don’t define them, i.e., don’tspecify a function body . We call these functions external and model calls tothem with the ESCall(fn, vn, [e0, . . . , en−1]). In the process of abstract linking,these external function calls will be replaced as the corresponding function

implementations are added (cf. Sect. 6.5.2).To make the effects of assembler execution visible at higher levels, we define

the XCall(fn, [e0, . . . , en−1], [p0, . . . , pm−1]) statement : pi are parameters tothe XCall, ei are the return values of it. In [AHL+09], XCalls are used in anintegrated correctness proof for a Verisoft system stack.

Definition 3.3 (Top-level Statement Flattening) A statement tree (seeFig. 3.1 spanned by Comp statements can be flattened into a list of top-levelstatements by the function s2l : stmt → stmt . We define:

s2l(s) =

{s2l(s1)@s2l(s2) if s = Comp(s1, s2)s otherwise

Correspondingly, we define s2lnoskip : stmt → stmt , which additionallyremoves all Skip statements:

s2lnoskip =

s2lnoskip(s1)@s2lnoskip(s2) if s = Comp(s1, s2)[ ] if s = Skips otherwise

Note, that the flattening only affects the top-level statements, that is innerstatements—like loop bodies—stay untouched (cf. Fig. 3.2).

Page 48: Verified Linking for Modular Kernel Verification - Institut f¼r

32 The C-like Programming Language C0

Ifte Loop

PAlloc Ass Loop

SCall

Ass

Figure 3.2: Flattened Statement Tree

3.2.4 Procedure Table

The procedure table of a C0 program contains the information of all of theprogram’s functions. Each function is defined by one procedure descriptor,which is defined as a record procT of four components:

• p.body ∈ stmt , the function body,

• p.params ∈ (Σ+ × ty) list , the parameters of the function given by pairsof names and types ,

• p.rtype ∈ ty, the return type, and

• p.lvars ∈ (Σ+ × ty) list , the local variables given by pairs of names andtypes.

Note, that for external functions the component p.body just contains a singleSkip statement.

Formally, the procedure table is a list of pairs of function names and theircorresponding descriptors and defined by the type proctableT : (Σ+×procT ) list .

3.3 C0 Small-Step Configurations

C0 small-step configurations keep track of the run-time information during theexecution of a C0 program. They consist of two components:x

1. the memory configuration stores information about the global, local, andheap variables and their values;

2. the program rest, which holds the statements that haven’t been executedyet.

3.3.1 Memory Configurations

Memory configurations store information about variables and the correspondingvalues. They are organized in memory frames: one frame for each the globalvariables and heap variables, and a list of frames and return destinations forthe local variables .

Page 49: Verified Linking for Modular Kernel Verification - Institut f¼r

3.3. C0 Small-Step Configurations 33

0 1 2 3 4 5 6 7

a b c x y

next prev d1 d2

ArrT (8,

StrT ([. . . ,d1 : IntT , . . .]))

StrT ([. . . ,d1 : IntT , . . .])

IntTgvarstr(gvararr

(gvargm(x), 4), d1)

gvararr

(gvargm(x), 4)

gvargm(x)

global symbol table

Figure 3.3: g-Variable Hierarchy

Generalized Variables

Small-step semantics defines variables structurally as so-called generalized vari-ables or g-variables. g-variables are defined inductively, with three base cases(global, local, and heap variables) and two inductive cases (structure and arrayaccess).

For vn ∈ Σ+, i, j ∈ N, gvargm(vn) denotes the global variable with namevn, gvarlm(i, vn) the local variable with name vn in the local memory framei, and gvarhm(j) the heap variable with index j. Note that global and localvariables are referenced by their name—and thus called named variables—whileheap variables are nameless and referenced by their index only.

For a name cn ∈ Σ+ and a g-variable g of structure type, the componentgvarstr (g, cn) is a g-variable, too. This is also true for the array elementgvararr (g, i) of a g-variable g of array type at index i ∈ N.

The following abstract data type defines g-variables formally:

gvar = gvargm(Σ+)| gvarlm(N,Σ+)| gvarhm(N)| gvararr (gvar ,N)| gvarstr (gvar ,Σ+)

Obviously, this definition leads to a hierarchical, tree-like structure of g-variables (see Fig. 3.3). We will now define several functions that allow tohandle this structure.

Definition 3.4 (Sub g-Variables) All g-variables in a sub-tree with a rootg—including the root itself—are called sub g-variables of g. We define the set

Page 50: Verified Linking for Modular Kernel Verification - Institut f¼r

34 The C-like Programming Language C0

of sub g-variables for a given g inductively:

g ∈ subg(g)Base Case

h ∈ subg(g)gvararr (h, i) ∈ subg(g)

h ∈ subg(g)gvarstr (h, cn) ∈ subg(g)

Inductive Cases

Definition 3.5 (Parent g-Variable) Correspondingly, we define the func-tion idparentg : gvar → gvar⊥ to walk up in the hierarchy of g-variables:

parentg(g) =

h if g = gvararr (h, i)h if g = gvarstr (h, cn)None otherwise

Definition 3.6 (Root g-Variable) Obviously, application of parentg to a g-variable g several times would lead us to the first ancestor—the root of thetree— of g. We call this variable the root g-variable of g and define the functionrootg : gvar → gvar that calculates it formally as follows:

rootg(g) =

rootg(h) if g = gvararr (h, i)rootg(h) if g = gvarstr (h, cn)g otherwise

Memory Frames

The C0 small-step semantics uses a flat and rather explicit memory model,similar to the one in [Nor98], defined by a mapping of of addresses—naturalnumbers—to memory cells. In order to store the value of a variable of type t, weneed sizeT (t) many memory cells. This means that the value of an elementarytype variable uses exactly one memory cell. Aggregate type values are stored inconsecutive sequences of memory cells.

Memory cells are formalized by an abstract data type, where we define aconstructor for each of the five elementary types:

mcellT = Int(Z) | Unsgnd(N) | Char(Z) | Bool(B) | Ptr(gvar ∪ {NullPointer})

Note that pointers are represented by a g-variable or the special null pointervalue NullPointer .

In addition to the content of a memory frame, we need to know whichvariables have already been initialized. We store this information in a list ofvariable names.

Last but not least, each memory frame has a list of its variables—given bytheir names—along with their types. Such lists are called symbol tables.

So, each memory frame is specified by a record mf of type mframeT withthe above three components:

Page 51: Verified Linking for Modular Kernel Verification - Institut f¼r

3.3. C0 Small-Step Configurations 35

• mf .cont : N→ mcellT , the content of the memory frame ,

• mf .init : Σ+ list , the list of initialized variables, and

• mf .st : (Σ× ty)list , the symbol table of the frame.

Definition 3.7 (Base Addresses of Variables) The base address of a vari-able is defined recursively over its position in the symbol table. If the variableis not present in the symbol table or if the symbol table is empty, the baseaddress is None. In all other cases, we compute recursively: if the variable isthe first entry in the symbol table, then the base address is zero. Otherwise, weset its base address to the base address in the tail of the list plus the abstractsize of the type at the first entry.

We define bav : (Σ+ × ty) list × Σ+ → N⊥ and set:

bav([], σ) = None

bav((vn, t)#xs, σ) =

{b0c if σ = vn

sizeT (t)⊕ bav(xs, σ) otherwise

with

i⊕ j =

{the(i) + the(j) if i 6= None and j 6= NoneNone otherwise

Definition 3.8 (Type of Variables) The type of a variable in a symbol tableis defined by the second component of the corresponding pair in that symboltable. Formally, we define typev : (Σ+ × ty) list × Σ+ → ty⊥ with

typev(st, x) = mapof (st, x)

Memory Configuration

As described in the introduction above, a memory configuration consists ofmemory frames. We define a configuration m formally by a record memconfTwith components:

• m.gm ∈ mframeT , the global variables’ memory frame.

• m.lm : (mframeT × gvar) list, the stack of local memories. Each memoryframe stands for one stack frame. The second component, a g-variable,defines, where the return value of the function associated with that framewill be stored (return destination).

• m.hm ∈ mframeT , the heap variables’ memory frame. Since heap vari-ables are nameless, the names in the symbol table m.hm.st are not used.Heap variables are initialized by definition, thus the field m.hm.init isignored.

Whenever we extend the stack, we put a new memory frame at the end ofthe existing list m.lm. This means, the current stack frame is the last stackframe in the list, which we abbreviate by toplm(m) = m.lm!(| lm | −1). Thus,we can keep the index of stack frames and the index of local variables (see Sect.3.1) invariant during program execution.

Page 52: Verified Linking for Modular Kernel Verification - Institut f¼r

36 The C-like Programming Language C0

Symbol Configuration

We introduce some auxiliary functions to refer to the symbol tables of thedifferent memory frames in a readable way:

gst : memconfT → (Σ+ × ty) listtoplst : memconfT → (Σ+ × ty) list

hst : memconfT → (Σ× ty) list

gst(m) = m.gm.st

toplst(m) = toplm(m).sthst(m) = m.hm.st

Often, we are just interested in the symbol tables of a certain memoryconfiguration, but not its content. To improve the formalism in these sections,we define a new record type named symbolconfT , which will store a symbolconfiguration, consisting of the three symbol tables . For sc ∈ symbolconfT wedefine:

• sc.gst : (Σ+ × ty) list , the symbol table of the global memory frame,

• sc.lst : (Σ+×ty) list list , a list of symbol tables of all local memory frames,and

• sc.hst : (Σ× ty) list , the heap memory frame symbol table.

For a given memory configuration m, we write short sc(m) to refer to itssymbol configuration.

3.3.2 Program Rest

The program rest stores those statements that haven’t been executed yet. Oninitialization, we start with the body of the main function of the C0 program.Afterward, the program rest grows or shrinks as defined through the executionof the program. Formally, a program rest pr is just a common C0 statement:pr ∈ stmt .

3.3.3 C0 Configuration

We can now give a complete formal definition of a configuration c as used inC0 small-step semantics. Therefore, we introduce a new record type confTC0 ,which consists of two components

• c.m ∈ memconfT , the memory configuration, and

• c.pr ∈ stmt , the program rest.

Page 53: Verified Linking for Modular Kernel Verification - Institut f¼r

3.4. Expression Evaluation 37

3.4 Expression Evaluation

3.4.1 Address of g-Variables

Definition 3.9 (Type of g-Variables) Similar to Def. 3.8, we define thetype of g-variables typeg : symbolconfT × gvar → ty. For σ ∈ Σ+ and i, j ∈ Nwe set the base cases:

typeg(sc, gvargm(σ)) = typev(sc.gst, σ)typeg(sc, gvarlm(i, σ)) = typev(sc.lst!i, σ)

typeg(sc, gvarhm(j)) = typev(sc.hst, j)

The inductive cases, i.e., array and structure accesses, are defined partiallyas follows:

typeg(sc, gvararr (g, i)) =

{t if typeg(g) = ArrT (t, j)undef otherwise

typeg(sc, gvarstr (g, x)) =

{the(mapof (c, x)) if typeg(g) = StrT (c)undef otherwise

Definition 3.10 (Named and Nameless g-Variables) To determine, if avariable is in global, local, or heap memory, we introduce the function memg :gvar → memname:

memg(gvargm(σ)) = gm

memg(gvarlm(i, σ)) = lm(i)memg(gvarhm(i)) = hm

memg(gvararr (g, i)) = memg(g)memg(gvarstr (g, σ)) = memg(g)

We distinguish between nameless variables, i.e., heap variables, and namedvariables . Thus, we define a predicate ?namedg : gvar → B formally:

memg(g) = gm ∨ (∃i ∈ N : memg(g) = lm(i))?namedg(g)

Definition 3.11 (Initialized g-Variables) We say, a g-variable x is initial-ized in a memory configuration m, if its root g-variable is in the set of initializedvariables of the corresponding memory frame. Heap variables are by definitionalways initialized.

We set for σ ∈ Σ+, i ∈ N and g ∈ gvar :

?initg(m, gvargm(σ)) = σ ∈ m.gm.init?initg(m, gvarlm(i, σ)) = σ ∈ m.lm!i.init

?initg(m, gvarhm(i)) = True?initg(m, gvararr (g, i)) = ?initg(m, g)?initg(m, gvarstr (g, σ)) = ?initg(m, g)

Page 54: Verified Linking for Modular Kernel Verification - Institut f¼r

38 The C-like Programming Language C0

Definition 3.12 (Base Address of g-Variables) The base address of a g-variable is defined by the function bag : symbolconfT × gvar → N. For namedg-variables, we set:

bag(sc, gvargm(σ)) = bav(sc.gst, σ)bag(sc, gvarlm(i, σ)) = bav(sc.lst!i, σ)

Unfortunately, bav cannot be used with nameless variables. In order todetermine the base address of a heap variable with index i in a symbol tablesc.hst, we sum up the abstract sizes of the types with index less than i:

bag(sc, gvarhm(i)) =

0 if i = 0i−1∑j=0

sizeT (snd(sc.hst!j)) otherwise

For array accesses, we define partially:

bag(sc, gvararr (g, i)) =

{bag(g) + i · sizeT (t) if typeg(g) = ArrT (t, j)undef otherwise

Dealing with structure accesses is more convenient, since the list of structcomponents is of the same type as a symbol table: (Σ+ × ty) list . Thus we canuse bav again in order to compute the offset of a given component σ within acomponent list cl :

bag(sc, gvarstr (g, σ)) =

{bag(sc, g) + the(bav(cl , σ)) if typeg(g) = StrT (cl)undef otherwise

3.4.2 Value of a g-Variable

Let’s first define a function that allows to read memory cells, the building blocksof the content of a memory frame.

Definition 3.13 (Memory Content) For a given memory configuration m,a memory name σ, and i, j ∈ N, we define the content of the memory in therange from i to j as follows:

mσ[i : j] =

m.gm.ct[i : j] if σ = gm

m.lm!i.ct[i : j] if σ = lm(i)m.hm.ct[i : j] if σ = hm

Definition 3.14 (Value of a g-Variable) We can now use the above defini-tion in order to describe formally the value of a g-variable g ∈ gvar in a memoryconfiguration m ∈ memconfT :

valueg(m, g) = mmemg(g)[bag(sc(m), g), sizeT (typeg(sc(m), g))]

3.4.3 Evaluating Expressions

In this subsection, we will deal with the actual functions for expression evaluationin C0 small-step semantics. We will only consider those expressions being of

Page 55: Verified Linking for Modular Kernel Verification - Institut f¼r

3.4. Expression Evaluation 39

relevance for the correctness proof in Chapter 8. This means, we won’t look intothe details of literal or operator evaluation, but which are covered in [Lei08].

In Isabelle, the evaluation of an expression returns either None or a so-calleddata slice data type. Here, we split up the monolithic Isabelle eval functioninto five functions, each returning one of the components of the data slice datatype:

• lval : tenv ×memconfT × expr → gvar⊥ computes the left hand value ofa given expression, that is its address. For expressions with no address,like literals, lval returns None. This corresponds to the lval component ofa data slice.

• rval : tenv × memconfT × expr → (N → mcellT )⊥ computes the righthand value of a given expression. For uninitialized expressions, rvalreturns None. This corresponds to the data component of a data slice.

• type : tenv × (Σ× ty) list × (Σ× ty) list × expr → ty⊥ returns the type ofan expression. Assuming a type correct memory, this corresponds to thetype component.

• ?init : tenv ×memconfT × expr → B is True for initialized expressions,otherwise False. initialized is the corresponding component in data slices.

• ?inter : tenv × (Σ × ty) list × (Σ × ty) list × expr → B is True, if theexpression is intermediate—i.e., not a memory object. Data slices have acorresponding component named intermediate.

Note that not all of the above functions require a memory configuration asinput. Instead, type and init take two symbol tables, one for the global memory,one for the local memory (usually the latter one is the symbol table of thetopmost stack frame).

Literals

We start with evaluating literal expressions. Regarding initialization, left valueand memory status, we define as follows:

?init(te,m,Lit(l)) = True?inter(te, gst , lst ,Lit(l)) = True

lval(te,m,Lit(l))) = undef .

We do not define these functions for complex literals.Regarding type of literals, we define two functions.

Definition 3.15 (Type of Literals) The type of a literal is given throughthe function type lit : lit → ty. We set:

type lit(Bool(b)) = BoolTtype lit(Unsgnd(u)) = UnsgndT

type lit(Int(i)) = IntTtype lit(Char(c)) = CharT

type lit(NullPointer) = NullT

Page 56: Verified Linking for Modular Kernel Verification - Institut f¼r

40 The C-like Programming Language C0

Definition 3.16 (Type of Complex Literals) The type of a complex literalis given through the function typeclit : litc → ty. We set:

typeclit(clPrim(l)) = type lit(l)typeclit(clArr(x#xs)) = ArrT (|x#xs|, typeclit(x))

typeclit(clStr(xs)) = StrT (map(typeclit,map(snd , xs)))

Definition 3.17 (Right Value of Literals) We define a function rval lit :lit → mcellT , which takes a literal and returns its right value. We set:

rval lit(Bool(b)) = Bool(b)rval lit(Unsgnd(u)) = Nat(u)

rval lit(Int(i)) = Int(i)rval lit(Char(c)) = Char(c)

rval lit(NullPointer) = Ptr(NullPointer)

Definition 3.18 (Right Value of Complex Literals) We define the func-tion rvalclit : litc → mcellT list to obtain the right value of complex literals.We set:

rvalclit(clPrim(l)) = rval lit(l)rvalclit(clArr([ ])) = [ ]

rvalclit(clArr(x#xs)) = rvalclit(x)@rvalclit(clArr(xs))rvalclit(clStr([ ])) = [ ]

rvalclit(clStr([x#xs])) = rvalclit(snd(x))@rvalclit(clStr(xs))

Using the above definitions, we can now define the type and value of literalexpressions as follows:

type(te, gst , lst , lit(l)) = btype lit(l)crval(te,m, lit(l)) = brval lit(l)c.

Variable Access

Accessing a variable σ means accessing a memory object, thus

?inter(te, gst , lst ,Var(σ)) = False.

C0 supports shadowing. This means that if both gst and lst define a variablewith name σ, we will access the local one. If there is no local variable of thatname, we access the global one. If the variable σ is neither in gst nor in lst , theevaluation fails. In this case, we set:

?init(te,m,Var(σ)) = Falsetype(te, gst , lst ,Var(σ)) = None

rval(te,m,Var(σ)) = Nonelval(te,m,Var(σ)) = None

Page 57: Verified Linking for Modular Kernel Verification - Institut f¼r

3.4. Expression Evaluation 41

In the other cases, that is σ is either in gst or lst , we set:

type(te, gst , lst ,Var(σ)) =

{typev(lst, σ) if σ ∈ {map(fst , lst)}typev(gst, σ) otherwise

for the type and

lval(te,mc,Var(σ)) ={bgvarlm(| m.lm | −1, σ)c if σ ∈ map(fst , toplst(m))bgvargm(σ)c otherwise

for the left hand value.Furthermore, we define the to σ corresponding memory frame mf as

mf =

{toplm(m) if σ ∈ {map(fst , toplst(m))}m.gm otherwise

This allows finally to define the two remaining evaluation functions in acomprehensive way:

?init(te,m,Var(σ)) = σ ∈ mf .initrval(te,m,Var(σ)) = bmf .ct[bav(mf.st, σ), sizeT (typev(mf .st, σ))]c

Pointer Dereferencing

Pointer dereferencing again means dealing with a memory object, i.e.,

?inter(te, gst , lst ,Deref (e)) = False.

The evaluation of a Deref (e) expression will fail, if certain constraints areviolated. These are:

• the sub expression e has to be of pointer type: type(te, gst , lst , e) =bPtrT (tn)c,

• the type name tn has to be defined in the type name environment:mapof (te, tn) = btc;

• the right hand value of e has to be a pointer, but not the null pointer:rval(te,m, e) = bPtr(p)c ∧ p 6= NullPointer ,

• p must not be a local variable, that is memg(p) ∈ {gm, hm}, and last butnot least,

• e has to be initialized: ?init(te,m, e) = True.

In the case of failure, we set ?init to False and lval , rval , and type to None.Otherwise, we set the remaining four evaluation functions as follows:

type(te, gst , lst ,Deref (e)) = mapof (te, tn)?init(te,m,Deref (e)) = ?initg(m, p)

lval(te,m,Deref (e)) = bpcrval(te,m,Deref (e)) = bvalueg(m, p)c

Page 58: Verified Linking for Modular Kernel Verification - Institut f¼r

42 The C-like Programming Language C0

Address-Of Operator

The Address-Of operator AddrOf (e) returns the address of a memory object,which itself is not a memory object, but always initialized:

?inter(te, gst , lst ,AddrOf (e)) = True?init(te,m,AddrOf (e)) = True

As with pointer dereferencing, applying the Address-Of operator can fail, ifone of the following requirements is not met:

• e has to be a memory object: ?inter(te, gst , lst , e) = False, and

• its left evaluation has to return a g-variable: lval(te,m, e) = bgc, andfinally

• there has to be a type name for the type of e in the type name environment:mapof (map(flip, te), type(te, gst , lst , e)) = btnc.

If any of these requirements are not met, we set type, lval , and rval to None.Otherwise, we define:

type(te, gst , lst ,AddrOf (e)) = bPtrT (tn)clval(te,m,AddrOf (e)) = undefrval(te,m,AddrOf (e)) = b[Ptr(g)]c

Array Element Access

The expression for array element access Arr(ea, ei) has two sub expressions:ea for the array expression and ei for the index expression. An array elementaccess is a memory object, iff the array expression is a memory object, that is

?inter(te, gst , lst ,Arr(ea, ei)) =?inter(te, gst , lst , ea),

and it is initialized, iff both sub expressions are initialized:

?init(te,m,Arr(ea, ei)) =?init(te,m, ea)∧?init(te,m, ei).

Furthermore, we require

• the array expression can be evaluated, which is lval(te,m, ea) = bgac andrval(te,m, ea) = bvac;

• the right evaluation of the index expression returns a numerical value, i.e.,

rval(te,m, ei) = bInt(i)c∨ rval(te,m, ei) = bUnsgnd(i)c∨ rval(te,m, ei) = bChar(i)c

• that ea is an array with n elements of type t, i.e., type(te, gst , lst , ea) =bArrT (n, t)c, where

Page 59: Verified Linking for Modular Kernel Verification - Institut f¼r

3.4. Expression Evaluation 43

• the index i is in range: i < n.

If the evaluation of the array element access fails, since one of the above criteriais not fulfilled, we set type, lval and rval to None.

In all other cases we define

type(te, gst , lst ,Arr(ea, ei)) = btclval(te,m,Arr(ea, ei)) = bgvararr (ga, i)crval(te,m,Arr(ea, ei)) = bva[i · sizeT (t), sizeT (t)]c

Structure Component Access

Similar to array element accesses, a structure component access Str(e, cn)consists of two parts: a sub expression for the structure access e and a componentname cn. A structure component access’ initialization and memory object statusis the same as those of the structure access, thus

?inter(te, gst , lst ,Str(e, cn)) = ?inter(te, gst , lst , e)?init(te,m,Str(e, cn)) = ?init(te,m, e)

The evaluation of Str(e, cn) fails, if one or more of the following constraintsare violated:

• e has to be of structure type, i.e., type(te, gst , lst , e) = bStrT (cl)c, and

• e can be evaluated in the sense that lval(te,m, e) = bgc and rval(te,m, e) =bvc;

• there is a t such that (cn, t) is in the component list cl of the struct, thatis mapof (cl, cn) = btc.

In the case of failure, we set lval , rval and type to None.Otherwise we define

type(te, gst , lst ,Str(e, cn)) = btclval(te,m,Str(e, cn)) = bgvarstr (g, cn)crval(te,m,Str(e, cn)) = bv[bav(cl, cn), sizeT (t)]c

Operators

As mentioned in the introduction to this subsection, we will not fully work outthe details of operator evaluation. More precisely, we do not explicitly take careof how the right hand value and the result type are obtained—we will use twouninterpreted functions for this purpose. The details of operator evaluation arecovered in [Lei08].

For unary operators, we define

type⊕1: unop × ty → ty

value⊕1: unop ×mcellT⊥ → mcellT⊥

and correspondingly for binary operators

type⊕2: binop × ty × ty → ty

value⊕2: binop ×mcellT⊥ ×mcellT⊥ → mcellT⊥

Page 60: Verified Linking for Modular Kernel Verification - Institut f¼r

44 The C-like Programming Language C0

Unary Operators A unary operator expression is never a memory object,thus we set

?inter(te, gst , lst ,UnOp(⊕1, e)) = True,

and it is initialized, iff the sub expression e is initialized:

?init(te,m,UnOp(⊕1, e)) =?init(te,m, e).

Successful operator application requires the following conditions to be ful-filled:

• the sub expression e can be evaluated, that is rval(te,m, e) = bvec, and

• applying the unary operator to ve returns some result: value⊕1(⊕1, ve) =

bvc.

If one or more of these requirements are not met, we set type, lval , and rvalto None. Else, we set

type(te, gst , lst ,UnOp(⊕1, e)) = btype⊕1(⊕1, the(type(te, gst , lst , e)))c

lval(te,m,UnOp(⊕1, e)) = undefrval(te,m,UnOp(⊕1, e)) = b[v]c

Binary and Lazy Operators Similarly, we proceed with binary and lazyoperators. For the rest of this paragraph, we set e = BinOp(⊕2, e1, e2) ore = LazyBinOp(⊕2, e1, e2). As with unary operators, binary and lazy operatorexpressions are never a memory object, that is

?inter(te, gst , lst , e) = True,

and they are initialized, iff both sub expressions are initialized:

?init(te,m, e) =?init(te,m, e1)∧?init(te,m, e2).

Here, the requirements for successful expression evaluation are:

• both sub expressions can be right evaluated, i.e., rval(te,m, e1) = bv1cand rval(te,m, e2) = bv2c, and

• application of the operator yields some result: value⊕2 (⊕2, v1, v2) = bvc,and finally

• we can determine some type both for e1 and e2:

type(te, gst , lst , e1) = bt1ctype(te, gst , lst , e2) = bt2c

If expression evaluation fails, we set lval , rval , and type to None. In allother cases, we set:

type(te, gst , lst , e) = btype⊕2(⊕2, e1, e2)c

lval(te,m, e) = undefrval(te,m, e) = b[v]c

Page 61: Verified Linking for Modular Kernel Verification - Institut f¼r

3.5. Execution of C0 Programs 45

3.5 Execution of C0 Programs

In this section we will treat the execution of C0 programs using the small-steps semantics. We first start with the definition of initial configurations (cf.Sect. 3.5.1).

We proceed with a formal definition of memory updates in Sect. 3.5.2, beforewe conclude with the definition of the C0 transition function (cf. Sect. 3.5.3)

3.5.1 Initial Configuration

As we have learned in Sect. 3.3.3, a C0 configuration consists of two components:the memory configuration and the program rest. In this section, we will define,how these two components are initialized.

Initial Memory

Global and heap variables in C0 are initialized with a default value dependingon their type. Then, we use these initial values to construct the initial memory.

Definition 3.19 (Initial Values) Let initval : ty → mcellT list define theinitial value for a given type. Let tn denote a type name. For the base case weset:

initval(IntT ) = [Int(0)]initval(UnsgndT ) = [Unsgnd(0)]

initval(BoolT ) = [Bool(False)]initval(CharT ) = [Char(0)]

initval(PtrT (tn)) = [Ptr(NullPointer)]initval(NullT ) = [Ptr(NullPointer)]

Given a type t, a natural number n, and a component name cn, we define forthe inductive case:

initval(ArrT (n, t)) = replicate(n, initval(t))initval(StrT [ ]) = [ ]

initval(StrT ((cn, t)#xs)) = initval(t)@initval(StrT (xs))

We can now use the function initval to define recursively initst, which takesa whole symbol table as input and returns a corresponding initialized contentfor it:

initst([ ]) = [ ]initst((vn, t)#xs) = initval(t)@initst(xs)

Definition 3.20 (Initial Memory Frame) Given a symbol table st, we de-fine an initial memory frame as follows:

initmem(st) =

ct = undefst = stinit = ∅

Page 62: Verified Linking for Modular Kernel Verification - Institut f¼r

46 The C-like Programming Language C0

Definition 3.21 Now, we take this wrapper together with the memory contentgenerator from Def. 3.19 to initialize all variables of a memory frame. Therefore,we define initvars : mframeT → mframeT and set for a given memory frameframe:

initvars(frame) =[ct = λi.initst(frame.st)!iinit = {map(fst , frame.st)}

]We have now all the ingredients to define the initial memory of a C0 machine.

For the global memory frame, we take the global variable’s symbol table toconstruct the initial content. Since there haven’t been any objects allocatedyet, the heap is empty.

The situation for the stack is a little trickier: we search the procedure tablefor the main function of the program. Then we take its parameters and localvariables and use them to construct the initial stack frame of our initial C0configuration.

Definition 3.22 (Initial Memory Configuration) Let pt be a proceduretable and gst a symbol table for the global variables. An initial memory config-uration of a C0 program is given through the function initmc : proctableT ×(Σ+ × ty) list → memconfT⊥.

If mapof (pt, ‘main’) = None, i.e., there is no main function in the program,we set initm(pt, gst) = None. Otherwise, denote with mp the main function ofthe program, which is mapof (pt, ‘main’) = bmpc. Then we set

initm(pt, gst) =

gm = initvars(initmem(gst))lm = [(initmem([email protected]), undef )]

hm = [initmem([])]

There is only the second component of an initial C0 machine configuration

missing, the initial program rest. This rest is defined by the body of theprogram’s main function after removing the last statement of it. This is infact a Return statement and would be the last statement of the program to beexecuted. Unfortunately, this would leave the stack in an undefined state. Forthis reason, termination of a C0 program is defined in a different way. We willhave a more precise look at this in Sect. 3.5.3.

Definition 3.23 (Removing the Last Statement) We define the functionremlast : stmt → stmt , which replaces the last statement in a tree with Skip.For an input statement s, we define recursively:

remlast(s) =

{Comp(s1, remlast(s2)) if s = Comp(s1, s2)Skip otherwise

Definition 3.24 (Initial C0 Configuration) We will now define the func-tion initconf : proctableT × (Σ+ × ty) list → cC0⊥. If there is no main functionin the corresponding C0 program, that is mapof (pt, ‘main’) = None, we returninitconf(pt, gst) = None. Else, let mp denote the main procedure of the program:mp = the(mapof (pt, ‘main’)).

initconf(pt, gst) =⌊ [

mem = the(initmem(pt, gst))prog = remlast(mp.body)

] ⌋

Page 63: Verified Linking for Modular Kernel Verification - Institut f¼r

3.5. Execution of C0 Programs 47

3.5.2 Memory Updates

Statements like SCall , PAlloc, Return, and Ass change the memory config-uration of a C0 machine. In this section, we are going to define a functionupdmm : memconfT × gvar × (N → mcellT ) → memconfT⊥ for memory up-dates. In Sect. 3.5.3, we will use this function to describe the semantics of thestatements mentioned above.

For the rest of this section, let m denote a memory configuration, g ag-variable, and v a value. In C0, partial updates of uninitialized variables areforbidden. This is why we require that the variable to be updated is eitherinitialized or a root g-variable.

If this is not the case, the memory update returns None:

¬(?init(m, g) ∨ rootg(g) = g)updmm(m, g, v) = None

Otherwise, we define the memory update semantics by a case distinction onthe root of g. Let b = bag(sc(m), g) denote the base address of g, and s =sizeT (typeg(sc(m), g)) its size.

For a global root g-variable—rootg(g) = gvargm(σ)—we set

updmm(m, g, v) :=⌊m

[gm.init := gm.init ∪ σgm.ct := gm.ct([b, s] := v[0, s])

]⌋For a local root g-variable—rootg(g) = gvarlm(i, σ)—we set

updmm(m, g, v) :=⌊m

[lm!i.init := lm!i.init ∪ σlm!i.ct := lm!i.ct([b, s] := v[0, s])

]⌋Finally, for a heap root g-variable—rootg(g) = gvarhm(j)—we set

updmm(m, g, v) :=⌊m[hm.ct := hm.ct([b, s] := v[0, s])

]⌋Note, that in the latter case we do not care about the init component of theheap memory frame, since heap variables are by definition always initialized.

3.5.3 C0 Transition Function

The execution of a C0 program is modeled by the small-step semantics transitionfunction δC0. Parametrized with a type name environment and a proceduretable, δC0 computes for a given configuration c either its successor configurationc′ or—in the case of a run-time error—None.

δC0 : tenv × proctableT × confTC0 → confTC0⊥

The function is defined by induction over the program rest. If the programrest is a single statement, we apply the transition function as described inthe paragraphs below. Otherwise—i.e., the program rest is a tree spanned bycompound statements—we apply δC0 recursively.

Skip

A program rest consisting of a single Skip, that is c.prog = Skip, means thatthe program has terminated. In C0, termination is modeled by a infinite fixpoint loop: δC0(te, pt, c) = bcc.

Page 64: Verified Linking for Modular Kernel Verification - Institut f¼r

48 The C-like Programming Language C0

Comp

Comp

Ass

s2

s1

δC0

Comp

Comp

Skip

s2

s1

Figure 3.4: Execution of Compound Statements with ‘Real’ Statements

Compound Statements

A compound statement Comp(s1, s2) combines two sub trees s1 and s2. Execu-tion is defined by recursion on the left sub tree.

The execution of that sub tree has finished, when there is only a Skipstatement left, i.e., s1 = Skip. In this case, we go on with the recursiveexecution of the right sub tree s2. We define

δC0(te, pt, c) =bc[prog := s2] if s1 = Skipbc′[prog := Comp(c′.prog, s2)]c if δC0(te, pt, c[prog := s1]) = bc′cNone otherwise

This means that we always execute the left-most statement in statement tree.Consumed left sub trees—those that just consist of a Skip statement—arepruned together with the corresponding compound statement (see Figures 3.43.5).

Conditional Statements

Executing a conditional statement Ifte(e, s1, s2) means that there are twopossible new program rests: either s1 or s2, depending on e. There are somerequirements on the expression e for a successful execution:

• e has to be of Boolean type: type(te, gst(c.m), lst(c.m), e) = bBoolT c,

• it is initialized, that is ?init(te, c.m, e) = True, and

• it can be right evaluated, that is rval(te, c.m, e) = bvc.

If one or more of these conditions are violated, execution fails and we setδC0(te, pt, c) = None. Otherwise, we define for c.prog = Ifte(e, s1, s2):

δC0(te, pt, c) =

{bc[prog := s1]c if v = Truebc[prog := s2]c otherwise

Page 65: Verified Linking for Modular Kernel Verification - Institut f¼r

3.5. Execution of C0 Programs 49

Comp

Comp

Skip

s2

s1

δC0

Comp

s2s1

Figure 3.5: Execution of Compound Statements with Skip Statements

While Loops

If the program rest consists of a loop, i.e., c.prog = Loop(e, s), there are twopossible program rests: Skip, if e evaluates to False, or Comp(s,Loop(e, s))otherwise—which means, we execute the loop body s as long as e is True.

Again, execution can fail, if not all of the following requirements on e aremet:

• e has to be of Boolean type: type(te, gst(c.m), lst(c.m), e) = bBoolT c,

• it is initialized, that is ?init(te, c.m, e) = True, and

• it can be right evaluated: rval(te, c.m, e) = bvc.

In the case of failure, we set δC0(te, pt, c) = None, otherwise we define:

δC0(te, pt, c) =

{bc[prog := Comp(s,Loop(e, s))]c if v = Truebc[prog := Skip]c otherwise

Assignments

Given a program rest c.prog = Ass(eleft, eright) and the following requirements:

• eleft can be left evaluated to some g-variable g:

lval(te, c.m, eleft) = bgc,

• eright is initialized, ?init(te, c.m, eright) = True, and

• can be right evaluated to some value v:

rval(te, c.m, eright) = bvc,

and finally

• the memory update succeeds: updmm(c.m, g, v) = bm′c.

If one or more of the above conditions are violated, we set δC0(te, pt, c) = None.Otherwise, we define

δC0(te, pt, c) =⌊c

[prog := Skipm := m′

]⌋

Page 66: Verified Linking for Modular Kernel Verification - Institut f¼r

50 The C-like Programming Language C0

Aggregate Literals We have already mentioned in Sect. 3.2.3 and 3.5.1,there is an extra statement for the assignment of aggregate literals: Assc. Itallows to assign complex values in one step of the C0 machine, which is essentialin the equivalence proof of the small-step semantics and the Hoare logic asdefined in [Sch06]. Since this proof is not relevant for our work, we do not getfurther into the details of it, but merely describe the semantics of Assc, whichare very similar to those of a ‘normal’ assignment.

For a program rest c.prog = Assc(eleft, al), we require that

• eleft can be left evaluated to some g-variable g, that is

lval(te, c.m, c.m) = bgc;

• the aggregate literal al can be right evaluated to some value v:

rvalal(te, c.m, al) = bvc,

• and finally the memory update is successful: updmm(c.m, g, v) = bm′c.

In the case of failure, we set δC0(te, pt, c) = None, otherwise we define

δC0(te, pt, c) =⌊c

[prog := Skipm := m′

]⌋Memory Allocation

Whenever we are to allocate a new object on the heap, we have to makesure first, that there is enough memory available to do so. Thus, we definea predicate ?heap : memconfT × ty → B, which determines this for a givenmemory configuration and a type. On the semantic level, we leave this predicateuninterpreted. In a concrete setting as described in [Lei08], parameters likeoverall memory size, current heap size, and compiler construction would be partof such a definition.

For a successful execution of the PAlloc(eleft, tn) statement we require,

• that eleft can be left evaluated to some g-variable g: lval(te, c.m, eleft) =bgc, and

• that the type name tn is defined in te: mapof (te, tn) = btc.

As before, we set δC0(te, pt, c) = None for the failure case.If both requirements are met, there are two ways to proceed depending

on the value of ?heap. Assuming that there is enough memory available, wefirst extend the heap with a new variable of the specified type and assign theappropriate initial value to it. In a second step, we create a pointer to thisvariable and assign it to the left expression. If there is not enough memory, weonly assign a null pointer to the left expression.

Definition 3.25 (Extending the Heap) We define now a function

extheap : (memconfT × ty → B)×memconfT × ty → memconfT ,

which handles the extension of the heap as described above. Let m denote amemory configuration and t the type of the variable to be added to the heap.

Page 67: Verified Linking for Modular Kernel Verification - Institut f¼r

3.5. Execution of C0 Programs 51

The base address of this variable is equal to the overall abstract size of thewhole heap—since it will be appended at its end. Thus we set

b =|hst(m)|−1∑

i=0

sizeT (snd(hst(m)!j)).

If ?heap(m, t) = True, that is there is enough memory available for a newvariable of type t, we define the new heap memory hm′ by

hm′ = m.hm

[st := hst(m)@[(undef , t)]ct := m.hm.ct([b, sizeT (t)] := initval(t)[0, sizeT (t)])

]In the case of insufficient memory—?heap(m, t) = False—, we just return theold heap: hm′ = m.hm.

We still have to deal with the second step: assigning either the null pointeror a pointer to the newly created variable to the left hand expression. We set:

p =

{Ptr(gvarhm(|hst(c.m)|)) if ?heap(c.m, t) = TrueNullPointer otherwise

Since p is initialized by definition (cf. Sect. 3.4) and the requirementsdescribed above are met, the following memory update will not fail and yield anew memory configuration m′:

updmm(extheap(?heap, c.m, t), g, p) = bm′c.

Finally, we define the resulting configuration by

δC0(te, pt, c) =⌊c

[prog := Skipm := m′

]⌋Function Calls

Given a program rest SCall(vn, fn, plist), i.e., a call of the function with namefn and a list of parameter expressions plist. vn denotes the name of the variable,where the return value will be stored later on. A function call is realized inthree steps:

1. We start with the extension of the stack by a new frame. The frame’ssymbol table is made up by the function parameters and its local variables.

2. Subsequently, we evaluate the parameter expression list and copy theresults into the new stack frame.

3. In the end, we set the function body as the new program rest.

Definition 3.26 (Parameter Passing) We start by defining the functioncopypars : memconfT × (N → mcellT ) list × Σ+ list → memconfT⊥. copypars

takes a list of values and tries to copy them into the topmost stack frame, wherea list of variable names specifies the variables to be updated.

Let vl denote a list of values, pnl a list of parameter names, and m a memoryconfiguration. We then define copypars(m, vl, pnl) recursively on the value listvl with the base case

copypars(m, [ ], pnl) = bmc.

Page 68: Verified Linking for Modular Kernel Verification - Institut f¼r

52 The C-like Programming Language C0

For the recursive case copypars(m, v#vl, pnl), we examine the memory updatewith the head of the value list v. If it fails, we set

copypars(m, v#vl, pnl) = None.

Otherwise, there exists a memory configuration m′ with

updmm(m, gvarlm(|m.lm| − 1, hd(pnl)), v) = bm′c

and we set

copypars(m, v#vl, pnl) = copypars(m′, vl, tl(pnl))

For reasons of better readability, we introduce the shorthand notationstfunc(f), which refers to the symbol table of a function f ∈ procT . Thissymbol table consists of the parameters of the function and its local variables:stfunc(f) = [email protected].

Definition 3.27 We will now take the stack extension and the parameter pass-ing and put them into one function extstack : tenv×memconfT ×Σ+×procT ×expr list → memconfT⊥. Let extstack(te,m, vn, f, pl) denote an application ofthis function.

First, we define the new stack frame mf ′ by

mf ′ =

st = stfunc(f)ct = undefinit = ∅

Then we add this new stack frame at the end of the existing stack and obtain anew intermediate memory configuration mimed:

mimed = m[lm := m.lm@[(mf ′, lval(te,m,Var(vn)))]]

Here, lval(te,m,Var(vn)) determines the g-variable where the return value willbe stored at the end of the function execution.

Now, we take this intermediate memory configuration and copy the parametervalues into it and obtain thereby the extended memory configuration:

extstack(te,m, vn, f, pl) =copypars(mimed,map(the(rval(te,m)), pl),map(fst , f.params)).

Stack extension can fail for two reasons:

• the left evaluation for the return destinations fails: lval(te,m,Var(vn)) =None.

• the evaluation of one or more parameter expressions fails: ∃p ∈ {pl} :rval(te,m, p) = None.

In these cases we set extstack(te,m, vn, f, pl) = None.

We can now define the successor configuration of a function call c.prog =SCall(vn, fn, pl) by using the definition above and setting the new program rest

Page 69: Verified Linking for Modular Kernel Verification - Institut f¼r

3.5. Execution of C0 Programs 53

appropriately. Let f denote the called function, i.e., the(mapof (pt, fn)) = f ,and assume extstack(te,m, vn, pl) = bm′c. Then we define

δC0(te, pt, c) =⌊c

[prog := f.bodym := m′

]⌋If our assumption fails, that is extstack(te,m, vn, pl) = None, the execution ofthe function call fails and we set δC0(te, pt, c) = None.

Returns

The execution of a program rest c.prog = Return(e) can be split into two steps.First, we evaluate the return expression e and use its value to update the returndestination of the topmost stack frame. Then, we remove this frame from thestack.

Successful execution has several requirements to be met:

• the return expression can be right evaluated, that is

rval(te, c.m, e) = bvc,

• it is initialized, i.e., ?init(te, c.m, e) = True, and finally

• the memory update of the return destination succeeds:

updmm(c.m, snd(toplm(c.m)), v) = bm′c.

In the failure case, we set again δC0(te, pt, c) = None. Otherwise, we define thetransition function for return statements by

δC0(te, pt, c) =⌊c

[prog := Skipm := m′[lm := butlast(m′.lm)]

]⌋External Function Calls

As mentioned in Sect. 3.2.3, external function calls refer to functions thatare just declared, but not defined. In Sect. 6.5.2, the occurrences of externalfunction calls will be replaced by actual function calls as described furtherabove. There is not much sense in defining a transition function for externalfunction calls. Nevertheless for reasons of completeness, we set for a programrest c.prog = ESCall(vn, fn, plist)

δC0(te, pt, c) = bc [prog := Skip]c

XCalls and In-line Assembler

In-line Assembler We will deal here with in-line assembly code only super-ficially, since the part of the CVM correctness proof, with which we deal in thiswork, relies only on pure C0 small step semantics.

In the early stages of the Verisoft project, we originally considered tointroduce an extra computational model of C with in-line assembler code.Practical formal verification work within Verisoft’s academic sub-project hastaught us yet that a dual model approach is more feasible. This means, we

Page 70: Verified Linking for Modular Kernel Verification - Institut f¼r

54 The C-like Programming Language C0

separate the computational model for assembler programs from the one for C0programs and thus also separate the proof work. Later on, we combine the tworesults with each other.

This is possible, because the compiler correctness theorem, as introduced in[Lei08], relates each C0 configuration to a corresponding configuration of theassembler machine, thus providing two traces of relating configurations. So,whenever the program rest consists of an in-line assembler statement, we canmove from this C0 configuration down to the relating assembler configuration.As we assume termination of in-line assembler portions (for a full list of assump-tions on in-line assembler and its formalization see [Tsy09]), we can now executethe whole instruction list provided by the Asm statement. The final assemblerconfiguration is then used to derive again the related C0 configuration from it.Note that this might include memory updates of variables of the C0 program.

There are a couple of examples in Verisoft, where this methodology hasbeen successfully applied. In [Tsy09], correctness for some of the CVM kernelprimitives has been shown. [Sta09] has formally verified a demand pagingalgorithm. Finally, [Alk09] has produced a correctness proof for a device driver.

XCalls XCalls are a way to make the results of in-line assembly code visibleon higher levels of the semantic stack, i.e., in the Hoare Logic of [Sch06].The meaning of each XCall is defined in an axiomatic way. The authors of[ASS08, AHL+09] have used this methodology in the pervasive verification ofthe paging mechanism used in Verisoft’s academic sub-project.

Page 71: Verified Linking for Modular Kernel Verification - Institut f¼r

Most of the fundamental ideas of science areessentially simple, and may, as a rule, be expressedin a language comprehensible to everyone.

Albert Einstein

Chapter 4

Devices

Contents4.1 Device Configurations . . . . . . . . . . . . . . . . . . . . 55

4.2 Device Communication . . . . . . . . . . . . . . . . . . . 56

4.3 Transition Functions . . . . . . . . . . . . . . . . . . . . . 58

In our device model [HIP05], we define single devices as finite state transitionsystems, that interact on one hand with the processor and on the other handwith an—not modeled—external environment (cf. Fig. 4.1). The externalenvironment can, for instance, represent a user typing on a keyboard or anetwork, which is connected to a device.

4.1 Device Configurations

In Verisoft, we support the following device types:

• a timer, which can be used, for instance, for scheduling,

• a hard disk,

• an UART serial interface, as it is used to connect terminals for example,

• an automotive bus controller (ABC), which is used in the Automotivesub-project of Verisoft, and

• a network interface controller (NIC).

Three of the above devices have been formally modeled in Isabelle/HOL: thehard disk [AH08], the serial interface [AHK+07], and the automotive buscontroller [Kna08, ABK08, IK05] used in the Verisoft automotive sub-project[BBG+05, ILP05].

The detailed discussion of these devices goes far beyond the scope of thiswork and it is not necessary for the further understanding. Each type of devicehas its own disjoint state space, which we abbreviate by confTx with x denotingthe device type: x ∈ {timer , hd, uart , abc, nic}. The union of all possible devicestates is denoted by confTdev.

55

Page 72: Verified Linking for Modular Kernel Verification - Institut f¼r

56 Devices

ExternalEnvironment

Device

mifi

mifo eifi

eifo

Processor

Device

mifi

mifo eifi

eifo

Figure 4.1: Devices Interacting with External Environment and Processor

Remark (Isabelle/HOL) In Isabelle/HOL, we model confTdev by an ab-stract data type with one constructor for each device type.

Let dmax denote the maximal number of devices in a system. Then eachdevice is associated with an unique identifier in the set {0, . . . , dmax − 1} = D.The overall generalized device configuration of such a system is then giventhrough the mapping

confTdevs = D→ confTdev.

The type for a given device state c can be obtained by the mapping typedev

with

typedev(c) = t⇔ c ∈ confT t.

We additionally define a function irqdev : confTdevs ×D→ B. For a givengeneralized device configuration cdevs, we say the device with id did is currentlysignaling an interrupt, iff irqdev(cdevs, did) = True.

4.2 Device Communication

Device communication is bidirectional with both the processor and the externalenvironment. For the processor part of communication, the devices are memorymapped. This means that certain parts of the device, the so-called ports, aremapped into the memory of the processor. Conventional memory operationscan then be used to communicate with these devices, that is with load and storeoperations on the corresponding addresses. In our device model, we restrict theamount of ports per device to portmax = 210.

Page 73: Verified Linking for Modular Kernel Verification - Institut f¼r

4.2. Device Communication 57

Device Communication on the Upper Layers

On upper-layer computational models, we abstract from assembly instructionsand memory. Plus, we want to work with block operations on devices, that iswe want to read and write chunks of data of size larger than one word. For thispurpose we introduce two new types to allow for modeling the communicationbetween processor and devices in that way: the memory interface input mifi—input from the processor to the device—and the memory interface outputMifo—output from the device to the processor. In CVM, we assume that deviceoutput is a list of integers and thus set Mifo = Z list .

A memory interface input din ∈ mifi is a record type with the followingcomponents:

• rep ∈ B: if rep = True, we will read or write repeatedly from thesame port of the device. Otherwise, we read from or write the words toconsecutive ports starting with the port specified in the port field.

• wr ∈ B: the write flag signals a write operation, iff wr = True and a readoperation else.

• port ∈ {0, . . . , portmax − 1} denotes the port on which we operate in thecase of rep = True; otherwise, it specifies the port with which we start.

• data ∈ Z list : in the case of wr = True, this component holds the data tobe written to the device. In the read case, only the length |data| of thelist matters, since it determines how many words we are going to readfrom the device.

Furthermore, the element

mifi ε = (rep = False, wr = False, port = 0, data = [0])

denotes the so-called idle mifi.In order to abstract from a single device, we extend this type by an extra

field did to determine, for which device the input is meant. We name this new,extended type Mifi .

Communication with the external environment is again device-specific. So,there is for each device type t a particular type for the input from the environ-ment to the device named Eifi t, the external interface input, and a type for theoutput from the device to the environment named Eifot, the external interfaceoutput. The union of all possible external inputs and outputs are named Eifiand Eifo, respectively.

Remark (Isabelle/HOL) In Isabelle/HOL, we model both Eifi and Eifo byabstract data types with one constructor for each device type.

Device Communication on the ISA Level

On the ISA level, devices are mapped into the memory. A certain address rangeis reserved for the devices, so that they can be accessed with conventional loadand store operations.

For memory interface input, the effective address of such an operation canbe used to determine the corresponding device id and port, while the type of

Page 74: Verified Linking for Modular Kernel Verification - Institut f¼r

58 Devices

the operation determines if the write flag is set or not. In the case of a writeoperation, we use the natural number representation of the value associatedwith this operation for the data field of the device input. Note that on the ISAlevel, we do not support block operations.

A detailed definition on how to compute memory interface input for deviceson the ISA level can be found in [Tve09, Alk09].

In addition, memory interface output on this level consists only of a sin-gle integer value—compared to a list of integers for the upper layer devicecommunication.

A upper layer memory interface input, which constitutes a block operation,can easily be translated into a finite sequence of low-level memory inputs.

4.3 Transition Functions

The device model which we introduce here is pseudo-parallel in the sense thatwe either do an internal step—the device consumes a processor input—or anexternal step—the device consumes an external input, but never both at thesame time. Thus, for each device there is an external and an internal transitionfunction. Again, we do not look into the details of these transition functionsbut merely treat them as uninterpreted functions.

While external steps can only produce external output, internal steps canresult both in external and internal output.

Let t denote a device type with t ∈ {hd, uart, nic, timer, abc}. Then, theinternal transition function for that device is given through

δintt : mifi × confT t → confT t ×Mifo × Eifot

and the external transition function through

δextt : Eifi t × confT t → confT t × Eifot.

For CVM again, we want to abstract from single devices and obtain general-ized transition functions for overall device configurations

δintdevs : Mifi × confTdevs → confTdevs ×Mifo × Eifo

andδextdevs : (D× Eifi)× confTdevs → confTdevs × Eifo.

With each application of the transition functions, one device makes a step.Let dinint ∈ Mifi denote an internal device input, cdevs ∈ confTdevs a

generalized overall device configuration. The device, which is supposed toprogress, is given through din int.did . We construct the internal input for a singledevice din′int ∈ mifi by throwing away the device identifier did. Additionally,we identify the device type t = typedev(cdevs(dinint.did)).

Now, we step the device using the appropriate device specific internaltransition function and obtain a new device state, internal output dout int andexternal output doutext:

(c′t, dout int, doutext) = δintt (din′int, cdevs(dinint.did)).

Page 75: Verified Linking for Modular Kernel Verification - Institut f¼r

4.3. Transition Functions 59

Finally, we can specify the result of the generalized internal transition functionas

δintdevs(dinint, cdevs) = (cdevs(dinint.did := c′t), dout int, doutext).

This works similar for the generalized external transition function. Letdid ∈ D denote a device id, dinext ∈ Eifi t an external device input andcdevs ∈ confTdevs a generalized device configuration. We identify the typeof the addressed device by t = typedev(cdevs(did)). Then, we step the devicewith the specific external transition function and obtain a new device state andexternal output:

(c′t, doutext) = δextt (dinext, cdevs(did)).

The result of the generalized external transition function can then be defined as

δextdevs((did, dinext), cdevs) = (cdevs(did := c′t), doutext).

Page 76: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 77: Verified Linking for Modular Kernel Verification - Institut f¼r

Engineers now have the ability to formally specifyproperties of their hardware design model using anindustry standard, and then verify these propertiesin dynamic verification (that is, simulation) orstatic verification (that is, formal verification), ...Prior to IEEE 1850, there were multipleproprietary ways of specifying properties andassertions, but not a standard. This meant thatthe same specification could not be used acrossmultiple tools. With a new standard, a single formof specification can be reused across multipleprocesses.

Harry Foster

Chapter 5

VAMP ISA and Assembler

Contents5.1 VAMP ISA . . . . . . . . . . . . . . . . . . . . . . . . . . 62

5.2 Combining ISA and Devices . . . . . . . . . . . . . . . . 64

5.3 Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . 66

User processes are applications running on top of the microkernel. We coulduse the C0 small-step semantics as introduced in Chapter 3. Yet, we do notonly want to model well-behaving friendly user processes, but also malevolentprocesses. Modeling in C0 would impose programming restrictions that do notcope with real world scenarios. For instance, a hacker would probably implementhis malicious code directly in assembler to make use of certain exploits. Thus werestrain from C0 program user process and will instead use assembler machinesto model user processes.

The hardware platform for CVM is given by the Verified ArchitectureMicroprocessor (VAMP), based on the DLX architecture [HP96] and introducedin [MP00]. A first VAMP implementation correctness proof has been presentedin [BJK+03, BJK+06]. During the Verisoft project, the VAMP has beenextended by I/O devices and address translation [DHP05].

There are several formal models related to the VAMP architecture. Foreach model, there exist two variants, one with device support and one without.Adjacent layers are related by simulations proofs [Tve09, Tsy09].

The most concrete layer is the gate-level implementation, above which wehave the instruction set architecture (ISA). Here, we already abstract fromthe actual hardware layout and define state transitions using the semantics ofthe processor’s instruction set. For the correctness proof sketched in Chapter7, this model of the hardware with devices represents the lowest layer in thesimulation proof.

Yet, data and instructions are still given through bit vectors, resulting inan inconvenient model for a programmer. For this purpose, we introduce theVAMP assembly language, which is a slight abstraction of the VAMP ISA.Here, addresses are represented by natural numbers, and register and memorycontents by integers. We consider this layer to be the programming modelfor low-level applications, and user processes will be represented by assemblermachines.

61

Page 78: Verified Linking for Modular Kernel Verification - Institut f¼r

62 VAMP ISA and Assembler

5.1 VAMP ISA

In this section, we will briefly introduce the instruction set architecture of theVAMP. Then, we will combine this model with the generalized device modelfrom Chapter 4. We will omit many details that can be found in [Tve09].

Remark (Isabelle/HOL) The following definitions are based on the formaldefinitions in Isabelle. While originally ISA memory was byte addressed (cf.[MP00]), the Isabelle version of it is word addressed.

5.1.1 Instruction Set Architecture

The VAMP instruction set architecture serves as the specification for thegate-level implementation. On the other hand, it offers a system softwareprogrammer’s view of the hardware with all details as interrupt handling,address translation, and execution modes.

ISA Configuration

An ISA configuration cisa is modeled by the record type confTisa with fields:

• pcp ∈ B30, the program counter, and

• dpc ∈ B30, the delayed program counter which specifies the address forthe next instruction fetch (for details on delayed branch mechanism cf.[MP00]);

• two register files, namely gprs : B5 → B32, the general purpose register

file and

• the special purpose register file sprs : B5 → B32;

• a memory mm : B30 → B32.

Frequently, we refer to the special purpose registers by an acronym denotingtheir meaning (cf. Tab. 5.1). For instance, sprs(ptl) denotes the page tablelength register sprs(10).

Remark (Isabelle/HOL) Unlike in the hardware, in Isabelle/HOL bit vec-tors can have variable length. The preservation of the constant length is atransition invariant that had to be proven.

The VAMP processor offers support for two execution modes. In systemmode, programs have direct access to the memory and have full control over thehardware via a set of privileged instructions. In user mode, memory access issubject to address translation and the use of privileged instructions will raise anexception. The current execution mode is defined by the content of the registersprs.mode: if it is 0, we are in system mode, otherwise we are in user mode.

Page 79: Verified Linking for Modular Kernel Verification - Institut f¼r

5.1. VAMP ISA 63

Index Function Name

0 Status Register sr1 Exception status register esr2 Exception cause register eca3 Exception PC epc4 Exception DPC edpc5 Exception data edata6 Rounding mode rm7 IEEE flags register ieeef8 Floating point condition code fcc9 Page table origin pto10 Page table length ptl11 Exception mode emode12 Not used13 Not used14 Not used15 Not used16 Used to distinguish system and user mode mode

Table 5.1: Special Purpose Registers

ISA Transitions

The transition function for the ISA

δisa : confTisa ×B19 ×B32 → confTisa

takes an ISA configuration cisa, an external interrupt vector eev, and datadout int provided by the devices, returning the successor configuration c′isa.Each bit in eev indicates, if the corresponding external interrupt is raised (cf.Tab. 6.1). Then, we continue with a check, whether cisa together with eev iscausing an interrupt. If so, we proceed with interrupt handling as describedin the next paragraph. Otherwise, we execute the next instruction from theaddress the dpc points to and proceed with a case-split on this instruction.

Interrupt Handling We distinguish between internal and external inter-rupts (cf. Tab. 6.1). Some interrupts are maskable, i.e., their occurrences areignored when they are masked. This mechanism is controlled by the statusregister sprs(sr), where a 0 bit denotes that this interrupt is disabled. We usethis mechanism for instance to prevent the CVM kernel from interruption asdescribed in Sect. 6.2.2.

When an unmasked interrupt occurs, the VAMP switches to system modeand enters the Interrupt Service Routine (ISR), which starts at address 0. Theactual interrupt handling is then controlled by the code starting there. Interrupthandling ends usually with a rfe (return from exception) privileged instruction,returning to user mode. Then, execution is continued: in the case of a repeatinterrupt with the instruction that caused the interrupt, in the continue casewith the instruction that follows.

Page 80: Verified Linking for Modular Kernel Verification - Institut f¼r

64 VAMP ISA and Assembler

Address Translation

The VAMP offers a single-level address translation support. Its memory isdivided into chunks of consecutive 4096 = 210 = 1K words called pages . In usermode, we interpret addresses of load/store operations as virtual addresses , thatis they are subject to address translation.

Address translation is realized using a page table. A page table is a dedicatedmemory region in the main memory, which starts at the address specified in thepage table origin register sprs(pto) and has sprs(ptl) + 1—the so-called (pagetable length)—many page table entries (PTEs) of word-size.

Let va denote a virtual address. The upper 20 bits px(va) = va[29 : 10] arecalled (virtual) page index and are used as an index into the page table. Thelower 10 bit wx(va) = va[9 : 0] are named word index .

For px(va) > sprs(ptl), a page fault interrupt is thrown . Otherwise, wecompute the corresponding page table entry as

pte = mm(sprs(pto) · 210 + px(va)).

This entry is then interpreted as follows:

• the upper 20 bits pte[31 : 12] are the physical page index ppx,

• pte[11] is the valid bit, and

• pte[10] is the protection bit.

If the valid bit is set, read operations on the virtual address are allowed.Furthermore, if the protection bit is cleared, writes are allowed, too. If theserequirements are met, the access of the virtual address va will be performedon the physical address pa(va) = [ppx(va);wx(va)]. Otherwise, a page faultinterrupt will be generated.

5.2 Combining ISA and Devices

When moving from gate-level implementation to instruction set architecture, webasically abstract from timing since we consider instructions and not cycles anylonger. The same instruction can have different run-times, depending on thestate of the gate-level machine. For instance, execution duration of load/storeoperations heavily depends on cache content, which is no longer visible at theinstruction set architecture level.

At the gate-level, processor and devices run with the same clock in lock-step.We make up for this loss of granularity on the ISA level by the introduction ofexecution interleaving of devices and processor.

Definition 5.1 (Configuration of ISA and Devices) A combined config-uration of VAMP ISA with devices is modeled by a record type confTi&d withtwo components: proc ∈ confTisa, the VAMP ISA configuration of the processor,and devs ∈ confTdevs, a generalized device configuration.

For an ISA configuration cisa, we introduce the notion of the effective addressea(cisa). In the case that the current instruction of this configuration is a loador store operation, this is the address from which we read or to which we write.In all other cases it is undefined.

Page 81: Verified Linking for Modular Kernel Verification - Institut f¼r

5.2. Combining ISA and Devices 65

The predicate ?store(cisa) will return True, if the current instruction ofcisa is a store operation, and False otherwise. Moreover, val(cisa) determinesthe integer representation of the value to be stored in case that the currentinstruction is a store instruction.

Finally, let DA denote the address range for devices. For an address ad inthis range, did(ad) yields the device id and port(ad) the port encoded by thisaddress. The detailed definition of the above functions can be found in [Tve09]and [Alk09].

We can now define the function mifi ISA, which returns the processor’s deviceinput in a configuration cisa.

mifi ISA(cisa) =(False,False, port(ea(cisa)), [0]) if ea(cisa) ∈ DA ∧ ¬?store(cisa)(False,True, port(ea(cisa)), [val(cisa)]) if ea(cisa) ∈ DA ∧ ?store(cisa)mifi ε otherwise

Furthermore, the function ω(cdevs) computes the external event vector eevfor the processor based on the current device configurations.

The transition function δi&d for the combined ISA and device model hasto distinguish, if the processor or a device are progressing next. Thus, itadditionally takes an oracle, called event, for input, modeled by the option type(D× Eifi)⊥. It returns a successor configuration and potentially an output tothe external environment.

If an event dinext is equal to None, the processor component makes a step,otherwise a device makes a step.

If there is neither external device input nor does the processor access adevice, that is dinext = None and ea(cisa) /∈ DA, the processor performs a localstep. In this case, we use the ISA transition function to obtain an update of thecurrent processor configuration, while there is no output to the environment:

δi&d(ci&d,None) = (δisa(ci&d.proc, ω(ci&d.devs), 032), ci&d.devs, eifoε).

In the case of dinext = None and ea(ci&d.proc) ∈ DA, a processor-devicestep is taken. Then, the device input mifi ISA(ci&d.proc), generated by theprocessor for the device with id did(ea(ci&d.proc)), is used with the internalstep function for devices:

(devs ′, dout int, doutext) =

δintdevs((did := did(ea(ci&d.proc)),mifi ISA(ci&d.proc)), ci&d.devs).

Then, the processor makes a step using the external event vector and output ofthe updated device configuration:

proc′ = δisa(ci&d.proc, ω(devs), dout int).

Finally, the result of a processor-device transition is given through

(proc′, devs ′, doutext).

At last, we consider external device steps, which happen when the event issome dinext, that is there exists environmental input for a device. In this case,

Page 82: Verified Linking for Modular Kernel Verification - Institut f¼r

66 VAMP ISA and Assembler

we use the external device transition function to update the devices,

(devs ′, doutext) = δextdevs(dinext, ci&d.devs),

while the processor component stays unchanged. So, we obtain

δi&d(ci&d, bdinextc) = (ci&d.proc, devs ′, doutext).

For a whole run, that is several steps of the combined model, we introducethe n-step transition function

δni&d : confT i&d × (N→ (D× Eifi)⊥)→ confT i&d × Eifo list .

It takes a combined start configuration ci&d and a sequence of events seq asinputs and returns both the combined configuration after applying the transitionfunction δi&d n times and the external output generated during the computation.We set

δ0i&d(ci&d, seq) = (ci&d, []).

We assume that

(ci&d′, eifos) = δni&d(ci&d, seq)

and

(ci&d′′, doutext) = δi&d(ci&d

′, seq(n+ 1))

to define

δn+1i&d (ci&d, seq) = (ci&d

′′, doutext#eifos).

5.3 Assembler

As mentioned in the introduction to this chapter, there exists a variant of VAMPassembler with devices, too. Yet in CVM, we use assembler as the programmingmodel for user processes, which can only communicate with devices indirectly viathe kernel. Thus, we will introduce here the computational model of assemblerwithout devices.

Compared to the instruction set architecture, the assembly model abstractsfrom several machine features:

• We exchange bit vectors in favor of naturals for addresses and integersfor values. Instructions are encoded by an abstract data type Instr .

• Address translation is not visible any longer. This means that any as-sembler computation either simulates a system mode execution or a usermode execution with already established memory virtualization.

• Interrupts are not visible any longer.

Page 83: Verified Linking for Modular Kernel Verification - Institut f¼r

5.3. Assembler 67

5.3.1 Abstracting from Bit Vectors

VAMP ISA uses 32-bit vectors for data and instructions, and 30-bit vectorsfor addresses. For the first two ones, we can either interpret these vectors asbinary numbers or as 2’s complement numbers. For the use at assembly level,we define two functions to convert between bit vectors and naturals/integers:toint : B32 → Z and tonat : B30 → N with

toint(bv) =

31∑i=0

bv[i] · 2i if bv[31] = 0

−231 +30∑i=0

bv[i] · 2i otherwise

tonat(bv) =29∑i=0

bv[i] · 2i

Any program is encoded as data in the main memory, thus given throughinteger values. We define a function toinstr : Z→ Instr ∪{undef }. Since not allintegers encode meaningful instructions, toinstr(x) either returns an instructionin Instr or undef . A detailed introduction of all VAMP instructions can befound in [MP00, Bey05, Dal06, Tve09].

In the following, we refrain from the explicit use of the above definedconversion functions, if not needed for understanding.

Definition 5.2 (Assembler Configuration) An assembler configuration ismodeled by the record type confTasm with components:

• pcp ∈ N and dpc ∈ N the two program counters,

• gprs : Z list , the general purpose register file,

• sprs : Z list , the special purpose register file,

• mm : N→ Z, the memory, a mapping from addresses to data.

5.3.2 Assembler Transitions

The VAMP assembler transition function

δasm : confTasm → confTasm

takes a configuration casm and returns its successor configuration casm′. As for

the VAMP ISA, assembler transitions are defined by a case distinction over thecurrent instruction, whose location is given by the delayed program counterof the current configuration casm: toinstr(casm.mm(casm.dpc)). The transitionfunction is defined explicitly in [Alk09].

A detail worth mentioning are the semantics for the two instructions dealingwith interrupt handling. The trap instruction is used to raise the trap interrupt,a mechanism which is used in CVM to invoke kernel primitives (see Sect. 6.2.2).The rfe instruction returns from interrupt handling, from where we proceed asdefined by the interrupt. Both instructions have effects that are not visible atthe assembly level and are therefore modeled by dummy transitions.

Page 84: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 85: Verified Linking for Modular Kernel Verification - Institut f¼r

There are no significant bugs in our releasedsoftware that any significant number of users wantfixed.

Bill Gates (1995)

Chapter 6

Communicating Virtual Machines

Contents6.1 CVM Configuration . . . . . . . . . . . . . . . . . . . . . 70

6.2 Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . 70

6.3 CVM Primitives . . . . . . . . . . . . . . . . . . . . . . . 77

6.4 Initial CVM Configuration and n-Step Transition Function 98

6.5 CVM Implementation and Abstract Linking . . . . . . . 99

Communicating Virtual Machines—short: CVM —are a hardware-abstrac-ting framework for microkernel programmers [IT08]. With CVM we introducea computational model for a fixed number of user processes—modeled byassembler machines (cf. Chapter 5)—that can interact with each other and witha fixed number of devices (cf. Chapter 4) via an abstract kernel, modeled by aC0 machine (cf. Chapter 3).

CVM does not support shared memory—neither between processes norbetween a process and a device—, so all communication is handled by theabstract kernel. This means that we assume an isolated memory for each userprocess (memory separation).

As implied by the term ‘abstract’, this kernel allows to abstract from low-level details like interrupt handling, memory virtualization, kernel entry andexit, and the actual implementation of the so-called CVM primitives (see Sect.6.3).

From the viewpoint of the upper layers in a system stack, a microkernelprogrammer can take advantage of these primitives in order to implementmore sophisticated kernel calls and task scheduling. In Verisoft, CVM is theplatform for two microkernels: VAMOS, the kernel of the academic system (cf.Sect. 1.1), and OLOS, a real-time operating system for automotive systems[IK05, KP07b, Kna08].

The rest of this chapter is structured in the following way: in Sect. 6.1, wewill make use of the computational models introduced in the chapters before,in order to define CVM configurations. In Sect. 6.2, we will discuss the CVMtransition function in detail. The formal specification of the CVM primitives ispresented in Sect. 6.3.

In Sect. 6.4, we define the initial CVM configuration and the n-step transitionfunction for CVM configurations.

69

Page 86: Verified Linking for Modular Kernel Verification - Institut f¼r

70 Communicating Virtual Machines

We obtain a concrete kernel by linking the low-level implementations withthe abstract kernel. We introduce a formalism for linking on the level of C0semantics and use this formalism to describe how to obtain such a concretekernel in Sect. 6.5.

6.1 CVM Configuration

A CVM configuration cCVM is formally modeled by a record type confTCVM

consisting of three components:

• the user process component up ∈ upT ,

• the abstract kernel component kern ∈ kernT ,

• and the device component devs ∈ confTdevs.

The user component stores information about all the user processes, the processthat is to be executed next, and the CVM interrupt mask. For a maximalnumber of user processes pmax, let P = {1, . . . , pmax} denote the set of processids. Then, we model the user component by the record type upT with threefields:

• procs : P → confTasm, mapping of process ids to assembler machineconfigurations encoding these processes;

• cp ∈ P⊥, the current user process field. It is either None or bpidc, withprocs(pid) being the next process to be executed;

• mask ∈ B32, the CVM interrupt mask, identical for all user processes.

Kernel components are modeled by the type kernT , which has components:

• a type name environment tn ∈ tenv,

• a procedure table pt ∈ proctableT

• and the C0 configuration encoding the abstract kernel, kconf ∈ confTC0 .

6.2 Transitions

In CVM, all components take turns in execution, that is either the kernelprogresses, or a user process, or a device. As already introduced in Sect. 5.2,we use an oracle to determine, whose turn is next.

Hence, a CVM transition δCVM(cCVM, dinext) takes a CVM configurationcCVM and an external device input dinext as parameters, yielding either a nextstate bcCVM

′c or None, if a run-time error has occurred, and potentially adevice output to the environment.

Assuming the first case and depending on dinext and cCVM.up.cp, either oneof the devices, a user process or the abstract kernel make a step:

• If dinext 6= None, i.e., there is some external device input, we computethe new generalized device configuration and update cCVM with it. Inthis case also some device output to the environment might be generated.

Page 87: Verified Linking for Modular Kernel Verification - Institut f¼r

6.2. Transitions 71

init

kernel user step

primitive

reset

¬?Prim ∧¬ ?Return

?Prim

JISR

¬JISR

wait

Return ∧cp = 0

JISR

¬JISR

Return ∧cp ≠ 0

Figure 6.1: CVM Control Flow

• If there is no external device input and the current process identifiercCVM.up.cp is None, the abstract kernel makes a step;

• Otherwise, the user process cCVM.up.procs(the(cCVM.up.cp)) progresses.

Operating systems provide idle mechanisms for times without user or kernelcomputation. This is either realized by an non-terminating idle process or viaa dedicated wait state of the kernel, called kernel wait . As long as there is nointerrupt, the kernel stays in this state. Otherwise, the kernel is re-entered asdescribed in Sect. 6.2.2.

CVM primitives are functions, which allow to alter the state of the hardware,of a device, of the kernel, or of a user process. Though their implementation isnot part of the abstract kernel, the effects of primitive execution are part of theCVM semantics.

In Fig. 6.1, we show the control flow for the kernel and user process case,respectively. Using this control flow, we we will now present in detail the formaldefinition for the CVM transition function

δCVM : confTCVM × (D× Eifi)⊥ → (confTCVM × (D× Eifo) list)⊥

in the above order: first device steps, then kernel steps, and finally user steps.

6.2.1 Device Steps

For an input (cCVM, dinext) with dinext = b(did, eifi)c, we use the generalizedexternal device transition function and obtain

(devs′, doutext) = δextdevs(the(dinext), cCVM.devs).

Page 88: Verified Linking for Modular Kernel Verification - Institut f¼r

72 Communicating Virtual Machines

Thus, the next configuration of CVM is given by

δCVM(cCVM, dinext) = b(cCVM[devs := devs′], doutext)c.

6.2.2 Kernel Steps

At first, we will define, how the abstract kernel is started. This happens eachtime, when we enter kernel mode, that is after a reset or when an unmaskedinterrupt has occurred during user process execution. In both cases, theabstract kernel main function—the abstract kernel dispatcher—is called, takingtwo unsigned integers as arguments: the exception cause and the exception data.Basically, these two values are taken from the hardware, though the concretekernel, which is handling kernel entry, is providing them to the abstract kernel.This mechanism is described in detail in [Tsy09]. On entering the kernel, weset the current process identifier to None and zero the interrupt mask:

cCVM.up.cp = NonecCVM.up.mask = 032.

Then, kernel execution can take different paths: when the program reststarts with a primitive call, we execute this primitive. If the execution of thekernel has terminated—the program rest is Skip—we leave the kernel and eitherswitch to kernel wait or to a user process. Otherwise, we proceed by executingthe next C0 statement of the abstract kernel’s program rest.

For better readability, we will use the following abbreviations for the re-mainder of the section:

• kconf for cCVM.kern.kconf ,

• te for cCVM.kern.te, and

• pt for cCVM.kern.pt.

Starting the Abstract Kernel

We define a function startak : confTC0 × N × N → confTC0 that is used instarting the abstract kernel. We assume that the abstract kernel’s dispatcherfunction is named dispatcher kernel, taking two parameters of type Unsgnd .

In order to capture the kernel start by the means of the C0 small-stepsemantics, we assume the existence of a dummy function1 with a single localvariable abs kernel res and two parameters eca and edata, exception causeand exception data, as provided by the hardware and delivered by the concretekernel. Its body merely consists of a function call for the abstract kernel

1We will explain in Sect. 9.3 how to get rid of this artificial construct.

Page 89: Verified Linking for Modular Kernel Verification - Institut f¼r

6.2. Transitions 73

dispatcher followed by a Return. We denote this body by s and set:

s =Comp(

SCall(abs kernel res,

dispatcher kernel,

Var(eca),Var(edata)),Return(Lit(Prim(Unsgnd 0))))−

At the starting point of the abstract kernel, we initialize the local memorystack of the abstract kernel with the corresponding memory frame for thisdummy function. This frame has an undefined memory content and its symboltable merely contains the variable name and type of abs kernel res. We definethis initial memory frame mf init formally as follows:

mf init = [ ct = undef ,st = [(abs kernel res,UnsgndT ),

(eca,UnsgndT ),(edata,UnsgndT )],

init = ∅].

The rest of the memory, that is the kernel heap and kernel global memory, stayunchanged compared to the point when we left the kernel last time. So, weobtain an updated memory m′ with

m′ = kconf .m[lm := [mf init, undef ]].

The complete C0 configuration when starting the abstract kernel is thengiven through

startak(kconf , eca, edata) = kconf [m := m′, prog := s].

Leaving the Kernel

When execution of the abstract kernel has terminated—kconf .prog = Skip—we switch from the kernel either to kernel wait or to a user process. This isdetermined by the return value of the abstract kernel dispatcher. If the valueof this variable is a valid user process id, i.e.,

pid = valueg(kconf .m, gvarlm(0, abs kernel res)) ∈ P,

we will switch to the corresponding user process cCVM.up.procs(pid), otherwise,we switch to kernel wait.

Switching to a user process is handled in the following way: Given a process idpid. Then, we analyze the corresponding user process vm = cCVM.up.procs(pid).If this user process has memory,2 that is vm.sprs!ptl ≥ 0, we make it the current

2In CVM, a ptl value of −1 by convention says that the process has no memory (cf.Sect. 5.1.1)—in contrast to regular hardware machines, where this denotes the maximummemory.

Page 90: Verified Linking for Modular Kernel Verification - Institut f¼r

74 Communicating Virtual Machines

user process by setting the current process field up′ = cCVM.up[cp := pid ].Otherwise, a run-time error has occurred and we return None:

δCVM(cCVM, [ ]) =

{b(cCVM[up := up′], [ ])c if vm.sprs!ptl ≥ 0None otherwise

.

Note, that the absence of run-time errors for a given abstract kernel is acorrectness criterion the implementer would usually want to prove.

Kernel Wait

Since our kernel is non-preemptive, that is non-interruptible, usually all inter-rupts are masked during kernel execution. When switching to kernel wait, weenable the external interrupts, so that the kernel can be ‘woken up’. Then, weset the program rest to an assembler statement with an empty instruction list:kconf ′ := kconf [prog := Asm[ ]].

The next state is given through

δCVM(cCVM, [ ]) = b(cCVM.kern[kconf := kconf ′], [ ])c.

In CVM kernel wait, the abstract kernel loops on a single assembler statementwith an empty instruction list until an external interrupt occurs. We do notmodel this step using the C0A semantics, but treat it as a special case of theCVM transition function. Thus, the assembler statement merely serves as anindicator and since this is the only place in the abstract kernel with an assemblerstatement, we can easily use the program rest to determine, if we are in kernelwait: hd(kconf .prog) = Asm[ ].

If a device interrupt has occurred, i.e.,

?irq =∨i∈D

irqdevs(cCVM.devs, i) = 1,

we start the abstract kernel as described above and set

kern′ := startak(kconf , eca, 0).

Note that devices do not generate exception data when raising an interrupt.Otherwise, we leave the CVM configuration as it is:

δCVM(cCVM, [ ]) =

{b(cCVM[kern := cCVM.[kern := kern ′], [ ])c if ?irq = 1b(cCVM, [ ])c otherwise

Kernel Primitive Steps

A user process invokes one of the the primitives using the trap instruction.On ISA level, this instruction raises the trap interrupt, and its argument—stored in the special purpose register sprs(edata)—specifies the number ofthe corresponding primitive. Since all primitives are numbered uniquely, theabstract kernel dispatcher uses the edata argument to determine the appropriateprimitive to be executed.

The actual implementation of these functions is part of the concrete kernel(cf. Sect. 6.5.1). The abstract kernel merely declares these primitives, i.e.specifies their signature but without implementation.

Page 91: Verified Linking for Modular Kernel Verification - Institut f¼r

6.2. Transitions 75

Let Prim ⊂ Σ+ denote the set of all primitive function names. For ap ∈ Prim, an abstract kernel program rest that starts with ESCall(vn, p, plist)denotes a primitive call. The semantics of each of these primitives is specified bya function ps, that takes a CVM configuration cCVM and a number of argumentscorresponding to the parameter list plist . It returns either an updated CVMconfiguration and possibly some device output to the external environment, orNone in the error case.

In Sect. 6.3, we give a complete definition of all primitive specificationfunctions. We define:

δ(cCVM, [ ]) = ps(cCVM, plist).

C0 Kernel Steps

If the program rest of the kernel is neither Skip, nor an assembler statement,nor a CVM primitive call, the next CVM state is given through the standardC0 transition function. In the error case—δC0(te, pt, kconf ) = None—we setδCVM(cCVM, [ ]) = None. Else, we have δC0(te, pt, kconf ) = bkconf ′c and define

δCVM(cCVM, [ ]) = b(cCVM[kern := cCVM.kern[kconf := kconf ′]], [ ])c

6.2.3 User Steps

We perform a user step when there is no external input for the devices andthe current process field is not equal to None. We denote this value withpid = the(cCVM.up.cp) and the user process with vm = cCVM.up.procs(pid).Moreover we abbreviate the interrupt mask with mask = cCVM.up.mask .

During user execution, either an interrupt occurs or not. In the latter case,we step the current user process using the assembler transition function asdescribed in Sect. 5.3. In the interrupt case, we update the current process fieldand start the abstract kernel.

Dealing with interrupts at this level is a little tricky, since the assemblysemantics has no notion of them. We define a function mca : confTasm ×confTdevs ×B32 → B

32 that takes a user process, a device configuration, and aCVM interrupt mask and returns a bit vector, the so-called interrupt vectoraccording to Table 6.1. For a user process vm, a device configuration cdevs, wedefine mca ′ as follows:

• mca ′[0] = 0, because the reset case is treated differently.

• If the current instruction is undecodable, i.e. toinstr(vm.mm(vm.dpc)) =undef , or if it is a privileged instruction, we set mca ′[1] = 1.

• For vm.dpc mod 4 6= 0, we have an instruction misalignment, and setmca ′[2] = 1.

• In the case of data misalignment, we set mca ′[3] = 1.

• We set mca ′[4] = 1, if the delayed PC points to a region outside of theuser process’ memory: vm.dpc ≥ (vm.sprs.PTL+ 1) · 212.

• The attempt to perform a memory operation on an address outside theuser process’ memory results in mca ′[5] = 1.

Page 92: Verified Linking for Modular Kernel Verification - Institut f¼r

76 Communicating Virtual Machines

• If the dpc points to an arithmetic operation, which would yield an integeroverflow, we set mca ′[6] = 1.

• If the current instruction is a trap instruction, we set mca ′[7] = 1.

• Currently, we do not take care of floating point exceptions and thus setmca ′[8] to mca ′[12] to 0.

• For the device interrupts, we take the corresponding information from theCVM device configuration, i.e. for 13 ≤ i ≤ 20, we set:

mca ′[i] = irqdev(cdevs, i− 13).

• The remaining bits are set to 0.

Finally, we apply the mask to mca ′ by bitwise conjunction and obtain for0 ≤ i ≤ 31:

mca(vm, cdevs,mask)[i] = mca ′[i] ∧mask [i].

Note that if the instruction fetch already fails, i.e. in the cases of instructionmisalignment, undecodable instructions, or a dpc outside the code region—weignore the other interrupt causes.

Moreover, we define a predicate JISR : B32 → B and set

JISR(mca) =31∨i=0

mca[i].

User Step without Interrupts

In the case of no interrupt, i.e. JISR(mca(vm, cCVM.devs, cCVM.up.mask)) =0, we use the assembler next state function to obtain the new user processconfiguration vm′ := δasm(vm) and denote the updated user component with

up′ = cCVM.up[procs := cCVM.up.procs(pid := vm′)].

Since we continue with the current user process, no other CVM component hasto be updated and we define

δCVM(cCVM, [ ]) = b(cCVM[up := up′], [ ])c.

User Step with Interrupts

If an interrupt has occurred, that is

JISR(mca(vm, cCVM.devs, cCVM.up.mask)) = 1,

we first determine the interrupt level according to Table 6.1 as following:

il = min{j.mca(vm, cCVM.devs, cCVM.up.mask)[j] = 1}.

If il is a continue interrupt, we step the user process and set vm′ = δasm(vm).Otherwise, we leave the configuration as it is, i.e. vm′ = vm.

Page 93: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 77

Bit Name Meaning ext? mask? cont?0 reset Reset yes no no1 ill Illegal Instruction no no no2 imal Instruction Misalignment no no no3 dmal Data Misalignment no no no4 pff Page Fault on Fetch no no no5 pfls Page Fault on Load/Store no no no6 ovf Integer Overflow no yes yes7 trap Trap Instruction no no yes

8–12 Floating Point Interrupts no yes yes13–20 Device Interrupts yes yes yes21–31 not used

Table 6.1: VAMP Interrupts

Additionally, we start the abstract kernel as described in Sect. 6.2.2, pro-viding parameters eca = mca(vm, cCVM.devs, cCVM.up.mask) and edata asprovided by the hardware. We denote the new kernel configuration with kern′

with

kern′ = cCVM.kern[kconf := startak(cCVM.kern.kconf , eca, edata)]

and the updated user component with up′ with

up′ = cCVM.up

[procs := cCVM.up.procs(pid := vm′)cp := None

]The transition is then given through

δ(cCVM, [ ]) =⌊(

cCVM

[up := up′

kern := kern′

], [ ])⌋

6.3 CVM Primitives

In this section, we will define the specification functions for the CVM primitives.We will first introduce some auxiliary functions, which will facilitate thesedefinitions (cf. Sect. 6.3.1).

We have divided the CVM primitives in groups: those dealing with processmanagement, e.g., the allocation and deallocation of user process memory,are presented in Sect. 6.3.2 (see Table 6.2). The second group contains theseprimitives used for inter-process communication (see Sect. 6.3.3). Device com-munication will be treated in Sect. 6.3.4. Primitives dealing with the kernelmemory are introduced in Sect. 6.3.5.

Finally, all primitives that do not fit into one of the groups mentioned beforeare bundled in Sect. 6.3.6.

6.3.1 Auxiliary Functions

readmm : (N→ Z)×N×N→ Z list takes an assembler memory, a start addressand a length as input. readmm(mm, sa, am) returns the values stored at the

Page 94: Verified Linking for Modular Kernel Verification - Institut f¼r

78 Communicating Virtual Machines

Name Function

reset resets a user process to initial stateclone clones a user processalloc allocates new virtual memory for a user processfree frees virtual memory for a user process

copy copies memory from one process to anotherGetGPR returns the content of a general purpose registerSetGPR sets the content of a general purpose registerSetMask sets the interrupt maskGetWord read a single word from user memorySetWord write a single word into user memory

VirtIOIn allows user processes to read from a deviceVirtIOOut allows user processes to write to a deviceWordIn as VirtIOIn, but only for one wordWordOut as VirtIOOut , but only for one word

P2Vcopy copies kernel memory to user memoryV2Pcopy copies user memory to kernel memoryPhysIOIn read from a device to kernel memoryPhysIOOut write from kernel memory to user memoryPhysIOInRange as PhysIOIn, but on a range of portsPhysIOOutRange as PhysIOOut , but on a range of ports

LoadOS load an operating system into memory

Table 6.2: CVM Primitives

addresses from sa to sa+ am− 1 in the memory mm. We define recursively:

readmm(mm, sa, 0) = [ ]readmm(mm, sa, i+ 1) = readmm(mm, sa, i)@[mm(sa+ i)]

Symmetrically, writemm : (N → Z) × N × Z list → (N → Z) takesan assembler memory, a start address, and a sequence of integers as input.writemm(mm, sa, data) returns the updated memory, where the values storedat addresses sa to sa+ |data|−1 are overwritten with the corresponding valuesfrom data. We define recursively:

writemm(mm, sa, [ ]) = mm

writemm(mm, sa, x#xs) = writemm(mm(sa := x), sa+ 1, xs)

When we copy data from one memory to the other, we will use the functioncopymm : (N→ Z)×N× (N→ Z)×N×N→ (N→ Z), which combines theabove two functions. The first memory determines the destination to wherewe write and the second memory determines the source from which we readthe data. The other parameters specify the start addresses and the amount ofwords to be copied. Let data = readmm(mms, sas, am) denote the data to becopied. Then the result of an execution of copymm(mmd, sad,mms, sas, am) iswritemm(mmd, sad, data).

Page 95: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 79

Some primitives deal with devices and can thus produce output to theexternal environment. To convert this output to the type specified in the CVMtransition function, we define toeifo : D× Eifo list → (D× Eifo) list and set:

toeifo(did , [ ]) = [ ]toeifo(did , x#xs) = (did , x)#toeifo(did , xs)

For some primitives, we have to compute the amount of memory in wordsused by a single user process. We define the function sizemm : confTasm → N

and set for a given machine vm:

sizemm(vm) = (vm.sprs!PTL+ 1) · 210.

The predicate ?inmem : confTasm ×N→ B takes a virtual machine and anaddress as input and returns True, if the address lies within the memory ofthat user process and False otherwise. We define

?inmem(vm, ad) =

{True if ad < sizemm(vm)False otherwise

.

Primitive calls in the concrete kernel are C0 function calls and returningfrom them is realized in the way defined in Sect. 3.5.3 for SCall and Returnstatements, respectively. Yet, in the abstract kernel, we deal with externalfunction calls (ESCall), which are merely modeled by dummy transitions inthe C0 semantics. To make the effects of a primitive call visible in the C0configuration of the abstract kernel, we thus have to take care explicitly of both(i) the update of the return destination and (ii) the update of the program rest.

Given an abstract kernel component kern with

hd(kern.kconf .prog) = ESCall(vn, prim, e1, . . . , en)

denoting the call of a primitive with name prim. The corresponding g-variablefor the return destination is then

rd = lval(kern.tn, kern.kconf .m,Var(vn)).

For a return value v, we obtain the updated memory by

m′ = updmm(kern.kconf .m, rd, v).

The program rest of kconf is of the form

kern.kconf .prog = (ESCall(vn, prim, e1, . . . , en))#pr.

The new program rest is then simply given by prog′ := Skip#pr. We combinethese two steps in one function and define

updret(kern, v) = kern[

kconf := kern.kconf[

m := m′

prog := prog′

]]

Page 96: Verified Linking for Modular Kernel Verification - Institut f¼r

80 Communicating Virtual Machines

6.3.2 Process Management

Reset

To reset a user process given by an assembler machine back into its initial state,we define the primitive

reset : confTCVM ×N→ (confTCVM × (D× Eifo) list)⊥,

which takes a single parameter pid denoting the process id of the process tobe reset.

Resetting a user process means:

• The page table length register PTL is set to −1, i.e. the machine has nomemory.

• The mode register mode is set to 1, that is the machine runs in user mode.

• The dpc and pcp are set to their initial values 0 and 4.

• All other general and special purpose registers are zeroed.

For a primitive call reset(cCVM, pid), we proceed as follows:Let vm denote the user process to be reset, i.e. vm = cCVM.up.procs(pid).

We denote with gprs′ = map(vm.gprs, λx.0) and sprs′ = map(vm.sprs, λx.0)the zeroed register files of vm. We can now define the reset virtual machinevm′ as follows:

vm′ = vm

gprs := gprs′

sprs := sprs′[PTL := −1,mode := 1]dpc := 0pcp := 4

Using this, we update the user process component: up′ = cCVM.up[procs :=cCVM.up.procs(pid := vm′)].

Successful execution of the reset primitive depends on the input value pid.If pid is not a valid user process id, i.e. pid /∈ P, execution fails and we setreset(cCVM, pid) = None. Otherwise, we return the new CVM configurationwith an updated kernel and user process component:

reset(cCVM, pid) =⌊(

cCVM

[up := up′,kern := updret(cCVM.kern, 0)

], [ ])⌋

Clone

The primitive

clone : confTCVM ×N×N→ (confTCVM × (D× Eifo) list)⊥

takes besides a CVM configuration two process ids as input. The first iddetermines the process to be cloned—the cloner—and the second one determinesthe process which is replaced by an identical copy of the first one—the clonee.

For a primitive call clone(cCVM, pid1, pid2) we proceed in the following way.Let cloner = cCVM.up.procs(pid1) denote the cloner process. Now we obtain

Page 97: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 81

a new user process component up′ by merely overwriting the clonee with thecloner, i.e.

up′ = cCVM.up[procs := cCVM.up.procs(pid2 := cloner)].

Successful execution of the clone primitive depends on the following require-ments:

• both process ids have to be valid, i.e pid1 ∈ P and pid2 ∈ P,

• the clonee process has to have no memory, i.e.

(cCVM.up.procs(pid2))!PTL = −1.

• Let TVM denote a constant, which stores the total available virtualmemory in words. Then, the overall amount of memory used after thecloning must not exceed this value:

pmax∑i=1

sizemm(cCVM.up.procs(i)) + sizemm(cloner) ≤ TVM .

Violating one or more of these preconditions means execution of the primitivefails. In this case we set clone(cCVM, pid1, pid2) = None. Otherwise we set

clone(cCVM, pid1, pid2) =⌊(

cCVM

[up := up′,kern := updret(cCVM.kern, 0)

], [ ])⌋

Alloc

CVM allows to dynamically allocate more memory for user processes by meansof the alloc : confTCVM ×N ×N → (confTCVM × (D × Eifo) list)⊥ primitive.alloc takes two arguments: the first one is the id of the user process, of whichthe memory is to be enlarged, and the second one is the number of pages bywhich that memory is to be extended.

For a primitive call alloc(cCVM, pid, pn) we proceed in the following way.Let vm = cCVM.up.procs(pid) denote the corresponding user process. At first,we blank the new part of the process’ memory with zeros. For this purpose, wecompute the first word address in this new part by sa = ((vm.sprs!PTL) + 1) ·1024. The new memory of the user process is then given through

mm′ := copymm(vm.mm, sa, (λx.0), 0, pn · 1024).

In a second step, we adjust the page table length register to the new value:

sprs′ := vm.sprs(PTL := (vm.sprs!PTL) + pn).

The updated user process is then given through

vm′ := vm

[sprs := sprs′,mm := mm′

]Allocation of new memory will fail, if one of the following conditions is

violated:

Page 98: Verified Linking for Modular Kernel Verification - Institut f¼r

82 Communicating Virtual Machines

• pid has to be valid, i.e. pid ∈ P, and

• the new memory size after allocation does not exceed the total availablevirtual memory:

pmax∑i=1

sizemm(cCVM.up.procs(i)) + pn · 1024 ≤ TVM .

In the case of failure, we set alloc(cCVM, pid, pn) = None. Otherwise, we define

alloc(cCVM, pid, pn) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

Free

The corresponding primitive to alloc for freeing memory is free : confTCVM ×N×N→ (confTCVM×(D×Eifo) list)⊥. Similarly, free takes two inputs besidesthe CVM configuration: the id of the user process, of which the memory is tobe downsized, and the number of pages by which the memory is to be reduced.

Let free(cCVM, pid, pn) denote a primitive call to de-allocate pn pages fromuser process vm = cCVM.up.proc(pid). If the process has less memory pagesthen pn, we set the page table length register to −1, i.e. vm has no morememory. Otherwise, we decrease the page table length register by pn:

sprs′ := vm.sprs

[PTL :=

{−1 if vm.sprs!PTL < pn

vm.sprs!PTL− pn otherwise

]

We abbreviate the user process with updated register file by vm′ = vm[sprs :=sprs′].

Execution of free fails, if the process id parameter is not valid, i.e. pid /∈ P.In this case we set free(cCVM, pid, pn) = None. Otherwise, we define

free(cCVM, pid, pn) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

6.3.3 Inter-Process Communication

Copy

Since CVM does not offer support for shared memory, all communicationbetween user processes has to be made explicitly via the primitive

copy : confTCVM ×N×N×N×N×N→ (confTCVM × (D× Eifo) list)⊥.

The copy(cCVM, pids, pidd, sas, sad, am) primitive takes five arguments besidesthe CVM configuration:

• two user process ids, namely pids for the source process and pidd for thedestination process,

Page 99: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 83

• two start addresses, sas for the source process and sad for the destinationprocess, and

• the amount of words am to be copied.

With mms = cCVM.up(pids).mm, we denote the source memory and the des-tination memory with mmd = cCVM.up.procs(pidd).mm. We define the newdestination memory mm′d as

mm′d = copymm(mms, sas,mmd, sad, am)

and denote the user process with the updated memory by vm′d := vmd[mm :=mm′d].

Execution of the primitive call will fail, if one or more of the followingrequirements are not met:

• both process ids have to be valid and not equal:

pids ∈ P ∧ pidd ∈ P ∧ pids 6= pidd;

• both end addresses lie within the memory of their processes:

?inmem(vms, sas + am− 1)∧?inmem(vmd, sad + am− 1).

In the case of failure, we set copy(cCVM, pids, sas, pidd, sad, am) = None. Oth-erwise, we set

copy(pids, sas, pidd, sad, am) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pidd := vm′d)],kern := updret(cCVM.kern, 0)

], [ ])⌋

GetGPR

The

GetGPR : confTCVM ×N×N→ (confTCVM × (D× Eifo) list)⊥

primitive can be used to read out a single general purpose register of a userprocess. Thus, GetGPR(cCVM, pid, r) takes a process id pid and a registernumber r as input. Let val = cCVM.up.procs(pid).gprs!r denote the contentof the register specified. This value will then be used to update the returndestination in the kernel.

Execution of the primitive call will fail, if one or more of the followingconditions are violated:

• pid is a valid process id, i.e. pid ∈ P, and

• r is a valid register number: r < 32.

In the failure case, we set GetGPR(cCVM, pid, r) = None. Otherwise we set

GetGPR(cCVM, pid, r) = b(cCVM[kern := updret(cCVM.kern, val)], [ ])c

Page 100: Verified Linking for Modular Kernel Verification - Institut f¼r

84 Communicating Virtual Machines

SetGPR

Correspondingly, we define the primitive

SetGPR : confTCVM ×N×N× Z→ (confTCVM × (D× Eifo) list)⊥,

which allows to write a value into a general purpose register of a user process.SetGPR(cCVM, pid, r, val) takes parameters pid to determine the user processinvolved in the call, r to specify the register to be written into, and finally valfor the corresponding value. Let vm = cCVM.up.procs(pid) be the user process.Then we define the new general purpose register file by

gprs′ = vm.gprs[r := val].

We abbreviate the updated user process with vm′ = vm[gprs := gprs′].The primitive call must comply with two preconditions. First, pid must

be a valid process id, i.e. pid ∈ P. Moreover, r must be a valid registernumber, i.e. r < 32, and −231 ≤ val < 231. In the case of failure, we setSetGPR(cCVM, pid, r, v) = None. Otherwise, we set

SetGPR(cCVM, pid, r, val) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

SetMask

User processes can mask device interrupts, such that their execution won’t beinterrupted. This mechanism is handled via the mask field mask of the userprocess component, which can be set using the primitive

SetMask : confTCVM ×N→ (confTCVM × (D× Eifo) list)⊥.

SetMask(cCVM,mask) takes a parameter mask, which defines the new maskto be set. Since the corresponding field of the CVM user process componentstores a 32-bit number, we first have to convert the natural number into a bitvector mask ′. There are two requirements on the successful execution of theprimitive.

• The length of the bit string must be 32 bits, i.e. |mask ′| = 32;

• since we only allow processes to mask device interrupts, the lower 13 bits

have to be 0:12∨i=0

mask′[i] = 0.

As before, we set SetMask(cCVM,mask) = None in the failure case and otherwise

SetMask(cCVM,mask) =⌊

(cCVM

[up := cCVM.up[mask := mask ′],kern := updret(cCVM.kern, 0)

], [ ])⌋

GetWord

Similar to the GetGPR primitive, the primitive

GetWord : confTCVM ×N×N→ (confTCVM × (D× Eifo) list)⊥

Page 101: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 85

allows to read a single word from a user process’ memory. The obtained valueis then used to update the return destination of the kernel.

For a primitive call GetWord(cCVM, pid, ad), let cCVM denote a CVM config-uration, pid a user process id, and ad an address in the memory of this process.Then we denote the value stored at this address by val and define

val = cCVM.up.proc(pid).mm(ad).

Execution of the primitive call will fail, if one of the following requirementsis not met:

• the process id has to be valid: pid ∈ P, and

• the address has to be in the address range of this process’ memory:?inmem(cCVM.up.procs(pid), ad) = True.

In the failure case, we define GetWord(cCVM, pid, ad) = None. Otherwise, weset

GetWord(cCVM, pid, ad) = b(cCVM[kern := updret(cCVM.kern, val)], [ ])c

SetWord

CVM allows to write a single word into the memory of a user process using theprimitive

SetWord : confTCVM ×N×N× Z→ (confTCVM × (D× Eifo) list)⊥.

A call of this primitive SetWord(cCVM, pid, ad, val) takes parameters pidto specify the user process and ad for the address, where the word val is tobe stored. With mm′, we denote the updated memory of the user processcCVM.up.procs(pid). We define mm′ = writemm(cCVM.up(pid), ad, val) andwrite vm′ = cCVM.up.procs(pid)[mm := mm′] for the virtual machine with theupdated memory.

The above primitive call will fail, if one of the following conditions is violated:

• pid has to denote a valid user process, i.e. pid ∈ P, and

• the address ad has to lie within the address range of the user process’memory: ?inmem(cCVM.up.procs(pid), ad) = True.

• val has to be in the range of −231 ≤ val < 231.

If execution fails, we define SetWord(cCVM, pid, ad, val) = None. Otherwise,we set

SetWord(cCVM, pid, ad, val) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

6.3.4 User Processes and Devices

In this subsection, we will deal with primitives that establish the communicationbetween a user process and a device. Naming goes back to the fact that userprocesses work with virtual memory.

Page 102: Verified Linking for Modular Kernel Verification - Institut f¼r

86 Communicating Virtual Machines

VirtIOIn

The CVM primitive

VirtIOIn : confTCVM ×N×N×N×N×N→ (confTCVM × (D×Eifo) list)⊥

can be used by user processes to read data word-wise from a single device port.A primitive call VirtIOIn(cCVM, did, port, pid, sa, am) takes besides a CVM

configuration the following arguments:

• did specifies the device id and

• port determines the corresponding port of the device, from which we planto read.

• pid specifies the process id of the user process and

• sa the start address in its memory where we want to write to.

• Finally, am is the amount of words we are going to read and write,respectively.

At first, we construct the necessary input dinint ∈ Mifi with

dinint =

rep = True,wr = False,port = port ,data = replicate(am, 0),did = did

Note, that only the length of the fifth component of the generalized device inputtype Mifi matters, since we do not write to the device but merely read from it.Then, we use this device input in the generic internal step function for devicesand denote with the tuple (devs′, dout int, eifos) the output of the device:

(devs′, dout int, eifos) = δintdevs(dinint, cdevs).

The dout int component of the device output is the data, which we haveread. We write this data into the memory of the specified user process:

vm′ = cCVM.up.procs(pid)[mm := writemm(mm, sa, dout int)]

and setup′ = cCVM.up[procs := cCVM.up.procs(pid := vm′)].

Successful execution of the primitive call relies on the following requirements:

• pid has to be a valid user process id, i.e. pid ∈ P,

• did has to be a valid device id, i.e. did ∈ D,

• port has to be within the range of valid ports, i.e. port < portmax, and

• sa has to be within the memory of the user process, i.e.

?inmem(cCVM.up.procs(pid), sa).

Page 103: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 87

Again, in the failure case, we define VirtIOIn(cCVM, did, port, pid, sa, am) =None. Otherwise, we set

VirtIOIn(cCVM, did, port, pid, sa, am) =cCVM

up := up′,kern := updret(cCVM.kern, 0),devs := devs′

, toeifo(did, eifos)

VirtIOOut

The correspondent primitive for user processes to write data to a single deviceport is

VirtIOOut : confTCVM×N×N×N×N×N→ (confTCVM× (D×Eifo) list)⊥.

Let VirtIOOut(cCVM, did, port, pid, sa, am) denote a call of the primitivewith these parameters:

• did specifies the device id and

• port determines the corresponding port of the device, to which we planto write.

• pid specifies the process id of the user process, and

• sa the start address in its memory from where we want to read.

• Finally, am is the amount of words we are going to read and write,respectively.

First, we read am many words from the user process’ memory:

data = readmm(cCVM.up.procs(pid).mm, sa, am).

Subsequently, we create the corresponding device input dinint ∈ Mifi usingdata:

dinint =

rep = True,wr = True,port = port ,data = data,did = did

and obtain the device output using the device transition function:

(devs′, dout int, eifos) = δintdevs(dinint, cCVM.devs).

Execution of the primitive call will fail, if one or more of the followingrequirements are not met:

• pid has to be a valid user process id, i.e. pid ∈ P,

• did has to be a valid device id, i.e. did ∈ D, and

• port has to be port within the range of valid ports, i.e. port < portmax,and

Page 104: Verified Linking for Modular Kernel Verification - Institut f¼r

88 Communicating Virtual Machines

• finally, sa has to be in the memory of the user process:

?inmem(cCVM.up.procs(pid), sa).

In the failure case, we define VirtIOOut(cCVM, did, port, pid, sa, am) = None.Otherwise, we set

VirtIOOut(cCVM, did, port, pid, sa, am) =⌊(cCVM

[kern := updret(cCVM.kern, 0),devs := devs′

], toeifo(did, eifos)

)⌋

WordIn

The

WordIn : confTCVM ×N×N→ (confTCVM × (D× Eifo) list)⊥

primitive reads a single word from a device and uses the value read as the returnvalue of the function call.

A call of this primitive WordIn(cCVM, did, port) takes a device id did asinput, specifying the device, and a port port from which to read. We use thedevice input dinint ∈ Mifi in order to use it in the generalized device transitionfunction and define

dinint =

rep = False,wr = False,port = port ,data = replicate(1, 0),did = did

.

The result of the device transition function is then given through

(devs′, dout int, eifos) = δintdevs(dinint, cCVM.devs).

The following conditions have to be fulfilled for a successful primitive call:

• did has to be a valid device id, i.e. did ∈ D, and

• port has to be port within the range of valid ports, i.e. port < portmax.

Violating one or both of them leads to a run-time error and we define

WordIn(cCVM, did, port) = None.

Otherwise, we set

WordIn(cCVM, did, port) =⌊(cCVM

[kern := updret(cCVM.kern, dout int),devs := devs′

], toeifo(did , eifos)

)⌋

Page 105: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 89

WordOut

Like the primitive in the section before, there is a corresponding one to write asingle word to a device:

WordOut : confTCVM ×N×N×N→ (confTCVM × (D× Eifo) list)⊥.

Let WordOut(cCVM, did, port, val) denote a call of the primitive, where didspecifies the device and port the port to which to write, and val the value tobe written. Then we define the device input dinint by

dinint =

rep = False,wr = True,port = port ,data = [val ],did = did

.We can now use this device input in the device transition function and obtain

(devs′, dout int, eifos) = δintdevs(dinint, cCVM.devs).

The requirements for the successful execution of the primitive call are asfollows:

• did has to be a valid device id, i.e. did ∈ D, and

• port has to be port within the range of valid ports, i.e. port < portmax.

In the failure case, we define WordOut(cCVM, did, port, val) = None. Otherwise,we set

WordOut(cCVM, did, port, val) =⌊(cCVM

[kern := updret(cCVM.kern, 0),devs := devs′

], toeifo(did , eifos)

)⌋6.3.5 Dealing with Physical Memory

Originally, CVM was not meant to support primitives dealing explicitly withphysical memory. Yet, while developing real-time operating system in Verisoft’sautomotive subproject [IK05, KP07b], it turned out necessary to introduceprimitives that allow to

• copy data from user (virtual) memory into the kernel (physical) memoryand vice versa, and

• copy data from devices into the kernel memory and vice versa.

Since the abstract kernel is given by a C0 machine configuration, the datatransfer between it and user processes and/or devices becomes a little moretricky. Thus, we will have to introduce some more auxiliary functions that dealwith the necessary data conversion.

First, we define a function mem2int : (N → mcellT ) → Z to convert thecontent of an integer memory cell (see Sect. 3.3.1) to an integer value. For amemory cell mc = Int(i), we set mem2int(mc) = i. Observe, that this function

Page 106: Verified Linking for Modular Kernel Verification - Institut f¼r

90 Communicating Virtual Machines

only produces meaningful results, if it is applied to memory cells with thecorresponding type stored in them.

Then, we need a function that allows to convert the value of a C0 variableinto a list of integer values as needed in the assembler layer. We name thisfunction c2i : (N → mcellT ) ×N → Z list , which takes two parameters: thefirst one is the memory content to be converted and the second one determinesthe size of the type to be converted minus one, We define:

c2i(cont, 0) = [mem2int(cont(0))]

for the base case and for the inductive case

c2i(cont, i+ 1) = c2i(cont, i)@[mem2int(cont(i+ 1))]

Furthermore, we have to convert part of a user process’ memory—i.e. anlist of integers—into a C0 memory content. Thus, we define the functioni2c : Z list → (N→ mcellT ) as follows:

i2c(xs) = λi.

{Int(xs!i) if i < |xs|undef otherwise

Due to readability in the following, we will abbreviate for a given CVMconfiguration cCVM

• the type name environment cCVM.kern.tn with tn,

• the procedure table cCVM.kern.pt with pt, and

• the actual C0 configuration of the abstract kernel cCVM.kern.kconf withcC0 .

P2Vcopy

The primitive

P2Vcopy : confTCVM × stmt ×N×N→ (confTCVM × (D× Eifo) list)⊥

allows to copy the value of a variable of the abstract kernel into the memory ofa user process.

A call of this primitive P2Vcopy(cCVM, prim, pid, sa) takes the followingparameters:

• A CVM configuration cCVM,

• prim, the actual C0 statement of this call—i.e. a statement of the formESCall(vn, P2Vcopy, plist),

• the user process id pid, specifying the process, and

• sa for the start address in its memory.

We assume that the variable, whose value is to be stored, is located in thefirst position of the parameter list of the statement prim, i.e. at plist !0, and

Page 107: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 91

that it is a pointer to an array with elements of type Int . The value of thereferenced array type variable is then given through

val = the(rval(tn, cC0 .m,Deref (plist !0)))

and the number of its elements is equal to its abstract type size, i.e.

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !0)))).

Now, we can compute data = c2i(val, am), i.e. the data which we want tostore. We update the corresponding user process’ main memory, where the newuser process configuration is defined as

vm′ = cCVM.up(pid)[mm := writemm(cCVM.up(pid).mm, sa, data].

The execution of this primitive will fail, if one of the following conditions isviolated:

• pid has to be a valid process id, i.e. pid ∈ P, and

• the end address sa+ am− 1 has to be in the memory range of that userprocess: ?inmem(cCVM.up(pid), (sa+ am− 1)) = True.

In the failure case, we define P2Vcopy(cCVM, prim, pid, sa) = None. Other-wise, we set

P2Vcopy(cCVM, prim, pid, sa) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

V2Pcopy

The copying of data from user processes to the abstract kernel is handled bythe primitive

V2Pcopy : confTCVM × stmt ×N×N→ (confTCVM × Eifo list)⊥.

Let V2Pcopy(cCVM, prim, pid, sa) denote a call of this primitive with pa-rameters

• cCVM, a CVM configuration,

• prim, the C0 statement of the call, i.e. a statement of form

ESCall(vn, V2Pcopy, plist),

• a process id pid and a start address sa.

The C0 variable, whose value we want to update, is an integer array, giventhrough a pointer in position three of the parameter list of prim, i.e. plist !2,while the first two parameters specify process id and start address. We accessthis variable via the left value of the corresponding expression evaluation:

var = the(lval(tn, cC0 .m,Deref (plist !2)))

Page 108: Verified Linking for Modular Kernel Verification - Institut f¼r

92 Communicating Virtual Machines

and its abstract size by

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !2)))).

We can now read the data from the user process’ memory

data = readmm(cCVM.up(pid), sa, am)

and convert it into a C0 memory content val = i2c(data). Finally, we updatethe abstract kernel’s C0 variable and obtain a new memory

updmm(cC0 .m, var, val) = bm′c.

We denote the new abstract kernel component of the CVM configuration bykern ′ and define:

kern ′ = cCVM.kern[kconf := cC0 [m := m′]].

There are certain requirements for a successful execution of the primitive:

• pid has to be a valid process id, i.e. pid ∈ P, and

• the end address sa+ am− 1 has to be in the memory range of that userprocess: ?inmem(cCVM.up.procs(pid), (sa+ am− 1)) = True.

In the failure case, we define V2Pcopy(cCVM, prim, pid, sa) = None. Otherwise,we set

V2Pcopy(cCVM, prim, pid, sa) = ⌊(cCVM

[kern := updret(kern ′, 0)

], [ ])⌋

PhysIOIn

In order to copy data read from a single device port into a variable of theabstract kernel, we introduce the primitive

PhysIOIn : confTCVM × stmt ×N×N→ (confTCVM × (D× Eifo) list)⊥.

Let PhysIOIn(cCVM, prim, did, port) denote a call of this primitive with thefollowing arguments:

• cCVM, a CVM configuration,

• prim, the C0 statement of the call, i.e. a statement of form

ESCall(vn, PhysIOIn, plist),

• a device id did and a port port.

As for the other primitives dealing with physical memory, the amount of datain words to be copied is determined by the abstract size of the kernel variable,in which we want to copy the data. We assume that the third parameter ofthe actual statement prim—i.e. plist !2—is a pointer expression referencing the

Page 109: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 93

corresponding variable of integer array type. We refer to this variable via theresult of the corresponding expression left evaluation

var = the(lval(tn, cC0 .m,Deref (plist !2)))

and its abstract size is

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !2)))).

The abstract size am of this kernel variable determines, how many wordswe are going to read from the device. We use this information to build theappropriate device input

dinint =

rep = True,wr = False,port = port ,data = replicate(am, 0),did = did

Let (devs′, dout int, eifos) denote the output of the device transition function,

i.e.(devs′, dout int, eifos) = δint

devs(dinint, cCVM.devs),

where dout int is the data we have read. We now construct the C0 memorycontent val = i2c(dout int) and use it to update the kernel memory:

m′ = updmm(cC0 .m, var, val).

We denote the new abstract kernel component of the CVM configuration bykern ′ and define:

kern ′ = cCVM.kern[kconf := cC0 [m := m′]].

Successful execution of the primitive call depends on the following require-ments:

• did has to denote a valid device id, that is did ∈ D, and

• port has to be a valid port, i.e. port < portmax.

In the failure case, we define PhysIOIn(cCVM, prim, did, port) = None.Otherwise, we set

PhysIOIn(cCVM, prim, did, port) =⌊(cCVM

[devs := devs′,kern := updret(kern ′, 0)

], toeifo(did , eifos)

)⌋PhysIOInRange

The primitive

PhysIOInRange : confTCVM × stmt ×N×N→ (confTCVM × Eifo list)⊥

does basically the same as the PhysIOIn primitive, but instead of reading froma single device port, we read from a range of consecutive ports.

Let PhysIOInRange(cCVM, prim, did, port) denote a call of this primitivewith the following arguments:

Page 110: Verified Linking for Modular Kernel Verification - Institut f¼r

94 Communicating Virtual Machines

• cCVM, a CVM configuration,

• prim, the C0 statement of the call, i.e. a statement of form

ESCall(vn, PhysIOInRange, plist),

• a device id did and a port port.

As for the other primitives dealing with physical memory, the amount of datain words to be copied is determined by the abstract size of the kernel variable,in which we want to copy the data. We require that the third parameter ofthe actual statement prim—i.e. plist !2—is a pointer expression referencing thecorresponding variable of integer array type (the first two parameters specifydevice id and port). We refer to this variable via the result of the correspondingexpression left evaluation

var = the(lval(tn, cC0 .m,Deref (plist !2)))

and its abstract size is

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !2)))).

The abstract size am of the kernel variables determines, how many wordswe are going to read from the device. We use this information to build theappropriate device input

dinint =

rep = False,wr = False,port = port ,data = replicate(am, 0),did = did

Let (devs′, dout int, eifos) denote the output of the device transition function,

i.e.(devs′, dout int, eifos) = δint

devs(dinint, cCVM.devs),

where dout int is the data we have read. We now construct the C0 memorycontent val = i2c(dout int) and use it to update the kernel memory: m′ =updmm(cC0 .m, var, val). We denote the new abstract kernel component of theCVM configuration by kern ′ and define:

kern ′ = cCVM.kern[kconf := cC0 [m := m′]].

Successful execution of the primitive call depends on the following require-ments:

• did has to denote a valid device id, that is did ∈ D, and

• the last port from which we are going to read has still to be a valid port,i.e. port+ am− 1 < portmax.

In the failure case, we define PhysIOIn(cCVM, prim, did, port) = None.Otherwise, we set

PhysIOIn(cCVM, prim, did, port) =⌊(cCVM

[devs := devs′,kern := updret(kern ′, 0)

], toeifo(did, eifos)

)⌋

Page 111: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 95

PhysIOOut

Symmetrically to the two primitives defined before, we introduce the primitive

PhysIOOut : confTCVM × stmt ×N×N→ (confTCVM × (D× Eifo) list)⊥,

which will take the value of an abstract kernel variable and write it to one ofthe devices. Here, data is written word-wise to a single port.

Let PhysIOOut(cCVM, prim, did, port) denote a call of this primitive withthe following arguments:

• cCVM, a CVM configuration,

• prim, the C0 statement of the call, i.e. a statement of form

ESCall(vn, PhysIOOut, plist),

• a device id did and a port port.

We assume that the variable, whose content we are to copy, is of an integerarray type and that it is given through a pointer at the first position of theparameter list in prim, i.e. plist !0. Thus, we obtain the value val of this variableby

val = the(rval(tn, c.m,Deref (plist !0))),

where the corresponding abstract type size defines the numbers of words towrite to the device:

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !0)))).

We build the appropriate device input

dinint =

rep = True,wr = True,port = port ,data = c2i(val, am),did = did

and use it in the step function for devices to obtain the output

(devs′, dout int, eifos) = δintdevs(dinint, cCVM.devs).

The execution of the primitive will fail, if one or more of the followingconditions are violated:

• did has to denote a valid device id, that is did ∈ D, and

• port has to be a valid port, i.e. port < portmax.

In the failure case, we define PhysIOOut(cCVM, prim, did, port) = None. Oth-erwise, we set

PhysIOOut(cCVM, prim, did, port) =⌊(cCVM

[devs := devs′,kern := updret(cCVM.kern, 0)

], toeifo(did , eifos)

)⌋

Page 112: Verified Linking for Modular Kernel Verification - Institut f¼r

96 Communicating Virtual Machines

PhysIOOutRange

As with the PhysIOIn and PhysIOInRange primitive, we define

PhysIOOutRange : confTCVM×stmt×N×N→ (confTCVM×(D×Eifo) list)⊥,

which will take the value of an abstract kernel variable and write it to one ofthe devices. Here, data is written word-wise to a range of ports.

Let PhysIOOutRange(cCVM, prim, did, port) denote a call of this primitivewith the following arguments:

• cCVM, a CVM configuration,

• prim, the C0 statement of the call, i.e. a statement of form

ESCall(vn, PhysIOOutRange, plist),

• a device id did and a port port.

We require that the variable whose content we are to copy, is of an integerarray type and that it is given through a pointer at the first position of theparameter list in prim, i.e. plist !0. Thus, we obtain the value val of this variableby

val = the(rval(tn, cC0 .m,Deref (plist !0))),

where the corresponding abstract type size defines the numbers of words towrite to the device:

am = sizeT (the(type(tn, toplst(cC0 .m), gst(cC0 .m),Deref (plist !0)))).

We build the appropriate device input

dinint =

rep = True,wr = True,port = port ,data = c2i(val, am),did = did

and use it in the step function for devices to obtain the output

(devs′, dout int, eifos) = δintdevs(dinint, cCVM.devs).

The execution of the primitive will fail, if one or more of the followingconditions are violated:

• did has to denote a valid device id, that is did ∈ D, and

• the last port, to which we are going to write, has to be a valid port, i.e.port+ am− 1 < portmax.

In the failure case, we define PhysIOOutRange(cCVM, prim, did, port) = None.Otherwise, we set

PhysIOOutRange(cCVM, prim, did, port) =⌊(cCVM

[devs := devs′,kern := updret(cCVM.kern, 0)

], toeifo(did, eifos)

)⌋

Page 113: Verified Linking for Modular Kernel Verification - Institut f¼r

6.3. CVM Primitives 97

6.3.6 Special Primitives

LoadOS

Since CVM is conceived as a microkernel programmers framework, it lacks crucialfeatures most operating systems offer, e.g., scheduling. Thus, microkernelsusually provide a mechanism that allows for loading an operating system afterthe kernel itself is running and install it as one of the kernels user processes.This operating system is then using the microkernel interface, i.e. the primitives,and its virtualization to realize the services offered to user processes running ontop of the operating system, thus making the microkernel invisible for them.

In CVM, we define for this purpose the primitive

LoadOS : confTCVM ×N×N×N×N→ (confTCVM × (D× Eifo) list)⊥.

A concrete call of this primitive LoadOS (cCVM, am, pid, did, sp) takes the fol-lowing arguments:

• cCVM, a CVM configuration,

• the number of pages am to load,

• the process id pid to whose memory the operating system will be loaded,

• from device did, and

• starting with page sp.

[Alk09] has formally defined a hard disk model, which relies on the ATA/AT-API standard. A hard disk configuration in this model is given by a recordtype with fields for sectors, buffer, and other components of a hard disk. Thetechnical details of such a configuration as well as the corresponding transitionfunction are not required to understand the semantics of the LoadOS primitive.Most importantly, there exists a word-addressable sector memory, given by thecomponent sm : N→ Z, which represents the content of the disk.

The relevant part of the device content—the image—starts at list positionsa = sp · 210 and comprises am many words, i.e.

data = readmm(cCVM.devs(did).sm, sa, am).

Now, we copy the image into the destination user process starting at address 0and obtain a new machine configuration up′ for this process defined as follows:

vm′ = cCVM.up.procs(pid)[mm := writemm(cCVM.up.procs(pid).mm, 0, data)].

Successful execution of the primitive call depends on meeting with thefollowing requirements:

• pid has to denote a valid process id, i.e. pid ∈ P,

• the last address to which we write has to be within the memory of theuser process: ?inmem(cCVM.up.procs(pid), am · 210), and

• the device did has to be a hard disk, i.e. typedev(cCVM.devs(did)) = hd,

Page 114: Verified Linking for Modular Kernel Verification - Institut f¼r

98 Communicating Virtual Machines

• which is big enough to hold the image, that is

|cCVM.devs(did).sm| ≥ (sa+ am) · 210 − 1.

In the failure case, we define LoadOS (cCVM, am, pid, did, sp) = None. Oth-erwise, we set

LoadOS (cCVM, am, pid, did, sp) =⌊(cCVM

[up := cCVM.up[procs := cCVM.up.procs(pid := vm′)],kern := updret(cCVM.kern, 0)

], [ ])⌋

6.4 Initial CVM Configuration and n-Step TransitionFunction

Definition 6.1 (Initial CVM Configuration) To create an initial configu-ration, we define a function initCVM : c0prog × confTdevs → confTCVM, whereboth the abstract kernel program Πabs and the initial device configurationsdevs are parameters of the CVM model. Then, we obtain the correspondinginitial CVM configuration c0CVM = initCVM(Πabs, devs) as follows:

• We set the current process field c0CVM.up.cp to None, since we start witha kernel step.

• The interrupt mask c0CVM .up.mask disables all maskable interrupts butthe one of the timer device.

• The type name environment and procedure table of the kernel componentc0CVM .kern are set to the ones provided by Πabs. The initial configurationof the abstract kernel is obtained as described in Sect. 3.5.1 with thedifference that we do use the abstract kernel dispatcher function insteadof the main function.

• The device configurations in c0CVM.devs are taken from devs. CVMassumes an already established memory virtualization. The hard disk,which is used by the page fault handler (cf. Sect. 6.5.1), is thus not visibleany longer. Instead, we replace this device with a dummy (idle) device.

• The user processes are initialized as described above with the CVMprimitive reset (cf. Sect. 6.3).

Definition 6.2 (n-Step CVM Transition Function) For a whole run, i.e.several steps of the CVM model, we introduce the n-step transition function

δnCVM : confTCVM × (N→ (D× Eifi)⊥)→ (confTCVM × (D× Eifo) list)⊥.

It takes a start configuration and a sequence of events as inputs and returns theresulting state after n steps together with the external output generated andaccumulated during the computation. If any of the steps leads to a run-timeerror, the whole computation yields None.

Page 115: Verified Linking for Modular Kernel Verification - Institut f¼r

6.5. CVM Implementation and Abstract Linking 99

Device I/O

User Processes

Zero Fill Page

Kernel Heap

Kernel Stack

Kernel Data

Kernel Code

Zero Page

System Memory

System Memory

User Memory

Figure 6.2: CVM Memory Map

6.5 CVM Implementation and Abstract Linking

As we have seen above, the abstract kernel is merely a ‘crippled’ C0 program,since it lacks all necessary low-level and hardware-dependent implementations.In order to obtain a compilable—and thus executable—kernel, we have to addcode parts to a given abstract kernel program Πk. We call these code parts theCVM implementation or short ΠCVM.

In the following subsection, we are going to present the necessary additions.A convenient way of merging the two programs is abstract linking. We describethis formalism in the second half of the section.

6.5.1 CVM Implementation

Data Structures

To simulate virtual machines and multi-processing, the CVM implementationhas to maintain certain data structures (cf. Fig. 6.2):

1. In the kernel global memory (or kernel data), we store an array pcb[ ]of so-called process control blocks (PCBs). For each user processes withid pid ∈ P, the process control block pcb[pid] is a structure type withcomponents to store the register files and the program counters of thecorresponding user process.

2. The current process field cCVM.up.cp is stored in a global variable cp oftype UnsgndT , the interrupt mask correspondingly in a global variablemask of the same type. Unlike cCVM.up.cp, which is set to None, we leave

Page 116: Verified Linking for Modular Kernel Verification - Institut f¼r

100 Communicating Virtual Machines

the cp variable unchanged on entering system mode3.

3. Certain general purpose registers are used by the compiler to store thestack and heap pointers [Lei08, LP08]. When we leave kernel executionand start user execution, the corresponding registers of the kernel areoverwritten and are thus lost, when we re-enter the kernel. This causesno problem for the stack pointer, since we re-initialize the kernel stackeach time on entering kernel mode. Yet for the heap, we have to keeptrack of the already allocated data structures in order not to overwritethem. Thus, we declare a global variable kheap, in which we store theend address of the kernel heap when leaving kernel mode, and which weuse to set the appropriate register on entering.

4. We have described the address translation mechanism of the VAMP inSect. 5.1. The required page tables for each user process are stored in anarray ptspace[ ] on the heap.

5. Some additional data structures required by the page fault handler tomanage the physical and swap memory.

Entering and Leaving System Mode

Entering system mode basically consists of three steps. Whenever the kernelstarts to execute, we initialize its program rest with the function init. In allcases but reset, init will take the current user process and store its registersinto the corresponding process control block pcb[cp] (kernel save).

In the next step, the CVM dispatcher cvmdispatch is called with pa-rameters pcb[cp].eca (exception cause), pcb[cp].edpc (exception dpc), andpcb[cp].edata (exception data). Since our kernel is non-preemptive and thusshould not be interrupted, we zero the status register of the hardware, i.e. maskall maskable interrupts.

In case of a page fault interrupt, cvmdispatch will call the page faulthandler. Otherwise, we continue with a call of the abstract kernel dispatcherabs kernel dispatch with parameters pcb[cp].eca and pcb[cp].edata.

To leave system mode in order to start the user process with id pid ∈ P,we set cp=i and restore the process’ register files and program counters usingits process control block pcb[pid] (kernel restore). Finally, we leave kernelexecution with a rfe (return from exception) instruction.

Note that a scheduler is not part of the low-level implementation. Thismeans that the abstract kernel has to provide for an appropriate handling oftimer interrupts.

Page Fault Handler

In Sect. 5.1.1, we have introduced the hardware part of address translation. Onthe software side, page faults triggered by the hardware are handled by a pieceof software called page fault handler.

3This is also the reason why do not relate these two values in the CVM correctnessstatement during kernel execution (cf. Sect. 7.2.6).

Page 117: Verified Linking for Modular Kernel Verification - Institut f¼r

6.5. CVM Implementation and Abstract Linking 101

Whenever a user process step raises such an interrupt, the page fault handleris invoked. Depending on the actual reason for the interrupt, the page faulthandler

• moves pages from physical to swap memory (if the physical memory isfull),

• copies the accessed page from swap memory into physical memory,

• updates the translation function.

Besides the page faults during user steps, the page fault handler might alsobecome involved in certain CVM primitives that access user memories, forinstance copy.

Certain data structures are required by the page fault handler in order toimplement these services.

1. The page tables (cf. Sect. 5.1.1) are both shared by the hardware and thepage fault handler.

2. Swap memory is organized in larger chunks of memory of size 220 words,called big pages. We maintain a big page table to organize these big pagesand to retrieve data from the swap memory.

3. Since the page fault handler is invoked by the kernel, it also has toaccess the process control blocks in order to update register values of userprocesses (e.g., the page table length register on freeing or allocating ofuser memory).

4. Finally, there are several lists for the bookkeeping of free and used pagesin user memory, exclusively used by the page fault handler for instance toimplement page replacement strategies.

The detailed discussion of our page fault handler implementation and correctnessis out of the scope of this work, but is presented in [ASS08, AHL+09, Sta09].

Basically, any implementation that satisfies the virtualization correctnesscriteria (cf. Sect. 7.2.1) can be used. The page fault handler we use in Verisoftis a C0 implementation with small assembler parts used to access the swaphard disk [Con06].

CVM Primitives Implementation

As we have seen in Sect. 6.3, some of these primitives manipulate just theregisters of a process, that is the process control block of that process in theimplementation.

Others, for instance those primitives that copy memory from one processto another or between processes and devices, are accessing data, which is notvisible in C0 structures of the kernel—though they might be visible in those ofthe corresponding user processes. In these cases, we work with assembler codein-lined into C0 code using the Asm statement (cf. Sect. 3.2.3).

In [ST08, Tsy09], implementation and code verification of primitives within-line assembler code is discussed in detail.

Page 118: Verified Linking for Modular Kernel Verification - Institut f¼r

102 Communicating Virtual Machines

6.5.2 Abstract Linking

As we have seen in the section before, we have to put together the abstractkernel program and the CVM implementation to obtain a complete concretekernel program, which can be compiled and executed. As we have seen inSect. 3.2, a C0 program consists of a type name environment, a procedure table,and a global symbol table.

We denote the two programs with Πabs = (teabs, ptabs, gstabs) and ΠCVM =(teCVM, ptCVM, gstCVM), abbreviating the type of a C0 program with c0prog =tenv × proctableT × (Σ+ × ty) list .

We will now define abstract linking, which is a formal way to combine thetwo implementations. In principle, we have do to the following: we mergethe type name environments, tek and teCVM, and the global symbol tables,gstk and gstCVM, of the two programs. Then, we scan the abstract kernelimplementation for external function calls to CVM primitives and replace themwith ordinary function calls. Finally, we merge the two procedure tables.

Not all programs are linkable in the sense that the linked code is compilableor even runnable. The preconditions under which abstract linking is possible,will be introduced in Sect. 7.2.2 when we talk about CVM correctness.

Definition 6.3 (Merging Type Name Environments) Type name envi-ronments have been introduced in Sect. 3.2 as lists of pairs of a type name anda type, i.e. tenv : (Σ+ × ty) list . We merge two type name environments byappending them while taking care not to create redundant list elements.

We define a function link te : tenv × tenv → tenv and set:

link te(ta, [ ]) = ta

link te(ta, x#tb) =

{link te(ta, tb) if x ∈ talink te(x#ta, tb) otherwise

Definition 6.4 (Merging Global Symbol Tables) Information about theglobal variables of a program is stored in a symbol table, which is a listof variable names and their associated type: (Σ+ × ty) list . The functionlinkgst : (Σ+ × ty) list × (Σ+ × ty) list → (Σ+ × ty) list merely appends one listto the other, so we set for two symbol tables gsta and gstb:

linkgst(gsta, gstb) = gsta@gstb.

Procedure Tables

We remember: CVM primitives in the abstract kernel program are declared, butnot defined, that is the corresponding procedure body consists of a single Skipstatement and there are no local variables declared. We call these declared-onlyprocedures external.

The implementation for this procedures comes from the procedure tableptCVM of the CVM implementation. On the other hand, there are externalprocedures in ΠCVM, whose implementation comes from the abstract kernel—forinstance the abstract kernel dispatcher.

The linking of two procedure tables therefore consists of several steps:

Page 119: Verified Linking for Modular Kernel Verification - Institut f¼r

6.5. CVM Implementation and Abstract Linking 103

1. In order to avoid duplicate statement ids, we renumber all non-structuralstatements in both procedure tables to regain uniqueness.

2. Then, we split both procedure tables in two parts: one containing the‘normal’ defined procedures, and one that contains the external ones.

3. The parts with the defined procedures are then simply concatenated.

4. The external procedures are scanned for entries that are defined in theother program to be linked. These entries will then be removed. We dothis for both programs, respectively.

5. A potential rest of external functions is then appended to the alreadylinked defined procedures.

6. All function bodies have to be searched for ESCall statements. If thefunctions called are now defined, we change the statement to a conventionalSCall statement.

Definition 6.5 (Renumbering) The compiler correctness proof in [Lei08]requires a unique numbering for all statements in a C0 program. We introducea very simple way to achieve this unique numbering for two procedure tables tobe merged: in one procedure table, we multiply all statement ids with 2, henceobtaining only even numbered statements. In the other procedure table wemultiply all ids with 2 and add 1, which yields only odd numbered statements.

For this purpose, we define two auxiliary functions odd stmt : stmt → stmtand evenstmt : stmt → stmt , and define recursively:

evenstmt(Skip) = Skipevenstmt(Comp(s1, s2)) = Comp(evenstmt(s1), evenstmt(s2))evenstmt(Ass(e1, e2, i)) = Ass(e1, e2, 2 · i)

evenstmt(Assc(e, l, i)) = Assc(e, l, 2 · i)evenstmt(PAlloc(e, tn, i)) = PAlloc(e, tn, 2 · i)

evenstmt(SCall(vn, fn, pl, i)) = SCall(vn, fn, pl, 2 · i)evenstmt(Return(e, i)) = Return(e, 2 · i)

evenstmt(Ifte(e, s1, s2, i)) = Ifte(e, evenstmt(s1), evenstmt(s2), 2 · i)evenstmt(Loop(e, s, i)) = Loop(e, evenstmt(s), 2 · i)

evenstmt(Ass(il, i)) = Ass(il, 2 · i)evenstmt(XCall(fn, rl, pl, i)) = XCall(fn, rl, pl, 2 · i)

evenstmt(ESCall(vn, fn, pl, i)) = ESCall(vn, fn, pl, 2 · i)

and for odd stmt likewise.Then, we obtain a renumbered procedure table by applying one of the above

functions to all of its function bodies, formally

map(evenstmt, pt),

and for odd statement ids

map(odd stmt, pt),

Page 120: Verified Linking for Modular Kernel Verification - Institut f¼r

104 Communicating Virtual Machines

Definition 6.6 (External Procedures) Given a C0 procedure table pt ∈proctableT (cf. Sect. 3.2.4). Such a procedure table consists of pairs of functionnames and their descriptors. We say a procedure table entry (fn, fdesc) isexternal if

fdesc.lvars = [ ] ∧ fdesc.body = Skip

and denote this with the predicate ?ext : Σ+ × procT → B.The list of external procedures of pt is then given through

ptext = filter(?ext, pt)

and, correspondingly, the list of internal procedures by

ptdef = filter(¬?ext, pt).

Definition 6.7 (Removing External Procedures) For two procedure ta-bles pta and ptb, we want to remove the external functions from ptb, whichare defined in pta. The comparison has to rely on the function name, sincethe associated descriptors do obviously not match. We define the functionrem : proctableT × proctableT → proctableT that returns the remainder of theexternal procedures in ptb. We set:

rem(pta, ptb) = filter((λx. fst(x) /∈ ptadef), ptbext).

Definition 6.8 (Merging Procedure Tables) As mentioned further above,the merged procedure table of pta and ptb consists of the defined functions ofboth procedure tables and those procedures that remain external.

For this purpose, we define a function linkpt : proctableT × proctableT →proctableT . Given two procedure tables pta and ptb, we first renumber thefunction bodies obtaining ptar = even(pta) and ptbr = odd(ptb). Then wemerge the renumbered procedure tables as follows:

linkpt(pta, ptb) = ptadefr @ptbdef

r @rem(ptar, ptbr)@rem(ptbr, ptar).

Updating Procedure Bodies

The last step in abstract linking is the update of the external function callstatements in the procedure bodies of the linked procedure tables.

Definition 6.9 (Updating a Statement Tree) Procedure bodies are madeup by statement trees, so we have to introduce a function that walks these treesrecursively and replaces external function calls where necessary.

For a single external function call, we check if the function called is stillexternal or if it is defined now. In the first case, we leave the statement as it is,in the latter case we replace it with a conventional function call statement.

For most statements, this function computes the identity. Thus, we omitthese cases and define only the interesting ones here.

Given a procedure table pt. For e2i : proctableT × stmt → stmt , we set:

e2i(pt,Comp(s1, s2)) = Comp(e2i(pt, s1), e2i(pt, s2))e2i(pt, Ifte(e, s1, s2)) = Ifte(e, e2i(pt, s1), e2i(pt, s2))

e2i(pt,Loop(e, s) = Loop(e, e2i(pt, s))

Page 121: Verified Linking for Modular Kernel Verification - Institut f¼r

6.5. CVM Implementation and Abstract Linking 105

and for external function calls

e2i(pt,ESCall(vn, fn, plist)) ={ESCall(vn, fn, plist) if fn ∈ {map(fst , ptext)}SCall(vn, fn, plist) otherwise

Definition 6.10 (Updating a Procedure Table) An element (fn, f) of aprocedure table can now be updated using the function introduced above. Wedefine repl : proctableT × (Σ+ × procT )→ Σ+ × procT and set:

repl(pt, (fn, f)) = (fn, f [body := e2i(pt, f.body)]).

To replace all outdated external function calls in one procedure table, wesimply apply this function with map:

replpt(pt) = map(repl pt).

Definition 6.11 (Abstract Linking) We combine all of the functions intro-duced in this section to define the overall abstract linking function

link : c0prog × c0prog → c0prog .

Given two programs Πa = (tea, gsta, pta) and Πb = (teb, gstb, ptb), we set

link(Πa,Πb) =

link te(tea, teb),linkgst(gsta, gstb),

replpt(linkpt(pta, ptb))

Page 122: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 123: Verified Linking for Modular Kernel Verification - Institut f¼r

Part II

CVM Correctness

107

Page 124: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 125: Verified Linking for Modular Kernel Verification - Institut f¼r

Crash programs fail because they are based ontheory that, with nine women pregnant, you canget a baby a month.

Werner von Braun

Chapter 7

CVM Implementation Correctness

Contents7.1 Auxiliary Functions . . . . . . . . . . . . . . . . . . . . . 109

7.2 Abstraction Relations . . . . . . . . . . . . . . . . . . . . 110

7.3 Sketch of a Correctness Theorem . . . . . . . . . . . . . . 128

Overall CVM correctness is a classical simulation theorem, where a VAMPinstruction set architecture with devices (cf. Sect. 5.2) simulates CVM asintroduced in Chapter 6. Single states of the two computational models arerelated by an abstraction relation, which subsumes several other abstractionrelations for the single components of a CVM configuration (cf. Sect. 6.1).

The rest of this chapter is outlined as follows: at first, we introduce themajor individual abstraction relations (Sect. 7.2). Particular attention will begiven to the relation between the abstract and the concrete kernel (Sect. 7.2.2)and the user process relation (Sect. 7.2.1).

Most of them rely on other work, for instance compiler correctness [Lei08,Pet07], page fault handler correctness [Sta09, Alk09], and low-level CVM codecorrectness [Tsy09]. We will provide references for those as necessary, but willnot discuss them in detail again.

Then, we combine the single relations to the overall abstraction relation,which we will use to formulate the overall CVM implementation correctnesstheorem (Sect. 7.3).

7.1 Auxiliary Functions

Frequently, we will access the memory regions, where variables of the concretekernel are stored. For global variables, these addresses are fixed and can bestatically computed using the compiler allocation function as introduced in[Lei08] (see also Fig. 6.2). We write short adci&d(σ) for the corresponding baseaddress of a C0 variable σ in an ISA configuration ci&d, which is assigned bythe compiler.

For the corresponding integer and natural interpretation of the bit vectorstored at the word address adci&d(σ), we use the notation 〈σ〉int and 〈σ〉nat,

109

Page 126: Verified Linking for Modular Kernel Verification - Institut f¼r

110 CVM Implementation Correctness

respectively, and define:

〈σ〉intci&d= toint(ci&d.proc.mm(adci&d(σ)))

〈σ〉natci&d= tonat(ci&d.proc.mm(adci&d(σ)))

7.2 Abstraction Relations

In this section, we will introduce the individual abstraction relations betweenthe various components of CVM and the hardware, the devices, and the concretekernel, respectively.

We start with B, a function relating the CVM user process component to aVAMP ISA configuration.

In a next step, we will treat the relations dealing with the CVM kernelcomponent. Of particular relevance for the rest of this work is konsis, theabstraction relation between the abstract and the concrete kernel.

Then, we will shortly introduce the—rather trivial—relation between thedevices in CVM and those on the hardware layer.

Before we will combine all individual abstraction relations into a singlecomprehensive one, we will introduce the relations for the current processidentifier and the interrupt mask.

7.2.1 User Process Relation

We remember that the user process component up of a CVM configurationcCVM contains a mapping of process ids to assembly machines encoding userprocesses.

The user process relation

B : (P→ confTasm)× confTi&d → B

relates this user process component to its representation in a VAMP ISAconfiguration.

To define the B relation, we define auxiliary functions to extract a userprocess configuration for a process with id pid from a given VAMP ISA withdevices configuration ci&d. Whenever a user process is not active—i.e. thekernel or another user process runs—, its registers are stored in the processcontrol blocks of the concrete kernel (cf. Sect. 6.5.1). The memory of a userprocess is distributed over the physical memory ci&d.proc.mm of the processorand the swap memory of the swap hard disk in ci&d.devs. Thus, we split thistask into two functions, one to extract the process’ memory, one to obtain itsregister file.

The so obtained virtual machine is then compared to the corresponding onestored in the user process component cCVM.up.procs(pid).

Extracting the Register Files

We consider two cases:

Page 127: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 111

1. If the current process is not pid or if we are in system mode, we usethe process control blocks to obtain the register files and the programcounters.

2. Otherwise—i.e. process pid is running—, we take the registers and countersdirectly from the processor component of ci&d.

Let x denote one of the program counters, that is either pcp or dpc. Wedefine a function getx that takes a combined ISA and device configuration, aprocess id and returns the program counter x.

getx(ci&d, pid) =

{ci&d.proc.x if pid = 〈cp〉natci&d

〈pcb[pid].x〉natci&dotherwise

Register files are represented in the assembly semantics as a list of integers.To construct a list of 32 values for a register file x from a processor configuration,we define:

r2lx(ci&d) = toint(ci&d.proc.x(0))# toint(ci&d.proc.x(1))# . . .

# toint(ci&d.proc.x(31))

Similarly, we define p2lx to extract the register file x from the process controlblocks of a process pid in a configuration ci&d.

p2lx(ci&d, pid) = 〈pcb[pid].x[0]〉intci&d

# 〈pcb[pid].x[1]〉intci&d

# . . .

# 〈pcb[pid].x[31]〉intci&d

We combine the above functions to extract the register set x ∈ {gprs, sprs}and set:

getx(ci&d, pid) =

{r2lx(ci&d) if pid = 〈cp〉natci&d

p2lx(ci&d, pid) otherwise

Definition 7.1 (Register Extraction) We can now define the function

get regs : confTi&d ×N→ confTasm

that takes a process id and a ISA and device configuration and returns anassembly configuration for the corresponding process as encoded in the hardware.We set:

get regs(ci&d, pid) =

pcp = getpcp(ci&d, pid),dpc = getdpc(ci&d, pid),gprs = getgprs(ci&d, pid),sprs = getsprs(ci&d, pid),mm = undef

Note, that we leave the memory undefined for now. We will define the extractionof the virtual memory from physical and swap memory in the next paragraph.

Page 128: Verified Linking for Modular Kernel Verification - Institut f¼r

112 CVM Implementation Correctness

Extracting the Memory

As we have seen in Sect. 5.1.1, the valid bit of a page table entry determines, ifthe corresponding page is present in the physical memory or not. Note, that thememory at ISA level is byte-addressed, while it is word-addressed on assemblylevel. Therefore, the following definitions vary slightly from the ones introducedin Sect. 5.1.1, e.g., there is no byte index for a virtual address, but a wordindex.

Definition 7.2 (Page Table Entry Extraction) For a ISA and device con-figuration ci&d and a process id pid, we extract the page table entry for a virtualaddress va using the function pte(ci&d, pid, va). In addition, let va′ denote thebit vector representation for va. Hence, we write px(va′) = va′[29 : 10] for thevirtual page index and wx(va′) = va′[9 : 0] for the word index of va′.

We abbreviate vm = get regs(ci&d, pid) and compute the page table entry as

pte(ci&d, pid, va) = ci&d.proc.mm(vm.sprs!pto · 210 + px(va′)).

The predicate ?valpte takes an address va, a configuration ci&d, and a processid pid. It evaluates to True, if the corresponding page for va is in physicalmemory and False, if it is in the swap memory. We set:

?valpte(ci&d, pid, va) =

{True if pte(ci&d, pid, va)[11] = 1False otherwise

For ?valpte(ci&d, pid, va), we obtain the corresponding physical word addresspa(ci&d, pid, va) = [pte(ci&d, pid, va)[29 : 10];wx(va′)].

The swap memory is located on a hard disk present in the device partci&d.devs of the combined configuration. Address translation for the swapdevice—i.e. relating virtual addresses to sectors on the hard disk—works obvi-ously differently from the address translation introduced in Sect. 5.1.1 and is notpart of the hardware, but the page fault handler. We omit the details of addresstranslation for the swap memory, which can be found in [ASS08] and [Sta09].Instead, we use the function sa(ci&d, pid, va), which yields the swap memoryaddress for a virtual address with ¬?valpte(ci&d, va), in an uninterpreted way.Furthermore, we assume the existence of a swap device with id did swap, a harddisk with a component sm, the swap memory.

Definition 7.3 (Memory Extraction) Now we can extract a virtual mem-ory from the physical and swap memory. We define getmm : confTi&d ×N→(N→ Z) and set

getmm(ci&d, pid) =

λva.toint

{ci&d.proc.mm(pa(ci&d, pid, va)) if ?valpte(ci&d, pid, va)ci&d.devs.did swap.sm(sa(ci&d, pid, va)) otherwise

Definition 7.4 (Equivalence of Assembly Configurations) We say twovirtual machine configurations are equivalent, iff

• both program counters are equal,

Page 129: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 113

• both register files are equal,

• the content of the two memories are equal.

We express this formally using the predicate ∼= and define for two assemblyconfigurations vm1 and vm2:

vm1∼= vm2 = vm1.dpc = vm2.dpc

∧ vm1.pcp = vm2.pcp

∧ (0 ≤ i ≤ 31 −→ vm1.gprs!i = vm2, gprs!i)∧ (0 ≤ i ≤ 31 −→ vm1.sprs!i = vm2.sprs!i)∧ (i < (vm1.sprs.ptl + 1) · 210 −→ vm1.mm(i) = vm2.mm(i))

Definition 7.5 (B-Relation) Combining both the extraction functions forregisters and memory, we obtain a virtual machine vmpid of process pid from agiven ISA and device configuration ci&d as follows:

vmpid(ci&d) = get regs(ci&d, pid)[mm := getmm(ci&d, pid)].

We say, the user process relation B holds for a user process component procs :P→ confTasm and a combined configuration ci&d ∈ confTi&d, iff all user processstored in procs are equal under the equivalence relation defined further aboveto the virtual machines extracted from the hardware, formally:

B(procs, ci&d) = (∀pid ∈ P : procs(pid) ∼= vmpid(ci&d)).

7.2.2 Kernel Relations

Abstract Kernel to Concrete Kernel

The relation between the abstract kernel and the concrete kernel is the mostcomplex one. In order to make it more understandable, we have split it up inseveral smaller relations, each relating specific parts of the abstract kernel totheir counterparts in the concrete kernel. In the rest of this section, we willintroduce these sub-relations and combine them finally to the konsis relation,which connects the abstract to the concrete kernel.

Definition 7.6 (Type Name Environment Consistency) Let tea denotea type name environment. Then, tea is consistent to another type nameenvironment teb, iff all elements in tea are also elements in teb. We definekonsistenvs : tenv × tenv → B and set

konsistenv(tea, teb) = {tea} ⊆ {teb}.

Definition 7.7 (Relating Recursion Depths) Compared to the abstractkernel, the concrete kernel has a certain computational overhead in the sense,that there have already happened some function calls before the call of theabstract kernel dispatcher.

Page 130: Verified Linking for Modular Kernel Verification - Institut f¼r

114 CVM Implementation Correctness

For instance, the dispatcher function, which calls the page fault handler ifnecessary, is only present in the concrete kernel, but not in the abstract one,where page faults are not visible at all.

This means that the local memory stack of the concrete kernel has necessarilymore stack frames than the one of the abstract kernel, in other words: therecursion depth of the first one is larger by a constant offset than the latter one.

We name this offset rdoff and describe the consistency of two local memoryrecursion depths by a predicate konsisrd : memconfT ×memconfT → B. Giventwo memory configurations ma and mb, we define:

konsisrd(ma,mb) = (|mb.lm| = |ma.lm|+ rdoff).

We have to define a relation which connects statements in the abstractkernel with statements in the concrete kernel. This cannot just be ordinaryequality, since the concrete kernel program has been created by abstract linking(cf. Sect. 6.5.2), that is:

• all statements have undergone renumbering, and

• external function calls have been replaced by conventional function calls.

Definition 7.8 (Statement Equivalence) We introduce the equivalence re-lation for statements eqstmt : stmt × stmt → B and define recursively:

eqstmt(Skip, s) = (s = Skip)eqstmt(Ass(e1, e2, i), s) = (∃j : s = Ass(e1, e2, j))

eqstmt(PAlloc(e, tn, i), s) = (∃j : s = PAlloc(e, tn, j))eqstmt(SCall(vn, fn, plist , i), s) = (∃j : s = SCall(vn, fn, plist , j))

eqstmt(ESCall(vn, fn, plist , i), s) = (∃j : s = SCall(vn, fn, plist , j))eqstmt(XCall(fn, elist1, elist2, i), s) = (∃j : s = XCall(fn, elist1, elist2, j))

eqstmt(Asm(il, i), s) = (∃j : s = Asm(il, j))eqstmt(Return(e, i), s) = (∃j : s = Return(e, j))eqstmt(Loop(e, t, i), s) = (∃j, u : eqstmt(t, u) ∧ (s = Loop(e, u, j)))

eqstmt(Ifte(e, t1, t2, i), s) = (∃j, u1, u2 : eqstmt(t1, u1)∧eqstmt(t2, u2) ∧ (s = Ifte(e, u1, u2, j)))

eqstmt(Comp(t1, t2), s) = (∃s1, s2 : eqstmt(t1, s1) ∧ eqstmt(t2, s2)∧(s = Comp(s1, s2)))

We will now use the eqstmt relation to reason about the defined functions inthe abstract and the concrete kernel. For all defined functions in the abstractkernel’s procedure table and the corresponding functions in the concrete kernel,we require that

• the statements in the function bodies are equal under the above relation,and

• the function parameters and local variables are the same.

Page 131: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 115

Definition 7.9 (Relating Procedure Tables) Given two procedure tablespta and ptb. Let (fn, f) denote an entry in the procedure table pta and (fn, f ′)its corresponding entry in ptb with f ′ = the(mapof (ptb, fn)).

We say (fn, f) and (fn, f ′) are equivalent under a predicate konsis func :(Σ+ × procT )× (Σ+ × procT )→ B, iff:

eqstmt(f.body , f ′.body)∧(f.params = f ′.params)∧(f.lvars = f ′.lvars)

We can now connect the two procedure tables pta and ptb using the abovepredicate:

konsispt(pta, ptb) = (∀(fn, f) ∈ {ptdefa }.∃(fn, f ′) ∈ {ptb}.konsis func(f, f ′)).

The predicates defined above are dealing with C0 programs, thus dealingwith static properties. We will now introduce several predicates connectingconfigurations of the abstract kernel with those of the concrete kernel. We startwith the program rest of a C0 configuration.

Definition 7.10 (Program Rest Consistency) For two program rests praand pr b, we define the equivalence predicate konsisprog : stmt × stmt → B andset:

konsisprog = (∀i < |s2l(pra)| : eqstmt(s2l(pra)!i), s2l(prb)!i).

We have to relate g-variables (cf. Sect. 3.3.1) of the abstract kernel tocorresponding g-variables in the concrete kernel. This is pretty easy for globalvariables, which have identical names in the concrete kernel. For local variablesof the abstract kernel, we find the corresponding local variables by adding theoffset rdoff to the local memory frame identifier.

With heap variables, things become a little more tricky. Since the concretekernel can allocate new memory independently from the abstract kernel, we haveto keep track, which abstract heap variables are connected to which concreteheap variables. We realize this bookkeeping by introducing a heap map functionhp : N→ N (cf. Fig. 7.1).

Definition 7.11 (Corresponding g-Variables) We introduce an allocationfunction kalloc : gvar × (N → N) → gvar , which takes a g-variable of theabstract kernel and a heap map function, returning the corresponding g-variablein the concrete kernel. We define inductively:

kalloc(gvargm(σ), hp) = gvargm(σ)kalloc(gvarlm(i, σ), hp) = gvarlm(i+ rdoff, σ)

kalloc(gvarhm(j), hp) = gvarhm(hp(j))kalloc(gvararr (a, i), hp) = gvararr (kalloc(a, hp), i)

kalloc(gvarstr (st, cn), hp) = gvarstr (kalloc(st, hp), cn)

Page 132: Verified Linking for Modular Kernel Verification - Institut f¼r

116 CVM Implementation Correctness

10

9

8

7

6

5

4

3

2

1

0

5

4

3

2

1

0

Abstract Heap Concrete Heap

hp(5) = 10

Figure 7.1: Mapping Abstract to Concrete Heap Variables

In the following sections, we will analyze and relate the initialization, typeand values of g-variables of the abstract kernel and their counterparts in theconcrete kernel. Yet, we will only consider reachable variables. We distinguishbetween reachable named g-variables, i.e. global and local variables, and reach-able nameless g-variables, that is heap variables. Reachability relies on validityof g-variables, which we will therefore introduce first.

Definition 7.12 (Validity of g-Variables) Let sc denote a symbol config-uration. We define the set gvars√ : symbolconft → gvar set of all valid g-variables—i.e. those of well-formed structure—for this symbol configurationinductively. For the base case, we define:

σ ∈ map(fst , sc.gst)gvargm(σ) ∈ gvar√(sc)

σ ∈ map(fst , sc.lst!i) i < |sc.lst |gvarlm(i, σ) ∈ gvar√(sc)

i < |sc.hst |gvarhm(i) ∈ gvar√(sc)

Page 133: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 117

Array variables are valid, if their parent g-variable (i) is valid and (ii) of arraytype, and the index is within the bounds of the array:

a ∈ gvar√(sc) typeg(a, sc) = ArrT (n, t) i < n

gvararr (a, i) ∈ gvar√(sc)

We have similar requirements for the validity of struct variables: the parentg-variable (i) has to be valid itself and (ii) of structure type, and the componentname has to be defined in the structure:

st ∈ gvar√(sc) typeg(st, sc) = StrT (cl) cn ∈ map(fst , cl)

gvarstr (st, cn) ∈ gvar√(sc)

Definition 7.13 (Reachability of g-Variables) Basing on the definition ofvalidity, we will now introduce the notion of reachability of g-variables formally.First, we will define the set of reachable named g-variables reachablenamed :memconfT → gvar set . This is pretty simple, since we only require that thesevariables are named and valid:

g ∈ gvar√(sc(m)) ?named(g)

g ∈ reachablenamed(m)

Second, we continue with the set of reachable nameless g-vars. We could definethis set inductively, too, as presented in [Lei08]. Unfortunately, this notion hasturned out to be too weak for the proof described in Chapter 8. Hence, we willintroduce a stronger notion of reachability for nameless g-variables, which isdefined inductively over a measure: reachablenameless : memconfT ×gvar×N→B.

Let h denote a valid nameless heap variable. For the base case, we look fora valid and initialized global or local variable g of pointer type, whose valuepoints to h. If this is the case, then h is reachable in 0 steps.

h ∈ gvar√(m)¬named(h) named(g) g ∈ gvar√(m) initg(m, g)

valueg(m, g) = Ptr(h) ∃tn. tyg(sc(m), g) = PtrT (tn) ∨NullTreachablenameless(m,h, 0)

If h is reachable in i steps, then it is also reachable in i+ 1 steps:

reachablenameless(m,h, i)reachablenameless(m,h, i+ 1)

If there exists a pointer variable g, which is reachable in i steps, and whosevalue points to h, then h is also reachable in i+ 1 steps:

reachablenameless(m, g, i) valueg(m, g) = Ptr(h)h ∈ gvar√(m) ¬?named(h) ∃tn. tyg(sc(m), g) = PtrT (tn) ∨NullT

reachablenameless(m,h, i+ 1)

Finally, h is reachable in i+ 1 steps, if it is the sub g-variable of g, which itselfis reachable in i steps:

reachablenameless(m, g, i) ¬?named(h) h ∈ gvar√(m) h ∈ subg(g)

reachablenameless(m,h, i+ 1)

Page 134: Verified Linking for Modular Kernel Verification - Institut f¼r

118 CVM Implementation Correctness

Definition 7.14 (Content and Value Equality) In Def. 7.11, we have de-fined how to obtain the corresponding concrete kernel g-variable for an ab-stract kernel g-variable. We will now define, when we consider the values ofsuch a pair of variables to be equivalent. First, we introduce the predicateeqcont : ty × (N → mcellT ) × (N → mcellT ) × (N → N) → B. eqcont takes aC0 type (cf. Sect. 3.2.1) and two memory frame contents (cf. Sect. 3.3.1) alongwith a heap map as arguments. In the following, let hp be a heap map and caand cb two memory contents.

For the basic types, i.e. BoolT , UnsgndT , IntT , and CharT , we simply check,if the content at address 0 is equal in both contents:

eqcont(BoolT ) = λ ca, cb, hp. ca(0) = cb(0)eqcont(UnsgndT ) = λ ca, cb, hp. ca(0) = cb(0)

eqcont(IntT ) = λ ca, cb, hp. ca(0) = cb(0)eqcont(CharT ) = λ ca, cb, hp. ca(0) = cb(0)

For the null pointer type NullT , we proceed similarly and check, if both memorycells at address 0 contain the null pointer literal:

eqcont(NullT ) = λ ca, cb, hp. ca(0) = NullPointer ∧ cb(0) = NullPointer

For the pointer type PtrT (σ), we need a case distinction:

• ca(0) contains a null pointer literal. In this case, this has also to be truefor cb(0).

• Otherwise, ca(0) is of the form Ptr(g), where g is a g-variable of theabstract kernel. Then, cb(0) has to hold a pointer to the correspondingg-variable in the concrete kernel, that is Ptr(kalloc(g, hp)).

We write formally:

eqcont(PtrT (σ)) = λ ca, cb, hp.{cb(0) = NullPointer if ca(0) = NullPointercb(0) = Ptr(kalloc(g, hp)) if ca(0) = Ptr(g)

For an array type ArrT (k, t), with k being the number of elements and t theirtype, we demand that for each array element the eqcont predicate holds. Forsuch an element with index i < k, we compute its offset by off (i, t) = sizeT (t) · i.The relevant parts of the memory frame contents ca and cb are defined asfollows:

c′a(i, t) = λ j.

{ca(off (i, t) + j) if j < sizeT (t)undef otherwise

c′b(j, t) = λ j.

{cb(off (i, t) + j) if j < sizeT (t)undef otherwise

We can now define the equivalence relation for a complete array type:

eqcont(ArrT (k, t)) = λ ca, cb, hp. ∀i < k. eqcont(t, c′a(i, t), c′b(i, t), hp)

Page 135: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 119

Similarly, we proceed with an struct type StrT (cl), where cl denotes a componentlist. For a component (σ, t)—a pair of component name and type—in this list,we obtain its offset by off (σ, cl). The detailed definition of this function can befound in [Lei08]. Again, the relevant parts of the memory frame contents caand cb for such a component are given through

c′a((σ, t), cl) = λ j.

{ca(off (σ, cl) + j) if j < sizeT (t)undef otherwise

c′b((σ, t), cl) = λ j.

{cb(off (σ, cl) + j) if j < sizeT (t)undef otherwise

The equivalence relation for a complete struct type and two contents is thendefined as follows:

eqcont(StrT (cl)) = λ ca, cb, hp.∀(σ, t) ∈ {cl}. eqcont(t, c′a(σ, cl), c′b(σ, cl))

Since we do not want to deal with memory frame contents, but with g-variablesand their values, we will now introduce the predicate

eqval : memconfT × gvar ×memconfT × gvar × (N→ N)→ B,

which bases on the above definitions. Given two g-variables ga and gb and twomemory configurations ma and mb. Then we define:

eqval(ga,ma, gb,mb) = eqcont(typeg(sc(ma), ga), valueg(ma, ga), valueg(mb, gb))

Definition 7.15 (Type & Initialization Equality) Given two g-variablesga and gb, and two memory configurations ma and mb. We introduce twopredicates eqtype : memconfT × gvar × memconfT × gvar → B and eqinit :memconfT×gvar×memconfT×gvar → B, which are valid, if the two variableshave the same type and the same initialization, respectively:

eqtype(ma, ga,mb, gb) = (typeg(sc(ma), ga) = typeg(sc(mb), gb))eqinit(ma, ga,mb, gb) = (initg(ma, ga) = initg(mb, gb))

Definition 7.16 (g-Variable Consistency) We combine now the individualrelations as introduced before to a single relation konsisgvars : memconfT ×memconfT × (N→ N)→ B, which connects variables of the abstract kernel tovariables in the concrete kernel.

Informally, we require the following for a variable x in the abstract kernel:

• if x is reachable, so is kalloc(x, hp) reachable in the concrete kernel;

• the initialization of x and kalloc(x, hp) is equal;

• if we are dealing with an elementary g-variable, we additionally require

– that the types are equal

Page 136: Verified Linking for Modular Kernel Verification - Institut f¼r

120 CVM Implementation Correctness

– and in the case that x is initialized, the values have to be equal, too.

We split konsisgvars in two smaller predicates, one dealing with named g-variables—konsisnamed—and one for nameless ones—konsisnameless. Let ma

and mb denote two C0 memory configurations and hp a heap map. Then, wedefine formally:

konsisnamed(ma,mb, hp) = ∀g.g ∈ reachablenamed(ma) =⇒kalloc(g, hp) ∈ reachablenamed(mb)∧eqinit(ma, g,mb, kalloc(g, hp))∧(?elem(typeg(sc(ma), g)) =⇒

(eqtype(ma, g,mb, kalloc(g, hp))∧(?init(ma, g) =⇒ eqval(g,ma, kalloc(g, hp),mb)))).

Correspondingly, we define for nameless g-variables:

konsisnameless(ma,mb, hp) = ∀g, i.reachablenameless(ma, g, i) =⇒reachablenameless(mb, kalloc(g, hp), i)∧(?elem(typeg(sc(ma), g)) =⇒

eqtype(ma, g,mb, kalloc(g, hp))∧eqval(g,ma, kalloc(g, hp),mb))).

We now combine the above two predicates:

konsisgvars(ma,mb, hp) =konsisnamed(ma,mb, hp)∧konsisnameless(ma,mb, hp)

Definition 7.17 (Symbol Table Consistency) Two global symbol tablesare consistent, iff all elements in the first one are also elements in the secondone. We define konsisgst : memconfT ×memconfT → B and set

konsisgst(ma,mb) = {gst(ma)} ⊆ {gst(mb)}.

Similarly, we define for local symbol tables a predicate konsis lst : memconfT ×memconfT → B. Here, the individual symbol tables of the abstract heap haveto be equivalent to the corresponding ones in the concrete stack, which areshifted by the offset rdoff:

konsis lst(ma,mb) =∀i. i < |sc(ma).lst | =⇒ sc(ma).lst !i = sc(mb).lst !(i+ rdoff)

Finally, we require regarding the heap symbol table that for an abstract heapvariable with index i, the type has to be equivalent to the corresponding concreteheap variable with index hp(i):

konsishst(ma,mb, hp) = ∀i. i < |hst(ma)|=⇒ snd(hst(ma)!i) = snd(hst(mb)!(hp(i))

Page 137: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 121

Definition 7.18 (Return Destination Consistency) On returning from afunction call, the return destination—a g-variable—associated with that func-tion’s stack frame will be updated with the function’s return value. We require,that the return destinations of corresponding stack frames are equivalent underthe kalloc function from Def. 7.11:

konsisreturn(ma,mb, hp) ≡ ∀i.i < |sc.lst(ma)| =⇒snd(mb.lm!(i+ rdoff)) = kalloc(snd(ma.lm!i), hp)

Definition 7.19 (Heap Map Injectivity, Boundedness) We have to ap-ply certain restrictions on our heap map functions. We require that

• for each abstract heap variable, there is exactly one corresponding concreteheap variable, that is injectivity, and

• the result of the heap map function for an abstract heap variable returnsan index within the concrete heap (boundedness).

We introduce two predicates that describe these properties formally. Given aheap map hp and two memory configurations ma and mb, we define:

hmapinj(ma, hp) ≡ ∀i, j. i 6= j ∧ i < |ma.hm| ∧ j < |ma.hm|=⇒ hp(i) 6= hp(j)

hmapbound(ma,mb, hp) = ∀i. i < |ma.hm| =⇒ hp(i) < |mb.hm|

Definition 7.20 As we have seen in Sect. 3.5.3, the result of a memory alloca-tion depends on the availability of heap memory. If there is no more memoryavailable for a given type t in a configuration ca, then the heap memory staysunchanged a null pointer will be assigned.

In order to keep the two kernels in sync, we need to state an invariant, whichguarantees that if there is enough memory in the abstract kernel, then there isalso enough heap memory available in the concrete kernel.

We define this invariant formally as:

heapinv(ca.m, cb.m) ≡?heap(ca.m, t) = True =⇒ ?heap(cb.m, t) = True.

The justification of this invariant is not obvious, since the concrete kernelmight use much more heap memory than the abstract kernel. Yet, our CVMlow-level implementation allocates memory in a very restricted way, namelyonly for the page tables. In particular, there is now memory allocation in theCVM primitives or even in recursive functions. So, the total heap consumptionof the CVM implementation is in fact statically computable.

Nevertheless, if the low-level implementation is changed or replaced, thecorresponding heap memory consumption has to be reconsidered in order todischarge the heapinv invariant.

Page 138: Verified Linking for Modular Kernel Verification - Institut f¼r

122 CVM Implementation Correctness

The above definition of the heap invariant is sufficient in the sense thatthe concrete kernel always uses at least as much heap memory as the abstractkernel. Thus, the scenario that abstract heap allocation fails while it succeedsin the concrete kernel can never happen.

Definition 7.21 (Konsis) We have now all necessary predicates to combinethem into one definition describing how the abstract and concrete kernel areconnected. We name this combining predicate konsis : tenv × proctableT ×confT × tenv × proctableT × confT → B and describe it formally:

konsis(tea, pta, ca, teb, ptb, cb) =∃hp. konsistenv(tea, teb)∧ konsispt(pta, ptb)∧ konsisprog(ca.prog , cb.prog)∧ konsisrd(ca.m, cb.m)∧ konsisgvars(ca.m, cb.m, hp)∧ konsisgst(ca.m, cb.m)∧ konsis lst(ca.m, cb.m)∧ konsishst(ca.m, cb.m, hp)∧ konsisreturn(ca.m, cb.m, hp)∧ hmapinv(ca.m, hp)∧ hmapbound(ca.m, cb.m, hp)∧ heapinv(ca.m, cb.m)

Concrete Kernel to ISA and Concrete Kernel Invariants

As we have learned in Chapter 6, the abstract kernel is an incomplete programwhich cannot be compiled and thus cannot be directly related to the hardware.Instead, we do this with the concrete kernel using the compiler correctnessrelation.

In [Lei08], the original version of the compiler correctness theorem wasformulated between the C0 small-step layer and the assembly layer. [Tsy09]has extended this work and has defined a relation sim-c0 -isa connecting a C0configuration with a VAMP ISA configuration.

By further adding requirements on the validity of the C0 configuration andthe structure of the heap and global memory, [Tsy09] has obtained the relation

kernel -sim-c0 -isa : confTC0 × confTC0 × confTi&d × (gvar → N)→ B.

Using this definition, the concrete kernel implementation, the C0 configura-tion encoding the page fault handler and the one encoding the concrete kernelto a ISA configuration can now be related to each other, using an allocationfunction mapping g-variables to their base address in the hardware.

This relation is not used in our proofs, but needed to present an overallcorrectness theorem sketch for CVM. [Tsy09] gives the full formal definitions ofkernel -sim-c0 -isa and uses it in her proofs.

Page 139: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 123

Finally, the page fault handler configuration on small-step level has to beconnected to the concrete kernel. In particular, the active and free lists used inthe page fault handler for memory management as well as the process controlblocks have to be well-formed and have corresponding values to those in theconcrete kernel. [Sta09] has described this by the predicate

concr -kernel -inv : confTC0 × confTC0 → B.

Weak Relations

The abstraction relations, which we have introduced in the sections before,are too strong in certain cases. For instance, when a user process is makingprogress, the local memory stack and the program rest of the abstract kernelare empty.

Hence, we have to formulate a weaker relation between the abstract and theconcrete kernel and name it konsisweak. In particular, we omit

• the program rest consistency, since there is no abstract program restduring user execution, and

• the invariants on local memories and symbol tables as well as the recursiondepth relation for the same reason.

The static properties, i.e. type name, procedure table and global symbol tableconsistency, still have to hold, as well as the properties of the heap and theg-variables.

Definition 7.22 (Weak Konsis) Let tea, teb denote two type name environ-ments, pta, ptb two procedure tables and ca, cb two C0 configurations. Theformal definition of the weaker konsis predicate

konsisweak : tenv × proctableT × confTC0 × tenv × proctableT × confTC0 → B

is then as follows:

konsisweak(tea, pta, ca, teb, ptb, cb) =∃hp. konsistenv(tea, teb)∧ konsispt(pta, ptb)∧ konsisgvars(m(ca),m(cb), hp)∧ konsisgst(m(ca),m(cb))∧ konsishst(m(ca),m(cb), hp)∧ hmapinv(m(ca), hp)∧ hmapbound(m(ca),m(cb), hp)

Furthermore, the kernel -sim-c0 -isa relation is also too strong during userexecution. For instance, the hardware registers are those of the user process,which obviously affects control and register consistency in the overall compilerconsistency (cf. [Lei08, Sect. 8.2]). Thus we do not relate the whole concretekernel to the ISA, but consider only its global and heap memory content and its

Page 140: Verified Linking for Modular Kernel Verification - Institut f¼r

124 CVM Implementation Correctness

heap symbol table (remember: there is no abstract local memory stack duringuser execution). A weaker form of this relation has been formulated by [Tsy09]:

weak -sim-c0 -isa : confTC0 × (N→ mcellT )× (N→ mcellT )×(Σ× ty) list × confTi&d × (gvar → N)→ B

Combining the Kernel Relations

Definition 7.23 ((Weak) Kernel Relation) All of the relations defined be-fore can now be combined into an overall kernel relation. Given a hardwareconfiguration ci&d and an allocation function alloc. For a CVM configurationcCVM, we abbreviate as follows: teabs for cCVM.kern.tn, ptabs for cCVM.kern.pt,and cC0 abs for cCVM.kern.kconf . Furthermore we abbreviate the concrete ker-nel program with Πk = (tek, ptk, gstk). Then there exist two C0 small-stepconfigurations for the page fault handler and the concrete kernel, such that theconcrete kernel to ISA and concrete kernel invariants as well as the relationbetween the abstract and the concrete kernel hold.

Formally, we define:

kernel -rel(Πk, cCVM.kern, ci&d, alloc) =∃pfhss, cC0 k.

kernel -sim-c0 -isa(pfhss, cC0 k, ci&d, alloc)∧ concr -kernel -inv(pfhss, cC0 k)∧ konsis(teabs, ptabs, cC0 abs, tek, ptk, cC0 k)

For the weaker kernel relation weak -kernel -rel , we use the weaker forms ofthe corresponding predicates. Moreover, we omit the concrete kernel invariantsdefined in concr -kernel -inv and require that

• the first heap variable gvarhm(0), which represents the page tables, residesat address heap-base, and

• if the current process identifier denotes some process id, this processhas some allocated memory corresponding the process control blocks asmaintained by the page fault handler.

We define this weaker relation formally:

weak -kernel -rel(Πk, cCVM.kern, ci&d, alloc, cCVM.up.cp) =∃pfhss, cC0 k.

weak -sim-c0 -isa(pfhss, cC0 k.m.gm.cont ,cC0 k.m.hm.cont , hst(cC0 k.m), ci&d, alloc)∧ konsisweak(teabs, ptabs, cC0 abs, tek, ptk, cC0 k)∧ alloc(gvarhm(0)) = heap-base∧ cCVM.up.cp = bpidc =⇒ 0 ≤ pfhss.abs-pcbs-ss[pid ].ptl)

Page 141: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 125

Linkable Programs

We have described in Sect. 6.5.2 how to obtain a concrete kernel program bylinking the low-level CVM implementation to the abstract kernel program.

Obviously, abstract linking does not produce meaningful programs for arbi-trary input in the sense that the obtained code is runnable and/or compilable.Thus, we restrict the set of pairs of programs that are linkable by introducing apredicate

linkable : c0prog × c0prog → B,

which takes two programs for input and evaluates to True, if these two programsare linkable and False otherwise.

Most of our requirements base on the validity of C0 programs and configu-rations. We refer to [Lei08, Sect. 5.2] for the full definitions of the individualvalidity predicates and describe their intuitive meaning here instead.

Informally, we require that

• the two type name environments are valid and that no type name isdeclared twice with two different types,

• both symbol tables gsta and gstb are valid,

• after linking, no external function calls are left,

• all internal functions are valid functions, and finally

• only one program has in-line assembly parts.

A type name environment is valid, if all type names are pairwise distinctand their associated types are valid.

A type is valid, if (i) it is a basic type, that is BoolT , IntT , UnsgndT

or CharT , (ii) it is a pointer PtrT (tn) and tn is defined in the type nameenvironment, (iii) it is a struct and all component names are pairwise distinctand their types are valid, too, (iv) it is a non-empty array type with a validelement type. We abbreviate the set of all valid type name environments byvalid tenv.

A symbol table is valid, if all variable names in it are pairwise distinct andeach type associated with a variable name is valid, too. For a given type nameenvironment te, we refer by valid st(te) to the set of valid symbol tables.

Let pt denote a procedure table, te a type name environment and gst a globalsymbol table. The set of valid functions valid fun(te, pt, gst) is then defined asfollows: A function f is valid (i) if its body is a valid statement, (ii) the lastand only the last statement in the function body of f is a return statement,(iii) the type of the return expression matches the return type of the function,(iv) and the symbol table consisting of parameters and local variables of f is avalid symbol table.

For valid statements, we basically require that for an assignment, the typeson the right side and on the left side match, and that the types of a functioncall parameters match with those of the called function.

Page 142: Verified Linking for Modular Kernel Verification - Institut f¼r

126 CVM Implementation Correctness

Definition 7.24 (Linkable Programs) Two programs Πa = (tea, pta, gsta)and Πb = (teb, ptb, gstb) are linkable, if the following holds:

linkable(Πa,Πb) = tea ∈ valid tenv ∧ teb ∈ valid tenv

∧ (∀(ta, tna) ∈ tea, (tb, tnb) ∈ teb. ta = tb =⇒ tna = tnb)∧ gsta ∈ valid st(tea) ∧ gstb ∈ valid st(teb)∧ (∀(fn, f) ∈ ptext

a .∃f ′. (fn, f ′) ∈ ptdefb )

∧ (∀(fn, f) ∈ ptextb .∃f ′. (fn, f ′) ∈ ptdef

a )∧ (∀(fn, f) ∈ ptdef

a . f ∈ valid fun(tea, pta, gsta))∧ (∀(fn, f) ∈ ptdef

b . f ∈ valid fun(teb, ptb, gstb))∧ ((∀(fn, f) ∈ pta.∀s ∈ s2l(f.body).¬is Asm(s)∨(∀(fn, f) ∈ ptb.∀s ∈ s2l(f.body).¬is Asm(s)))

7.2.3 Device Relation

The relation between the CVM devices and those in the hardware is rathersimple. The only difference is that in CVM the swap device with id did swap

is not visible any longer, since page faults are not visible in the CVM model.Thus, we exclude this swap device from our device relation: We define devsim :confTdevs × confTdevs → B and set for two generalized device configurationsdevs1 and devs2:

dev sim(devs1, devs2) = (∀i ∈ D, i 6= did swap : devs1(i) = devs2(i))

7.2.4 Interrupt Mask and Current Process

As we have seen in Sect. 6.5.1, the concrete kernel has two variables to storethe interrupt mask and the current user process identifier: mask and cp. Thevalues of these variables have to be equal to the CVM components cp and mask.[Tsy09] has introduced two abstraction relations SRrel : confTi&d ×B32 → B

and CP rel : confTi&d ×N→ B, formally defined as follows:

SRrel(ci&d,mask) =

{True if toint(mask) = 〈mask〉intci&d

False otherwise

CP rel(ci&d, cp) =

{True if cp = 〈cp〉natci&d

False otherwise

7.2.5 Other Invariants

Besides the abstraction relations, which we have introduced in the sections before,there are some minor invariants dealing with subtleties of the implementation.They are presented in detail by [Tsy09].

The relation user -invariant : confTi&d → B for instance ensures that thehardware is in user mode (sprs(mode) = 1), the page table length register

Page 143: Verified Linking for Modular Kernel Verification - Institut f¼r

7.2. Abstraction Relations 127

CVM Configuration

Kernel DevicesProcesses Irq Mask Current ProcUser Process Component

Processor Devices

ISA + Devices Configuration

ConcreteKernel

(weak) konsis

B

SRrel CPre

l

devs

im

(weak) sim-C0-isa

Figure 7.2: CVM Abstraction Relations

sprs(ptl) and page table origin register sprs(pto) in the instruction set architec-ture are equivalent to the values stored in the process control blocks, and thatthe interrupt mask sprs.sr corresponds to the one stored in the variable mask.

The reg-invariant : confTi&d → B says that all internal interrupts aredisabled, i.e., the bits sprs(sr)[31 : 13] are zeroed. Furthermore, it states thatthe hardware is in system mode (sprs(mode) = 0).

There exists also another form of reg-invariant that describes the hardwareregisters, whenever we are in kernel wait : weak -reg-invariant : confTi&d → B.This invariant guarantees that—unlike during ‘normal’ kernel execution—theexternal interrupts are enabled and that the program counters dpc and pcpestablish the program loop that is used to implement kernel wait.

7.2.6 Putting It All Together

Definition 7.25 (CVM Abstraction Relation) We can now combine allof the above abstraction relations into one CVM relation (see Fig. 7.2).

CVMrelation : c0prog × confTCVM × confTi&d × (N→ mcellT )→ B.

Depending on the possible CVM transitions described in Sect. 6.2, wedistinguish three different cases:

• In kernel wait (cf. Sect. 6.2.2), the weak kernel relation and the weakregister invariant hold, and the current process variable has value 0.

• For user steps (cf. Sect. 6.2.3)—i.e. cCVM.up.cp = bpidc—, the weakkernel relation, the CPrel relation and the user invariant hold.

Page 144: Verified Linking for Modular Kernel Verification - Institut f¼r

128 CVM Implementation Correctness

• For kernel steps different from kernel wait, the kernel relation and theregister invariant hold.

In all cases, the user process relation and the device relation hold.

CVMrelation(Πk, cCVM, ci&d, alloc) =dev sim(cCVM.devs, ci&d.devs)∧B(cCVM.up.procs, ci&d)∧SRrel(ci&d, cCVM.up.mask)∧if (* Kernel Steps *)

cCVM.up.cp = Nonethen

if (* Kernel Wait *)cCVM.kern.kconf .prog = Asm[ ]

thenweak -kernel -rel(Πk, cCVM.kern, ci&d, alloc,None)∧CP rel(ci&d, 0)∧weak -reg-invariant(ci&d)

else (* Other Kernel Step *)kernel -rel(Πk, cCVM.kern, ci&d, alloc)∧reg-invariant(ci&d)

else (* User Step *)

weak -kernel -rel(cCVM.kern, ci&d, alloc, the(cCVM.up.cp))∧CP rel(ci&d, the(cCVM.up.cp))∧user -invariant(ci&d)

7.3 Sketch of a Correctness Theorem

The CVM top-level correctness theorem is a classical simulation theorem: aVAMP instruction set architecture with devices simulates the CVM model. Ithas been originally presented in [AHL+09]. We start with an initial combinedISA and device configuration c0i&d = (cisa, cdevs).

Furthermore, we assume that the concrete kernel program Πk has beenloaded to the main memory cisa.mm. This hardware state is described by thepredicate init-isa-conf (Πk, cisa).

As we have seen in Sect. 5.2, the combined transition function of VAMPISA and devices is parametrized over a sequence of external device inputsdin isa

ext : N → (D × Eifi)⊥ (cf. Sect. 4.2). Obviously, there have to be certainrequirements that this sequence has to meet:

• the sequence has to be fair in the sense that the processor progressesinfinitely often:

∀i ∈ N : ∃j > i : din isaext(j) = None.

• the sequence has to be well-typed, that is that device type and externaldevice input type match for each element of the sequence:

∀i ∈ N : din isaext(i) = b(did, ev)c ∧ t = typedev(devs(did)) =⇒ ev ∈ Eifot.

Page 145: Verified Linking for Modular Kernel Verification - Institut f¼r

7.3. Sketch of a Correctness Theorem 129

We combine these preconditions to one predicate precond -seq-isa(din isaext, cdevs).

In addition, we have some requirements that have to be satisfied by theswap hard disk cdevs(did swap):

• requests—sector transfers—from or to the hard disk are handled in finitetime, and

• the swap hard disk cdevs(did swap) is large enough, i.e. it can store thekernel image and still offer enough space for swap memory as needed bythe page fault handler.

We subsume these requirements as invariant-hd(cdevs(did swap)).As we have seen in Sect. 6.4, the swap device as present in the VAMP ISA

and device configuration is replaced by an idle device for the CVM configuration.We denote this slightly updated device configuration by cCVM

devs .The corresponding external device input sequence dinCVM

ext has to satisfysimilar requirements as the one on VAMP ISA level, i.e., it has to be fair andwell-typed. We denote this using the predicate precond -seq-cvm.

For a sub sequence of dinCVMext , we can easily count the number of processor

steps in it. We define recursively

count-proc-steps(dinCVMext , 0) =

{1 if dinCVM

ext (0) = None0 otherwise

and

count-proc-steps(dinCVMext , i+ 1) ={

1 + count-proc-steps(dinCVMext , i) if dinCVM

ext (i+ 1) = Nonecount-proc-steps(dinCVM

ext , i) otherwise

In the Verisoft project, there are two ways regarding the treatment of heapoverflows. In one scenario, the compiler returns a null pointer, when trying toallocate a new heap object on insufficient space. The other approach, whichwe use in the stack verification, guarantees correct translation only under theassumption that the compiled code does not allocate more heap memory thanavailable.

Throughout the execution of CVM, the abstract kernel can exceed itsmaximal heap and/or stack size, hence create a run-time error. We thereforeformulate two measure functions asize-heap and asize-stack , which return thecurrent stack and heap size of the abstract kernel, and require that those valuesstay within the bounds given through the two constants abs-heap-max -size andabs-stack -max -size.

For the VAMOS personality, both preconditions can be discharged prettyeasily: since the kernel only allocates two heap objects during initialization,but no further ones during execution, the heap size can be computed statically.For the stack size, the situation is similar. Since there are no recursive functioncalls in VAMOS, the maximal recursion depth can be computed statically andhence also the stack size. The necessary framework has been built and appliedexemplarily by Leinenbach and Alkassar [Alk09].

Page 146: Verified Linking for Modular Kernel Verification - Institut f¼r

130 CVM Implementation Correctness

Using all of the above definitions, we can now formulate the CVM top-levelsimulation theorem, which says that a CVM computation starting from aninitial CVM configuration (cf. Sect. 6.4) and parametrized over an externaldevice input sequence dinCVM

ext can be simulated by a VAMP ISA with devicescomputation.

Theorem 7.1 (CVM Top-Level Correctness)For all n ∈ N denoting a number of non-device steps in the CVM model, whichlead to some CVM configuration—that is the kernel has not produced a run-timefault—we have to show that there exists a step number N ′ for the ISA anddevice model, after which the combined abstraction relation CVMrelation holds:

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧init-isa-conf ((Πk, cisa) ∧ precond -seq-isa(dinisa

ext, cdevs)∧invariant-hd(cdevs(did swap)) =⇒∃dinCVM

ext .precond -seq-cvm(dinCVMext , cdevs)∧

(∀n.∃N.count-proc-steps(dinCVMext , N) = n∧

(∀c′CVM, dout ′ext.

δNcvm(initCVM(Πabs, cCVMdevs ), dinCVM

ext ) = bc′CVM, dout ′extc∧asize-heap(hst(c′CVM.kern.kconf .m)) ≤ abs-heap-max -size∧asize-stack(c′CVM.kern.kconf .m.lm) ≤ abs-stack -max -size =⇒

(∃N ′, alloc′.

CVMrelation(Πk, c′CVM, fst(δN

i&d((cisa, cdevs), dinisaext)), alloc′))))

The proof of this theorem can be split according to the possible steps in theCVM model and as visible in the top-level abstraction relation CVMrelation(cf. Def. 7.25).

In the remainder of this work, we will deal with the case of a kernel step,which is not kernel wait nor a primitive call. More precisely, we will show howthe relation of the abstract kernel and the concrete kernel konsis is preservedduring such a step. User step correctness, including the page fault interruptcase, is summarized in [AHL+09].

We will now formulate inductively the correctness theorem for the casedescribed above This means in particular:

• We start in a machine configuration (cisa, cdevs) ∈ confTi&d, which isrelated to a CVM configuration cCVM and a concrete kernel program Πk

using an allocation function alloc.

• Since the kernel is running, the current process identifier is None.

• We assume that we are not in kernel wait, i.e. the abstract kernel programrest does not consist of an Asm statement.

• We exclude the case of a primitive call, which would be denoted by anabstract kernel rest starting with an ESCall statement.

• Kernel execution has not terminated, which would be denoted by aprogram rest merely consisting of a single Skip statement.

Page 147: Verified Linking for Modular Kernel Verification - Institut f¼r

7.3. Sketch of a Correctness Theorem 131

Furthermore, we have requirements similar to those of the top-level correct-ness theorem (see Theorem 7.1):

• there exists a low-level CVM implementation that is linkable with theabstract kernel and hence yields the concrete kernel program;

• the external device input sequence satisfies well-typedness and fairness;

• the swap hard disk is big enough and its invariants hold.

Last but not least, there are additional assumptions on

• the well-formedness of the ISA configuration—is-dlx -conft(cisa)—;

• the boundedness of the abstract kernel heap and stack;

• that the concrete kernel program is present in the main memory of theISA—code-invariant-isa(Πk, cisa);

Theorem 7.2 (Kernel Step, no Primitive, not Kernel Wait)Given and abstract kernel program Πabs = (teabs, ptabs, gstabs) and an low-level CVM implementation ΠCVM, which are linkable to a concrete kernelimplementation Πk = (tek, ptk, gstk). Furthermore we assume that the kernel isto progress and that we do not have to deal with kernel wait, kernel terminationor a primitive call and that we are in a configuration cCVM, in which thecombined abstraction relation holds for a processor and device configuration(cisa, cdevs). Assuming that the next non-device step in the CVM model will notcreate a run-time fault but yield some successor configuration, there exists a stepnumber N ′ for the combined ISA and device model, after which the combined

Page 148: Verified Linking for Modular Kernel Verification - Institut f¼r

132 CVM Implementation Correctness

abstraction relation holds again.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧precond -seq-isa(dinisa

ext, cdevs)∧asize-heap(hst(c′CVM.kern.kconf .m)) ≤ abs-heap-max -size∧asize-stack(c′CVM.kern.kconf .m.lm) ≤ abs-stack -max -size∧invariant-hd(cdevs(did swap)) ∧ code-invariant-isa(Πk, cisa)∧is-dlx -conft(cisa) ∧ cCVM.up.cp = None∧¬is Asm(hd(s2(cCVM.kern.kconf .prog)))∧¬is Skip(cCVM.kern.kconf .prog)∧¬is ESCall(hd(s2l(cCVM.kern.kconf .prog)))∧CVMrelation(Πk, cCVM, (cisa, cdevs), alloc) =⇒∃dinCVM

ext .precond -seq-cvm(dinCVMext , cdevs)∧

(∃N.count-proc-steps(dinCVMext , N) = 1∧

(∀c′CVM.

∃dout ′ext. δNcvm(initCVM(Πabs, cdevs), dinCVM

ext ) = bc′CVM, dout ′extc∧asize-heap(hst(c′CVM.kern.kconf .m)) ≤ abs-heap-max -size∧asize-stack(c′CVM.kern.kconf .m.lm) ≤ abs-stack -max -size =⇒

(∃N ′, c′isa, c′devs.

fst(δN′

i&d((cisa, cdevs), dinisaext)) = (c′isa, c

′devs)∧

(∃alloc′.CVMrelation(Πk, c′CVM, (c

′isa, c

′devs), dinisa

ext), alloc′)))

Page 149: Verified Linking for Modular Kernel Verification - Institut f¼r

Do not consider it proof just because it is writtenin books, for a liar who will deceive with his tonguewill not hesitate to do the same with his pen.

Maimonides, Spanish Philosopher, 1135–1204

Chapter 8

Preservation of Kernel Consistency

Contents8.1 Auxiliary Lemmas . . . . . . . . . . . . . . . . . . . . . . 133

8.2 Weak Validity of C0 Configurations . . . . . . . . . . . . 137

8.3 Expression Evaluation in the two Kernels . . . . . . . . . 139

8.4 Static Properties . . . . . . . . . . . . . . . . . . . . . . . 150

8.5 Dynamic Properties . . . . . . . . . . . . . . . . . . . . . 152

8.6 Preservation of Kernel Consistency by C0 Kernel Steps . 180

In this chapter we will present the proof that the relation between theabstract and the concrete kernel, as introduced in Sect. 7.2.2, Def. 7.21, ispreserved by a C0 kernel step within the CVM model (cf. Sect. 6.2.2).

We break down this proof in several parts. In Sect. 8.1, we will define andprove some auxiliary lemmas needed in the remainder of this chapter. Since theabstract kernel is a crippled program, its configurations cannot be valid in thesense of [Lei08]. We therefore define the notion of weak validity in Sect. 8.2.

Expression evaluation in the two kernels and the relation of its results is ofcrucial importance for the proof work. In Sect. 8.3, we will treat this topic indetail.

We continue with the static properties, that is type name environment,procedure table and global symbol table consistency. The corresponding lemmasand proofs are detailed in Sect. 8.4.

Finally, we will consider the dynamic properties, e.g., recursion depth, returndestination and g-variable consistency (Sect. 8.5).

With Sect. 8.6, we conclude this chapter by putting together the variousresults into the comprising correctness theorem.

8.1 Auxiliary Lemmas

Lemma 8.1 (Equal Types Invariant) Assuming that the symbol table re-lations and the heap invariants hold, and that x is a valid g-variable in theabstract kernel, then its type is equal to the corresponding g-variable in the

133

Page 150: Verified Linking for Modular Kernel Verification - Institut f¼r

134 Preservation of Kernel Consistency

concrete kernel.

konsisgst(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsishst(cabs.m, ck.m) ∧ ck ∈ conf √(tek, ptk)∧hmapinj(cabs.m, hp) ∧ konsisrd(cabs.m, ck.m)∧x ∈ gvar√(sc(cabs.m))

=⇒eqtype(cabs.m, x, ck.m, kalloc(x, hp))

Proof The proof is done by induction over the g-variable x. It is very straight-forward; basically we just expand the invariants over the symbol tables for thebase cases and instantiate the induction hypotheses for array and structurevariables. q.e.d.

Lemma 8.2 (Valid g-Variable Invariant) We assume that the symbol tablerelations, the recursion depth and the heap invariants hold, and that x is a validg-variable in the abstract kernel, then the corresponding g-variable kalloc(x, hp)in the concrete kernel is also valid.

konsisgst(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsishst(cabs.m, ck.m) ∧ ck ∈ conf √(tek, ptk)∧hmapinj(cabs.m, hp) ∧ hmapbound(cabs.m, ck.m, hp)∧konsisrd(cabs.m, ck.m) ∧ x ∈ gvar√(sc(cabs.m))

=⇒kalloc(x, hp) ∈ gvar√(sc(ck.m))

Proof We prove this lemma by an induction over the structure of g-variablex.

Case 1: Given a global variable, i.e. x = gvargm(σ). Due to validity ofthe variable, there exists a type t, such that (σ, t) ∈ {sc(cabs.m.gm)}.Expanding the invariant on the global symbol tables konsisgst, this entryalso exists in the linked global symbol configuration. Since kalloc is theidentity for global variables, kalloc(gvargm(σ), hp) is valid in the concretekernel, too.

Case 2: In the local case x = gvarlm(σ, i), validity is defined by two crite-ria: (i) σ is defined in the i-th symbol table of the abstract local memory,σ ∈ {map(fst , sc((cabs.m.lm)!i))}, and (ii) i is less than the recursiondepth of the abstract kernel: i < |cabs.m.lm|. Using the invariants onlocal symbol tables, konsis lst, and on recursion depth, konsisrd, we obtainthat σ is also obtained in the concrete local symbol table at positioni+rdoff, which is still less than the concrete kernel’s recursion depth. Thisimplies that gvarlm(σ, i+ rdoff) = kalloc(gvarlm(σ, i), hp) is also valid inthe concrete kernel.

Page 151: Verified Linking for Modular Kernel Verification - Institut f¼r

8.1. Auxiliary Lemmas 135

Case 3: For a heap variable x = gvarhm(i) in the abstract kernel, theonly validity criterion is that the heap index is within bounds, that isi < |cabs.m.hm|.hp(i) is an index within the concrete heap, since the concrete heap isbounded by hmapbound(cabs.m, ck.m, hp).

So, gvarhm(hp(i)) = kalloc(gvarhm(i), hp) is a valid concrete variable.

Case 4: We come to the inductive cases of array and structure variables.Both cases are very similar, hence we only elaborate on x = gvararr (a, i).

Validity for array variables means that the root g-variable a is valid andof array type, and that the index i is less than the array size.

From Lemma 8.1 we obtain that kalloc(a, hp) has the same type as a.From the induction hypothesis, we know that it is also valid. Hence,gvararr (kalloc(a, hp), i) = kalloc(gvararr (a, i), hp) is a valid g-variable ofthe concrete kernel. q.e.d.

Lemma 8.3 (Root g-Variable with kalloc) If g is a root g-variable, thenkalloc(g) is a root variable, too:

rootg(g) = g =⇒ root(kalloc(g, hp)) = kalloc(g, hp)

Proof The proof is straightforward, basing on a case distinction over the typeof variables, using the definition of kalloc. q.e.d.

Lemma 8.4 (Transitivity of Sub g-Variables) The relation subg for subg-variables is transitive under kalloc.

x ∈ gvar√(sc(cabs.m) ∧ y ∈ gvar√(sc(cabs.m)

hmapinv(cabs.m, hp)=⇒(x ∈ subg(y)⇐⇒ kalloc(x, hp) ∈ subg(kalloc(y, hp)))

Proof We split the proof into two cases.

Case 1: At first, we start with

x ∈ subg(y) =⇒ kalloc(x, hp) ∈ subg(kalloc(y, hp)).

We proceed with an induction over the g-variable x. For the base cases—global, local, and heap variables—the proof is trivial, since sub g-variablemeans identity in these cases (cf. Def. 3.4). Let us consider the globalvariable case exemplarily:

gvargm(σ) ∈ subg(y) =⇒ gvargm(σ) = y

So we have to show that

kalloc(gvargm(σ), hp) ∈ subg(kalloc(gvargm(σ), hp)),

which is covered by the base case in the definition of sub g-variables.

Page 152: Verified Linking for Modular Kernel Verification - Institut f¼r

136 Preservation of Kernel Consistency

For the inductive cases, we concentrate on array variables, that is x =gvararr (a, i).

There are now two possibilities: since gvararr (a, i) ∈ subg(y), eithery is identical to gvararr (a, i) or a is a sub g-variable of y. The firstpossibility is covered again by the base case of sub g-variables. For thesecond one, we use the induction hypothesis to obtain that kalloc(a, hp) ∈subg(kalloc(y, hp)), which by definition implies that

kalloc(gvararr (a, i), hp) ∈ subg(kalloc(y, hp)).

Case 2: Now we start with the assumption that

kalloc(x, hp) ∈ subg(kalloc(y, hp)).

We proceed again with an induction over x. Here, the base cases are verystraightforward. For the heap variable case we additionally need heapmap injectivity to obtain a unique index.

In the array case, we have again the two possibilities. Assuming thatgvararr (kalloc(a, hp), i) = kalloc(y, hp), we have to prove gvararr (a, i) =y—which is true due to the injectivity of kalloc. For the other possibility,we use the induction hypothesis to obtain a ∈ subg(y), which triviallyimplies by definition that

gvararr (a, i) ∈ subg(y). q.e.d.

Nearly in all proofs in this chapter, we need to show that the concretekernel’s program rest starts with the same statement as the abstract kernel’sprogram rest—modulo the statement id (due to renumbering, see Def. 6.5).

Lemma 8.5 (Equivalence of Program Rest Heads) Let the consistencyrelation for program rests konsisrd hold for two C0 configurations cabs and ck.Then the heads of the program rest of these two configurations are equivalentunder the function eqstmt:

konsisprog(cabs.prog , ck.prog) ∧ (s2l(cabs.prog)) 6= [ ]=⇒ eqstmt(hd(s2l(cabs.prog)), hd(s2l(ck.prog)))

Proof From the definition of s2l (cf. Sect. 3.2.3), we know that its result is alist of length at least 1, hence

0 < |s2l(cabs.prog)|

and0 < |s2l(ck.prog)|,

or in other words that there is at least one element in each of the two lists,i.e. there exists a head in each of them.

Using konsisprog(cabs.prog , ck.prog) and the definition of program rest con-sistency (cf. Def. 7.10), which says that for all elements in s2l(cabs.prog) thecorresponding element in s2l(ck.prog) is equivalent under eqstmt, we finallyobtain.

eqstmt(s2l(cabs.prog)!0, s2l(ck.prog)!0) q.e.d.

Page 153: Verified Linking for Modular Kernel Verification - Institut f¼r

8.2. Weak Validity of C0 Configurations 137

8.2 Weak Validity of C0 Configurations

The Isabelle/HOL versions of the proofs presented in this chapter make extensiveuse of existing lemmas about the C0 small-step semantics. These lemmasoften have quite big assumptions, a lot of them dealing with validity of C0configurations in the sense of [Lei08, Sect. 5.5, Def. 5.38] or at least parts ofthem.

The definition of valid C0 configurations is huge and the detailed definitiondoes not contribute to a better understanding of this work, hence we will omitit here.

Nonetheless we have to mention that configurations encoding the abstractkernel cannot be valid in the sense of this definition, since not all of the functionsin the abstract procedure table are valid, for which [Lei08, Def. 5.14] requiresthat

• its body is a valid statement,

• the local and parameter symbol tables are valid,

• the last and only the last statement of the function body is a Returnstatement, and

• the type of the return expression matches the return type of the function.

Since the abstract kernel necessarily has external functions, whose body merelyconsists of a Skip statement, the last two requirements cannot be met by allabstract kernel functions. Thus, we introduce the notion of weak valid functions,for which we only require that the body and the local and parameter symboltables are valid.

The definition of valid procedure tables ([Lei08, Def. 5.17]) makes use ofthe notion of valid functions, as it requires that all functions in that proceduretable have to be valid in the strong sense.

Hence we also have to come up with a weakened version for valid proceduretables, where we distinguish if a function is called via an SCall statement andhas a body different from a single Skip statement. We start with the definitionof two auxiliary functions, before we proceed with weak validity for proceduretables.

Definition 8.1 (Exists Statement) We define a predicate existsstmt takingtwo arguments, a statement s and a predicate over statements P . existsstmt

Page 154: Verified Linking for Modular Kernel Verification - Institut f¼r

138 Preservation of Kernel Consistency

evaluates to True, iff at least one of the sub statements of s satisfies P , formally:

existsstmt(Skip, P ) = P (Skip)existsstmt(Comp(s1, s2), P ) = P (Comp(s1, s2))

∨existsstmt(s1, P )∨existsstmt(s2, P )

existsstmt(Ass(e), P ) = P (Ass(e))existsstmt(PAlloc(e), P ) = P (PAlloc(e))existsstmt(Return(e), P ) = P (Return(e))

existsstmt(SCall(e, fn, plist), P ) = P (SCall(e, fn, plist))existsstmt(ESCall(e, fn, plist), P ) = P (XCall(e, fn, plist))

existsstmt(XCall(fn, elist , plist), P ) = P (ESCall(e, fn, plist))existsstmt(Ifte(e, s1, s2), P ) = P (Ifte(s1, s2)) ∨ existsstmt(s1, P )

∨existsstmt(s2, P )existsstmt(Loop(e, s), P ) = P (Loop(e, s)) ∨ existsstmt(s, P )

Definition 8.2 (Used Function) We use the above definition to define thenotion of a used function. A function with name fn is used in a procedure table,if there exists a statement s in any of the function bodies of pt with s being afunction call of fn. We describe this property by funcused : Σ+×proctableT → B

and define recursively:

funcused(fn, [ ]) = Falsefuncused(fn, (x, f)#xs) = (existsstmt(x.body ,

λs. ∃e, plist . s = SCall(e, fn, plist))

This definition allows us to weaken the definition of valid procedure tablesby a case split over its functions:

• For a used function and for all functions with a body different from asingle Skip statement, we simply use the requirements of valid functions.

• For all others, we require that they only satisfy the conditions of weakvalidity of functions.

In simple words, this definition says that for defined and used functions, weleave the validity predicate as it is defined in [Lei08], while we use the weakenedform for functions which are only declared and not called.

This definition of weak procedure table validity is finally used to define weakvalid C0 configurations, which we denote with conf weak√ compared to conf √.Here again, we leave all requirements of strong validity unchanged and merelyreplace the procedure table validity by its weakened form.

Our Isabelle/HOL proofs use approximately 50 lemmas from [Lei08] withmost of them assume a given valid C0 configuration. For all of these lemmaswe have shown, that weak C0 configuration validity is sufficient, so their usehas been justified.

Similar to [Lei08, Lemma 5.17], there exists a lemma that weak validity ispreserved by the C0 transition function.

Page 155: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 139

Lemma 8.6 (Preservation of Weak Validity) Given a weak valid C0 con-figuration cC0 with respect to a page table pt and a type name environment te.If there exists a successor configuration cC0

′ obtained by applying the transitionfunction, then cC0

′ is weak valid, too.

cC0 ∈ conf weak√ (te, pt) ∧ δC0(te, pt, cC0 ) = bcC0′c

=⇒cC0

′ ∈ conf weak√ (te, pt)

8.3 Expression Evaluation in the two Kernels

Expression evaluation plays a crucial role in the C0 small-step semantics—hencealso in the correctness proofs presented in this chapter.

In this section, we will consider expressions in the abstract and concretekernel. We will formulate and prove lemmas about their type, validity and theresults of their evaluation.

Lemma 8.7 (Type of Expressions Equality) Let tek be the type name en-vironment the concrete kernel, which is encoded by a valid configuration ck.Furthermore, let the consistency relations for recursion depth, global and localsymbol tables hold, and let e denote an expression of type t, which is valid inthe abstract kernel. Then it is also of type t in the concrete kernel.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m) ∧ konsistenv(teabs, tk)∧konsisgst(cabs.m, ck.m) ∧ e ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))∧ck ∈ conf √(tek, ptk) ∧ type(teabs, toplst(cabs.m), gst(cabs.m, ), e) = btc

=⇒type(tek, toplst(ck.m), gst(ck.m, ), e) = btc

Proof We will prove this lemma by induction over e. There are two base cases,namely literals and variable accesses. The literal case is trivial since purelysyntactical, so we only present the variable access case.

Case 1: For a variable access e = Var(σ), there are two possibilities:either we are dealing with a local variable or with a global variable (seealso Sect. 3.4.3).

Assuming that σ is the name of a local variable, we derive that (σ, t) ∈{toplst(cabs.m)}. By definition of toplst we know that this is the symboltable of the topmost stack frame, that is sc.lst(cabs.m)!(|cabs.m.lm| − 1),with |cabs.m.lm| − 1 denoting the recursion depth of the abstract kernel.

From konsis lst(cabs.m, ck.m) we obtain

sc(cabs.m).lst !(|cabs.m.lm| − 1) = sc(ck.m).lst !(|cabs.m.lm| − 1 + rdoff),

hence (σ, t) ∈ {sc(ck.m).lst !(|cabs.m.lm| − 1 + rdoff)}.

Page 156: Verified Linking for Modular Kernel Verification - Institut f¼r

140 Preservation of Kernel Consistency

Exploiting the recursion depth consistency relation, we know that this isexactly the recursion depth of the concrete kernel in configuration ck andcan conclude:

|cabs.m.lm| − 1 + rdoff = |ck.m.lm| − 1=⇒ (σ, t) ∈ {sc(ck.m).lst !(|ck.m.lm| − 1)}=⇒ (σ, t) ∈ {toplst(ck.m)}

and—using the definition of type—this finally implies

type(tek, toplst(ck.m), gst(ck.m, ), e) = btc

For global variables, the proof is even simpler: knowing that (σ, t) ∈{gst(cabs.m)}, which is equivalent to (σ, t) ∈ {sc.gst(cabs.m)}, we obtainthat this pair also has to be an element of the linked global symboltable—since the abstract one is a subset of it as given by the relationkonsisgst:

(σ, t) ∈ {gst(cabs.m)}=⇒ (σ, t) ∈ {gst(ck.m)} (using konsisgst)=⇒ (σ, t) ∈ {gst(cabs.m)} (Def. of gst)=⇒ type(tek, toplst(ck.m), gst(ck.m, ),Var(σ)) = btc (Def. of type)

Case 2: The proofs for the arithmetic operations are all very similar,since they merely rely on instantiating the induction hypotheses. Hencewe present only the proof for binary arithmetic operations, that is e =BinOp(op, e1, e2). From the fact that e is a valid expression of the abstractkernel, we obtain that also the sub expressions are valid

e1 ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))e2 ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))

and there exist two types t1 and t2 for these sub expressions:

type(teabs, toplst(cabs.m), gst(cabs.m, ), e1) = bt1ctype(teabs, toplst(cabs.m), gst(cabs.m, ), e2) = bt2c.

We can now use the induction hypothesis to show that the sub expressiontypes are equivalent for the concrete kernel, that is

type(tek, toplst(ck.m), gst(ck.m, ), e1) = bt1ctype(tek, toplst(ck.m), gst(ck.m, ), e2) = bt2c

which we then finally use to conclude

type(tek, toplst(ck.m), gst(ck.m, ),BinOp(op, e1, e2)) = btc,

since the type of BinOp only depends on the types of the sub-expressions.

Page 157: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 141

Case 3: Let e denote a pointer dereferencing expression, that is e =Deref (ex). This means that

type(teabs, toplst(cabs.m), gst(cabs.m),Deref (ex)) = btc.

From the assumption that e is a valid expression in the abstract kernel,we obtain that the sub expression ex is valid and of a pointer type:

ex ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))type(teabs, toplst(cabs.m), gst(cabs.m), ex) = bPtr(tn)c

and furthermore there is a corresponding element in the abstract typename environment, such that

(tn, t) ∈ {teabs}.

Using the above facts together with the induction hypothesis, we obtainfor the type of ex

type(tek, toplst(ck.m), gst(ck.m), ex) = bPtr(tn)c.

Due to type name environment consistency, we know that (tn, t) is alsoan element in the concrete type name environment:

(tn, t) ∈ {tek}. (8.1)

By the definition, this implies for the type of the comprising dereferencingexpression:

type(tek, toplst(ck.m), gst(ck.m),Deref (ex)) = btc.

Case 4: The proof works in a similar way for an Address-Of expression,that is e = AddrOf (ex) with

type(teabs, toplst(cabs.m), gst(cabs.m),AddrOf (ex)) = bPtr(tn)c.

From the assumption that AddrOf (ex) is valid we obtain for the subexpression ex that there exists a type t with

type(teabs, toplst(cabs.m), gst(cabs.m), ex) = btc

and

(tn, t) ∈ {teabs}.

We use the induction hypothesis to obtain that this also holds in theconcrete kernel:

type(tek, toplst(ck.m), gst(ck.m), ex) = btc.

From this, equation 4, and the fact that because of type name environmentconsistency teabs is a subset of tek, we know that (tn, t) ∈ {tek} and finally

type(tek, toplst(ck.m), gst(ck.m),AddrOf (ex)) = bPtr(tn)c.

Page 158: Verified Linking for Modular Kernel Verification - Institut f¼r

142 Preservation of Kernel Consistency

Remark (Isabelle/HOL) The above argumentation neglects the factthat there could be more type names in tek associated to the type t, hencethe conclusion in the last step would not be justified.

In Isabelle, type name environments are modeled as lists and the functionmapof returns the first witness in a list that satisfies the criterion.

By construction (cf. Def. 6.3) of tek, the elements originally originatingfrom the abstract type name environment always come before those of thelow-level CVM implementation. So, in both cases the result of the mapofoperation on the type name environments of the abstract and concretekernel is (tn, t).

Case 5: We will omit the detailed proofs for both the array element accesscase e = Arr(ea, ei) and the structure access case e = Str(es, cn). Bothproofs are very similar to the one presented above for binary operations.

For the array case, we deduce the validity and types for both sub ex-pressions and instantiate then the induction hypothesis adequately. Forstructure components its even simpler, since there is only one sub expres-sion and the component name is static. q.e.d.

Lemma 8.8 (Transitivity of Valid Expressions) Let tek denote the typename environment of the concrete kernel, which is given by a valid configurationck. Furthermore, let the consistency relations for recursion depth, global andlocal symbol tables hold, and let e denote an expression, which is valid in theabstract kernel. Then e is also valid in the concrete kernel.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m) ∧ konsistenv(teabs, tk)∧konsisgst(cabs.m, ck.m) ∧ e ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))∧ck ∈ conf √(tek, ptk)

=⇒e ∈ validexprs(tek, toplst(ck.m), gst(ck.m))

Proof We will prove this lemma by induction over e. For most cases, theproof is trivial and we will omit the details.

Case 1: For literals, that is e = Lit(l), the definition says that l is a validliteral. Since this l is syntactically the same in the concrete kernel, e isalso valid there.

Case 2: Let e = Var(σ) denote a local or global variable access. Due tosimilarities we will only consider the local variable case.

Expanding the definition of valid expressions, we obtain that σ is definedin the topmost local symbol table:

(σ, t) ∈ {toplst(cabs.m)}.

Page 159: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 143

From konsis lst(cabs.m, ck.m) we know that the local symbol tables in bothkernels are equal modulo the shift constant rdoff regarding the recursiondepth.

Using konsisrd, we derive that (σ, t) is also defined in the topmost localsymbol table of the abstract kernel, which satisfies the criterion for a validvariable access expression.

Case 3: For e = Deref (e1), i.e. pointer dereferencing, we expand thedefinition of valid expressions and obtain:

• e1 is valid in the abstract kernel,

• it is of a pointer type t = PtrT (tn), that is mapof (teabs, tn) = btc,and

• the type name is defined in the type name environment of the abstractkernel.

We use the induction hypothesis to derive that e1 is also valid in theconcrete kernel. Due to the type name relation konsistenv(teabs, tek), weknow that (tn, t) ∈ {tek}.Finally, we use Lemma 8.7 to obtain that e is of pointer type in theconcrete kernel, too. These are all the requirements for a valid pointerdereferencing in the concrete kernel.

Case 4: Let e = AddrOf (e1) denote an Address-Of expression. Then, e1 isvalid, too, and its type t is defined in the abstract type name environment,i.e. t ∈ {map(snd , teabs)}. Moreover, e1 is a memory object.

From the induction hypothesis we obtain that e1 is also valid in theconcrete kernel. Using Lemma 8.7 yields that e1 is of type t in theconcrete kernel, too, while the invariant konsistenv guarantees that t ∈{map(snd , tek)}.This is all we need to prove that e is a valid expression in the concretekernel.

Case 5: We will not present the proof for the structure case, since it isvery similar to the array case e = Arr(ea, ei).

From the assumption that e is valid, we derive that both sub expressionsare valid, too, and that ei is of a numerical type.

Using the induction hypothesis and expression type equality from Lemma8.7, we prove this case. q.e.d.

Lemma 8.9 (Relating Expression Evaluation) Given cabs, a weak validabstract kernel configuration, and ck a valid configuration of the concrete kernel,which has been obtained by abstract linking. Furthermore, let the invariants forthe global and local symbol tables, the recursion depth and the type name envi-ronment as well as the invariant for g-variables hold. For any valid expression ein the abstract kernel of an elementary type, which can be right-hand evaluated,(i) there also exists a right-hand value in the concrete kernel, (ii) which is equiv-alent under the eqcont relation, if e is initialized. Furthermore, the initialization

Page 160: Verified Linking for Modular Kernel Verification - Institut f¼r

144 Preservation of Kernel Consistency

of e is equal in both kernels, and if e is a memory object in the abstract kernel,then it is one in the concrete kernel, too.

Ultimately, if e can be left evaluated to some g-variable g in the abstractkernel, the left evaluation in the concrete kernel will return the correspondingg-variable kalloc(g, hp).

We define this formally as follows:

ck ∈ conf √(tek, ptk) ∧ linkable(Πabs,ΠCVM)∧

cabs ∈ conf weak√ ∧ konsistenv(teabs, tk)∧Πk = link(Πabs,ΠCVM) ∧ konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ e ∈ validexprs(teabs, toplst(cabs.m), gst(cabs.m))∧konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧type(teabs, toplst(cabs.m), gst(cabs.m), e) = btc∧rval(teabs, cabs.m, e) = bvc=⇒rval(tek, ck.m, e) = byc∧?init(teabs, cabs.m, e) = ?init(tek, ck.m, e)∧?inter(teabs, cabs.m, e) = ?inter(tek, ck.m, e)∧(∀g. lval(teabs, cabs.m, e) = bgc =⇒ lval(tek, ck.m, e) = bkalloc(g, hp)c)∧(?init(teabs, cabs.m, e) ∧ ?elemT (t) =⇒ eqcont(t, v, y))

Proof Proof by induction over the expression e. We will omit the trivial casesof arithmetic operations and literals.

Case 1: Let e denote a variable access, that is e = Var(σ). From the factthat this is a valid expression of the abstract kernel, we can derive that σis defined either in the global symbol table or in the topmost local symboltable:

(σ, t) ∈ {toplst(cabs.m)} ∨ (σ, t) ∈ {gst(cabs.m)}

The proof for both the local and the global case are very similar, thus wewill only work out on the first case, i.e. σ is the name of a local variable.Hence, the left-hand value of e is

lval(teabs, cabs.m,Var(σ)) = bgvarlm(|cabs.m.lm| − 1, σ)c.

From konsislst(cabs.m, ck.m) we know that the abstract kernel’s localsymbol tables are equivalent to those in the concrete kernel—shiftedby the offset rdoff. Together with the type equivalence for expressionevaluation from Lemma 8.7 and the recursion depth consistency betweenthe abstract and the concrete kernel we obtain:

(σ, t) ∈ {toplst(ck.m)}

and hence from the definition of expression evaluation

lval(tek, ck.m,Var(σ)) = bgvarlm(|ck.m.lm| − 1, σ)cbkalloc(gvarlm(|cabs.m.lm| − 1, σ), hp)c.

Page 161: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 145

This satisfies the proof goal regarding the left-hand evaluation of variableaccesses.

In a (weak) valid configuration, any expression that can be left evaluated,i.e. lval is not equal to None, yields a reachable g-variable (cf. [Lei08,Theorem 8.1]), hence

gvarlm(|cabs.m.lm| − 1, σ) ∈ reachablenamed(cabs.m) (8.2)

and for the concrete kernel

gvarlm(|ck.m.lm| − 1, σ) ∈ reachablenamed(ck.m)=⇒ kalloc(gvarlm(|cabs.m.lm| − 1, σ), hp) ∈ reachablenamed(ck.m)

(8.3)

Since we have assumed g-variable consistency, we can now unpack thedefinition of konsisgvars and obtain that the initialization of both variablesis equal:

?initg(gvarlm(|cabs.m.lm| − 1, σ))= ?initg(kalloc(gvarlm(|cabs.m.lm| − 1, σ), hp)).

From the definition of expression evaluation for variable access we knowthat

?init(teabs, cabs.m,Var(σ))= σ ∈ toplm(cabs.m).init= ?initg(gvarlm(|cabs.m.lm| − 1, σ)), (Def. 3.11)

and hence

?init(teabs, cabs.m,Var(σ)) = ?init(tek, ck.m,Var(σ)).

For the rest of this case, let us assume that the variables, which areaccessed, are initialized and of elementary type. We will now obtain theright-hand value rval for the variable access in the abstract kernel andrelate it to the value of the g-variable given through the left-hand value.

Note that since t is elementary, the corresponding type size sizeT equalsone:

rval(teabs, cabs.m,Var(σ))= btoplm(cabs.m).ct[bav(toplst(cabs.m), σ), 1]c (cf. Sect. 3.4.3)= btoplm(cabs.m).ct[bag(sc(cabs.m), gvarlm(|cabs.m.lm| − 1, σ)), 1]c

(Def. 3.12)

which is the value of the g-variable as defined in Def. 3.14:

bvalueg(cabs.m, gvarlm(|cabs.m.lm| − 1, σ))c (8.4)

We proceed in a similar way for the concrete kernel and obtain

rval(tek, ck.m,Var(σ))= bvalueg(ck.m, gvarlm(|ck.m.lm| − 1, σ))c= bvalueg(ck.m, kalloc(gvarlm(|cabs.m.lm| − 1, σ), hp))c. (8.5)

Page 162: Verified Linking for Modular Kernel Verification - Institut f¼r

146 Preservation of Kernel Consistency

We will now expand the konsisgvars invariant (cf. Def. 7.16). Using thefact that gvarlm(|cabs.m.lm|−1, σ) is reachable (8.2) and the assumptionson initialization and type, we obtain

eqval(gvarlm(|cabs.m.lm| − 1, σ), cabs.m,

kalloc(gvarlm(|cabs.m.lm| − 1, σ), hp), ck.m)

which we expand to

eqcont(t, valueg(cabs.m, gvarlm(|cabs.m.lm| − 1, σ)),valueg(ck.m, kalloc(gvarlm(|cabs.m.lm| − 1, σ), hp))

and obtain finally using (8.4) and (8.5):

eqcont(t, the(rval(teabs, cabs.m,Var(σ))), the(rval(tek, ck.m,Var(σ)))).

Case 2: We will now deal with pointer dereferencing, i.e. e = Deref (e1).Since rval(teabs, cabs.m, e) = bvc, we know that the right-hand expressionevaluation of the sub expression e1 yields a non-null pointer, that is

rval(teabs, cabs.m, e1) = bPtr(p)c∧p 6= NullPointer

where the type of e1 is given through

type(teabs, toplst(cabs.m), gst(cabs.m), e1) = bPtrT (tn)c (8.6)

and

mapof (teabs, tn) = btc. (8.7)

This means that the left-hand value of Deref (e1) in the abstract kernel isthe g-variable that e1 points to, that is

lval(teabs, cabs.m,Deref (e1)) = bpc, (8.8)

and the right-hand evaluation is equal to the value of this variable p:

rval(teabs, cabs.m,Deref (e1)) = valueg(cabs.m, p). (8.9)

Using the induction hypothesis, we know that there exists a y′ such that

rval(tek, ck.m, e1) = by′c

and

eqcont(PtrT (tn),Ptr(p), y′).

Expanding the definition of eqcont for non-null pointer types, we obtain

y′ = Ptr(kalloc(p, hp)). (8.10)

Page 163: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 147

Furthermore, we derive that since e1 is initialized in the abstract kernel,so it is in the concrete kernel—?init(te,mk, e1) = True—, and that thetypes of e1 are equal in both kernel configurations (see equation (8.6),using Lemma 8.7).

The invariant konsistenv ensures that the type associated with the typename tn in the concrete kernel is the same type as in the abstract kernel,namely t.

Finally, since p is not a local variable, so kalloc(p) is not local neither bydefinition of kalloc.

Since all of the above facts ensure also the successful expression evalua-tion for Deref (e1) in the concrete kernel, too, we can now compute thecorresponding results. From (8.10) we can derive the left-hand value ofDeref (e1) as

lval(tek, ck.m,Deref (e1)) = bkalloc(p, hp)c. (8.11)

and for the right-hand value

rval(tek, ck.m,Deref (e1)) = valueg(ck.m, kalloc(p, hp)). (8.12)

Both the abstract kernel and the concrete kernel configurations are valid,which implies that the left-hand values obtained by expression evaluationyields reachable variables. Furthermore we know that t is an elementarytype by assumption. We can now again unfold the konsisgvars invariant(Def. 7.16), by which we can syntactically show our goal.

Case 3: Let e be an Address-Of expression, i.e. e = AddrOf (e1). Sincethe evaluation of this expression in the abstract kernel was successful, wecan conclude that the assumptions regarding right-hand evaluation andtype on the sub expression e1 have been satisfied, too, that is

lval(teabs, cabs.m, e1) = bpc

and

mapof (map(flip, teabs), type(teabs, toplst(cabs.m), gst(cabs.m), e1)) = btnc.

From the definition of expression evaluation we obtain for the right-handvalue a content list with exactly one element, namely a pointer to p:

rval(teabs, cabs.m,AddrOf (e1)) = b[Ptr(p)]c (8.13)

Using the induction hypothesis, we obtain for the concrete kernel

lval(tek, ck.m, e1) = bkalloc(p, hp)c (8.14)

and

?inter(tek, toplst(ck.m), gst(ck.m), e1) = False (8.15)

In addition, we use the type name invariant konsistenv to derive

mapof (map(flip, tek), type(tek, toplst(ck.m), gst(ck.m), e1)) = btnc.

Page 164: Verified Linking for Modular Kernel Verification - Institut f¼r

148 Preservation of Kernel Consistency

These are all requirements for a successful evaluation of AddrOf (e1) inthe concrete kernel. Hence, the right-hand evaluation returns

rval(tek, ck.m,AddrOf (e1)) = b[Ptr(kalloc(p, hp))]c. (8.16)

Furthermore, the type of the expression is identical in both the abstractand the concrete kernel, namely

type(teabs, toplst(cabs.m), gst(cabs.m),AddrOf (e1)) =type(tek, toplst(ck.m), gst(ck.m),AddrOf (e1)) =bPtrT (tn)c

and both expressions are initialized by definition.

Using these facts together with the definition of konsisgvars, we havecompleted the proof for this case.

Case 4: The cases for an array element access and a structure componentaccess are very similar. Hence, we will only discuss the case of an arrayelement access, i.e. Arr(ea, ei).

We know that the sub expression ei—the index expression—is of a numer-ical type, which is by definition an elementary type. Hence we can usethe induction hypothesis to obtain that the initialization of ei is equal inboth kernel configurations.

For the initialization of the array element access the definition of expressionevaluation says that

?init(teabs, cabs.m,Arr(ea, ei)) =?init(teabs, cabs.m, ea) ∧ ?init(teabs, cabs.m, ei).

This means that in the case of an uninitialized sub expression ei, the prooffor the array case is already complete1. So let us assume that both subexpressions ea and ei are initialized.

In addition, an array access is a memory object, iff the array expressionea is a memory object. By using the induction hypothesis, we obtain

?inter(teabs, cabs.m, ea) = ?inter(tek, ck.m, ea)

and hence

?inter(teabs, cabs.m,Arr(ea, ei)) = ?inter(tek, ck.m,Arr(ea, ei)).

Let i denote the right-hand value of ei in the abstract kernel and i′ theone for the concrete kernel, respectively. Furthermore let ti denote thetype of ei. Then we derive from the induction hypothesis that for theright-hand values of ei holds

eqcont(ti, i, i′)

1We would just use the induction hypothesis for ei and obtain that it is uninitialized inthe concrete kernel, too.

Page 165: Verified Linking for Modular Kernel Verification - Institut f¼r

8.3. Expression Evaluation in the two Kernels 149

and since ti is elementary, this equation can be simplified to i = i′.

Let us consider the array expression ea now. The left-hand evaluation inthe abstract kernel returns a g-variable g of an array type ArrT (n, t). Forthe corresponding right-hand value we use Def. 3.14 and obtain

rval(teabs, cabs.m, ea)= bvalueg(cabs.m, g)c

Using the induction hypothesis, we left evaluate ea in the concrete kernelto kalloc(g, hp) with a right-hand value

rval(tek, ck.m, ea)= bvalueg(ck.m, kalloc(g, hp))c.

If t is not elementary, then the proof for the array access case is donenow. So let us assume ?elemT (t). Then we can expand the right-handevaluation as follows:

rval(teabs, cabs.m, ea)= bvalueg(cabs.m, g)c= bmmemg(g)[bag(sc(cabs.m), g), sizeT (typeg(sc(m), g))]c= bmmemg(g)[bag(sc(cabs.m), g), n · sizeT (t)]c= bmmemg(g)[bag(sc(cabs.m), g), n]c= bvac

We proceed likewise with kalloc(g, hp) in the concrete kernel and obtainits value v′a.

From the preconditions for array element access evaluation we know thati < n, that is the index expression yields an index which is smaller thanthe array size.

This means that the left-hand value—gvararr (g, i)—is a sub g-variable ofg (see also Def. 3.4). Using Def. 3.12, we obtain the corresponding baseaddress as

bag(sc(cabs.m), gvararr (g, i))= bag(sc(cabs.m), g) + i · sizeT (t)= bag(sc(cabs.m), g) + i. (since sizeT (t) = 1)

By definition of valueg, the value of this sub g-variable is given through

valueg(cabs.m, gvararr (g, i))= valueg(cabs.m, g)!i · sizeT (t)= valueg(cabs.m, g)!i= va!i

For the concrete kernel we use gvararr (kalloc(g, hp), i) and obtain a valuev′a!i.

Page 166: Verified Linking for Modular Kernel Verification - Institut f¼r

150 Preservation of Kernel Consistency

gvararr (g, i) has been obtained by evaluating a valid expression in a validconfiguration, i.e. it is a reachable g-variable. Furthermore, it is initializedby assumption and of elementary type. As in the cases before, we unfoldthe definition of konsisgvars to obtain

eqval(t, valueg(cabs.m, gvararr (g, i)),valueg(ck.m, kalloc(gvararr (g, i), hp))

= eqval(t, valueg(cabs.m, gvararr (g, i)),valueg(ck.m, gvararr (kalloc(g, hp), i)) (using Def. of kalloc)

which is equivalent to

eqcont(t, va!i, v′a!i),

which is exactly the definition of the right-hand evaluation of the arrayelement access. q.e.d.

8.4 Static Properties

In this section, we will deal with the static properties of the overall correctnessrelation between the abstract and the concrete kernel. Static means that theseproperties reason about the components of a C0 program, hence componentsthat do not change during the kernel execution.

As there are three components for a C0 program (cf. Sect. 3.2)—a typename environment, a procedure table, and a global symbol table—, so thereare three abstract relations that deal with these components:

1. konsistenv is relating a type name environment to another,

2. konsispt connects two procedure tables, and

3. konsisgst is talking about two global symbol tables.

Lemma 8.10 (Preservation of konsistenv) Let Πabs and ΠCVM denote anabstract kernel and a low-level CVM implementation, which are linkable witheach other. Furthermore, let Πk denote the result of the abstract linking of thesetwo programs.

Then the type name environment teabs of Πabs is consistent with the linkedtype name environment tek:

linkable(Πabs,ΠCVM)∧Πk = link(Πabs,ΠCVM) =⇒

konsistenv(teabs, tek)

Proof From Πk = link(Πabs,ΠCVM), and here in particular

tek = link te(teabs, teCVM),

we know by construction (cf. Def. 6.11) that tek is pairwise distinct and thatall elements in teabs are also elements of tek, and hence

{teabs} ⊆ {tek},

Page 167: Verified Linking for Modular Kernel Verification - Institut f¼r

8.4. Static Properties 151

which is exactly the definition of konsistenv as given in Def. 7.6:

konsistenv(teabs, tek). q.e.d.

Lemma 8.11 (Preservation of konsispt) Let Πabs and ΠCVM denote twolinkable programs, with Πk representing the corresponding linked program.

Then, the procedure table ptabs is consistent with the linked procedure tableptk under the function konsispt.

linkable(Πabs,ΠCVM)∧Πk = link(Πabs,ΠCVM) =⇒

konsispt(ptabs, ptk)

Proof Let (fn, f) denote any defined procedure of the abstract kernel proceduretable, i.e. (fn, f) ∈ {ptabs}.

From linkable(Πabs,ΠCVM) we know that the names of the defined functionsof ptabs and ptCVM are pairwise disjoint, that is:

{map(fst , ptdefabs)} ∩ {map(fst , ptdef

CVM)} = ∅.

The procedure table of the concrete kernel ptk is the result of

replpt(linkpt(ptabs, ptCVM)),

as defined in Def. 6.10. This means that (fn, f ′) ∈ {ptk} with (fn, f ′) =repl(ptk, (fn, f)) or in words: f ′ is equivalent to f except that external functioncalls are replaced by ‘normal’ function calls and that the statements have beenrenumbered (cf. Def. 6.10).

This is exactly the equivalence as defined by the relation eqstmt, which hasbeen introduced in Def. 7.8, hence:

konsis func(f, f ′) =eqstmt(f.body , f ′.body)∧(|s2l(f.body)| = |s2l(f ′.body)|∧(f.params = f ′.params)∧(f.lvars = f ′.lvars)

Since we have picked an arbitrary defined abstract kernel function, we canextend this property for all defined functions in ptabs:

(∀p ∈ {ptdefabs}.∃q ∈ {ptk}.konsis func(p, q))

which is in fact the expanded definition of konsispt(ptabs, ptk) as introducedin Def. 7.9. q.e.d.

The global symbol table is not only a component of the C0 program, but isalso part of the (dynamic) memory configuration. Hence, the correctness lemmahas to include certain assumptions about the next state function, though weshow that the global symbol table does not change at all during the executionof the program.

Page 168: Verified Linking for Modular Kernel Verification - Institut f¼r

152 Preservation of Kernel Consistency

Lemma 8.12 (Preservation of konsisgst) Given cabs and ck denoting C0configurations encoding the abstract kernel and the concrete kernel, where theglobal symbol table of the abstract kernel is consistent with the one of the concretekernel under the relation konsisgst.

Under the assumption that the C0 transition function does not yield Nonefor any of the two configurations, the relation will also hold for the two successorconfigurations c′abs and c′k:

δC0(teabs, ptabs, cabs) = bc′absc∧δC0(tek, ptk, ck) = bc′kc∧konsisgst(gst(cabs.m), gst(ck.m)) =⇒

konsisgst(gst(c′abs.m), gst(c′k.m))

Proof The proof idea is to show that the global symbol table—though partof the C0 configuration—is not changed by any statement at all, that is it isinvariant.

We start with the fact that δC0(teabs, ptabs, cabs) 6= None. In this case weknow that the C0 transition function is defined over the head of the programrest2 hd(s2l(cabs)). By a case distinction over this statement and expandingthe next state function we can show that the global symbol table is not alteredby any C0 statement, that is it stays the same for each C0 step:

δC0(teabs, ptabs, cabs) = bc′absc∧ =⇒gst(cabs.m) = gst(c′abs.m) (8.17)

We do the same for the concrete kernel and obtain

δC0(tek, ptk, ck) = bc′kc∧ =⇒gst(ck.m) = gst(c′k.m) (8.18)

Finally, we combine the above two results (8.17) and (8.18) to show:

gst(cabs.m) = gst(c′abs.m) ∧ gst(ck.m) = gst(c′k.m)∧konsisgst(gst(cabs.m), gst(ck.m)) =⇒

konsisgst(gst(c′abs.m), gst(c′k.m)) q.e.d.

8.5 Dynamic Properties

Some of the kernel relations talk about parts of the kernel configurations thatmight change during execution. We call this share dynamic properties.

Most of the proofs in this section rely on the fact that expression evaluationin the abstract and concrete kernel returns results, which are equivalent undercertain relations.

In particular we want to assure that a successful expression evaluation inthe abstract kernel—a property the implementer of an abstract kernel would

2Actually, δC0 is defined inductively over the statement tree. Yet, there exists a lemmathat shows equivalence in execution for a flattened statement tree, where the head of the listis the next statement to be executed.

Page 169: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 153

probably want to show—also means that the evaluation in the concrete kernelwill not fail.

The following lemmas will define these expectations formally.

Lemma 8.13 (Absence of Run-Time Errors) Let cabs and ck denote two(weak) valid configurations encoding the abstract and concrete kernel, the latterone being obtained by abstract linking.

Moreover, let the invariants regarding the symbol tables, the recursion depth,the type name environments, the program rest and the g-variables in both kernelshold.

Given a program rest, which does not start with an assembly statement, anexternal function call, or an XCall, and assuming that the abstract kernel doesnot produce a run-time error, that is there exists a successor configuration c′abs.

Then, concrete kernel execution will not produce a run-time error neither:

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))=⇒∃c′k. δC0(tek, ptk, ck) = bc′kc

Proof We will prove this lemma by case distinction over the head of theprogram rest of the abstract kernel. Due to readability, we will omit the proofsteps showing that the first statements in the program rests of both kernels areequivalent under eqstmt. This fact can be simply deduced using the konsisprog

invariant and Lemma 8.5.For similar reasons, we do not explicitly describe the deduction of valid

expressions required by Lemma 8.9. This follows directly from the validity ofthe configurations and hence the validity of the statement at the head of theprogram rest (cf. [Lei08]).

Most proof cases are similar, since the existence of a successor configurationmerely relies on a successful memory update. Thus we will not work out theproof for all statements, but concentrate on representative ones.

Case 1: Let the program rest start with an assignment, that is

hd(s2l(cabs.prog)) = Ass(el, er).

Since there is a successor configuration in the abstract kernel, expressionevaluation has not failed there, that is

lval(teabs, cabs.m, el) = bgc

and

rval(teabs, cabs.m, er) = bvc.

Page 170: Verified Linking for Modular Kernel Verification - Institut f¼r

154 Preservation of Kernel Consistency

Furthermore, the memory update has been successful, too, i.e. g is initial-ized or a root g-variable.

From Lemma 8.9 we obtain that the expression evaluation in the concretekernel will be successful, too:

lval(tek, ck.m, el) = bkalloc(g, hp)c

and for the right expression

rval(tek, ck.m, er) = bv′c.

Also, if g has been initialized in the abstract kernel, so is kalloc(g, hp)initialized in the concrete kernel. In the case that g is a root g-variable,then this is also true for kalloc(g, hp) (using Lemma 8.3).

Hence the memory update will succeed and by definition of the C0transition function, there is a successor configuration in the concretekernel, too.

Case 2: Given a program rest that starts with a memory allocation, thatis hd(s2l(cabs.prog)) = PAlloc(el, tn).

Left evaluation has succeeded in the abstract kernel and has yielded ag-variable g. With Lemma 8.9 we obtain the left value kalloc(g, hp) inthe concrete kernel. Due to type name consistency, tn is also defined inthe linked type name environment tek.

Depending on sufficient memory, the memory update in the concretekernel is done either with a null pointer or a pointer to a newly createdheap variable gvarhm(|hst(ck.m)|).g is reachable in the abstract kernel, so we obtain initialization equality forg and kalloc(g, hp), the g-variable to be updated in the concrete kernel, bymerely expanding the definition of konsisgvars. Since the memory updatehas been successful in the abstract kernel, g has to be initialized andhence kalloc(g, hp) is initialized, so the concrete kernel memory updatewill succeed, too.

Case 3: Let the program rest start with a function call, that is

hd(s2l(cabs.prog)) = SCall(σ, fn, plist).

Let us consider the concrete kernel. The execution of a function callbasically fails, if either the left evaluation of the return destination fails,that is

lval(tek, ck.m,Var(σ)) = None

or if right evaluation of one or more parameters fails:

∃p ∈ {plist} : rval(tek, ck.m, p) = None.

Using Lemma 8.9 and the fact that expression evaluation has not failed inthe abstract kernel, we deduce that this will not happen in the concretekernel neither, so there exists a successor configuration. q.e.d.

Page 171: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 155

Lemma 8.14 (Preservation of Recursion Depth Consistency) Let cabsand ck denote two configurations of the abstract and the concrete kernel, whichare in the set of valid configurations.

Furthermore let the recursion depth consistency konsisrd and the programrest consistency hold for both configurations.

In addition, we assume that the program rest of the abstract kernel does notstart with an assembly statement, an external function call, an XCall statement,or that kernel run has already terminated.

Then the recursion depths of the successor configurations c′abs and c′k willalso be consistent:

cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))=⇒konsisrd(c′abs.m, c

′k.m)

Proof We prove the above lemma by case distinction over the current state-ment of the abstract kernel hd(s2l(cabs.prog)).

For all cases but function calls and returns this is trivial, since none of theother statements adds or removes stack frames. Thus, we concentrate on thesetwo cases.

We assumption that there is a successor configuration for the concrete kernel,can easily been shown using Lemma 8.13.

Case 1: We start with function calls, i.e.

hd(s2l(cabs.prog)) = SCall(vn, fn, pl)).

From konsisprog(cabs.prog , ck.prog), Lemma 8.5, and Def. 7.8 we obtainthat also the program rest of the concrete kernel starts with a functioncall statement:

hd(s2l(ck.prog)) = SCall(vn, fn, pl).

From δC0(teabs, ptabs, cabs) = bc′absc we know that the function call suc-ceeds. This means by definition of the transition function that a newstack frame has been added to the local memory, so we obtain that therecursion depth has been increased by 1:

|c′abs.m.lm| = |cabs.m.lm|+ 1 (8.19)

and likewise for the concrete kernel using δC0(tek, ptk, ck) = bc′kc

|c′k.m.lm| = |ck.m.lm|+ 1 (8.20)

Page 172: Verified Linking for Modular Kernel Verification - Institut f¼r

156 Preservation of Kernel Consistency

Using the induction hypothesis konsisrd(cabs.m, ck.m) along with (8.19)and (8.20), we show

|c′k.m.lm| = |ck.m.lm|+ 1= |cabs.m.lm|+ rdoff + 1= |c′abs.m.lm|+ rdoff,

which is exactly the definition of konsisrd (cf. Def. 7.7) :

konsisrd(c′abs.m, c′k.m).

Case 2: Similarly, we proceed with a Return statement at the head of theprogram rest, that is

hd(s2l(cabs.prog)) = Return(e).

Again, we deduce that the head of the concrete kernel’s program rest alsostarts with a Return statement using Lemma 8.5:

hd(s2l(ck.prog)) = Return(e).

Since execution of the Return statement succeeds, we know that thetopmost stack frame has been removed from the local memory, i.e. therecursion depth of the new abstract kernel configuration has been decreasedby 1:

|c′abs.m.lm| = |cabs.m.lm| − 1 (8.21)

and for the concrete kernel’s recursion depth

|c′k.m.lm| = |ck.m.lm| − 1, (8.22)

respectively.

Using konsisrd(cabs.m, ck.m), (8.21), and (8.22), we finally show by ex-panding the definition of konsisrd:

|c′k.m.lm| = |ck.m.lm| − 1= |cabs.m.lm|+ rdoff − 1= |c′abs.m.lm|+ rdoff,

which is again equivalent to konsisrd(c′abs.m, c′k.m). q.e.d.

8.5.1 Program Rest Consistency

Informally, program rest consistency guarantees that the execution of theconcrete kernel follows the same path as abstract kernel execution.

For most statements we rely on certain invariants, like return destination andprocedure table consistency, to prove that program rest consistency holds. Forconditional statements, we have to make sure that expression evaluation in theabstract kernel corresponds to the one in the concrete kernel (see Lemma 8.9).For others—like assignments—program rest consistency is obvious.

Page 173: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 157

Lemma 8.15 (Preservation of Program Rest Consistency) Given cabsand ck, two (weak) valid C0 configurations encoding the abstract and concretekernel, respectively. Furthermore, we assume that the program rest does notstart with an assembler statement, an external function call or an XCall, andthat execution has not terminated. Moreover, let the invariants on symboltables, recursion depth, type name environment, procedure tables, g-variables,and program rest hold.

If there is no run-time error in the abstract kernel, then program rest con-sistency will also hold in the successor configurations of both kernels, formally:

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧konsispt(ptabs, ptk)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(cabs.prog)=⇒konsisprog(c′abs, c

′k)

Proof We will present proofs for the preservation of program rest consistencyduring kernel execution. The proof basically consists of a case distinction overthe head statement of the program rest. We will omit the trivial cases andconcentrate on one representative for statements with similar proofs.

Case 1: The next state function δC0 defines a case distinction for a pro-gram rest starting with Skip: either the whole program rest consists ofsingle statement—the termination case— or other statements follow theSkip.

For the abstract kernel we know by assumption that we are not in thetermination case: ¬is Skip(cabs.prog). Hence, we know that the length ofthe flattened abstract program rest is greater 1, since there has to exist asecond statement:

|s2l(cabs.prog)| > 1.

From konsisprog(cabs.prog , ck.prog) we obtain that the flattened concretekernel program rest is at least as long as the abstract one, hence we canconclude |s2l(ck.prog)| > 1, which then implies

¬is Skip(s2l(ck.prog)).

For this case, the C0 transition function defines the tail of the old programrest as the new configuration’s program rest, that is

s2l(c′abs.prog) = tl(s2l(cabs.prog))

Page 174: Verified Linking for Modular Kernel Verification - Institut f¼r

158 Preservation of Kernel Consistency

and likewise for the concrete kernel

s2l(c′k.prog) = tl(s2l(ck.prog))

This means that the length of the new program rest has been decreasedby 1 and all statements but the consumed Skip have been shifted oneposition to the left in the new program rest:

|s2l(c′abs.prog)| = |s2l(cabs.prog)| − 1∀i < |s2l(c′abs.prog)| : s2l(c′abs.prog)!i = s2l(cabs.prog)!(i+ 1)

and for the concrete kernel

|s2l(c′k.prog)| = |s2l(ck.prog)| − 1∀i < |s2l(c′k.prog)| : s2l(c′k.prog)!i = s2l(ck.prog)!(i+ 1).

Together with the assumption konsisprog(cabs.prog , ck.prog), we can finallyshow the thesis by expanding the definition of konsisprog.

Case 2: The proof for the If-Then-Else case relies on the fact that theconditional expression is evaluated consistently in both kernels, so thatthe program rests are updated equivalently.

Since cabs is valid, the head of the program rest is also valid. In theIfte(e, s1, s2) case this means that e is a Boolean expression:

type(teabs, toplst(cabs.m), gst(cabs.m), e) = BoolT .

Since there is a successor configuration for cabs, right-hand expressionevaluation cannot have failed, that is

rval(teabs, cabs.m, e) = bvc.

Using Lemma 8.9, we obtain that there is also a right-hand value of e inthe concrete kernel

rval(tek, ck.m, e) = bv′c

and

eqcont(BoolT , v, v′)

which we simplify—using the definition of eqcont—to

v = v′.

Furthermore, depending on v, the program rest of the abstract kernelstarts either with s1 or s2:

s2l(c′abs.prog) =

{s1#tl(s2l(cabs.prog)) if v = Trues2#tl(s2l(cabs.prog)) otherwise

Page 175: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 159

Due to konsisprog, the head of the concrete program rest is also a condi-tional statement:

hd(s2l(ck.prog)) = Ifte(e, s′1, s′2),

where s1 and s′1, as well as s2 and s′2 are equivalent under eqstmt.

Since expression evaluation has returned equal values in both kernel,program rest consistency is also preserved in both kernel successor config-urations.

Case 3: Let the program rest start with a function call, that is

hd(s2l(cabs.prog)) = SCall(σ, fn, plist).

We abbreviate the called function with f = the(mapof (ptabs, fn)).

Since there is a successor configuration c′abs for the abstract kernel, theprogram rest has been updated by replacing the function call statementwith the body of fn, that is

s2l(c′abs.prog) = s2l(f.body)#tl(s2l(cabs.prog))

Let c′k denote the successor configuration of the concrete kernel, whichexists following Lemma 8.13. This means that there is an entry in theconcrete procedure table ptk with name fn and a descriptor f ′, such that

s2l(c′k.prog) = s2l(f ′.body)#tl(s2l(ck.body)).

Since fn is a defined function in the abstract kernel—(fn, f) ∈ ptdefabs—,

we can expand the invariant on procedure tables konsispt(ptabs, ptk) (cf.Def. 7.9). In particular, this means that

eqstmt(f.body , f ′.body).

This is all we need to prove the function call case. q.e.d.

8.5.2 Symbol Table Consistency

Symbol table consistency guarantees that corresponding variables in the abstractand concrete kernel are of the same type. In this section, we will show thatthe consistency relations are preserved during C0 kernel steps. In Sect. 8.4,Lemma 8.12, we have already proven that for global symbol tables.

Lemma 8.16 (Preserving Consistency of konsis lst) Let cabs be a weakvalid abstract kernel configuration and ck a valid concrete kernel configuration.Furthermore, let the invariants on symbol tables, program rest, type nameenvironments, procedure table, recursion depth, and g-variables hold. If theabstract program rest does not start with an external function call or an assemblerstatement and if the abstract kernel has not terminated and the C0 transition

Page 176: Verified Linking for Modular Kernel Verification - Institut f¼r

160 Preservation of Kernel Consistency

function yields an abstract successor configuration, then the consistency betweenthe local symbol tables will be preserved.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧konsispt(ptabs, ptk)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))

=⇒konsis lst(c′abs.m, c

′k.m)

Proof This proof is done by case distinction over the head of the abstractprogram rest.

We remember: konsis lst(cabs.m, ck.m) says that for each local symbol tablein the abstract kernel, there exists a corresponding, equal symbol table in theconcrete kernel (cf. Def. 7.17). Here, corresponding means that the abstractkernel index is shifted by the constant recursion depth offset:

konsis lst(cabs.m, ck.m) =∀i. i < |sc(cabs.m).lst | =⇒ sc(cabs.m).lst !i = sc(ck.m).lst !(i+ rdoff).

In most cases, the proof is very straightforward, since most statements donot change the local symbol tables. In fact, only function calls and returns arerelevant. We will concentrate on these two cases.

Case 1: We start with function calls, that is

hd(s2l(cabs.prog)) = SCall(σ, fn, plist)).

From the fact that execution of the abstract kernel has not failed, weknow that for the called function fn, there exists a descriptor f such that(fn, f) ∈ {ptdef

abs}.Furthermore, there has been added a new frame to the existing localmemory stack, whose symbol table is made up by the symbol table of thecalled function:

toplst(c′abs.m) = stfunc(f)= [email protected].

Using Lemma 8.13, which implies that concrete kernel execution does notfail neither, together with the invariant on procedure tables, we obtain afunction descriptor f ′ for fn in the concrete kernel for which holds:

f ′.params = f.params ∧ f ′.lvars = f.lvars.

Page 177: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 161

This means that the symbol table of the topmost local stack frame ofthe concrete kernel—which has just been added—is equal to the functionsymbol table of f ′, which again is equal to the function symbol table of f :

toplst(c′k.m) = stfunc(f ′)= stfunc(f)= [email protected].

We take this intermediate result and the preservation of recursion depthconsistency as defined in Lemma 8.14 together with the fact that allother local symbol tables stay untouched. By expanding the definition ofkonsis lst, we prove the function call case.

Case 2: Let the abstract program rest start with a return statement, i.e.

hd(s2l(cabs.prog)) = Return(e).

The successful execution of this statement in the abstract kernel hasremoved the topmost frame from the local memory stack. The samehappens in the concrete kernel.

This means that in both successor configurations, the recursion depth hasbeen decreased by 1:

|sc(c′abs.m).lst | = |sc(cabs.m).lst | − 1|sc(c′k.m).lst | = |sc(ck.m).lst | − 1

Together with the fact that all other frames stay unchanged and that theinvariant on local symbol tables holds for them by assumption, we provethis case. q.e.d.

Lemma 8.17 (Preserving Consistency of konsishst) Let cabs be a weakvalid abstract kernel configuration and ck be a valid concrete kernel configuration.Furthermore, let the invariants on symbol tables, program rest, type nameenvironments, procedure table, recursion depth, and g-variables hold, and inparticular the invariants on the heap. If the abstract program rest does not startwith an external function call or an assembler statement and if the abstractkernel has not terminated and the C0 transition function yields an abstractsuccessor configuration, then the consistency between the heap symbol tables will

Page 178: Verified Linking for Modular Kernel Verification - Institut f¼r

162 Preservation of Kernel Consistency

be preserved.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧konsishst(cabs.m, ck.m, hp)konsispt(ptabs, ptk)∧heapinv(cabs.m, ck.m)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))

=⇒∃hp′. konsishst(c′abs, c

′k, hp

′)

Proof We prove this lemma by case distinction over the head of the abstractprogram rest. All cases but memory allocation are trivial, since the otherstatements do not change the heap symbol table.

Given a program rest head hd(s2l(cabs.prog)) = PAlloc(e, tn). This meansthere is a type t with mapof (te,abs , tn) = btc.

There are two possible successful execution paths for memory allocation:

• either there is enough heap memory available to allocate a new object oftype t—?heap(cabs.m, t) = True,

• or heap memory is used up, i.e. ?heap(cabs.m, t) = False.

In the latter case, the heap memory stays unchanged, that is c′abs.m.hm =cabs.m.hm. Since we assume that the invariant on the heap symbol table holdsfor cabs and ck, we are done setting hp′ = hp.

Let us assume that there is enough heap memory available in the abstractkernel. In this case, a new element has been appended to the old heap symboltable:

hst(c′abs.m) = hst(cabs.m)@[(undef , t)]

Due to the invariant on heap memory, heapinv(cabs.m, ck.m), there is alsoenough memory available in the concrete kernel3. Furthermore, the type nametn is associated with the same type t in the concrete kernel, due to type nameconsistency. Hence we add the same element to the concrete heap symbol tableas in the abstract kernel, thus

snd(hst(c′abs.m)!(|hst(cabs.m)|)) = snd(hst(c′k.m)!(|hst(ck.m)|)) (8.23)

We update the heap map function as follows:

hp′(i) =

{|hst(ck.m)| if i = |hst(cabs.m)|hp(i) otherwise

3The concrete kernel can have only more allocated memory, but never less than theabstract kernel

Page 179: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 163

Using the assumption that the invariant on the heap symbol table has heldbefore together with (8.23), we finally obtain konsishst(c′abs, c

′k, hp

′). q.e.d.

8.5.3 Return Destination Consistency

Return destination consistency states that the return destinations of correspond-ing stack frames are equivalent under kalloc.

Lemma 8.18 (Preservation of konsisreturn) Let cabs and ck be two (weak)valid configurations encoding both kernels. Furthermore, let the invariants onsymbol tables, program rest, type name environments, procedure table, recursiondepth, and g-variables hold. If the abstract program rest does not start withan external function call or an assembler statement and if the abstract kernelhas not terminated and the C0 transition function yields an abstract successorconfiguration, then the return destination consistency will be preserved.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧konsispt(ptabs, ptk) ∧ konsishst(cabs.m, ck.m)konsisreturn(cabs, ck, hp)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))=⇒konsisreturn(c′abs, c

′k, hp)

Proof We prove this lemma by case distinction over the head of the abstractprogram rest. All cases but return and function call are trivial, hence we omitthe former ones.

Case 1: We start with a function call, that is

hd(s2l(cabs.prog)) = SCall(σ, fn, plist).

The return destination of this function call is a g-variable which is giventhrough the left-hand evaluation of the expression Var(σ). We know thatthis left-hand value exists, since abstract kernel execution has yielded asuccessor configuration:

lval(teabs, cabs.m,Var(σ)) = bgc.

Corresponding to the definition of the C0 transition function, this g-variable is then used as the second component of the newly added framein the local memory stack, that is:

snd(toplm(c′abs.m)) = g.

Page 180: Verified Linking for Modular Kernel Verification - Institut f¼r

164 Preservation of Kernel Consistency

From Lemma 8.9 we obtain that the left-hand evaluation of the variableaccess in the concrete kernel is successful, too:

lval(tek, ck.m,Var(σ)) = bkalloc(g, hp)c.

Hence, the second component of the new topmost stack frame in thesuccessor configuration of the concrete kernel is

snd(toplm(c′abs.m)) = kalloc(g, hp).

Using the recursion depth invariant and Lemma 8.14, we know that therecursion depth—and thus the topmost stack frame— is shifted by aconstant rdoff to the recursion depth of the abstract kernel.

Hence, we obtain:

snd(toplm(c′k.m)) = snd(lst(c′k.m)!(|lst(c′abs.m)|+ rdoff))= kalloc(g, hp)= kalloc(snd(toplm(c′abs.m)), hp)= kalloc(snd(c′abs.m.lm!(|lst(c′abs.m)|), hp))

Since all other stack frames of the successor configurations stay unchanged,we can exploit the assumption of konsisreturn(cabs.m, ck.m, hp) togetherwith the equation above to prove this case.

Case 2: The return case hd(s2l(cabs.prog)) = Return(e) is much simpler.

Successful execution in the abstract kernel leads to a new local memory,which is equal to the old one without the topmost stack frame.

The same happens in the concrete kernel. This means that recursiondepth has been decreased by one in both successor configurations. Allother stack frames stay unchanged.

By simply using the assumption konsisreturn(cabs.m, ck.m, hp), we havecompleted the proof for this case. q.e.d.

8.5.4 Heap Invariants

The heap invariants capture two properties of the heap: the first is injectivityof the heap map, that is different arguments to this function deliver differentresults, the second one is boundedness, i.e. the values of the heap map—heapindices—stay within the boundaries of the concrete heap size.

Lemma 8.19 (Preservation of Heap Invariants) Let cabs and ck be two(weak) valid configurations encoding both kernels. Furthermore, let the invari-ants on symbol tables, program rest, type name environments, procedure table,recursion depth and g-variables hold. If the abstract program rest does not startwith an external function call or an assembler statement and if the abstractkernel has not terminated and the C0 transition function yields an abstract

Page 181: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 165

successor configuration, then there exists an (updated) heap map such that theheap invariants hold in the successor configurations, too.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧hmapinj(cabs.m, hp) ∧ hmapbound(cabs.m, ck.m, hp)

heapinv(cabs.m, ck.m)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))=⇒∃hp′. hmapinj(c

′abs.m, hp

′) ∧ hmapbound(c′abs.m, c′k.m, hp

′)

Proof We conduct this proof by case distinction over the head of the ab-stract program rest. All cases but memory allocation, i.e. hd(s2l(cabs.prog)) =PAlloc(e), are trivial and we omit them.

In C0, each newly allocated heap object gets an index larger than all otherobjects allocated before. This is due to the fact that there is no explicitdeallocation of memory in C0.

We consider again the two possible execution paths depending on heapmemory availability. The case when no memory is available is easy: since thenew heap memory is equal to the old one, we leave the heap map as it is:

hp′ = hp.

Since we have assumed the validity of both invariants, hmapinj(cabs.m, hp) andhmapbound(cabs.m, ck.m, hp), the proof is done.

Let us consider the positive case, that is ?heap(cabs.m, t) = True, withmapof (teabs, tn) = btc. Then, the new heap has been extended by an element,both in the abstract and in the concrete kernel due to heapinv(cabs.m, ck.m),type name consistency, and absence of run-time errors (cf. Lemma 8.13).

In this case, we update the heap map function in the following way:

hp′(i) =

{|ck.m.hm| if i = |cabs.m.hm|hp(i) otherwise

From hmapbound(cabs.m, ck.m, hp) we know that the value |ck.m.hm| has notbeen assigned to any abstract heap index before:

hmapbound(cabs.m, ck.m, hp) ≡∀i. i < |cabs.m.hm| =⇒ hp(i) < |ck.m.hm|

Together with the assumption that the heap map has been injective before, weobtain the injectivity for hp′ in the successor configuration:

hmapinj(c′abs.m, hp

′).

Page 182: Verified Linking for Modular Kernel Verification - Institut f¼r

166 Preservation of Kernel Consistency

This is also true for the boundedness of the heap map. Since it has beenbounded before by assumption, we only have to consider the function value forthe new abstract heap index i = |cabs.m.hm|, which is:

hp′(i) = |ck.m.hm|= |c′k.m.hm| − 1.

Hence we obtain:

∀i.i < |c′abs.m.hm| =⇒ hp′(i) < |c′k.m.hm|≡ hmapbound(c′abs.m, c

′k.m, hp

′). q.e.d.

8.5.5 g-Variable Consistency

The consistency relation between g-variables of the abstract and concrete kernelis the most crucial of all kernel relations. Informally, it says: when a g-variableis reachable in the abstract kernel, so is its counterpart in the concrete kernel;plus, their initialization status is the same and their types are equal in the casethat they are elementary. Finally, if the abstract kernel variable is elementaryand initialized, then its value and the value of its concrete counterpart are equalunder eqval.

Lemma 8.20 (Equal Content after Memory Update) The value of sev-eral g-variables might be changed by a memory update, so we have to make surethat the updates in the abstract and concrete kernel ‘match’ somehow.

Given two (weak) valid configurations for the two kernels and two values vand v′ that are used in the successful memory update of the valid g-variablesg and kalloc(g, hp). Moreover, let v and v′ be equal under the eqcont function.If x is valid, initialized, of elementary type, and value equal with its concretecounterpart, then this is also true after the memory update.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧updmm(cabs.m, g, v) = bm′absc ∧ updmm(ck.m, kalloc(g, hp), v′) = bm′kckonsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsishst(cabs.m, ck.m)∧hmapinj(cabs.m, hp) ∧ hmapbound(cabs.m, ck.m, hp)∧x ∈ gvar√(sc(cabs.m) ∧ g ∈ gvar√(sc(cabs.m)∧(?initg(cabs.m, x) −→ eqval(x, cabs.m, kalloc(x, hp), ck.m))∧?elemT (typeg(sc(cabs.m), x)) ∧ eqcont(typeg(sc(cabs.m), g), v, v′)

=⇒?initg(m′abs, x) −→ eqval(m′abs, x,m

′k, kalloc(x, hp))

Proof We consider an arbitrary but fixed g-variable x. We distinguish twocases: either x is changed by the memory update or not. If x is a sub g-variableof g, then its value will be updated. Otherwise, it stays unchanged.

Page 183: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 167

Case 1: We start with the easier case, that is x /∈ subg(g). Using thisalong with the fact that x is elementary yields that there is no overlapbetween the memory regions of x and g:

bag(sc(cabs.m), x) /∈{bag(sc(cabs.m), g), . . . ,bag(sc(cabs.m), x) + (sizeT (typeg(sc(cabs.m), g))− 1)}.

Due to the transitivity of sub g-variables (cf. Lemma 8.4) and type equalityunder kalloc (cf. Lemma 8.1), we deduce the corresponding property forkalloc(x, hp) and kalloc(g, hp) in the concrete kernel.

This means that the values of both x and kalloc(x, hp) are not changedby the memory update, hence

valueg(m′abs, x) = valueg(cabs.m, x)valueg(m′k, kalloc(x, hp)) = valueg(ck.m, kalloc(x, hp)).

Using the assumption that value equality for x and kalloc(x, hp) has beenestablished before the memory update, we simply expand the definitionof eqval to complete the proof for this case.

Case 2: Let x be a sub g-variable of g, i.e. x ∈ subg(g). So, kalloc(x, hp)is in the set of sub g-variables of kalloc(g, hp).

By definition, g is definitely initialized after the memory update. This isalso true for all its sub g-variables, hence

?initg(m′abs, x) = True. (8.24)

As we have seen in Sect. 3.5.2, the value of g after the memory update(see Def. 3.10 and Def. 3.14) is equal to v, that is

v = valueg(m′abs, g)= m′abs.memg(g).ct[bag(sc(m′abs), g), sizeT (typeg(sc(m

′abs), g))]

(8.25)

Similarly, we obtain for v′ and kalloc(g, hp) in the concrete kernel:

v′ = valueg(m′k, kalloc(g, hp))= m′k.memg(kalloc(g, hp)).ct[bag(sc(m′k), kalloc(g, hp)),

sizeT (typeg(sc(m′k), kalloc(g, hp)))] (8.26)

In the case that g is of an elementary type, its size is equal to 1 and xand g are trivially identical. Similarly, this is also true for kalloc(g, hp)and kalloc(x, hp), using the corresponding lemmas 8.1 and 8.4.

Formally we obtain:

v = valueg(m′abs, g)= mmemg(g)[bag(sc(m′abs), g), 1)]= mmemg(x)[bag(sc(m′abs), x), 1)]= valueg(m′abs, x)

Page 184: Verified Linking for Modular Kernel Verification - Institut f¼r

168 Preservation of Kernel Consistency

and for the concrete kernel v′ = valueg(m′k, kalloc(x, hp)). Using theassumption eqcont(typeg(sc(cabs.m), g), v, v′) and the definition of eqval,we finally obtain

eqval(x,m′abs, kalloc(x, hp),m′k),

which together with equation (8.24) completes the proof for the case thatx = g.

Let us consider the non-trivial case, i.e. x ∈ subg(g)∧x 6= g. We rememberthat the value of g is equal to v after the memory update (see eq. (8.25)).

We recall the structure of g-variables as presented in Sect. 3.3.1. Theirtree-like structure can finally be broken down into a set of elementarysub g-variables (see Fig. 3.3). As we have seen in Sect. 3.3.1, our memorymodel is flat in the sense that we merely string together g-variables (seealso Def. 3.12).

This results in a compact memory, which is free of holes, or in otherwords: for each memory cell within the memory range of a non-elementaryg-variable, there exists a elementary g-variable, whose value is stored inthis memory cell.

Hence, there exists an index i, such that

bag(sc(m′abs), x) = bag(sc(m′abs), g) + i

with 0 ≤ i < sizeT (typeg(sc(cabs.m), g)). Due to type equality, thisindex i also exists for kalloc(x, hp) in the concrete kernel with respect tokonsis(g, hp).

Using the fact that x is elementary and thus has a type size of 1, weobtain for the value of x:

valueg(m′abs, x) = m′abs.memg(x).ct[bag(sc(m′abs), x), 1)]= m′abs.memg(g).ct[bag(sc(m′abs), g),

sizeT (typeg(sc(m′abs), g))](i)

= v(i)

Similarly, we proceed in the concrete kernel to obtain

valueg(m′k, kalloc(x, hp)) = v′(i)

The definition of eqcont (cf. Def. 7.14) for the aggregate type case guaran-tees that the equality relation holds for all elements of that type. Usingthe assumption eqcont(typeg(sc(cabs.m), g), v, v′), this implies

eqcont(typeg(sc(m′abs), x), v(i), v′(i)),

which is by definition and using the fact that the abstract and concretememory have been updated appropriately

eqval(x,m′abs, kalloc(x, hp),m′k). q.e.d.

Page 185: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 169

Lemma 8.21 (konsisnamed on Memory Updates) We will now use therecent lemmas to prove that the g-variable consistency for named g-variables ispreserved by a memory update.

Given two kernel configurations cabs and ck. We assume that with two equalvalues, v and v′, the memory update of the g-variables g and kalloc(g, hp) issuccessful. If the konsisnamed relation has held before, then it will be preservedby the memory updates.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧updmm(cabs.m, g, v) = bm′absc ∧ updmm(ck.m, kalloc(g, hp), v′) = bm′kc∧ konsispt(ptabs, ptk)∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(cabs.m, ck.m, hp)∧konsispt(ptabs, ptk) ∧ konsishst(cabs.m, ck.m)∧konsisreturn(cabs, ck, hp) ∧ eqcont(typeg(sc(cabs.m), g), v, v′)

=⇒konsisnamed(m′abs,mk, hp)

Proof We remember: konsisnamed is an invariant over all named—hencereachable—g-variables in the abstract kernel. Without limitation of generality,let x denote such a g-variable.

We distinguish two proof cases: either x is a sub g-variable of the variable gused in the memory update or it is not.

Case 1: We start with the sub g-variable case, that is x ∈ subg(g). Weuse Lemma 8.4 to obtain kalloc(x, hp) ∈ subg(kalloc(g, hp)).

We assume that x is a named g-variable in the new abstract memoryconfiguration:

x ∈ reachablenamed(m′abs) ≡ x ∈ gvar√ ∧ ?named(x). (8.27)

Using Lemma 8.2 on transitivity of g-variable validity and the trivial fact,that kalloc(x, hp) is named, too, we derive

kalloc(x, hp) ∈ reachablenamed(m′k). (8.28)

The variable used in a memory update is always initialized after theupdate has happened. So, all sub g-variables of g are initialized, too (cf.Def. 3.11).

This means that

?initg(m′abs, x) = True = ?initg(m′k, kalloc(x, hp))=⇒ eqinit(m′abs, x,m

′k, kalloc(x, hp)) (8.29)

Let us assume that x is of an elementary type, i.e.

?elem(typeg(sc(m′abs), x)). (8.30)

Page 186: Verified Linking for Modular Kernel Verification - Institut f¼r

170 Preservation of Kernel Consistency

Using Lemma 8.1 on type equality under kalloc, we deduce

eqtype(m′abs, x,m′k, kalloc(x, hp)). (8.31)

Furthermore, from equation (8.29) we know that x is initialized. So Letus consider the value of x after the memory update. Using the auxiliaryLemma 8.20 for value equality after memory updates and the fact thatsymbol configurations are not altered by a memory update, we obtain

eqval(x,m′abs, kalloc(x, hp),m′k). (8.32)

Ultimately, we combine the equations (8.27), (8.28), (8.29), (8.30), (8.31),and (8.32) to complete the proof for this case.

Case 2: Now we will consider the case that x is not a sub g-variable ofthe updated g: x /∈ subg(g). Using the transitivity of sub g-variables asspecified in Lemma 8.4, we obtain kalloc(x, hp) /∈ subg(kalloc(g, hp)).

The first steps of this case are identical to those in the sub g-variable casebefore: x and kalloc(x, hp) are also reachable in the succeeding memoryconfigurations m′abs and m′k (see equations (8.27) and (8.28)).

Since the invariant was valid in the configurations cabs and ck, the initial-ization of both x and kalloc(x, hp) was equal at this point in time. [Lei08]has shown that the initialization of variables not afflicted by a memoryupdate (i.e. that are not sub g-variables of the updated variable) staysuntouched. This gives us

eqinit(m′abs, x,m′k, kalloc(x, hp)) (8.33)

Let us assume again that x is elementary:

?elem(typeg(sc(m′abs), x)). (8.34)

The rest of this case’s proof is equivalent to the case before. We use againLemma 8.1 to establish type equality and—in case that x is initialized—Lemma 8.20 to obtain value equality after the memory update. q.e.d.

Reachability of Nameless g-Variables

Things are slightly different and more complicated when dealing with namelessvariables. We have to strengthen our arguments slightly, in particular regardingthe values used in the memory update. Thus, we will introduce the notion ofreachable pointers first.

Definition 8.3 (Memcells Containing Pointers to Reachable Objects)We will define a predicate mcell reachable : memconfT ×mcellT → B for mem-ory cells containing pointers to reachable objects. The predicate evaluatesto True, if a pointer memory cell contains a pointer to a reachable memoryobject regarding a given memory configuration m or if it is of non-pointer type,formally:

NullPointermcell reachable(m,Ptr(NullPointer))

Page 187: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 171

g ∈ reachablenamed(m)mcell reachable(m,Ptr(g))

reachablenameless(m, g, i)mcell reachable(m,Ptr(g))

c ∈ {Int(i),Unsgnd(u),Bool(b),Char(z)}mcell reachable(m, c)

We lift this definition to the level of memory content by defining a predicatecont reachable : (N → mcellT ) ×N → B. Given a piece of memory content ctand a bound n ∈ N, the predicate evaluates to True if all memory cells of thiscontent up to n− 1 contain reachable pointers, formally:

cont reachable(m, ct, n) = ∀i < n.mcell reachable(m, ct(i))

Lemma 8.22 (konsisnameless on Memory Update) For kernel configura-tions cabs and ck, we assume that with two equal values, v and v′, the memoryupdate of the g-variables g and kalloc(g, hp) is successful. If the konsisgvars

relation has held before, then its sub relation konsisnameless will be preserved bythe memory updates.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧updmm(cabs.m, g, v) = bm′absc ∧ updmm(ck.m, kalloc(g, hp), v′) = bm′kc∧konsispt(ptabs, ptk) ∧ konsisreturn(cabs, ck, hp)∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(cabs.m, ck.m, hp)∧konsishst(cabs.m, ck.m) ∧ eqcont(typeg(sc(cabs.m), g), v, v′)

contreachable(cabs.m, v, sizeT (typeg(sc(cabs.m, g))))

=⇒konsisnameless(m′abs,m

′k, hp)

Proof We remember the definition of konsisnameless:

konsisnameless(cabs.m, ck.m, hp) ≡∀x, i.reachablenameless(cabs.m, x, i) =⇒reachablenameless(ck.m, kalloc(x, hp), i)∧(?elem(typeg(sc(cabs.m), x) =⇒eqtype(cabs.m, x, ck.m, kalloc(x, hp))∧eqval(x, cabs.m, kalloc(x, hp), ck.m)))

First, we fix an arbitrary nameless g-variable x. Then, the proof is conductedby induction over the reachability measure i (cf. Def. 7.13).

Page 188: Verified Linking for Modular Kernel Verification - Institut f¼r

172 Preservation of Kernel Consistency

Case 1: We begin with the induction start, i.e. i = 0. Hence, we assumethat x is reachable in 0 steps in the successor memory configuration m′abs:

reachablenameless(m′abs, x, 0). (8.35)

At first, we will show that kalloc(x, hp) is reachable in 0 steps in the newmemory configuration of the concrete kernel m′k.

Expanding the definition of nameless reachability, we obtain that thereexists a valid named pointer variable p, which points to x:

?named(p)∧p ∈ gvar√(m′abs)∧valueg(m′abs, p) = Ptr(x)∧tyg(sc(m′abs), p) = PtrT (tn)∧?init(m′abs, p) = True (8.36)

Furthermore, x itself is a valid nameless g-variable.

The validity of a named variable also implies its reachability. Togetherwith the fact that the symbol configurations do not change during amemory update, we derive that p was also reachable before the memoryupdate.

Using the transitivity of g-variable validity yields that kalloc(p, hp) wasreachable in ck.m and hence is reachable in m′k, too:

kalloc(p, hp) ∈ reachablenamed(m′k) (8.37)

By expanding the consistency relation for named g-variables and using(8.36), we obtain the equivalence of initialization and type for p andkalloc(p, hp) in the old memory configurations. Both type and initializa-tion are invariant regarding a memory update, so we know:

?init(m′abs, p) = ?init(m′k, p) =⇒eqinit(m′abs, p,m

′k, kalloc(p, hp)) (8.38)

and

eqtype(m′abs, p,m′k, kalloc(p, hp)) (8.39)

In Lemma 8.21 we have shown that the consistency relation for namedg-variables is preserved by a memory update: konsisnamed(m′abs,m

′k, hp).

Since p is initialized, the values of p and kalloc(p, hp) are equivalent undereqval:

eqval(p,m′abs, kalloc(p, hp),m′k),

which we expand using the fact that p is of pointer type:

valueg(m′k, kalloc(p, hp)) = Ptr(kalloc(x, hp)). (8.40)

Page 189: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 173

Let us summarize what we have until now: we know that there existsa reachable and hence valid named g-variable kalloc(p, hp) of pointertype PtrT (tn), which points to kalloc(x, hp), and is initialized (eq. (8.37),(8.38), (8.39), (8.40)).

There is only one requirement left to satisfy reachability in 0 steps, whichis kalloc(x, hp) being a valid nameless g-variable. This can easily bededuced using Lemma 8.2 and the definition of kalloc, thus

reachablenameless(m′k, kalloc(x, hp), 0). (8.41)

We come now to type equality of x and kalloc(x, hp). Our theorem onlymakes a statement for variables of elementary type, hence we assume:

?elem(typeg(sc(m′abs), x))

Using [Lei08, Theorem 8.1], which says that nameless heap g-variablesbeing reachable after a memory update have already been reachable before,and the pointer reachability of v we obtain from (8.35):

∃j. reachablenameless(cabs.m, x, j).

This is the necessary precondition to expand the definition of the con-sistency relation for nameless g-variables. Due to symbol configurationinvariance, we simply obtain type equality for the successor configurations:

eqtype(m′abs, x,m′k, kalloc(x, hp)). (8.42)

Finally, there is still value equivalence to show for x and kalloc(x, hp).Again, we expand konsisnameless(cabs.m, ck.m, hp) to derive that this wasthe case before the memory update.

This—together with the assumptions and the fact that nameless (heap)variables are initialized by definition—is used to instantiate Lemma 8.20,hence obtaining that the values of x and kalloc(x, hp) are also equivalentafter the memory update:

eqval(m′abs, x,m′k, kalloc(x, hp)). (8.43)

Combining equations (8.35), (8.41), (8.42), and (8.43) finish the proof forthe induction start.

Case 2: Let us now consider the induction step, that is we assume

reachablenameless(m′abs, x, i+ 1). (8.44)

We start again with the proof for the reachability of kalloc(x, hp) in thenew concrete memory m′k.

Corresponding the definition of reachablenameless (cf. Def. 7.13) in theinductive case, there are three different ways in which x can be reachablein m′abs.

1. The first, trivial case, is that x is also reachable in i steps. We justuse the induction hypothesis to prove this case.

Page 190: Verified Linking for Modular Kernel Verification - Institut f¼r

174 Preservation of Kernel Consistency

2. In the second case, there is a heap variable p, which is itself reachablein i steps and points to a valid x:

reachablenameless(m′abs, p, i)∧typeg(sc(m

′abs), p) = PtrT (tn)∧

valueg(m′abs, p) = Ptr(x)∧x ∈ gvar√(m′abs).

We instantiate the induction hypothesis for p and kalloc(p, hp), re-spectively, and obtain type and value equality. This and transitivityof g-variable validity satisfy the preconditions for

reachablenameless(m′k, kalloc(x, hp), i+ 1).

3. In the third and last case, there exists a heap variable p, which isagain reachable in i steps, and x is a sub g-variable of it:

reachablenameless(m′abs, p, i)∧x ∈ subg(p)∧x ∈ gvar√(m′abs).

Here again, the induction hypothesis gives us the reachability forkalloc(p, hp) in m′k in j steps. In Lemma 8.4 we have shown that x ∈subg(p) implies kalloc(x, hp) ∈ subg(kalloc(p, hp)). So, kalloc(x, hp)is reachable in m′k in i+ 1 steps.

We still have to show type and value equality for x and kalloc(x, hp).Since the first one is quite trivial (Lemma 8.1 and symbol configurationinvariance), we consider only the latter one.

We proceed similarly to the induction start: We consider the case that xis elementary. Since it is reachable in m′abs, so it must have been reachablein cabs.m. This means that x and kalloc(x, hp) have had the same valuesbefore the memory update (expanding the definition of konsisnameless).

Using Lemma 8.20 with these results, we finally obtain

eqval(x,m′abs, kalloc(x, hp),m′k). q.e.d.

Lemma 8.23 (Equal Content for g-Variables) We will now expand valueequality of the abstract and concrete kernel variables to those of non-elementarytypes.

Let the invariant on g-variables hold for two (weak) valid kernel configura-tions cabs and ck. Then, the content of a reachable g-variable g is equal under

Page 191: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 175

eqcont with the content of its concrete counterpart kalloc(g, hp).

ck ∈ conf √(tek, ptk) ∧ linkable(Πabs,ΠCVM)∧

cabs ∈ conf weak√ ∧ konsistenv(teabs, tk)∧Πk = link(Πabs,ΠCVM) ∧ konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧?init(cabs.m, g) ∧ g ∈ reachablegvars(cabs.m)=⇒eqcont(typeg(sc(cabs.m), g), valueg(cabs.m, g), valueg(ck.m, kalloc(g, hp))

Proof We distinguish between g being elementary or not. The first case istrivial, since we simply expand the konsisgvars invariant.

So let us consider the non-elementary case. Then, the structure of g is giventhrough a set of elementary g-variables with base addresses within the range of[bag(sc(cabs.m), g), sizeT (typeg(sc(cabs.m), g))].

For an arbitrary sub g-variable x let 0 ≤ i < sizeT (typeg(sc(cabs.m), g))denote its offset in this range and let v denote the value of g. The correspondingvalue of x is then v(i).

We proceed in a similar way for kalloc(g, hp), whose value we abbreviatewith v′, and kalloc(x, hp), its sub g-variable with offset i.

Furthermore, since g is reachable and initialized, so are all its sub g-variables, in particular x. By applying the definition of konsisgvars, we obtaineqval(x, cabs.m, kalloc(x, hp), ck.m), which is equivalent to

eqcont(typeg(sc(cabs.m), x), v(i), v′(i)). (8.45)

This holds for all sub g-variables of g, and hence for all offsets i in the rangementioned above. With the same argumentation as in Lemma 8.20 regardingthe structure of g-variables, we inductively show that eq. (8.45) holds for allarray elements or struct components, respectively, comprised by g.

This is exactly the definition of eqcont for non-elementary variables aspresented in Def. 7.14. q.e.d.

Lemma 8.24 (g-Variable Consistency) We can now formulate the lemmafor the preservation of g-variable consistency for an ordinary C0 kernel step.Given two kernel configurations cabs and ck and assuming that the usual kernelrelations hold—in particular the one for g-variable consistency—and that thereexists a successor configuration in the abstract kernel, then g-variable consistency

Page 192: Verified Linking for Modular Kernel Verification - Institut f¼r

176 Preservation of Kernel Consistency

will be preserved.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc ∧ δC0(tek, ptk, ck) = bc′kc∧konsisprog(cabs, ck) ∧ konsistenv(teabs, tek)∧konsisrd(cabs.m, ck.m) ∧ konsis lst(cabs.m, ck.m)∧konsisgst(cabs.m, ck.m) ∧ konsisgvars(teabs, cabs.m, tek, ck.m, hp)∧konsispt(ptabs, ptk) ∧ konsishst(cabs.m, ck.m)∧konsisreturn(cabs, ck, hp)∧hmapinj(cabs.m, hp) ∧ hmapbound(cabs.m, ck.m, hp)∧heapinv(cabs.m, ck.m)∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))

=⇒∃hp′. konsisgvars(teabs, c′abs.m, tek, c

′k.m, hp

′)

Proof The proof is done by case distinction over the head of the abstractprogram rest hd(s2l(cabs.prog)). For statements that do not update the memory,thus have no affect on the reachability of g-variables and their values, the proofis very straightforward. Hence, we will concentrate on the more interestingcases.

Case 1: We start with assignments, that is

hd(s2l(cabs.prog)) = Ass(el, er).

The proof for the assignment case basically relies on the successful appli-cation of both lemmas 8.21 and 8.22, which state that in the case of asuccessful memory update the konsisgvars relation will be preserved. Thus,we will concentrate on discharging the assumptions of these lemmas.

Since there is a successor configuration c′abs for the abstract kernel, thememory update has been successful according to the transition function ofC0. This means that left evaluation has yielded an initialized g-variable,right evaluation some value and that the right expression is of some typet, formally:

∃g, v, t.lval(teabs, cabs.m, el) = bgc∧rval(teabs, cabs.m, er) = bvc∧?init(teabs, cabs.m, er) = True∧type(teabs, cabs.m, el) = btc∧updmm(cabs.m, g, v) = bc′abs.m

′c

We obtain an initialized kalloc(g, hp) and v′ from the expression evaluationin the concrete kernel using Lemma 8.9. Hence, the memory update will

Page 193: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 177

be successful in the concrete kernel, too:

updmm(ck.m, kalloc(g, hp), v′) = bc′k.mc.

We still have to show eqinit(typeg(sc(cabs.m)), v, v′). There are two casesto be considered: either er is a memory object—?inter(teabs, cabs, er) =False—or not. In the latter case, er is definitely of elementary type (whichcan be easily checked looking at the definitions of expression evaluationin Sect. 3.4) and we can apply Lemma 8.9. The same holds for er being amemory object of elementary type.

Let us consider the case that er is a memory object of non-elementarytype. Then, there exists a g-variable x such that lval(teabs, cabs, er) = bxcand valueg(cabs.m, x) = v. Furthermore, this variable is reachable dueto [Lei08, Theorem 8.1] and initialized, since er is initialized. So we canapply Lemma 8.23 to obtain eqcont(typeg(sc(cabs.m)), v, v′).

So there is only pointer reachability left to show. In [Lei08, Theorem 8.1],the proof has been done that the right value of an expression, given thatit is initialized, in a valid configuration only contains reachable pointermemory cells, hence:

cont reachable(cabs.m, v, sizeT (t)).

This is all we need to complete the proof for this case.

Case 2: Let us now consider memory allocation, i.e. hd(s2l(cabs.prog)) =PAlloc(e, tn). We obtain a g-variable g by left evaluation of e and a typet by mapof (teabs, tn) = btc.For the proof, we distinguish on the availability of heap memory in theabstract kernel.

We start with no memory available, that is ?heap(cabs.m, t) = False. Inthis case, the heap memory stays unchanged and the special null pointervalue is assigned to g.

Due to the heap invariant heapinv(cabs.m, ck.m), the same happens in theconcrete kernel. Both g and kalloc(g, hp) are reachable and—at latestafter the memory update—initialized. Furthermore, they are of the sametype, since type name environment consistency yields the same type tassociated with the type name tn in the concrete kernel. Since the valueof both g and kalloc(g, hp) is now NullPointer ,

eqval(g, c′abs.m, kalloc(g, hp), c′k.m)

holds, too. This is all we need to show konsisgvars(c′abs.m, c′k.m).

Now we examine the case in which enough heap memory is available. Inan intermediate step (cf. Def. 3.25), we extend the heap symbol tableof both kernels by appending an entry (undef , t) and initialize the valueof the new heap variable according to Def. 3.19. Let us denote theseintermediate memories with m′abs = extheap(?heap, cabs.m, t) and m′k =extheap(?heap, ck.m, t), respectively.

We update our heap map function hp in such a way that the new abstractheap element index is mapped to the new concrete element index . The

Page 194: Verified Linking for Modular Kernel Verification - Institut f¼r

178 Preservation of Kernel Consistency

mapping stays unchanged for all other values and we denote the updateheap map by hp′:

hp′(x) =

{|hst(ck.m)| if x = |hst(cabs.m)|hp(x) otherwise

Note that Lemma 8.19 guarantees that the heap invariants are preservedby such an update.

In a second step, we obtain the successor memory configuration by updat-ing the value of g with a pointer to the new heap variable:

c′abs.m = updmm(m′abs, g,Ptr(gvarhm(|hst(cabs.m)|)))

and similarly for the concrete kernel, using Lemma 8.13:

c′k.m = updmm(m′k, kalloc(g, hp),Ptr(gvarhm(|hst(ck.m)|))).

Since both g and kalloc(g, hp) are reachable, the new heap variablesto which they point are reachable, too. Furthermore, we know thatgvarhm(|hst(ck.m)|) = kalloc(gvarhm(|hst(cabs.m)|), hp′).

Obviously, both heap variables are of the same type, namely t. Let usassume that t is elementary. Then the value of these variables is equalto the initial value of t, which is 0 for IntT , UnsgndT and CharT , Falsefor BoolT , and NullPointer for pointer types. In any case, the valueequivalence holds (see Def. 7.14 for elementary types).

So far for the new heap variables. We still have to have a look at g andkalloc(g, hp) (which is equal to kalloc(g, hp′)). Both g-variables have beenobtained by left evaluating an expression in a valid configuration, so theyare reachable in this configuration.

In addition, reachability of the updated variable is invariant. Hence, bothg and kalloc(g, hp′) are reachable in c′abs.m and c′k.m, respectively, andinitialized.

Furthermore, they are of the same—elementary—type PtrT (tn) Since thememory update was successful in the abstract kernel, the value of g inthe successor configuration is

valueg(c′abs.m, g) = Ptr(gvarhm(|hst(cabs.m)|))

and for kalloc(g, hp′) in the concrete kernel

valueg(c′k.m, g) = Ptr(kalloc(gvarhm(|hst(cabs.m)|), hp′)).

Using the definition of eqcont for pointer types (cf. Def. 7.14), we finallydeduce

eqval(g, c′abs.m, kalloc(g, hp′), c′k.m).

Page 195: Verified Linking for Modular Kernel Verification - Institut f¼r

8.5. Dynamic Properties 179

Case 3: We will now consider function calls, that is hd(s2l(cabs.prog)) =SCall(σ, fn, plist).

The relevant parts of a function call regarding g-variable consistency arethe stack extension and the parameter passing. The new stack framebeing added by the call defines a return destination g, which comesfrom the expression evaluation of Var(σ). Using Lemma 8.9, we knowthat the return destination in the concrete kernel is kalloc(g, hp). Sincethese two variables are reachable in cabs and ck and since their valuesare not changed, the invariant on g-variables still holds in the successorconfigurations of both kernels.

Let us now deal with parameter passing. The called function fn has afunction descriptor f , such that (fn, f) ∈ {ptabs}. Due to procedure tableconsistency, there exists a corresponding entry (fn, f ′) in the concreteprocedure table ptk with f ′.params = f.params and f ′.lvars = f.lvars.

As we have seen in Sect. 3.5.3, at first the local memory stack is extendedby a new stack frame mf ′ with

mf ′ =

st = stfunc(f)ct = undefinit = ∅

.We denote the memories extended with mf ′ withmabs andmk, respectively.Subsequently, the parameter list plist is right evaluated. In the next step,the parameter names of the called function are obtained. Finally, thememory is updated, e.g., given a parameter name π and a value v:

updmm(mabs, gvarlm(|cabs.m.lm|, π), v) = bm′absc.

In the concrete kernel, we proceed likewise. Since the parameters are thesame, so are the names. Let π and v′ denote such a parameter name andits corresponding value.

So, the corresponding memory update in the concrete kernel looks likethis:

updmm(mk, gvarlm(|ck.m.lm|, π), v′)= updmm(mk, gvarlm(|cabs.m.lm|+ rdoff, π), v′)= updmm(mk, kalloc(gvarlm(|cabs.m.lm|, π), hp), v′)= bm′kc.

gvarlm(|cabs.m.lm|, π) and kalloc(gvarlm(|cabs.m.lm|, π), hp) are reachablein mabs and mk, since they are valid and named. Furthermore, they areinitialized due to the memory update and of equal type, since the functionsymbol tables are equal.

Finally, we obtain content equality for v and v′ in the same manner aswe did it for assignments further above, depending on the expressionevaluation returning a memory object or not.

Using Lemma 8.9, we prove that the konsisnamed holds for these twovariables in the two successor configurations.

Page 196: Verified Linking for Modular Kernel Verification - Institut f¼r

180 Preservation of Kernel Consistency

We proceed likewise by induction over the parameter list of the functioncall. Since all other variables stay unchanged, both regarding reachabil-ity and value, the invariant on g-variable consistency also holds in thesuccessor configurations c′abs and c′k.

Case 4: The last case is an abstract program rest starting with a returnstatement, i.e. hd(s2l(cabs.prog)) = Return(e). The proof for this case isvery similar to the one dealing with assignments.

The variable updated by a return statement is specified by the returndestination of the current stack frame. For the abstract kernel, this is:

g = snd(toplm(cabs.m))= snd(cabs.m.lm!(|sc.lst(cabs.m)| − 1)).

Exploiting the invariants on return destinations and recursion depth,konsisreturn and konsisrd, we obtain for the concrete kernel:

snd(toplm(ck.m))= snd(ck.m.lm!(|sc.lst(ck.m)| − 1))= snd(ck.m.lm!(|sc.lst(cabs.m)|+ rdoff − 1))= kalloc(g, hp).

Both variables are reachable, since they are named and valid.

We right evaluate e to obtain the return values: v in the abstract kerneland v′ in the concrete kernel, where we obtain

eqcont(typeg(sc(cabs.m), g), v, v′)

in a similar way as we did it in the assignment case.

Again, memory updates do not change the reachability of the updatedvariable, so both g and kalloc(g, hp) are reachable and initialized in thesuccessor configurations. Applying Lemma 8.21 finally completes theproof for this case. q.e.d.

8.6 Preservation of Kernel Consistency by C0 KernelSteps

In the previous sections, we have proven for ordinary C0 steps—no XCalls, noexternal function calls, no assembler—that the individual relations between theabstract and concrete kernel components are preserved under the assumptionthat the abstract kernel does not yield a run-time error.

Furthermore, we have shown that if the abstract kernel proceeds, i.e. yieldssome successor configuration using the C0 transition function, then concretekernel execution will not fail neither.

We will now summarize these results into one theorem stating that theabstract and concrete kernel relation konsis will be preserved.

Page 197: Verified Linking for Modular Kernel Verification - Institut f¼r

8.6. Preservation of Kernel Consistency by C0 Kernel Steps 181

Lemma 8.25 (Induction Step for konsis) Let cabs denote a weak valid ab-stract kernel configuration and ck a valid concrete kernel configuration, wherethe concrete kernel programs results from linking the abstract kernel to thelow-level CVM implementation.

Further we assume that the kernel relation konsis holds and that the programrest of the abstract kernel neither starts with an external function call, an as-sembler or a XCall statement, nor that abstract kernel execution has terminated,and that there exists an abstract successor configuration obtained by applyingthe C0 transition function.

Then there exists also a concrete successor configuration, such that the kernelrelation holds for these two new configurations.

linkable(Πabs,ΠCVM) ∧Πk = link(Πabs,ΠCVM)∧konsis(teabs, ptabs, cabs, tek, ptk, ck)∧cabs ∈ conf weak√ (teabs, ptabs) ∧ ck ∈ conf √(tek, ptk)∧δC0(teabs, ptabs, cabs) = bc′absc∧¬is Asm(hd(s2l(cabs.prog))) ∧ ¬is Skip(cabs.prog)∧¬is ESCall(hd(s2l(cabs.prog))) ∧ ¬is XCall(hd(s2l(cabs.prog)))

=⇒∃c′k. δC0(tek, ptk, ck) = bc′kc ∧ konsis(teabs, ptabs, c′abs, tek, ptk, c

′k)

Page 198: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 199: Verified Linking for Modular Kernel Verification - Institut f¼r

A conclusion is simply the place where someone gottired of thinking.

Unknown

Chapter 9

Summary

Contents9.1 Achievements . . . . . . . . . . . . . . . . . . . . . . . . . 183

9.2 Integrating Our Results . . . . . . . . . . . . . . . . . . . 185

9.3 Operating Systems Verification . . . . . . . . . . . . . . . 187

In this Chapter, we will first summarize on the results presented in thisthesis (Sect. 9.1). Subsequently, we discuss the work that has yet to be done inorder to integrate our results into the overall correctness theorem (Sect. 9.2) Wewill then discuss some possible directions for future work in operating systemsverification (Sect. 9.3).

9.1 Achievements

CVM Computational Model In this thesis, we have introduced the formalsemantics of CVM, a hardware-abstracting framework for microkernel program-mers. The CVM semantics definitions have not only been presented in thiswork, but they are also formalized within the Isabelle/HOL interactive theoremprover environment. Unlike the related work, CVM

• offers serious support for virtual memory and its management,

• integrates user processes and devices into the computational model,

• considers the low-level hardware in detail.

To the best of our knowledge, there exists no other project covering all of theabove features.

The development of CVM semantics started with [GHLP05]. Though thecurrent formal semantics has been largely specified by us, it obviously benefittedfrom contributions and demands from many people in the Verisoft project, whohave been using CVM in one or the other way.

183

Page 200: Verified Linking for Modular Kernel Verification - Institut f¼r

184 Summary

Correctness and Pervasiveness CVM correctness is formulated as a sim-ulation theorem, where the hardware simulates the CVM model. Individualstates of the computational models are related by an abstraction relation, whichsubsumes several other abstraction relations for the single components of aCVM configuration.

We have defined the relation between abstract and concrete kernel. Theuser process relations bases on our work and has been continued by Tsyban.[Tsy09] has formulated the other correctness relations and invariants, whichare used in the top-level correctness theorem sketch as presented in [AHL+09],basing on an initial version of Tsyban.

For standard abstract kernel steps, that is those, in which no primitive iscalled or in which the kernel goes to kernel wait, we have presented the proofthat the kernel relation is preserved. Particularly this involves that the concretekernel does not produce any run-time faults if the abstract kernel does not.This proof has also been formalized to a very large extent in Isabelle/HOL.

Furthermore, CVM has been developed with pervasive system verificationin mind. This means that for all layers in the system stack, we have developedcomputational models or have integrated models from other sources within theVerisoft project.

While most other OS verification projects aim to prove that the implemen-tation satisfies a certain security policy, the CVM top-level correctness theoremis a statement on total functional correctness. Practically, this implies alreadymore or less complex security policies as memory separation of user processes.Yet, it is also sufficient for much stronger correctness criteria, in particular ifone aims to prove functional correctness of layers above CVM as it is done inthe Verisoft project.

Compositionality and Modularization There are certainly different phi-losophies when verifying a system stack. One is to take the overall actualimplementation of everything at hand—compiler, hardware, low-level CVMimplementation—and disclose the full state of all components to the layersabove.

There is nothing wrong with this if one merely aims to verify one specificgiven stack. It even makes verification life easier, since one can resort to alllow-level detail at any time and does not have to worry about clean and conciseinterfaces.

Yet, this approach falls short whenever it comes to specification and/orimplementation modifications on any layer: the proofs have to be re-checkedand adapted, if possible. Moreover, the bigger the system stack gets, the moredemanding it becomes for the verification engineer to handle this complexity—oreven worse: become acquainted with an existing design and proof.

It is not a new idea to aim for encapsulation and modularization. In softwaretechnology and the design of programming languages, the same experienceswith growing software have led already decades ago to the same principles. Thisis why we have chosen to stick with the credo as much detail as necessary, asless as possible.

In particular, we have introduced the notion of abstract linking and linkabil-ity of C0 programs. This allows to separate the low-level hardware-dependentimplementation from the actual kernel implementation. Not for nothing, CVM

Page 201: Verified Linking for Modular Kernel Verification - Institut f¼r

9.2. Integrating Our Results 185

has been designed as a hardware-independent framework for microkernel pro-grammers, where the abstract kernel is a parameter provided by the implementer.

This allows us to define a clean interface for the verification above CVMlevel: one can show code correctness for the abstract kernel (e.g., absence ofrun-time faults) using comfortable tool support and rely on the correctness ofthe CVM implementation, given that the resource restrictions and linkabilityrequirements are met.

Furthermore, by proceeding like this, we were able to use and re-use plentyof results (and in particular lemmas) from [Lei08]. The proof we have presenteddemonstrates the feasibility of this approach: the correctness relation betweenthe abstract and the concrete kernel—obtained by linking—is preserved.

9.2 Integrating Our Results

As a matter of fact, our work did not always proceed with the speed for whichwe have hoped. In order to be able to continue her work on the top-levelcorrectness criterion, Tsyban has formulated her own version of a relation forthe abstract and the concrete kernel.

Yet, the integration of our correctness proof requires some predictable andmanageable effort. In the remainder of this section, we will sketch the changesand extra work that has to be done.

The Dummy Function The dummy function, whose existence we haveassumed in Sect. 6.2.2, is a crutch to allow for the use of the existing C0small-step semantics and its rich resources on lemmas when arguing about theabstract kernel. In the concrete kernel, the abstract kernel dispatcher is calledby the low-level CVM dispatcher in the same way as the dummy function.

A meta-theorem that the concrete kernel obtained by linking behaves in thesame way with or without this dummy function has to be shown. Yet, sincethis function is never called, the proof of such a theorem is supposedly rathersimple.

For instance, our construction fits perfectly well with the kernel -rel result

relation from [Tsy09], which relates the cup variable of the concrete kernel withthe abs kernel res variable of the abstract kernel.

Induction Start For the induction start, given through an abstract kernelconfiguration after reset, it has to be proven that the kernel relation holds. Wewould relate this abstract kernel configuration to a concrete kernel configurationwhere the head of the program rest also points to a call of the abstract kerneldispatcher.

In the abstract kernel, there has been no memory allocated, its globalvariables are disjoint from those of the low-level implementation (hence of initialvalue) and the stack has been freshly initialized. These facts make the proof formost kernel invariants rather easy. As Fig. 9.1 illustrates this for consistency ofthe program rest at induction start.

Resource Restrictions Currently we assume that memory allocation doesnot fail in the concrete kernel if it succeeds in the abstract kernel. Thisassumption has to be discharged.

Page 202: Verified Linking for Modular Kernel Verification - Institut f¼r

186 Summary

SCallabs_kernel_disp Return

SCallabs_kernel_disp Return CVM low-level

program rest

abstract kernel program rest

concrete kernel program rest

Figure 9.1: Abstract and Concrete Program Rest at Induction Start

Yet, the low-level CVM implementation in Verisoft makes currently verylittle use of memory allocation (and fortunately not in a recursive manner),basically only when creating the page tables for the (fixed number of) userprocesses and some data structures for the page fault handler. Computationof memory consumption can hence be done in the conventional way for theabstract kernel, with the constant offset for the CVM implementation beingsimply added.

Kernel Wait and Primitives For kernel wait and the primitives it still hasto be shown that kernel consistency is respected (in the sense that it holdsafter a primitive call and after leaving kernel wait, not necessarily in between).The proof has to focus mainly on the consistency relation for g-variables, inparticular heap variables (due to global variable disjointness). In the currentCVM low-level implementation, primitives do not take pointer arguments,making things quite easier. This is not true when dealing with physical I/O,where data structures of the abstract kernel are directly changed. Yet, therehave been no such primitives been verified in Verisoft up to now.

Theorem Integration [Tsy09] has used a correctness relation kernel -rel forthe abstract and concrete kernel. Basically, this relation covers a subset ofthe sub relations of konsis as defined in the chapters before and uses concretenumbers instead of constants, as given through the current low-level CVMimplementation.

For instance Tsyban’s conversion function abs2conc [Tsy09, Def. 7.5] usesan offset of 2 for local variables, where we use the constant rdoff, and uses aconstant shift for heap variables, where we prefer a mapping function betweenthe abstract and the concrete heap (note, that this implies that the concretekernel is only allowed to allocate memory at the very beginning, but never againduring execution). The instantiation of kalloc with appropriate parameters ismore or less trivial.

The relation for g-variables in the abstract and concrete kernel, kernel -relGvar

[Tsy09, Def. 7.7], is syntactically nearly the same as the one of konsisgvars (cf.Def. 7.10). Generally, our correctness statement given by konsis is stronger,such that it should be feasible to prove that it implies the correctness statementof Tsyban—with a reasonable amount of work.

Regarding abstract linking, Tsyban prefers to swap the parameters comparedto our use of link in this thesis. Since the compiler used in the Verisoft project

Page 203: Verified Linking for Modular Kernel Verification - Institut f¼r

9.3. Operating Systems Verification 187

allocates C0 functions in the assembler memory in the order they are definedin the procedure table of the compiled program, she can use this to obtain theconcrete address of the low-level init() and dispatcher() function, whichshe uses in her proofs.

Though this is a pretty strong assumption, since the use of a differentcompiler would render large parts of the proof useless, we do not require a certainorder in the linking of procedure tables (unlike for type name environments,see also the remark at Lemma 8.7). Hence, we could easily adapt our linkingprocess in the desired way.

Last but not least, our correctness theorem as presented in Lemma 8.25has to be applied within the proof for the correctness of a kernel step (cf.Theorem 7.2). Besides the instantiation of the compiler correctness [Lei08], thiswould require to prove preservation of the other correctness relations.

Of all these relations, the user process relation B is probably the mostdemanding one. Since in kernel mode, user process relation bases on the processcontrol blocks, that is heap variables of the concrete kernel (cf. Sect. 6.5.1 and7.2.1), we basically have to show that the abstract kernel does not alter thesevariables. The current CVM low-level implementation does not make use ofpointers as function parameters, so we can be sure that this will not happen,making the proof seemingly not too tedious (though it is a strong assumptionon the actual code, see also next section).

9.3 Operating Systems Verification

The academic system was not meant to be a high-performance system, butto show the feasibility of the Verisoft approach to a system stack of realisticcomplexity regarding size and functionality. This has been accomplished andwe have the necessary theory at hand.

Yet, to play devil’s advocate: the system has been built with formal verifica-tion in mind and neither the programming languages nor the hardware involvedare commercially available or relevant.

To lift the quality of our results to industrial heights, we have to apply themethodology to an industrial system. In fact, this is part of the Verisoft XTproject, where the Hypervisor and PikeOS verification benefits considerablyfrom the experiences made in specifying and verifying an operating system.

Though the settings are by far more complex, the top-level correctnesscriteria are similar: memory separation and functional correctness of the kernelcalls. The existing theory has of course be extended, in order to handle featureslike preemption, memory sharing and the fact that the kernel itself runs manythreaded in a truly concurrent way.

An analysis, how our results and those of the L4.verified project [KEH+09]could be used to mutual improvement would be of great interest.

Last but not least, the current CVM low-level implementation does notmake use of pointers as function parameters. This means we can be sure thatthe abstract kernel never gets access to concrete kernel only data structures(i.e. CVM data structures), a property that makes for instance proving of theB-relation much easier. It would be interesting to analyze the problems thatarise when allowing for function pointers and its consequences regarding heapseparation.

Page 204: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 205: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography

[ABK08] Eyad Alkassar, Peter Boehm, and Steffen Knapp. Formal correct-ness of a gate-level automotive bus controller implementation. InBernd Kleinjohann, Lisa Kleinjohann, and Wayne Wolf, editors, 6thIFIP Working Conference on Distributed and Parallel EmbeddedSystems (DIPES08), pages 57–68. Springer, 2008.

[AH08] Eyad Alkassar and Mark A. Hillebrand. Formal functional verifica-tion of device drivers. In Natarajan Shankar and Jim Woodcock,editors, 2nd IFIP Working Conference on Verified Software: Theo-ries, Tools, and Experiments (VSTTE’08), volume 5295 of LNCS,pages 225–239. Springer, 2008.

[AHK+07] E. Alkassar, M. Hillebrand, S. Knapp, R. Rusev, and S. Tverdy-shev. Formal device and programming model for a serial interface.In B. Beckert, editor, Proceedings, 4th International VerificationWorkshop (VERIFY), Bremen, Germany, pages 4–20. CEUR-WSWorkshop Proceedings, 2007.

[AHL+09] Eyad Alkassar, Mark A. Hillebrand, Dirk C. Leinenbach, Norbert W.Schirmer, Artem Starostin, and Alexandra Tsyban. Balancing theload. Leveraging a semantics stack for systems verification. Journalof Automated Reasoning, 2009.

[Alk09] Eyad Alkassar. OS Verification Extended - On the Formal Ver-ification of Device Drivers and the Correctness of Client/ServerSoftware. PhD thesis, Saarland University, Computer ScienceDepartment, 2009.

[ASS08] Eyad Alkassar, Artem Starostin, and Norbert Schirmer. Formal per-vasive verification of a paging mechanism. In C. R. Ramakrishnanand Jakob Rehof, editors, Fourteenth International Conference onTools and Algorithms for the Construction and Analysis of Systems(TACAS 2008), volume 4963 of LNCS, pages 109–123. Springer,2008.

[BB04] Bernhard Beckert and Gerd Beuster. Formal specification ofsecurity-relevant properties of user interfaces. In Proceedings, 3rdInternational Workshop on Critical Systems Development withUML, Lisbon, Portugal, Munich, Germany, 2004. TU MunichTechnical Report TUM-I0415.

189

Page 206: Verified Linking for Modular Kernel Verification - Institut f¼r

190 Bibliography

[BB06a] Bernhard Beckert and Gerd Beuster. Guaranteeing consistencyin text-based human-computer interaction. In Proceedings, Inter-national Workshop on Formal Methods for Interactive Systems(FMIS), Macao SAR China, 2006.

[BB06b] Bernhard Beckert and Gerd Beuster. A method for formalizing,analyzing, and verifying secure user interfaces. In Zhiming Liu andJifeng He, editors, Formal Methods and Software Engineering, 8thInternational Conference on Formal Engineering Methods, ICFEM2006, Macao, China, November 1-3, 2006, Proceedings, volume4260 of Lecture Notes in Computer Science. Springer, 2006.

[BB09] Christoph Baumann and Thorsten Bormer. Verifying the PikeOSmicrokernel: An overview of the Verisoft XT avionics project. In4th International Workshop on Systems Software Verification (SSV2009), Electronic Notes in Theoretical Computer Science. ElsevierScience B.V., 2009. To appear.

[BBBB09a] Christoph Baumann, Bernhard Beckert, Holger Blasum, andThorsten Bormer. Better avionics software reliability by codeverification – A glance at code verification methodology in theVerisoft XT project. In Embedded World 2009 Conference, Nurem-berg, Germany, March 2009. Franzis Verlag. To appear.

[BBBB09b] Christoph Baumann, Bernhard Beckert, Holger Blasum, andThorsten Bormer. Formal verification of a microkernel used independable software systems. In Bettina Buth, Gerd Rabe, andTill Seyfarth, editors, Computer Safety, Reliability, and Security(SAFECOMP 2009), Lecture Notes in Computer Science, Hamburg,Germany, 2009. Springer. To appear.

[BBG+05] Sven Beyer, Peter Bohm, Michael Gerke, Mark Hillebrand, Tom Inder Rieden, Steffen Knapp, Dirk Leinenbach, and Wolfgang J. Paul.Towards the formal verification of lower system layers in automotivesystems. In ICCD ’05, pages 317–324. IEEE Computer Society,2005.

[BBJ79] T.A. Berson and G.L. Barksdale Jr. KSOS: Development method-ology for a secure operating system. In AFIPS Conference Pro-ceedings, volume 48, pages 365–371, 1979.

[BDD+92] Manfred Broy, Frank Dederich, Claus Dendorfer, Max Fuchs,Thomas Gritzner, and Rainer Weber. The Design of DistributedSystems - An Introduction to FOCUS. Technical Report TUM-I9202, Technische Universitat Munchen, January 1992.

[Bev87] William R. Bevier. A verified operating system kernel. TechnicalReport 11, Computational Logic Inc., Austin, Texas, 1987.

[Bev88] William R. Bevier. Kit: A study in operating system verification.Technical Report 28, Computational Logic Inc., Austin, Texas,1988.

Page 207: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography 191

[Bev89a] William R. Bevier. Kit: A study in operating system verification.IEEE Trans. Softw. Eng., 15(11):1382–1396, 1989.

[Bev89b] William R. Bevier. Kit and the short stack. J. Autom. Reasoning,5(4):519–530, 1989.

[Bey05] Sven Beyer. Putting It All Together: Formal Verification of theVAMP. PhD thesis, Saarland University, Computer Science De-partment, March 2005.

[BGH+06] Jewgenij Botaschanjan, Alexander Gruler, Alexander Harhurin,Leonid Kof, Maria Spichkova, and David Trachtenherz. Towardsmodularized verification of distributed time-triggered systems. InJayadev Misra, Tobias Nipkow, and Emil Sekerinski, editors, FM2006: Formal Methods, 14th International Symposium on FormalMethods, Hamilton, Canada, August 21–27, 2006, Proceedings,volume 4085 of Lecture Notes in Computer Science, pages 163–178.Springer, 2006.

[BHMY89] William R. Bevier, Warren A. Hunt, Jr., J S. Moore, and William D.Young. An approach to systems verification. Journal of AutomatedReasoning, 5(4):411–428, 1989.

[BHW06] Gerd Beuster, Niklas Henrich, and Markus Wagner. Real worldverification — experiences from the verisoft email client. In Pro-ceedings of the Workshop on Empirical Succesfully ComputerizedReasoning (ESCoR 2006), 2006.

[BJK+03] Sven Beyer, Christian Jacobi, Daniel Kroening, Dirk Leinenbach,and Wolfgang Paul. Instantiating uninterpreted functional unitsand memory system: Functional verification of the VAMP. InD. Geist and E. Tronci, editors, Proc. of the 12th Advanced ResearchWorking Conference on Correct Hardware Design and VerificationMethods (CHARME), volume 2860 of Lecture Notes in ComputerScience, pages 51–65. Springer, 2003.

[BJK+06] Sven Beyer, Christian Jacobi, Daniel Kroening, Dirk Leinenbach,and Wolfgang Paul. Putting it all together: Formal verification ofthe VAMP. International Journal on Software Tools for TechnologyTransfer, 8(4–5):411–430, August 2006.

[BKKS05] Jewgenij Botaschanjan, Leonid Kof, Christian Kuhnel, and MariaSpichkova. Towards verified automotive software. In SEAS ’05:Proceedings of the Second International Workshop on SoftwareEngineering for Automotive Systems, pages 46–51. ACM, 2005.

[BKM95] R. S. Boyer, M. Kaufmann, and J. S. Moore. The boyer-mooretheorem prover and its interactive enhancement. Computers &Mathematics with Applications, 29(2):27 – 62, 1995.

[BLW08] Sascha Bohme, Rustan Leino, and Burkhart Wolff. HOL-Boogie –An interactive prover for the Boogie program verifier. In TheoremProving in Higher Order Logics (TPHOLs 2008), volume 5170

Page 208: Verified Linking for Modular Kernel Verification - Institut f¼r

192 Bibliography

of Lecture Notes in Computer Science, pages 150–166, Montreal,Quebec, Canada, 2008. Springer.

[Boa96] Ariane 501 Inquiry Board. Ariane 5 - Flight 501 Failure. http://homepages.inf.ed.ac.uk/perdita/Book/ariane5rep.html,July 1996.

[Bog08] Sebastian Bogan. Formal Specification of a Simple Operating Sys-tem. PhD thesis, Saarland University, Computer Science Depart-ment, August 2008.

[BS93a] William R. Bevier and Lawrence M. Smith. A mathematical modelof the mach kernel: Atomic actions and locks. Technical Report 89,Computational Logic Inc., Austin, Texas, 1993.

[BS93b] William R. Bevier and Lawrence M. Smith. A mathematical modelof the mach kernel: Entities and relations. Technical Report 88,Computational Logic Inc., Austin, Texas, 1993.

[Car97] Luca Cardelli. Program fragments, linking, and modularization. InIn ACM Symp. on Principles of Programming Languages, pages266–277. ACM Press, 1997.

[CDH+09] Ernie Cohen, Markus Dahlweid, Mark Hillebrand, Dirk Leinenbach,Micha l Moskal, Thomas Santen, Wolfram Schulte, and StephanTobies. VCC: A practical system for verifying concurrent C. InTheorem Proving in Higher Order Logics (TPHOLs 2009), volume5674 of Lecture Notes in Computer Science, Munich, Germany,2009. Springer. Invited paper, to appear.

[CMST09] Ernie Cohen, Micha l Moskal, Wolfram Schulte, and Stephan Tobies.A precise yet efficient memory model for C. In 4th InternationalWorkshop on Systems Software Verification (SSV 2009), ElectronicNotes in Theoretical Computer Science. Elsevier Science B.V., 2009.To appear.

[Con06] Cosmin Condea. Design and implementation of a page fault handlerin C0. Master’s thesis, Saarland University, 2006.

[Dal06] Iakov Dalinger. Formal Verification of a Processor with MemoryManagement Units. PhD thesis, Saarland University, July 2006.

[Dav04] David E. Hoffman. CIA slipped bugs to Soviets. http://www.msnbc.msn.com/id/4394002, 2004.

[DDB08] Matthias Daum, Jan Dorrenbacher, and Sebastian Bogan. Modelstack for the pervasive verification of a microkernel-based operatingsystem. In Bernhard Beckert and Gerwin Klein, editors, Proceed-ings, 5th International Verification Workshop (VERIFY), Sydney,Australia, volume 372 of CEUR Workshop Proceedings, pages 56–70.CEUR-WS.org, August 2008.

[DDW09] Matthias Daum, Jan Dorrenbacher, and Burkhart Wolff. Provingfairness and implementation correctness of a microkernel scheduler.42, Numbers 2-4:349–388, 2009.

Page 209: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography 193

[DDWS08] Matthias Daum, Jan Dorrenbacher, Burkhart Wolff, and MareikeSchmidt. A verification approach for system-level concurrent pro-grams. In Jim Woodcock and Natarajan Shankar, editors, Veri-fied Software: Theories, Tools, Experiments Second InternationalConference, VSTTE 2008, Toronto, Canada, October 6–9, 2008.Proceedings, volume 5295 of Lecture Notes in Computer Science,pages 161–176, Toronto, Canada, October 2008. Springer.

[DHP05] Iakov Dalinger, Mark Hillebrand, and Wolfgang Paul. On theverification of memory management mechanisms. In D. Borrioneand W. Paul, editors, Proceedings of the 13th Advanced ResearchWorking Conference on Correct Hardware Design and Verifica-tion Methods (CHARME 2005), volume 3725 of Lecture Notes inComputer Science, pages 301–316. Springer, 2005.

[EKD+07] Kevin Elphinstone, Gerwin Klein, Philip Derrin, Timothy Roscoe,and Gernot Heiser. Towards a practical, verified kernel. In Proceed-ings of the 11th Workshop on Hot Topics in Operating Systems,pages 117–122, San Diego, CA, USA, May 2007.

[EKE08] Dhammika Elkaduwe, Gerwin Klein, and Kevin Elphinstone. Veri-fied protection model of the seL4 microkernel. In Jim Woodcockand Natarajan Shankar, editors, Proceedings of VSTTE 2008 —Verified Software: Theories, Tools and Experiments, volume 5295of Lecture Notes in Computer Science, pages 99–114, Toronto,Canada, Oct 2008. Springer.

[Elp04] Kevin Elphinstone. Future directions in the evolution of the L4microkernel. In Gerwin Klein, editor, Proceedings of the NICTAworkshop on OS verification 2004, Technical Report 0401005T-1,Sydney, Australia, Oct 2004. National ICT Australia.

[FSDG08] Xinyu Feng, Zhong Shao, Yuan Dong, and Yu Guo. Certifying low-level programs with hardware interrupts and preemptive threads. InPLDI ’08: Proceedings of the 2008 ACM SIGPLAN conference onProgramming language design and implementation, pages 170–182,New York, NY, USA, 2008. ACM.

[FSGD09] Xinyu Feng, Zhong Shao, Yu Guo, and Yuan Dong. Certifying low-level programs with hardware interrupts and preemptive threads.Journal of Automated Reasoning, 42(2-4):301–347, April 2009.

[GHLP05] Mauro Gargano, Mark Hillebrand, Dirk Leinenbach, and WolfgangPaul. On the correctness of operating system kernels. In J. Hurd andT. F. Melham, editors, 18th International Conference on TheoremProving in Higher Order Logics (TPHOLs 2005), volume 3603 ofLecture Notes in Computer Science, pages 1–16. Springer, 2005.

[GM82] J. A. Goguen and J. Meseguer. Security policies and securitymodels. Security and Privacy, IEEE Symposium on, 0:11, 1982.

[GM84] Joseph A. Goguen and Jose Meseguer. Unwinding and inferencecontrol. Security and Privacy, IEEE Symposium on, 0:75, 1984.

Page 210: Verified Linking for Modular Kernel Verification - Institut f¼r

194 Bibliography

[Gro01] Network Working Group. Simple Mail Transport Protocol. http://www.apps.ietf.org/rfc/rfc2821.html, April 2001.

[Har85] Norman Hardy. Keykos architecture. SIGOPS Oper. Syst. Rev.,19(4):8–25, 1985.

[HH01] Michael Hohmuth and Hermann Haertig. Pragmatic nonblockingsynchronization for real-time systems. In Proceedings of the 2001Usenix Annual Technical Conference (USENIX ’01), 2001.

[Hil05] Mark A. Hillebrand. Address Spaces and Virtual Memory: Speci-fication, Implementation, and Correctness. PhD thesis, SaarlandUniversity, 2005.

[HIP05] Mark Hillebrand, Tom In der Rieden, and Wolfgang Paul. Dealingwith I/O devices in the context of pervasive system verification. InICCD ’05, pages 309–316. IEEE Computer Society, 2005.

[HP96] John L. Hennessy and David A. Patterson. Computer Architecture:A Quantitative Approach. Morgan Kaufmann, San Mateo, CA,second edition, 1996.

[HP07] M. A. Hillebrand and W. J. Paul. On the architecture of systemverification environments. In Haifa Verification Conference 2007,October 23-25, 2007, Haifa, Israel, LNCS. Springer, 2007.

[HPLR94] Robert Harper, Frank Pfenning, Peter Lee, and Eugene Rollins. Acompilation manager for standard ml of new jersey. In In 1994ACM SIGPLAN Workshop on ML and its Applications, pages136–147, 1994.

[HSY06] David S. Hardin, Eric W. Smith, and William D. Young. A robustmachine code proof framework for highly secure applications. InACL2 ’06: Proceedings of the sixth international workshop on theACL2 theorem prover and its applications, pages 11–20, New York,NY, USA, 2006. ACM.

[HT05] Michael Hohmuth and Hendrik Tews. The VFiasco approach for averfied operating system. Technical Report TUD-FI05-15, TechnicalUniversity Dresden, Dresden, Germany, December 2005.

[HW73] C. A. R. Hoare and Niklaus Wirth. An axiomatic definition of theprogramming language pascal. Acta Inf., 2:335–355, 1973.

[Hyp09] Microsoft Hyper-V Server: Homepage. http://www.microsoft.com/hyper-v-server/en/us/default.aspx, 2009.

[IK05] Tom In der Rieden and Steffen Knapp. An approach to the pervasiveformal specification and verification of an automotive system. InFMICS ’05, pages 115–124. IEEE Computer Society, 2005.

[ILP05] Tom In der Rieden, Dirk Leinenbach, and Wolfgang Paul. Towardsthe pervasive verification of automotive systems. In DominiqueBorrione and Wolfgang Paul, editors, Proceedings of the 13th Ad-vanced Research Working Conference on Correct Hardware Design

Page 211: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography 195

and Verification Methods (CHARME 2005), volume 3725 of LectureNotes in Computer Science, pages 3–4. Springer, 2005.

[Inf81a] Information Sciences Institute. Internet Protocol, Darpa InternetProgram, Protocol Specification. http://www.apps.ietf.org/rfc/rfc791.html, September 1981.

[Inf81b] Information Sciences Institute. Transmission Control Protocol -Protocol Specification. http://www.apps.ietf.org/rfc/rfc793.html, September 1981.

[IP08] Tom In der Rieden and Wolfgang J. Paul. Beweisen als Ingenieur-wissenschaft: Verbundprojekt Verisoft (2003–2007). In Bernd Reuseand Roland Vollmar, editors, Informatikforschung in Deutschland,pages 321–326. Springer, 2008.

[ISO99] ISO 9899:1999: Programming languages – C. International Stan-dardization Organization, 1999.

[IT08] Tom In der Rieden and Alexandra Tsyban. CVM - a verifiedframework for microkernel programmers. In 3rd intl Workshop onSystems Software Verification (SSV08), to appear. Elsevier ScienceB. V., 2008.

[Jac02] Christian Jacobi. Formal Verification of a Fully IEEE-compliantFloating-Point Unit. PhD thesis, Saarland University, 2002.

[Jan95] Mark Janeba. The pentium problem. http://www.willamette.edu/~mjaneba/pentprob.html, 1995.

[Jon03] Simon Peyton Jones, editor. Haskell 98 Language and Libraries– The Revised Report. Cambridge University Press, Cambridge,England, 2003.

[KEH+09] Gerwin Klein, Kevin Elphinstone, Gernot Heiser, June Andronick,David Cock, Philip Derrin, Dhammika Elkaduwe, Kai Engelhardt,Rafal Kolanski, Michael Norrish, Thomas Sewell, Harvey Tuch,and Simon Winwood. seL4: Formal verification of an OS kernel.In Proceedings of the 22nd ACM Symposium on Operating SystemsPrinciples, Big Sky, MT, USA, Oct 2009. ACM. To appear.

[Kle09] Gerwin Klein. Operating system verification — an overview.Sadhana, 34(1):27–69, Feb 2009.

[Kna08] Steffen Knapp. The Correctness of a Distributed Real-Time System.PhD thesis, Universit”at des Saarlandes, 2008.

[KP07a] Steffen Knapp and Wolfgang Paul. Pervasive verification of dis-tributed real-time systems. In Manfred Broy, Johannes Grunbauer,and Tony Hoare, editors, Software System Reliability and Security,volume 9 of IOS Press, NATO Security Through Science Series.Sub-Series D: Information and Communication Security, pages239–297, 2007.

Page 212: Verified Linking for Modular Kernel Verification - Institut f¼r

196 Bibliography

[KP07b] Steffen Knapp and Wolfgang J. Paul. Realistic worst case executiontime analysis in the context of pervasive system verification. InProgram Analysis and Compilation, Theory and Practice: EssaysDedicated to Reinhard Wilhelm, volume 4444 of Lecture Notes inComputer Science, pages 53–81. Springer, 2007.

[Kro01] Daniel Kroening. Formal Verification of Pipelined Microprocessors.PhD thesis, Saarland University, Computer Science Department,2001.

[KS06] Christian Kuhnel and Maria Spichkova. Upcoming automotivestandards for fault-tolerant communication: FlexRay and OSEK-time FTCom. In International Workshop on Engineering of Fault-Tolerant Systems (EFTS 2006), 12–13 June 2006, Luxembourg,pages 68–79, June 2006.

[Lei08] Dirk Carsten Leinenbach. Compiler Verification in the Context ofPervasisve System Verification. PhD thesis, Saarland University,Computer Science Department, 2008.

[Lev95] Nancy Leveson. Medical Devices: The Therac-25. Addison-Wesley,1995.

[Lie95] Jochen Liedtke. On micro-kernel construction. In Proceedings ofthe 15th ACM Symposium on Operating systems principles, pages237–250. ACM Press, 1995.

[LP08] Dirk Leinenbach and Elena Petrova. Pervasive compiler verification– from verified programs to verified systems. In 3rd intl Workshop onSystems Software Verification (SSV08), to appear. Elsevier ScienceB. V., 2008.

[LS09] Dirk Leinenbach and Thomas Santen. Verifying the MicrosoftHyper-V Hypervisor with VCC. In 16th International Symposiumon Formal Methods (FM 2009), Lecture Notes in Computer Science,Eindhoven, the Netherlands, 2009. Springer. Invited paper, toappear.

[LWB06] Christina Lindenberg, Kai Wirt, and Johannes Buchmann. Formalproof for the correctness of RSA-PSS. Cryptology ePrint Archive,Report 2006/011, 2006.

[MD79] E.J. McCauley and P.J. Drongowski. KSOS: The design of a secureoperating system. In AFIPS Conference Proceedings, volume 48,pages 345–353, 1979.

[MMS08] Stefan Maus, Micha l Moskal, and Wolfram Schulte. Vx86: x86assembler simulated in C powered by automated theorem proving.In Jose Meseguer and Grigore Rosu, editors, Algebraic Methodologyand Software Technology (AMAST 2008), volume 5140 of LectureNotes in Computer Science, pages 284–298, Urbana, IL, USA, July2008. Springer.

Page 213: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography 197

[Moo89] J S. Moore. System verification. Journal of Automated Reasoning,5(4):409–410, 1989.

[Moo00] J. Strother Moore. Towards a mechanically checked theory ofcomputation: the acl2 project. pages 547–574, 2000.

[MP00] Silvia M. Muller and Wolfgang J. Paul. Computer Architecture:Complexity and Correctness. Springer, 2000.

[MWE03] David Greve Matthew, Matthew Wilding, and W. Mark Van Eet.A separation kernel formal security policy. In Proc. Fourth Interna-tional Workshop on the ACL2 Theorem Prover and Its Applications,2003.

[MWE05] David Greve Matthew, Matthew Wilding, and W. Mark Van Eet.High assurance formal security policy modeling. In Proceedingsof the 17th Systems and Software Technology Conference 2005(SSTC’05), 2005.

[NBFR80] P.G. Neumann, R.J. Boyer, K.N. Feiertag, and L. Robinson. AProvably Secure Operating System: The system, its applications,and proofs. Report CSL-116, Computer Science Laborartory, SRIInternational, Menlo Park, California, May 1980.

[NF03] Peter G. Neumann and Richard J. Feiertag. PSOS revisited. Com-puter Security Applications Conference, Annual, 2003.

[Nor98] Michael Norrish. C Formalised in HOL. PhD thesis, University ofCambridge, Computer Laboratory, December 1998.

[NPW02] Tobias Nipkow, Lawrence C. Paulson, and Markus Wenzel. Is-abelle/HOL: A Proof Assistant for Higher-Order Logic, volume2283 of Lecture Notes in Computer Science. Springer, 2002.

[NYS07] Zhaozhong Ni, Dachuan Yu, and Zhong Shao. Using XCAP tocertify realistic systems code: Machine context management. InTheorem Proving in Higher Order Logics, volume 4732 of Lec-ture Notes in Computer Science, pages 189–206. Springer Berlin /Heidelberg, 2007.

[OSR92] Sam Owre, Natarajan Shankar, and John M. Rushby. PVS: Aprototype verification system. In 11th International Conferenceon Automated Deduction (CADE), volume 607 of Lecture Notes inComputer Science, pages 748–752. Springer, 1992.

[PCH84] T. Perrine, J. Codd, and B. Hardy. An overview of the kernalizedsecure operating system (KSOS). In Proceedings of the SeventhDoD/NBS Computer Security Initiative Conference, volume 48,pages 146–160, Gaithersburg, Maryland, September 1984.

[Pet07] Elena Petrova. Verification of the C0 Compiler Implementation onthe Source Code Level. PhD thesis, Saarland University, ComputerScience Department, 2007.

Page 214: Verified Linking for Modular Kernel Verification - Institut f¼r

198 Bibliography

[Pik09] PikeOS RTOS Homepage. http://www.sysgo.com/products/pikeos-rtos-technology/, 2009.

[RBF+89] Richard Rashid, Robert Baron, Ro Forin, David Golub, and MichaelJones. Mach: A system software kernel. In In Proceedings of the1989 IEEE International Conference, COMPCON, pages 176–178.Press, 1989.

[RL77] Lawrence Robinson and Karl N. Levitt. Proof techniques forhierarchically structured programs. Commun. ACM, 20(4):271–283, 1977.

[RLNS75] Lawrence Robinson, Karl N. Levitt, Peter G. Neumann, andAshok R. Saxena. On attaining reliable software for a secureoperating system. In Proceedings of the international conferenceon Reliable software, pages 267–284, New York, NY, USA, 1975.ACM.

[SA93] Zhong Shao and Andrew W. Appel. Smartest recompilation. InIn ACM Symp. on Principles of Programming Languages, pages439–450. ACM Press, 1993.

[Sch05] Norbert Schirmer. A verification environment for sequential imper-ative programs in Isabelle/HOL. In F. Baader and A. Voronkov,editors, Logic for Programming, Artificial Intelligence, and Reason-ing, 11th International Conference, LPAR 2004, volume 3452 ofLecture Notes in Computer Science, pages 398–414. Springer, 2005.

[Sch06] Norbert Schirmer. Verification of Sequential Imperative Programsin Isabelle/HOL. PhD thesis, Technical University of Munich, 2006.

[SDNM04] Jonathan Shapiro, Michael Scott Doerrie, Eric Northup, and MarkMiller. Towards a verified, general-purpose operating system kernel.In 1st NICTA Workshop on Operating System Verification, pages1–19, October 2004.

[Spi98] Katharina Spies. Eine Methode zur formalen Modellierung vonBetriebssystemkonzepten. PhD thesis, Technische UniversitatMunchen, 1998.

[SS06] Swaroop Sridhar and Jonathan S. Shapiro. Type inference forunboxed types and first class mutability. In PLOS ’06: Proceedingsof the 3rd workshop on Programming languages and operatingsystems, page 7, New York, NY, USA, 2006. ACM.

[SSF99] Jonathan S. Shapiro, Jonathan M. Smith, and David J. Farber.Eros: a fast capability system. In In Symposium on OperatingSystems Principles, pages 170–185, 1999.

[ST08] Artem Starostin and Alexandra Tsyban. Correct microkernelprimitives. In G. Klein R. Huuck and B. Schlich, editors, 3rd intlWorkshop on Systems Software Verification (SSV08), volume 217of ENTCS, pages 169–185. Elsevier Science B. V., 2008.

Page 215: Verified Linking for Modular Kernel Verification - Institut f¼r

Bibliography 199

[Sta09] Artem Starostin. Formal Verification of Demand Paging. PhDthesis, Saarland University, Computer Science Department, 2009.To appear.

[SW00] Jonathan S. Shapiro and Sam Weber. Verifying the EROS con-finement mechanism. In SP ’00: Proceedings of the 2000 IEEESymposium on Security and Privacy, page 166, Washington, DC,USA, 2000. IEEE Computer Society.

[Tew07] Hendrik Tews. Microhypervisor verfication: possible approachesand relevant properties. In Proceedings of the NLUUG Voor-jaarsconferentie 2007, 2007.

[The99] The Common Criteria Project Sponsoring Organisations. CommonCriteria for Information Technology Security Evaluation version 2.1,Part I. http://www.commoncriteriaportal.org/public/files/ccpart1v21.pdf, 1999.

[Tic86] Walter F. Tichy. Smart recompilation. ACM Transactions onProgramming Languages and Systems (TOPLAS), 8(3):273–291,1986.

[TS08] Sergey Tverdyhev and Andrey Shadrin. Formal verification ofgate-level computer systems. In K.Y. Rozier, editor, 6th NASALangley Formal Methods Workshop, pages 56–58. NASA Scientificand Technical Information (STI), 2008.

[Tsy09] Alexandra Tsyban. Formal Verification of a Framework for Micro-kernel Programmers. PhD thesis, Saarland University, ComputerScience Department, 2009. To appear.

[Tve05] Sergey Tverdyshev. Combination of Isabelle / HOL with automatictools. In Bernhard Gramlich, editor, Frontiers of Combining Sys-tems: 5th International Workshop, FroCoS 2005, Vienna Austria,September 19–21, 2005. Proceedings, volume 3717 of Lecture Notesin Computer Science, pages 302–309. Springer, 2005.

[Tve09] Sergey Tverdyshev. Formal Verification of Gate-Level ComputerSystems. PhD thesis, Saarland University, 2009.

[Ver03] The Verisoft project. http://www.verisoft.de/, 2003.

[WKP80] Bruce J. Walker, Richard A. Kemmerer, and Gerald J. Popek. Spec-ification and verification of the ucla unix security kernel. Commun.ACM, 23(2):118–131, 1980.

Page 216: Verified Linking for Modular Kernel Verification - Institut f¼r
Page 217: Verified Linking for Modular Kernel Verification - Institut f¼r

Index

!, 11⊥, 10∼=, 113〈 〉intci&d

, 110〈 〉natci&d

, 110b c, 10ω, 65⊕, 35. (operator), 9?elemT , 28?heap, 51?init , 39?inmem, 79?inter , 39?namedg, 37?store, 65?val , 112@, 11#, 11

abs-heap-max -size, 129abs-stack -max -size, 129abstract data type, 10

constructor, 10abstraction relation, 109academic sub project, see academic

systemacademic system, 3, 4ACL2, 15adci&d , 109addr-of, 28address, 62

effective, 64physical, 64translation, 62, 64virtual, 64

AddrOf , 28alloc, 81Ariane 5 Disaster, 2

array, 24access, 28variable, 33

Arr , 28asize-heap, 129asize-stack , 129Asm, 30Ass, 30Assc, 30assembler, see assemblyassembly, 23

code, 5configuration

equivalence, 112inline, 53instruction, 57machine, 61program, 5

automotive sub project, 55

B, 110, 113B, 9B-Relation, 113bag, 38base address, 35bav, 35BinOp, 28bit, 63

protection, 64valid, 64vector, 67

BitC, 16boundedness, 121butlast , 11

C++, 15C0, 5c0prog , 102c2i, 90

201

Page 218: Verified Linking for Modular Kernel Verification - Institut f¼r

202 Index

cache, 5casm, 67CAV, 3

ACL2, 15Isabelle/HOL, 5PVS, 5

cCVM, 70cisa, 62CLI stack, 7, 14clone, 80code-invariant-isa, 131common criteria, 2, 17

EAL7, 17communicating virtual machines, see

CVMComp, 30compiler, 4, 5

correctness, 109relation, 122

componentdevice, 70kernel, 70user process, 70

compositionality, 7computations, 28

arithmetic, 28logical, 28

computer error, 2computer system, 1

safety-critical, 1security-critical, 1

concr -kernel -inv , 123configuration

C0, 23validity, 125weak validity, 137

CVM, 69initial, 45initial CVM, 98memory, 32small-step, 32

confTasm, 67confTC0 , 36, 70confTCVM, 70confTdev, 55confTdevs, 56confTi&d, 64confTisa, 62consistency, 113

g-variable, 119

procedure table, 115program rest, 115recursion depth, 113type name environment, 113

cont reachable, 171copy , 82copymm , 78copypars, 51count-proc-steps, 129Coyotos, 16cp, 70CP rel, 126current user process, 70CVM, 6, 23, 69

n-step transition function, 98abstraction relation, 127correctness, 109device component, 70device step, 71dispatcher, 100implementation, 99initial configuration, 69, 98interrupt mask, 70kernel component, 70kernel step, 72primitive, 69primitive implementation, 101transition function, 69user process component, 70

cvmdispatch, 100CVMrelation, 127

D, 56data slice, 39δasm, 67δC0, 47δi&d, 65δni&d, 66δCVM, 70δextdevs , 58δintdevs , 58δisa, 63δnCVM, 98δextt , 58δintt , 58

Deref , 28device, 55

ABC, 55automotive bus controller, 55block operation, 57

Page 219: Verified Linking for Modular Kernel Verification - Institut f¼r

Index 203

communication, 56configuration, 55external interface input, 57external interface output, 57external transition, 58generalized configuration, 56generalized transition, 58hard disk, 55internal transition, 58interrupt, 56maximal number, 56memory interface input, 57memory interface output, 57memory mapped, 56model, 55network interface, 55NIC, 55port, 56relation, 126serial interface, 55swap, 126timer, 55UART, 55

dev sim, 126did , 65did swap, 126dmax, 56dpc, 62

e2i , 104ea, 64eca, 72edata, 72Eifi , 57Eifo, 57Eifot, 57email client, 4environment

external, 55eqcont, 118eqinit, 119eqstmt, 114eqtype, 119eqval, 119EROS, 16ESCall , 31evenstmt, 103exception

cause, 72data, 72

execution, 62mode, 62

expr , 28expression, 23

evaluation, 23, 139initialized, 39left value, 39literal, 28memory object, 39right value, 39type, 39

extheap, 50extstack, 52

False, 9filter , 12FLINT, 17Focus, 17FPGA, 4free, 82function, 10

C0 transition, 23allocation, 122anonymous, 9body, 31call, 24CVM transition, 69declared, 102defined, 102external, 31, 104external device transition, 58internal, 125internal device transition, 58ISA transition, 63parameters, 32partial, 10return type, 32valid, 125

g-variable, 33address, 37base address, 38consistency, 119, 166corresponding, 115elementary, 27initialized, 37nameless

reachability, 170parent, 34reachable, 116

Page 220: Verified Linking for Modular Kernel Verification - Institut f¼r

204 Index

root, 34, 135sub, 34type, 37value, 38

equality, 118garbage collector, 24getdpc, 111GetGPR, 83getgprs, 111getmm, 112getpcp, 111getsprs, 111GetWord , 84getx, 111gprs, 62gst , 36gvars√, 116gvar , 33gvararr , 33gvargm , 33gvarhm , 33gvarlm , 33gvarstr , 33

hard diskinvariant, 129

Haskell, 16hd, 11heap

extension, 50invariant, 164

preservation of, 164map, 121

boundedness, 121injectivity, 121

memory, 121availability, 121

object, 129overflow, 129size, 129variable, 33

heapinv, 122hmapbound, 121hmapinj, 121Hoare logic, 5hst , 36Hyper-V, 18

i2c, 90idle

mechanism, 71process, 71

Ifte, 30implication, 9inference rules, 9init, 100initconf, 46initCVM, 98initst, 45initval, 45initvars, 46injectivity, 121Instr , 66instruction

fetch, 76instruction set architecture, see ISAinterrupt, 62

device, 74external, 74floating point, 76handling, 62illegal, 76mask, 70, 126misalignment, 76overflow, 76page fault, 64reset, 76timer, 100vector, 75

invariant-hd , 129IP, 6irqdev, 56ISA, 61

configuration, 62well-formedness, 131

Isabelle/HOL, 5, 10is-dlx -conft , 131

JISR, 76

kalloc, 115kernel, 23

C0 step, 75abstract, 8, 23abstract dispatcher, 72call, 69compilable, 99concrete, 8, 70

invariants, 122entry, 69

Page 221: Verified Linking for Modular Kernel Verification - Institut f¼r

Index 205

executable, 99exit, 69leave, 73non-preemptive, 74pre-emptible, 15primitive step, 74property, 150

dynamic, 152static, 150

relation, 124restore, 100save, 100separation, 17start, 72step, 131user step, 75wait, 71, 74weak relation, 124

kernel -rel , 124kernel -sim-c0 -isa, 122kernT , 70kheap, 100KIT, 7, 15konsis, 110, 113, 122konsis func, 115konsisgst, 120konsisgvars, 119konsishst, 120konsis lst, 120konsisnamed, 120konsisnameless, 120konsisprog, 115konsispt, 115konsisrd, 114konsisreturn, 121konsistenvs, 113konsisweak, 123KSOS, 14

L4.verified, 7, 16λ, 9lambda calculus, 9LazyBinOp, 28link , 105linkable, 125linkgst, 102linking, 8, 17, 31

abstract, 31, 99linkpt, 104link te, 102

list, 10butlast , 11map, 11replicate, 11i-th element, 11filter , 12mapof , 12append, 11concatenation, 10Cons, 11element, 10head, 11length, 11Nil, 11tail, 11

literal, 28aggregate, see complex literalcomplex, 28right value, 40type, 40

Lit , 28LoadOS , 97Loop, 30toplst , 36lval , 39

Mach kernel, 17map, 11mapof , 12mask , 70mask, 100mca, 75mcell reachable, 170mcellT , 34memconfT , 35memg, 37memory

byte-addressable, 62cells, 34configuration, 32content, 35

content, 118extraction, 112frame, 32initial, 45isolated, 69local, 73main, 64management, 4management unit, see MMU

Page 222: Verified Linking for Modular Kernel Verification - Institut f¼r

206 Index

name, 38object, 39page, 64physical, 5, 100range, 38separation, 69shared, 69swap, 100update, 47virtualization, 66

mframeT , 34microkernel, 6mifi , 57, 65Mifo, 57MMU, 5mode, 62

execution, 62system, 62

entering, 100leaving, 100

user, 62mode, 62modularization, 7

N, 9None, 10NullPointer , 34

odd stmt, 103OLOS, 18, 24op, 28operating system, 13

verification, 13operating systems, 6

SOS, 6operator, 28

binary, 26, 44lazy, 26, 44unary, 25, 43

OS, see operating systemOSEKtime-like Operating System,

see OLOS

P, 70p2l, 111P2Vcopy , 90pa, 64page fault, 64page fault handler, 100page index

physical, 64virtual, 64

page table, 64big, 101entry, 64

extraction, 112length, 64origin, 64

PAlloc, 30parameter

passing, 51parent , 34Pascal, 14pcb[], 99pcp, 62Pentium Bug, 2PhysIOIn, 92PhysIOInRange, 93PhysIOOut , 95PhysIOOutRange, 96Πabs, 102ΠCVM, 99pid, 70Πk, 99PikeOS, 19pmax, 70pointer, 24

dereferencing, 28reachable, 170

port , 65portmax, 56ppx, 64precond -seq-cvm, 129precond -seq-isa, 129predicate, 9Prim, 75primitive

alloc, 81call, 75clone, 80copy, 82free, 82GetGPR, 83GetWord, 84implementation, 101inter-process, 77kernel memory, 77LoadOS, 97name, 75number, 75

Page 223: Verified Linking for Modular Kernel Verification - Institut f¼r

Index 207

P2Vcopy, 90PhysIOIn, 92PhysIOInRange, 93PhysIOOut, 95PhysIOOutRange, 96process management, 77reset, 80SetGPR, 84SetMask, 84SetWord, 85special, 77specification, 75V2Pcopy, 91VirtIOIn, 86VirtIOOut, 87WordIn, 88WordOut, 89

proceduredescriptor, 32external, see functiontable, 32

procedure tableconsistency, 115

preservation of, 151process, 6, 70

concurrent, 6control block, 99current, 126id, 70identifier, 70user, 70

processorstep, 129

procs, 70procT , 32proctableT , 32program, 4, 23

C0, 27, 102validity, 125

concurrent, 4linkable, 102, 125rest, 32

consistency, 115, 156equivalence, 136

program counter, 62delayed, 62

proof, 3machine-checked, 3

protocol, 4PSOS, 13

PTE, 64pte, 64, 112ptl, 62, 64pto, 64PVS, 5, 14px, 64

quantifiersexistential, 10universal, 10

r2lx, 111rdoff, 114reachability, 116reachablenamed, 117reachablenameless, 117readmm , 78record, 9

component, 9update, 9

recursion depth, 113consistency, 113, 155

preservation of, 155maximal, 129

reg-invariant , 127weak -reg-invariant , 127register, 62

file, 62extraction, 110

general purpose, 62invariant, 127page table length, 62page table origin, 64special purpose, 62weak invariant, 127

relationabstraction, 109kernel, 109user process, 109weak, 123

rem, 104repl , 105replicate, 11replpt, 105reset , 80return

destination, 79value, 79

Return, 30return destination, 35, 121

Page 224: Verified Linking for Modular Kernel Verification - Institut f¼r

208 Index

consistency, 121, 163preservation of, 163

rfe, 67RSA-PSS, 7run-time error, 153

absence of, 153rval , 39rvalclit, 40rval lit, 40

s2l, 31s2lnoskip , 31sc, 36SCall , 30seL4, 7, 16sequence, 9

fair, 128sub sequence, 9well-typed, 129

serial interface, 7SetGPR, 84SetMask , 84sets, 12SetWord , 85SHA-1, 7Σ+, 9signature module, 4sim-c0 -isa, 122sizemm, 79sizeT , 28Skip, 30SMTP, 6software

application, 4system, 4

Some, 10SOS, 6SPECIAL, 13sprs, 62SRI, 14SRrel, 126stack, 51

extension, 52local memory, 73

stack verification, 8startak, 72statement

equivalence, 114renumbering, 103validity, 125

statements, 25, 30stmt , 30, 36Str , 28structure

access, 28variable, 33

subg, 34symbol

configuration, 36table, 34, 36

consistency, 120validity, 125

symbol tableconsistency, 159

preservation of, 159global

consistency, 152invariance, 152

symbolconfT , 36system

stack, 4

TCP, 6tenv , 27the, 10Therac-25, 2tl, 11toeifo, 79toinstr, 67toint, 67tonat, 67toplm, 36trap

argument, 74instruction, 74interrupt, 74

trap, 67, 74True, 9type

ArrT , 27BoolT , 27CharT , 27IntT , 27NullT , 27PtrT , 27StrT , 27UnsgndT , 2732-bit signed integers, 2432-bit unsigned integers, 248-bit signed char, 24

Page 225: Verified Linking for Modular Kernel Verification - Institut f¼r

Index 209

abstract data, 10abstract size, 28aggregate, 24array, 24assign types match, 125basic, 24boolean, 24elementary, 27equality, 119match, 125name, 27option, 10pointer, 24return, 32structure, 24valid, 125

type, 39type name environment, 27

consistency, 113preservation of, 150

validity, 125typeclit, 40typedev, 56typeg, 37type lit, 40typev, 35

UCLA Secure Unix, 14undef , 10, 51UnOp, 28updmm , 47updret , 79upT , 70user -invariant , 127

V2Pcopy , 91val , 65valid fun, 125valid st, 125valid tenv, 125value, 38

initial, 45of variable, 38

value⊕1, 44

value⊕2, 44

valueg, 38VAMOS, 18, 24, 129VAMP, 4, 23variable, 24

access, 28

base address, 35consistency, 119corresponding, 115elementary, 27generalized, see g-variableglobal, 32heap, 32initialized, 35local, 24named, 33, 37nameless, 33, 37reachable, 116type, 35validity, 116value, 38

equality, 118Var , 28VCC, 18verification, 3

code, 8pervasive, 3

Verisoft, 3, 18, 23, 129academic system, 3automotive system, 18methodology, 3sub project, 3

Verisoft XT, 13, 18VFiasco, 15VirtIOIn, 86VirtIOOut , 87

weak -kernel -rel , 124weak -sim-c0 -isa, 124word index, 64WordIn, 88WordOut , 89writemm , 78wx, 64

XCall , 31

Z, 9