Abstract
SAFE is a clean-slate design for a highly secure computer system, with pervasive mechanisms for tracking and limiting information flows. At the lowest level, the SAFE hardware supports fine-grained programmable tags, with efficient and flexible propagation and combination of tags as instructions are executed. The operating system virtualizes these generic facilities to present an information-flow abstract machine that allows user programs to label sensitive data with rich confidentiality policies. We present a formal, machine-checked model of the key hardware and software mechanisms used to dynamically control information flow in SAFE and an end-to-end proof of noninterference for this model.
We use a refinement proof methodology to propagate the noninterference property of the abstract machine down to the concrete machine level. We use an intermediate layer in the refinement chain that factors out the details of the information-flow control policy and devise a code generator for compiling such information-flow policies into low-level monitor code. Finally, we verify the correctness of this generator using a dedicated Hoare logic that abstracts from low-level machine instructions into a reusable set of verified structured code generators.
Keywords
Introduction
The SAFE design is motivated by the conviction that the insecurity of present-day computer systems is due in large part to legacy design decisions left over from an era of scarce hardware resources. The time is ripe for a complete rethink of the entire system stack with security as the central focus. In particular, designers should be willing to spend more of the abundant processing power available on today’s chips to improve security.
A key feature of SAFE is that every piece of data, down to the word level, is annotated with a tag representing policies that govern its use. While the tagging mechanism is very general [9,34], one particularly interesting use of tags is for representing information-flow control (IFC) policies. For example, an individual record might be tagged “This information should only be seen by principals
At the programming-language level, rich IFC policies have been extensively explored, with many proposed designs for static [43,51,68,73,77,96] and dynamic [4–7,40,44,72,75,78,88] enforcement mechanisms and a huge literature on their formal properties [43,77, etc.]. Similarly, operating systems with information-flow tracking have been a staple of the OS literature for over a decade [36,55,56,66,97,97]. But progress at the hardware level has been more limited, with most proposals concentrating on hardware acceleration for taint-tracking schemes [19,25,26,31,32,89,92]. SAFE extends the state of the art in two significant ways. First, the SAFE machine offers hardware support for sound and efficient purely-dynamic tracking of both explicit and implicit flows (i.e., information leaks through both data and control flow) for arbitrary machine code programs – not just programs accepted by static analysis, or produced by translation or transformation. Moreover, rather than using just a few “taint bits,” SAFE associates a word-sized tag to every word of data in the machine – both memory and registers. In particular, SAFE tags can be pointers to arbitrary data structures in memory. The interpretation of these tags is left entirely to software: the hardware just propagates tags from operands to results as each instruction is executed, following software-defined rules. Second, the SAFE design has been informed from the start by an intensive effort to formalize critical properties of its key mechanisms and produce machine-checked proofs, in parallel with the design and implementation of its hardware and system software. Though some prior work (surveyed in Section 12) shares some of these aims, to the best of our knowledge no project has attempted this combination of innovations.
Abstractly, the tag propagation rules in SAFE can be viewed as a partial function from argument tuples of the form (opcode, pc tag, argument 1 tag, argument 2 tag, …) to result tuples of the form (new pc tag, result tag), meaning “if the next instruction to be executed is opcode, the current tag of the program counter (PC) is pc tag, and the arguments expected by this opcode are tagged argument 1 tag, etc., then executing the instruction is allowed and, in the new state of the machine, the PC should be tagged new pc tag and any new data created by the instruction should be tagged result tag.” (The individual argument-result pairs in this function’s graph are called rule instances, to distinguish them from the symbolic rules used at the software level.) In general, the graph of this function in extenso will be huge; so, concretely, the hardware maintains a cache of recently-used rule instances. On each instruction dispatch (in parallel with the logic implementing the usual behavior of the instruction – e.g., addition), the hardware forms an argument tuple as described above and looks it up in the rule cache. If the lookup is successful, the result tuple includes a new tag for the PC and a tag for the result of the instruction (if any); these are combined with the ordinary results of instruction execution to yield the next machine state. Otherwise, if the lookup is unsuccessful, the hardware invokes a cache fault handler – a trusted piece of system software with the job of checking whether the faulting combination of tags corresponds to a policy violation or whether it should be allowed. In the latter case, an appropriate rule instance specifying tags for the instruction’s results is added to the cache, and the faulting instruction is restarted. Thus, the hardware is generic and the interpretation of policies (e.g., IFC, memory safety or control flow integrity [9,34]) is programmed in software, with the results cached in hardware for common-case efficiency.
The first contribution of this paper is to explain and formalize, in the Coq proof assistant [90], the key ideas in this design via a simplified model of the SAFE machine, embodying its tagging mechanisms in a distilled form and focusing on enforcing IFC using these general mechanisms. In Section 2, we outline the features of the full SAFE system and enumerate the most significant simplifications in our model. In Section 3, we present the high-level programming interface of our model, embodied by an abstract IFC machine with a built-in, purely dynamic IFC enforcement mechanism and an abstract lattice of IFC labels. We then show, in three steps, how this abstract machine can be implemented using the low-level mechanisms we propose. The first step introduces a symbolic IFC rule machine that reorganizes the semantics of the abstract machine, splitting out the IFC enforcement mechanism into a separate judgment parameterized by a symbolic IFC rule table (Section 4). The second step defines a generic concrete machine (Section 5) that provides low-level support for efficiently implementing many different high-level policies (IFC and others) with a combination of a hardware rule cache and a software fault handler. The final step instantiates the concrete machine with a concrete fault handler enforcing IFC. We do this using an IFC fault handler generator (Section 6), which compiles the symbolic IFC rule table into a sequence of machine instructions implementing the IFC enforcement judgment.
Our second contribution is a machine-checked proof that this simplified SAFE system is correct and secure, in the sense that user code running on the concrete machine equipped with the IFC fault handler behaves the same way as on the abstract machine and enjoys the standard noninterference property that “high inputs do not influence low outputs.” The interplay of the concrete machine and fault handler is complex, so some proof abstraction is essential. (Previous projects such as the CompCert compiler [58], the seL4 [54,66] and CertiKOS [39,82] microkernels, and the RockSalt SFI checker [65] have demonstrated the need for significant attention to organization in similar proofs.) In our proof architecture, a first abstraction layer is based on refinement. This allows us to reason in terms of a high-level view of memory, ignoring the concrete implementation of IFC labels, while setting up the intricate indistinguishability relation used in the noninterference proof. A second layer of abstraction is required for reasoning about the correctness of the fault handler. Here, we rely on a verified custom Hoare logic that abstracts from low-level machine instructions into a reusable set of verified structured code generators.
In Section 7 we prove that the IFC fault handler generator correctly compiles a symbolic IFC rule table and a concrete representation of an abstract label lattice into an appropriate sequence of machine instructions. We then introduce a standard notion of refinement (Section 8) and show that the concrete machine running the generated IFC fault handler refines the abstract IFC machine and vice-versa, using the symbolic IFC rule machine as an intermediate refinement point in each direction of the proof (Section 9). In our deterministic setting, showing refinement in both directions guarantees that the concrete machine does not diverge or get stuck when handling a fault. We next introduce a standard termination-insensitive noninterference (TINI) property (Section 10) and show that it holds for the abstract machine. Since deterministic TINI is preserved by refinement, we conclude that the concrete machine running the generated IFC fault handler also satisfies TINI. In Section 11, we explain how the programming model and formal development of the first sections can be extended to accommodate two important features: dynamic memory allocation and tags representing sets of principals. This extension, carried out after the development of the basic model, gives us confidence in the robustness of our methodology. We close with a survey of related work (Section 12) and a discussion of future directions (Section 13). Our Coq formalization is available at
A preliminary abridged version of this work appeared in the proceedings of the POPL 2014 conference [8]. This extended and improved version includes:
more examples and clarifying explanations in the formal sections; a more detailed technical description of the formalization: the semantics of the abstract, symbolic and concrete machines, the language for expressing symbolic IFC rules, our verified structured code generators, and TINI-preserving refinements; more details of the proofs; a more extensive discussion of related work, including more recent work on transplanting the tagging mechanism of SAFE onto a mainstream RISC processor [29] and using it to enforce properties beyond IFC [9,34].
Overview of SAFE
To establish context, we begin with a brief overview of the full SAFE system, concentrating on its OS- and hardware-level features. More detailed descriptions can be found elsewhere [30,33–35,45,46,57,63]. SAFE’s system software performs process scheduling, stream-based interprocess communication, storage allocation and garbage collection, and management of the low-level tagging hardware (the focus of this paper). The goal is to organize these services as a collection of mutually suspicious compartments following the principle of least privilege (a zero-kernel OS [84]), so that an attacker would need to compromise multiple compartments to gain complete control of the machine. It is programmed in a combination of assembly and Tempest, a new low-level systems programming language.
The SAFE hardware integrates a number of mechanisms for eliminating common vulnerabilities and supporting higher-level security primitives. To begin with, SAFE is (dynamically) typed at the hardware level: each data word is indelibly marked as a number, an instruction, a pointer, etc. Next, the hardware is memory safe: every pointer consists of a triple of base, bounds, and offset (compactly encoded into 64 bits [35,57]), and every pointer operation includes a hardware bounds check [57]. Finally, the hardware associates each word in the registers and memory, as well as the PC, with a large (59-bit) tag. The hardware rule cache, enabling software-specified propagation of tags from operands to result on each machine step, is implemented using a combination of multiple hash functions to approximate a fully-associative cache [33].
An unusual feature of the SAFE design is that formal modeling and verification of its core mechanisms have played a central role in the design process since the beginning. The original goal – formally specifying and verifying the entire set of critical runtime services – proved to be too ambitious, but key security properties of simplified models have been verified both at the level of Breeze [45] (a mostly functional, security-oriented, dynamic language used for user-level programming on SAFE) and, in the present work, at the hardware and abstract machine level. We also used random testing of properties like noninterference as a means to speed the design process [46].
Our goal in this paper is to develop a clear, precise, and mathematically tractable model of one of the main innovations in the SAFE design: its scheme for efficiently supporting high-level data use policies using a combination of hardware and low-level system software. To make the model easy to work with, we simplify away many important facets of the real SAFE system. In particular, (i) we focus only on IFC and noninterference, although the tagging facilities of the SAFE machine are generic and can be applied to other policies (more recent work illustrates this point [8,34]; we return to it at the end of Section 12); (ii) we ignore the Breeze and Tempest programming languages and concentrate on the hardware and runtime services; (iii) we use a stack instead of registers, and we distill the instruction set to just a handful of opcodes; (iv) we drop SAFE’s fine-grained privilege separation in favor of a more conventional user-mode/kernel-mode dichotomy; (v) we shrink the rule cache to a single entry (avoiding issues of replacement and eviction) and maintain it in kernel memory, accessed by ordinary loads and stores, rather than in specialized cache hardware; (vi) we focus on termination-insensitive noninterference and omit a large number of more advanced IFC-related concepts that are supported by the real SAFE system (dynamic principals, downgrading, public labels, integrity, clearance, etc.); (vii) we handle exceptional conditions, including potential security violations, by simply halting the whole machine; and (viii) most importantly, we ignore concurrency, process scheduling, and interprocess communication, assuming instead that the whole machine has a single, deterministic thread of control. We believe that most of these restrictions can be lifted without fundamentally changing the structure of the model or of the proofs. For instance, recent follow-on work by some of the authors [47] discusses a mechanized proof of noninterference for a similar abstract machine featuring registers and a richer IFC policy. The absence of concurrency is a particularly significant simplification, given that we are talking about an operating system that offers IFC as a service. However, we conjecture that it may be possible to add concurrency to our formalization, while maintaining a high degree of determinism, by adapting the approach used in the proof of noninterference for the seL4 microkernel [66,67]. We return to this point in Section 13.
Abstract IFC machine
We begin the technical development by defining a very simple stack-and-pointer machine with “hard-wired” dynamic IFC. This machine concisely embodies the IFC mechanism we want to provide to higher-level software and serves as a specification for the symbolic IFC rule machine (Section 4) and for the concrete machine (Section 5) running our IFC fault handler (Section 6). The three machines share a tiny instruction set (Fig. 1) designed to be a convenient target for compiling the symbolic IFC rule table into machine instructions (the Coq development formalizes several other instructions, including

Instruction set.
The machine manipulates integers (ranged over by n, m, and p); unlike the real SAFE machine, we make no distinction between raw integers and pointers (we re-introduce this distinction in Section 11). Each integer is marked with an individual IFC label (ranged over by L) that denotes its security level. We call a pair of an integer n and its corresponding label L an atom, written
An abstract machine state

Semantics of abstract IFC machine.
The step relation of the abstract machine, written
The stepping rules in Fig. 2 adapt a standard purely dynamic IFC enforcement mechanism [4,75] to a low-level machine, following recent work by Hriţcu et al. [46]. (Readers less familiar with the intricacies of dynamic IFC may find some of these side conditions a bit mysterious. A longer explanation can be found in [46], but the details are not critical for present purposes.) The rule for
More recent work further improves precision compared to the no-sensitive-upgrades policy [5,15,44,46]. We adopted no-sensitive-upgrades in this work because it is simpler and requires less bookkeeping.
We assume the observer of the events generated by
All data in the machine’s state are labelled, and this simple machine manages labels to ensure noninterference as defined and proved in Section 10. There are no instructions that dynamically raise the label (classification) of an atom. Such an instruction,
In the abstract machine described above, IFC is tightly integrated into the step relation in the form of side conditions on each instruction. In contrast, the concrete machine (i.e., the “hardware”) described in Section 5 is generic, designed to support a wide range of software-defined policies (IFC and other). The machine introduced in this section serves as a bridge between these two models. It is closer to the abstract machine – indeed, its machine states and the behavior of the step relation are identical. The important difference lies in the definition of the step relation, where all the IFC-related aspects are factored out into a separate judgment. We can think of the IFC mechanism as being implemented in a separate “IFC rule processor” distinct from the main “CPU.” In the concrete machine, the CPU part will remain unchanged, but the IFC rule processor will be implemented mostly in software (by the fault handler), with the hardware only providing caching of rule instances. While factoring out IFC enforcement into a separate reference monitor [80] is commonplace [2,75,78], our approach goes further. We define a small DSL for describing symbolic IFC rules and obtain actual monitors by interpreting this DSL (in this section) and by compiling it into machine instructions using verified structured code generators (in Section 6 and Section 7). This architecture makes it easier to implement other IFC mechanisms (e.g., permissive upgrades [5]), beyond the simple one in Section 3. Since the DSL compilation is verified, we prove that the concrete machine of Section 5 is noninterfering when given any correct monitor written in the DSL. Showing that a monitor is correct, on the other hand, involves a simple refinement proof (Lemma 9.2), and a noninterference proof for the abstract machine (Theorem 10.5), but is independent of the code generation infrastructure and corresponding proofs.

Semantics of symbolic rule machine, parameterized by
More formally, each stepping rule of the new machine (see Fig. 3) includes a uniform call to an IFC enforcement relation, which itself is parameterized by a symbolic IFC rule table
Let us illustrate, for a few cases, how this new judgment is used in the stepping relation (Fig. 3). The stepping rule for
A symbolic IFC rule table

Rule table
These symbolic expressions are written in a simple domain-specific language (DSL) of operations over an IFC lattice. The grammar of this DSL (Fig. 5) includes label variables

Symbolic IFC rule language syntax.
The IFC enforcement judgment looks up the corresponding symbolic IFC rule in the table and directly evaluates the symbolic expressions in terms of the corresponding lattice operations. In contrast, in Section 6 we compile this rule table into the IFC fault handler for the concrete machine. Formally, the IFC enforcement judgment is defined by the two following cases, depending on whether the second output label is relevant or not:
Here ρ is a 4-tuple of labels,

Symbolic IFC rule language semantics.
The concrete machine provides low-level support for efficiently implementing many different high-level policies (IFC and others) with a combination of a hardware rule cache and a software cache fault handler. In this section we focus on the concrete machine’s hardware, which is completely generic, while in Section 6 we describe a specific fault handler corresponding to the IFC rules of the symbolic rule machine.
The concrete machine has the same general structure as the more abstract ones, but differs in several important respects. One is that it annotates data values with integer tags
A second important difference is that the concrete machine has two modes: user mode (
The concrete machine has the same instruction set as the previous ones, allowing user programs to be run on all three machines unchanged. But the tag-related semantics of instructions depends on the privilege mode, and in user mode the semantics further depends on the state of the rule cache. In the real SAFE machine, the rule cache may contain thousands of entries and is implemented as a separate near-associative memory [33] accessed by special instructions. Here, for simplicity, we use a cache with just one entry, located at the start of kernel memory, and use
The rule cache holds a single rule instance, represented graphically like this:
Location 0 holds an integer representing an opcode. (Since the exact choice of representation doesn’t matter, we will denote each opcode with a lowercase identifier – for example, we might define
(Note that we are showing just the “payload” part of these seven atoms; by convention, the tag part is always
, the output is
; otherwise it is undefined. If 0 is the tag representing the label ⊥, 1 represents ⊤, and −1 is the default tag
There are two sets of stepping rules governing the behavior of the concrete machine in user mode; which set applies depends on whether the current machine state matches the current contents of the rule cache. In the “cache hit” case (Fig. 7), the instruction executes normally, with the cache’s output determining the new PC tag and result tag (if any).

Concrete step relation: user mode, cache hit case.

Concrete step relation: user mode, cache miss case.
In the “cache miss” case (Fig. 8), the relevant parts of the current state (opcode, PC tag, argument tags) are stored into the input part of the single cache line and the machine simulates a
To see how this works in more detail, consider the two user-mode stepping rules for the
In the first rule (cache hit), the side condition demands that the input part of the current cache contents have the form
, where
, determines the tag
In the second rule (cache miss), the notation
, so the machine needs to enter the fault handler. The next machine state is formed as follows: (i) the input part of the cache is set to the desired form
; (ii) a new return frame is pushed on top of the stack to remember the current PC and privilege bit (
What happens next is up to the fault handler code. Its job is to examine the contents of the first five kernel memory locations and either (i) write appropriate tags for the result and new PC into the sixth and seventh kernel memory locations and then perform a
As explained in Section 2, in this work we assume for simplicity that policy violations are fatal. Recent work [45] has shown that it is possible to recover from IFC violations while preserving noninterference.

Concrete step relation (kernel mode).
In kernel mode (Fig. 9), the treatment of tags is almost completely degenerate: to avoid infinite regress, the concrete machine does not consult the rule cache while in kernel mode. For most instructions, tags read from the current machine state are ignored (indicated by
The only significant exceptions to this pattern are
A final point is that
As an illustration of how all this works, suppose again that
On the other hand, if the tags on both operands are 1 (i.e., ⊤), then the first step will miss in the cache and reduction will proceed as follows:
Now we assemble the pieces. A concrete IFC machine implementing the symbolic rule machine defined in Section 4 can be obtained by installing appropriate fault handler code in the kernel instruction memory of the concrete machine presented in Section 5. In essence, this handler must emulate how the symbolic rule machine looks up and evaluates the DSL expressions in a given IFC rule table. We choose to generate the handler code by compiling the lookup and DSL evaluation relations directly into machine code. (An alternative would be to represent the rule table as abstract syntax in the kernel memory and write an interpreter in machine code for the DSL, but the compilation approach seems to lead to simpler code and proofs.)

Generation of fault handler from IFC rule table.
The handler compilation scheme is given in Fig. 10. Each
The top-level handler works in three phases. The first phase,
The second phase of the top-level handler,
The third and final phase of the top-level handler tests the boolean just pushed onto the stack and either returns to user code (instruction is allowed) or jumps to address −1 (disallowed).
The code for symbolic rule compilation is built by straightforward recursive traversal of the rule DSL syntax for label-valued expressions (
To raise the level of abstraction of the handler code, we make heavy use of structured code generators; this makes it easier both to understand the code and to prove it correct using a custom Hoare logic that follows the structure of the generators (see Section 7). For example, the
We now turn our attention to verification, beginning with the fault handler. We must show that the generated fault handler emulates the IFC enforcement judgment
Correctness statement. Let
The correctness statement is captured by the following two lemmas. Intuitively, if the symbolic IFC enforcement judgment allows some given user instruction, then executing
(Fault handler correctness, allowed case).
Suppose that
Then
(Fault handler correctness, disallowed case).
Suppose that
Then, for some final stack
Proof methodology. The fault handler is compiled by composing generators (Fig. 10); accordingly, the proofs of these two lemmas reduce to correctness proofs for the generators. We employ a custom Hoare logic for specifying the generators themselves, which makes the code generation proof simple, reusable, and scalable. This is where defining a DSL for IFC rules and a structured compiler proves to be very useful approach, e.g., compared to symbolic interpretation of hand-written code.
Our logic comprises two kinds of Hoare triples. The generated code mostly consists of self-contained instruction sequences that terminate by “falling off the end” – i.e., that never return or jump outside themselves, although they may contain internal jumps (e.g., to implement conditionals). The only exception is the final step of the handler (third line of
Self-contained-code Hoare triples. The triple
Also, because the concrete machine is deterministic, these triples express total, rather than partial, correctness, which is essential for proving termination in Lemmas 7.1 and 7.2. To aid automation of proofs about code sequences, we give triples in weakest-precondition style.
We build proofs by composing atomic specifications of individual instructions, such as
with specifications for structured code generators, such as
(We emphasize that all such specifications are verified, not axiomatized as the inference rule notation might suggest.) We also prove a specification for the specialized case statement
The concrete implementations of the lattice operations are also specified using triples in this style.
For the two-point lattice, it is easy to prove that the implemented operators satisfy these specifications; Section 11 describes an analogous result for a lattice of sets of principals.
Going a bit further towards bridging the gap between the symbolic rule and concrete machines, we prove specifications for the generation of label expressions
and for the code generated to implement the application of a symbolic IFC symbolic rule. For instance, the case where the instruction is allowed is described by the following specification (the integer 1 pushed on the output stack encodes the fact that the rule is allowed):
Escaping-code Hoare triples. To be able to specify the entire code of the generated fault handler, we also define a second form of triple, if O computes if O computes
Or, in symbols,
To compose self-contained code with escaping code, we prove two composition laws for these triples, one for pre-composing with specified self-contained code and another for post-composing with arbitrary (unreachable) code:
We use these new triples to specify the
Everything comes together in verifying the fault handler. We use contained-code triples to specify everything except for
Refinement
We have two remaining verification goals. First, we want to show that the concrete machine of Section 5 (running the fault handler of Section 6 compiled from
We phrase these two results using the notion of machine refinement, which we develop in this section, and which we prove in Section 10 to be TINI preserving. In Section 9, we prove a two-way refinement (one direction for each goal), between the abstract and concrete machines, via the symbolic rule machine in both directions.
From here on we sometimes mention different machines (abstract, symbolic rule, or concrete) in the same statement (e.g., when discussing refinement), and sometimes talk about machines generically (e.g., when defining TINI for all our machines); for these purposes, it is useful to define a generic notion of machine.
A generic machine (or just machine) is a 5-tuple
Conceptually, a machine’s program is included in its input data and gets “loaded” by the function
A generic step
where we write ϵ for the empty trace and
When relating executions of two different machines through a refinement, we establish a correspondence between their traces. This relation is usually derived from an elementary relation on events,
Given a relation
We are now ready to define refinement.
(Refinement).
Let 
(Plain lines denote premises, dashed ones conclusions.)
In order to prove refinement, we need a variant that considers executions starting at arbitrary related states.
(Refinement via states).
Let 
If the relation on inputs is compatible with the one on states, we can use state refinement to prove refinement.
Suppose
Our plan to derive a refinement between the abstract and concrete machines via the symbolic rule machine requires composition of refinements.
Let
Refinements between concrete and abstract
In this section, we show that (1) the concrete machine refines the symbolic rule machine, and (2) vice versa. Using (1) we will be able to show in Section 10 that the concrete machine is noninterfering. From (2) we know that the concrete machine faithfully implements the abstract one, exactly reflecting its execution traces.
Abstract and symbolic rule machines
The symbolic rule machine (with the rule table For both abstract and symbolic rule machines, input data is a 4-tuple
The symbolic rule machine instantiated with the rule table
(Abstract and symbolic rule machines as generic machines).
We prove this refinement using a fixed but arbitrary rule table,
(Concrete machine as generic machine).
The input data of the concrete machine is a 4-tuple
The input data and events of the symbolic rule and concrete machines are of different kinds; they are matched using relations (
The concrete IFC machine refines the symbolic rule machine, through
We prove this theorem by a refinement via states (Lemma 9.7); this, in turn, relies on two technical lemmas (Lemmas 9.5 and 9.6).
We begin by defining a matching relation
We define
where the new notations are defined as follows. The relation
To prove refinement via states, we must account for two situations. First, suppose the concrete machine can take a user step. In this case, we match that step with a single symbolic rule machine step. We write
Let
We know that
The second case is when the concrete machine faults into kernel mode and returns to user mode after some number of steps.
Let
Since the concrete machine performs a faulting step from
Given two matching states of the concrete and symbolic rule machines, and a concrete execution starting at that concrete state, these two lemmas can be applied repeatedly to build a matching execution of the symbolic rule machine. There is just one last case to consider, namely when the execution ends with a fault into kernel mode and never returns to user mode. However, no output is produced in this case, guaranteeing that the full trace is matched. We thus derive the following refinement via states, of which Theorem 9.4 is a corollary.
The pair
By composing the refinement of Lemma 9.2 and the refinement of Theorem 9.4 instantiated to the concrete machine running
The concrete IFC machine refines the abstract IFC machine via
The previous refinement,
The abstract IFC machine refines the concrete IFC machine via
This guarantees that traces of the abstract machine are also emitted by the concrete machine. As above we use the symbolic rule machine as an intermediate step and show a state refinement of the concrete into the symbolic rule machine. We rely on the following lemma.
Let
where
Because
The two top-level refinement properties (Theorems 9.4 and 9.9) share the same notion of matching relations but they have been proved independently in our Coq development. In the context of compiler verification [58,81], another proof methodology has been favored: a backward simulation proof can be obtained from a proof of forward simulation under the assumption that the lower-level machine is deterministic. (CompCertTSO [81] also requires a receptiveness hypothesis that trivially holds in our context.) Since our concrete machine is deterministic, we could apply a similar technique. However, unlike in compiler verification where it is common to assume that the source program has a well-defined semantics (i.e. it does not get stuck), we would have to consider the possibility that the high-level semantics (the symbolic rule machine) might block and prove that in this case either the IFC enforcement judgment is stuck (and Lemma 9.6 applies) or the current symbolic rule machine state and matching concrete state are both ill-formed.
Noninterference
In this section we define TINI [1,43] for generic machines (recall Definition 8.1), and present a set of unwinding conditions [37] sufficient to guarantee TINI for a generic machine (Theorem 10.3); we show that the abstract machine of Section 3 satisfies these unwinding conditions and thus satisfies TINI (Theorem 10.5), that TINI is preserved by refinement (Theorem 10.6), and finally, using the fact that the concrete IFC machine refines the abstract one (Theorem 9.4), that the concrete machine satisfies TINI (Theorem 10.8).
Termination-insensitive noninterference (TINI). To define noninterference, we need to talk about what can be observed about the output trace produced by a run of a machine.
(Observation).
A notion of observation for a generic machine is a 3-tuple
The predicate
This definition truncates the longer trace to the same length as the shorter and then demands that the remaining elements be pairwise identical.
(TINI).
A machine
Notice that the input data for our machines includes the program to be executed; hence, we can apply the definition above to the execution of different programs. The reason for calling this notion “termination insensitive” is that, because of truncated traces in (3), we only model the case where we distinguish two runs of the same program by observing two distinguishable events that occur on the same position. Hence, this definition does not attempt to protect against attackers that try to learn a secret by seeing whether a program terminates or not: our observers cannot distinguish between successful termination, failure with an error, or entering an infinite loop with no observable output. This TINI property is standard for a machine with output [1,43].4
It is called “progress-insensitive noninterference” in a recent survey [43]. We have stated it for inductively defined executions and traces (1), which is all we need in this paper, but it can easily be lifted to coinductive executions and traces: not only successfully terminating and finitely failing executions, but also infinite executions. This holds because TINI is a 2-safety hyperproperty [22]; a formal proof of this can be found in our Coq development.
Unwinding conditions. Having defined TINI for generic notions of machine and observation, we now explain a sufficient set of conditions for such a machine to have the TINI property and sketch a proof of TINI from these conditions. The proof technique is standard [37].
A silent action cannot be observed, so we extend the given predicate
Two actions are indistinguishable to o if either they are equal, or if neither can be observed by o.
A machine
We outline the proof, which motivates each of the sanity and unwinding conditions. To prove TINI we must consider pairs of traces of machine evaluations starting from initial states
Now suppose the two evaluations have arrived at two indistinguishable states,
Condition (9) says that, if
On the other hand, suppose
Finally, the case where
TINI for abstract IFC machine. We now instantiate Theorem 10.3 with the abstract machine defined in Section 3, showing it satisfies TINI for the following notion of observation:
Let
To instantiate Theorem 10.3 we must exhibit relations of observability and indistinguishability on states. We outline these definitions and the proofs of the sanity and unwinding conditions here.
A state
Indistinguishability of states is defined by two clauses: the first for observable states (left), and the other for non-observable ones (right).
Here we abuse the notation of lifting,
Let’s have a more detailed look at the definition of state indistinguishability. For observable states, we simply require that all the state components be indistinguishable. For non-observable ones, however, we must make the relation more permissive. Indeed, the abstract IFC machine steps from an observable state to a non-observable state when, e.g., branching on the value of a secret. When that happens, the tight correspondence on states no longer holds. Depending on the value of a secret, the machine could, e.g., jump to different instruction addresses, put different numbers of values on its stack, perform more or fewer function calls, etc. Because of that, we must allow states with different
The relations
Most sanity conditions are easy consequences of the definitions, and do not require detailed explanation. We give an overview of the most interesting aspects of the proof; a more detailed account can be found in the formal development.
The
As for the second conclusion of (9), since indistinguishable low states have equal
Most of the cases of condition (10) – stepping from an unobservable state to another unobservable state – are trivial, since they only manipulate values or unobservable return frames on top of the stack (which by construction are irrelevant when checking whether the corresponding stacks are indistinguishable). The only exception is the
Finally, the precondition of (11) (stepping from unobservable to observable states) only applies when both states execute matching
TINI preserved by refinement.
Suppose that the generic machine
for all
for all
for all
Then, if
We include a brief proof sketch to convey the meaning of the theorem and the role of the compatibility conditions; intuitively, they say that
Some formulations of noninterference are subject to the refinement paradox [48], in which refinements of a noninterferent system may violate noninterference. We avoid this issue by employing a strong notion of noninterference that restricts the amount of non-determinism in the system and is thus preserved by any refinement (Theorem 10.6).5
Since our abstract machine is deterministic, it is easy to show this strong notion of noninterference for it. In Section 13 we discuss a possible technique for generalizing to the concurrent setting while preserving a high degree of determinism.TINI for concrete machine with IFC fault handler. It remains to define a notion of observation on the concrete machine, instantiating the definition of TINI for this machine. This definition refers to a concrete lattice
Let
Finally, we prove that the backward refinement proved in Section 9 (Theorem 9.8) satisfies the compatibility constraints of Theorem 10.6, so we derive the main result:
The concrete IFC machine running the fault handler

Additional instructions for extensions.
Thus far we have presented our methodology in the context of a simple machine architecture and IFC discipline. We now show how it can be scaled up to a significantly more sophisticated setting, where the basic machine is extended with a frame-based memory model supporting dynamic allocation and a system call mechanism for adding special-purpose primitives. Building on these features, we define an abstract IFC machine that uses sets of principals as its labels and a corresponding concrete machine implementation where tags are pointers to dynamically allocated representations of these sets. While still much less complex than the real SAFE system, this extended model serves as good evidence of the robustness our approach, and how it might apply to more realistic designs: The new features were added by incrementally adapting the Coq formalization of the basic system, without requiring any major changes to the initial proof architecture.

Semantics of selected new abstract machine instructions.
Figure 11 shows the new instructions supported by the extended model. Instruction

Semantics of selected new concrete machine instructions.
High-level programming languages usually assume a structured memory model, in which independently allocated frames are disjoint by construction and programs cannot depend on the relative placement of frames in memory. The SAFE hardware enforces this abstraction by attaching explicit runtime types to all values, distinguishing pointers from other data. Only data marked as pointers can be used to access memory. To obtain a pointer, one must either call the (privileged) memory manager to allocate a fresh frame or else offset an existing pointer. In particular, it is not possible to “forge” a pointer from an integer. Each pointer also carries information about its base and bounds, and the hardware prevents it from being used to access memory outside of its frame.
Frame-based memory model. In our extended system, we model the user-level view of SAFE’s memory system by adding a frame-structured memory (similar to [59]), distinguished pointers (so values, the payload field of atoms and the tag field of concrete atoms, can now either be an integer
It would be interesting to describe an implementation of the memory manager in a still-lower-level concrete machine with no built-in
The most important new rule of the extended abstract machine is
IFC and memory allocation. We require that the frame identifiers produced by allocation at one label not be affected by allocations at other labels; e.g.,
A few small modifications to existing instructions in the basic machine (Fig. 2) are needed to handle pointers properly. In particular: (i)
Concrete allocator. The extended concrete machine’s semantics for
Proof by refinement. As before, we prove noninterference for the concrete machine by combining a proof of noninterference of the abstract machine with a two-stage proof that the concrete machine refines the abstract machine. By using this approach we avoid some well-known difficulties in proving noninterference directly for the concrete machine. In particular, when frames allocated in low and high contexts share the same region, allocations in high contexts can cause variations in the precise pointer values returned for allocations in low contexts, and these variations must be taken into account when defining the indistinguishability relation. For example, Banerjee and Naumann [11] prove noninterference by parameterizing their indistinguishability relation with a partial bijection that keeps track of indistinguishable memory addresses. Our approach, by contrast, defines pointer indistinguishability only at the abstract level, where indistinguishable low pointers are identical. This proof strategy still requires relating memory addresses when showing refinement, but this relation does not appear in the noninterference proof at the abstract level. The refinement proof itself uses a simplified form of memory injections [59]. The differences in the memory region structure of both machines are significant, but invisible to programs, since no information about frame ids is revealed to programs beyond what can be obtained by comparing pointers for equality. This restriction allows the refinement proof to go through straightforwardly.
To support the implementation of policy-specific primitives on top of the concrete machine, we provide a new system call instruction. The
In the abstract and symbolic rule machines, a system call implementation is an arbitrary Coq function that removes a list of atoms from the top of the stack and either puts a result on top of the stack or fails, halting the machine. The system call implementation is responsible for computing the label of the result and performing any checks that are needed to ensure noninterference.
In the concrete machine, system calls are implemented by kernel routines and the call table contains the entry points of these routines in the kernel instruction memory. Executing a system call involves inserting the return address on the stack (underneath the call arguments) and jumping to the corresponding entry point. The kernel code terminates either by returning a result to the user program or by halting the machine.
This feature has no major impact on the proofs of noninterference and refinement. For noninterference, we must show that all the abstract system calls preserve indistinguishability of abstract machine states; for refinement, we show that each concrete system call correctly implements the abstract one using the machinery of Section 7.
Labeling with sets of principals
The full SAFE machine supports dynamic creation of security principals. In the extended model, we make a first step toward dynamic principal creation by taking principals to be integers and instantiating the (parametric) lattice of labels with the lattice of finite sets of integers. This lattice is statically known, but models dynamic creation by supporting unbounded labels and having no top element. In this lattice, ⊥ is ∅, ∨ is ∪, and ⩽ is ⊆. We enrich our IFC model by adding a new classification primitive
At the concrete level, a tag is now a pointer to an array of principals (integers) stored in kernel memory. To keep the fault handler code simple, we do not maintain canonical representations of sets: one set may be represented by different arrays, and a given array may have duplicate elements. (As a consequence, the mapping from abstract labels to tags is no longer a function; we return to this point below.) Since the fault handler generator in the basic system is parametric in the underlying lattice, it doesn’t require any modification. All we must do is provide concrete implementations for the appropriate lattice operations:
A more realistic system would keep canonical representations of sets and avoid unnecessary allocation in order to improve its memory footprint and tag cache usage. But even with the present simplistic approach, both the code for the lattice operations and their proofs of correctness are significantly more elaborate than for the trivial two-point lattice. In particular, we need an additional code generator to build counted loops, e.g., for computing the join of two tags.
To avoid reasoning about memory updates as far as possible, we code in a style where all local context is stored on the stack and manipulated using
Stateful encoding of labels. Changing the representation of tags from integers to pointers requires modifying one small part of the basic system proof. Recall that in Section 6 we described the encoding of labels into tags as a pure function
If tags are pointers to data structures, it is crucial that these data structures remain intact as long as the tags appear in the machine state. We guarantee this by maintaining the very strong invariant that each execution of the fault handler only allocates new frames, and never modifies the contents of existing ones, except for the
The TINI formulation is similar in essence to the one in Section 10, but some subtleties arise for concrete output events, since their tags cannot be interpreted on their own anymore. We wish to (i) keep the semantics of the concrete machine independent of high-level policies such as IFC and (ii) give a statement of noninterference that does not refer to pointers. To achieve these seemingly contradictory aims, we model an event of the concrete machine as a pair of a concrete atom plus the whole state of the kernel memory. This memory is not visible to observers in the formulation of TINI, but instead determines which events’ payloads they are able to observe. This is done by extending our notion of observation with a function that interprets every concrete event present in the output trace in higher-level terms. This interpretation abstracts away from low-level representation issues, such as the layout of data structures in memory, and allows us to give a more natural definition of event indistinguishability in the formulation of TINI. For instance, in the extended system described above, the interpretation of a pointer tag is the set of principals that pointer represents in kernel memory – that is, the contents of the array it points to. This allows us to define the event indistinguishability relation by simple equality.
Our model of observation in terms of an interpretation function is an idealization of what happens in the real SAFE machine, where communication of labeled data with the outside world involves cryptography. Extending this model this is left as future work.
Related work
The SAFE design spans a number of research areas, and a comprehensive overview of related work would be huge. We focus here on a small set of especially relevant points of comparison.
Language-based IFC. Static approaches to IFC have generally dominated language-based security research [69,73,77,93]; however, statically enforcing IFC at the lowest level of a real system is challenging. Soundly analyzing native binaries with reasonable precision is hard (static IFC for low-level code usually stops at the bytecode level [13,38,42,60]), even more so without the compiler’s cooperation (e.g., for stripped or obfuscated binaries). Proof-carrying code [12,13,38] and typed assembly language [62,94,95] have been used for enforcing IFC on low-level code without low-level analysis or adding the compiler to the TCB. In SAFE [30,35] we follow a different approach, enforcing noninterference using purely dynamic checks, for arbitrary binaries in a custom-designed instruction set. The mechanisms we use for this are similar to those found in recent work on purely dynamic IFC for high-level languages [2,4–7,40,41,44,45,64,72,75,78,83,88]; however, as far as we know, we are the first to push these ideas to the lowest level.
seL4. Murray et al. [66] recently demonstrated a machine-checked noninterference proof for the implementation of the seL4 microkernel. This proof is carried out by refinement and reuses the specification and most of the existing functional correctness proof of seL4 [54]. Like the TINI property in this paper, the variant of intransitive noninterference used by Murray et al. is preserved by refinement because it implies a high degree of determinism [67]. This organization of their proof was responsible for a significant saving in effort, even when factoring in the additional work required to remove all observable non-determinism from the seL4 specification. Beyond these similarities, SAFE and seL4 rely on completely different mechanisms to achieve different notions of noninterference (seL4 admits intransitive IFC policies, capturing the “where” dimension of declassification [79], while we consider transitive ones). Whereas, in SAFE, each word of data has an IFC label and labels are propagated on each instruction, the seL4 kernel maintains separation between several large partitions (e.g., one partition can run an unmodified version of Linux) and ensures that information is conveyed between such partitions only in accordance with a fixed access control policy.
PROSPER. In parallel work, Dam et al. [27,28,53] verified information flow security for a tiny proof-of-concept separation kernel running on ARMv7 and using a Memory Management Unit for physical protection of memory regions belonging to different partitions. The authors argue that noninterference is not well suited for systems in which components are supposed to communicate with each other. Instead, they use the bisimulation proof method to show trace equivalence between the real system and an ideal top-level specification that is secure by construction. As in seL4 [66], the proof methodology precludes an abstract treatment of scheduling, but the authors contend this is to be expected when information flow is to be taken into account. In more recent work, Balliu et al. [10] propose a symbolic execution-based information flow analysis for machine code, and use this technique to verify a separation kernel system call handler, a UART device driver, and a crypto service modular exponentiation routine.
TIARA and ARIES. The SAFE architecture embodies a number of innovations from earlier paper designs. In particular, the TIARA design [84] first proposed the idea of a zero-kernel operating system and sketched a concrete architecture, while the ARIES project proposed using a hardware rule cache to speed up information-flow tracking [16]. In TIARA and ARIES, tags had a fixed set of fields and were of limited length, whereas, in SAFE, tags are pointers to arbitrary data structures, allowing them to represent complex IFC labels encoding sophisticated security policies [63], for instance decentralized ones [69,87]. Moreover, unlike TIARA and ARIES, which made no formal soundness claims, SAFE proposes a set of IFC rules aimed at achieving noninterference; the proof we present in this paper, though for a simplified model, provides evidence that this goal is feasible.
RIFLE and other binary-rewriting-based IFC systems. RIFLE [91] enforces user-specified information-flow policies for x86 binaries using binary rewriting, static analysis, and augmented hardware. Binary rewriting is used to make implicit flows explicit; it heavily relies on static analysis for reconstructing the program’s control-flow graph and performing reaching-definitions and alias analysis. The augmented hardware architecture associates labels with registers and memory and updates these labels on each instruction to track explicit flows. Additional security registers are used by the binary translation mechanism to help track implicit flows. Beringer [14] recently proved (in Coq) that the main ideas in RIFLE can be used to achieve noninterference for a simple While language. Unlike RIFLE, SAFE achieves noninterference purely dynamically and does not rely on binary rewriting or heroic static analysis of binaries. Moreover, the SAFE hardware is generic, simply caching instances of software-managed rules.
While many other information flow tracking systems based on binary rewriting have been proposed, few are concerned with soundly handling implicit flows [23,61], and even these do so only to the extent they can statically analyze binaries. Since, unlike RIFLE (and SAFE), these systems use unmodified hardware, the overhead for tracking implicit flows can be large. To reduce this overhead, recent systems track implicit flows selectively [52] or not at all [49,74] – arguably a reasonable tradeoff in settings such as malware analysis or attack detection, where speed and precision are more important than soundness.
Hardware taint tracking. The last decade has seen significant progress in specialized hardware for accelerating taint tracking [19,25,26,31,32,89,92]. Most commonly, a single tag bit is associated with each word to specify if it is tainted or not. Initially aimed at mitigating low-level memory corruption attacks by preventing the use of tainted pointers and the execution of tainted instructions [19,25,89], hardware-based taint tracking has also been used to prevent high-level attacks such as SQL injection and cross-site scripting [26]. In contrast to SAFE, these systems prioritize efficiency and overall helpfulness over the soundness of the analysis, striking a heuristic balance between false positives and false negatives (missed attacks). As a consequence, these systems ignore implicit flows and often do not even track all explicit flows. While early systems supported a single hard-coded taint propagation policy, recent ones allow the policy to be defined in software [26,32,92] and support monitoring policies that go beyond taint tracking [18,31,32,76]. Harmoni [32], for example, provides a pair of caches that are quite similar to the SAFE rule cache. Possibly these could even be adapted to enforcing noninterference, in which case we expect the proof methodology introduced here to apply.
Timing and termination. Our TINI property ignores both termination and timing: a program that diverges, fails, or takes varying amounts of time to run based on a sensitive input is considered secure. The full SAFE design includes a clearance-based access-control mechanism [88] for addressing termination and timing covert channels (i.e., high-bandwidth channels through which malicious code can exfiltrate secrets it directly has access to). Stefan et al. [86] have also shown that in a concurrent setting such leaks can be prevented by an adapted IFC mechanism, at the risk of spawning very large numbers of threads. We believe that this IFC mechanism could also be enforced using the hardware mechanisms we describe here. A recently proposed technique for instruction-based scheduling [17,85] is aimed at preventing leaks via the internal timing side-channel (e.g., malicious code sharing the same processor inferring secrets through timing variations arising from cache misses). This could probably be adapted to SAFE, and since the SAFE processor is very simple the mitigation could work well [24]. Finally, several mechanisms have been proposed for mitigating the external timing side-channel (i.e., leakage of secrets to an attacker making timing observations over the network) and thus reducing the rate at which bits can be leaked [3,98]. We do not consider any of these attacks or mitigations in this work.
Verification of low-level code. The distinctive challenge in verifying machine code is coping with unstructured control flow. Our approach using structured generators to build the fault handler is similar to the mechanisms used in Chlipala’s Bedrock system [20,21] and by Jensen et al. [50], but there are several points of difference. These systems each build macros on top of a powerful low-level program logic for machine code (Ni and Shao’s XCAP [71], in the case of Bedrock), whereas we take a simpler, ad-hoc approach, building directly on our stack machine’s relatively high-level semantics. Both these systems are based on separation logic, which we can do without since (at least in the present simplified model) we have very few memory operations to reason about. We have instead focused on developing a simple Hoare logic specifically suited to verifying structured runtime-system code; e.g., we omit support for arbitrary code pointers, but add support for reasoning about termination. We use total-correctness Hoare triples (similar to Myreen and Gordon [70]) and weakest preconditions to guarantee progress, not just safety, for our handler code. Finally, our level of automation is much more modest than Bedrock’s, though still adequate to discharge most verification conditions on straight-line stack manipulation code rapidly and often automatically.
Work on testing noninterference. The abstract machine in Section 3 was proposed by Hriţcu et al. [46], extended in this work with dynamic allocation and data classification (Section 11), and recently further extended by Hriţcu et al. to a sophisticated machine featuring a highly permissive flow-sensitive dynamic enforcement mechanism, public labels, and registers [47]. While the focus of that work is on verifying noninterference by random testing, it also shows how to use invariants discovered during testing to formalize proofs of noninterference in Coq.
Although the abstract machine and IFC mechanism considered here are simpler than the most complex ones of Hriţcu et al. [47], our main concerns are the concrete machine, the IFC fault handler, and the key properties of this combination, all of which are novel. We believe nevertheless that our methodology could be extended to that setting as well, verifying an implementation of this extended IFC machine by a lower-level one. Depending on the hardware capabilities at the lower level, some of the features of the machine could have to be implemented in software, requiring further proofs. For instance, this extended IFC machine still relies on a protected stack for soundly performing function calls and returns: on a call, the entire register file is stored on this stack, so that it can be restored upon a return, thereby preventing data leakage. At the lowest level, this protected stack could be implemented with a regular stack living in kernel space, managed through special system calls.
Tagging hardware beyond IFC. Although the tagging mechanism we discuss arose in the context of the SAFE system, and was primarily designed for information-flow control, it is sufficiently generic to be implemented in other architectures and to enforce more security policies.
In follow-on work, Dhawan et al. [34] adapt the tagging mechanism to a more conventional RISC processor, using it to implement policies such as memory safety and control-flow integrity. They evaluate the performance of the mechanism on benchmark simulations, which indicate a modest impact on speed (typically under 10%) and power ceiling (less that 10%), even when enforcing multiple policies simultaneously.
Azevedo de Amorim et al. [9] use Coq to formalize a generic version of the symbolic machine of Section 4; that machine is different from the one discussed here in that it is based on a more conventional processor design (e.g., with registers instead of a protected stack), and serves as a high-level substrate for programming many different security policies, including compartmentalization and memory safety. Finally, they formulate the intended effect of each policy as a security property, using formal proofs to show that each policy enforces the corresponding property.
A recent project at Draper Labs [29] is working to extend the RISC-V processor with tag propagation hardware in the style of the SAFE processor. As of March 2016, a prototype able to boot Linux is running on FPGA boards.
Conclusions and future work
We have presented a formal model of the key IFC mechanisms of the SAFE system: propagating and checking tags to enforce security, using a hardware cache for common-case efficiency and a software fault handler for maximum flexibility. To formalize and prove properties at such a low level (including features such as dynamic memory allocation and labels represented by pointers to in-memory data structures), we first construct a high-level abstract specification of the system, then refine it in two steps into a realistic concrete machine. A bidirectional refinement methodology allows us to prove (i) that the concrete machine, loaded with the right fault handler (i.e. correctly implementing the IFC enforcement of the abstract specification) satisfies a traditional notion of termination-insensitive noninterference, and (ii) that the concrete machine reflects all the behaviors of the abstract specification. Our formalization reflects the programmability of the fault handling mechanism, in that the fault handler code is compiled from a rule table written in a small DSL. We set up a custom Hoare logic to specify and verify the corresponding machine code, following the structure of a simple compiler for this DSL.
The development in this paper concerns three deterministic machines and simplifies away concurrency. While the lack of concurrency is a significant current limitation that we would like to remove by moving to a multithreading single-core model, we still want to maintain the abstraction layers of a proof-by-refinement architecture. This requires some care so as not to run afoul of the refinement paradox [48] since some standard notions of noninterference (for example possibilistic noninterference) are not preserved by refinement in the presence of non-determinism. One promising path toward this objective is inspired by the recent noninterference proof for seL4 [66,67]. If we manage to share a common thread scheduler between the abstract and concrete machines, we could still prove a strong double refinement property (concrete refines abstract and vice versa) and hence preserve a strong notion of noninterference (such as the TINI notion from this work) or a possibilistic variation.
Although this paper focuses on IFC and noninterference, the tagging facilities of the concrete machine are completely generic and have been used since to enforce completely different properties like memory safety, compartment isolation, and control-flow integrity [9]. Moreover, although the rule cache/fault handler design arose in the context of SAFE, it has since been adapted to a conventional RISC processor [34].
Footnotes
Acknowledgments
We are grateful to Maxime Dénès, Deepak Garg, Greg Morrisett, Toby Murray, Jeremy Planul, Alejandro Russo, Howie Shrobe, Jonathan M. Smith, Deian Stefan, and Greg Sullivan for useful discussions and helpful feedback on early drafts. We also thank the anonymous reviewers for their insightful comments. This material is based upon work supported by the DARPA CRASH and SOUND programs through the US Air Force Research Laboratory (AFRL) under Contracts No. FA8650-10-C-7090 and FA8650-11-C-7189. This work was also partially supported by NSF award 1513854 Micro-Policies: A Framework for Tag-Based Security Monitors. The views expressed are those of the authors and do not reflect the official policy or position of the Department of Defense or the U.S. Government.
