ConceptioArchivearXiv CS
arXiv CSopen access

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

· arxiv_cs
arXiv CS · Papers · License: Open Access
Open Source ↗Direct PDF ↓
cryptographycybersecurityprivacysecurity
cryptography, security, privacy, cybersecurity

arXiv:2609.17399v1 [cs.CR] 15 Sep 2026

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version) Shixin Song

Davide Davoli

Elias Storme

[email protected] Massachusetts Institute of Technology Cambridge, United States

[email protected] MPI-SP Bochum, Germany

[email protected] DistriNet, KU Leuven Leuven, Belgium

Marton Bognar

Dominique Devriese

Frank Piessens

[email protected] DistriNet, KU Leuven Leuven, Belgium

[email protected] DistriNet, KU Leuven Leuven, Belgium

[email protected] DistriNet, KU Leuven Leuven, Belgium

Tamara Rezk [email protected] Inria Sophia Antipolis, France

Abstract Capability-based architectures such as CHERI provide strong support for the architectural isolation of software components. To additionally protect against microarchitectural leakage, software can be written in a constant-time fashion. Modern processors, however, rely heavily on speculative execution, which can invalidate the constant-time guarantees and leak isolated secrets transiently. In this work, we show that providing secure speculation for CHERI is non-trivial, and that existing proposals fail to preserve the confidentiality guarantees. We develop a formal framework for reasoning jointly about capability safety, speculative execution, and information-flow security, and use it to demonstrate potential leaks. We then present SCHERI , a new processor design within this framework, and formally prove that it provides end-to-end secure speculation guarantees for the constant-time policy. Our results provide formal foundations and practical guidance for building future capability-based processors, which are resilient to Spectre attacks for constant-time programs.

CCS Concepts • Security and privacy → Formal security models.

Keywords CHERI, formal execution models, Spectre, microarchitectural security, constant-time programming, information flow

1

Introduction

The ongoing prevalence of memory-safety vulnerabilities has motivated capability-based hardware architectures that deliver enforceable guarantees by construction. The CHERI (Capability Hardware Enhanced RISC Instructions) architecture emerged in 2010 [29] as a candidate for this vision, offering fine-grained memory protection and principled reasoning about pointer provenance. At the architectural level, CHERI enforces strong spatial memory-safety guarantees and enables robust compartmentalization [28]. The CHERI

architecture has gained momentum over the last decade with the development of CHERI-RISC-V [24] and the ARM Morello project [1] that implements CHERI in ARM. However, CHERI memory safety assumes a non-speculative execution model. Modern high-performance processors rely heavily on speculation, and, since the discovery of transient-execution attacks [20], it has become clear that speculation can violate architectural guarantees by exposing microarchitectural side effects. While misspeculated instructions are rolled back at the architectural level, their effects on microarchitectural state (e.g., caches) may persist, enabling attackers to leak secrets through side channels [19]. CSC. Prior work has demonstrated that CHERI is not immune to this problem [15]. In particular, Fuchs et al. [16] introduced the Capability Speculation Contract (CSC), which constrains speculative execution to preserve CHERI’s capability invariants. They showed that violations of these invariants in speculative implementations (e.g., CHERI-Toooba) lead to concrete attacks such as Meltdown-CF, which break CHERI’s memory-safety guarantees in practice. CSC can be understood as an instance of the broader class of hardwaresoftware contracts [19] for secure speculation, which aim to provide principled co-design between hardware mechanisms and software reasoning. CSC restores speculative sandboxing by ensuring that speculative memory accesses respect capability permissions and bounds. While effective for preserving speculative sandboxing, CSC does not provide confidentiality guarantees when legitimately accessible secrets can still leak through timing, cache, or other microarchitectural channels. We show that achieving provably secure speculation for the constant-time policy in CHERI requires moving beyond sandboxing, toward confidentiality guarantees. BLACKOUT. An important step toward this goal was taken by BLACKOUT [12], which extends CHERI with hardware-supported taint tracking via blinded capabilities to enable data-oblivious computation. BLACKOUT raises faults when secret data influences control flow or memory accesses, thereby aiming to prevent both conventional and speculative side-channel leakage. At a high level,

Song et al.

BLACKOUT moves beyond sandboxing toward non-interferencestyle guarantees by tracking the flow of secret data through computation. However, as we will show, BLACKOUT does not fully achieve secure speculation for the constant-time policy. These limitations highlight a deeper issue: existing approaches lack a unified formal framework capable of expressing and comparing memory safety, sandboxing, and secure speculation guarantees. Our contributions. In this paper, we address this gap by developing a formal framework for reasoning about CHERI-like architectures under speculative execution and strong leakage models. Within this framework, we: • define a speculative semantics for CHERI-like processors with a strong attacker model and a notion of speculative constant time; • show that the CSC does not guarantee confidentiality under leakage models stronger than speculative sandboxing; • analyze BLACKOUT under these stronger leakage models, identifying sources of speculative information leakage; • propose SCHERI , eliminating leaks while preserving practicality; • formally prove that SCHERI satisfies end-to-end secure speculation guarantees in our model. Our results provide both formal foundations and practical guidance for building CHERI processors resilient to speculative sidechannel attacks. This version of the paper is extended from the conference version [26], containing the full hardware semantics (Appendix C) and proofs (Appendix D).

2

Background

CHERI. CHERI is a capability architecture that extends conventional ISAs with hardware-enforced memory protection. Instead of representing pointers as plain integers, CHERI uses capabilities: unforgeable references that carry both an address and metadata such as bounds, permissions, and validity information. Memory accesses through capabilities are checked in hardware, preventing out-of-bounds dereferences and unauthorized use of pointers. Capabilities can only be derived from existing capabilities, ensuring monotonicity: derived capabilities cannot gain permissions or exceed the bounds of their parent capability except at controlled security boundary crossings. These mechanisms provide strong spatial memory safety and support fine-grained compartmentalization. Practical realizations include CHERI-RISC-V [24], and ARM Morello [1]. While CHERI substantially strengthens architectural memory safety, these guarantees are defined at the ISA level and therefore assume architecturally correct execution. They do not automatically extend to (speculative) microarchitectural behavior. Speculative execution and attacks. Modern processors improve performance through out-of-order and speculative execution. Instructions may execute before prior branches, permissions checks, or data dependencies are fully resolved. If speculation is later found incorrect, architectural state is rolled back, but transient effects on microarchitectural state, such as cache contents, predictor state, or execution timing, may remain observable. Transient-execution attacks exploit this behavior to leak secrets via persistent microarchitectural effects. Spectre-style attacks [20]

mistrain predictors to transiently execute attacker-chosen instruction sequences, Meltdown-style attacks [22] exploit incorrect forwarding from faulting or unauthorized operations. These attacks demonstrate that architectural safety alone is insufficient when secrets can influence transient execution. For CHERI, this means that even capability-safe software may leak information if speculative execution bypasses intended checks or transiently exposes secret-dependent behavior. Security properties: sandboxing and constant-time. Mitigations for speculative execution attacks and their desired security properties can be described using a hardware-software contract [19]. It is useful to distinguish two types of security properties: • Sandboxing or compartmentalization, ensuring that a potentially malicious program cannot speculatively read outside of its architecturally designated sandbox, and • Constant-time or data-obliviousness, ensuring that a benign program does not leak secrets through microarchitectural channels. These are very different properties: sandboxing considers code that does not architecturally compute on secrets but tries to get access speculatively, whereas constant-time considers code that does architecturally compute on secrets, and should not leak them through side channels, even speculatively. Hence, they typically require different mitigations under speculation. CSC aims to prevent leakage in the sandboxing model, while BLACKOUT targets the speculative constant-time property.

3

Threat Model

We consider an attacker with the goal of inferring secret information from a CHERI system through microarchitectural side-channel observations. The attacker cannot directly read privileged state, break cryptographic primitives, or violate the architectural memory protection guarantees of CHERI, but they can execute untrusted code on the same processor (concurrently or across time-sharing contexts) and trigger victim execution with chosen public inputs. As is standard in the Spectre literature, the attacker may mistrain branch predictors, influence microarchitectural state to cause contention, and observe timing differences caused by victim execution. We consider a broad class of microarchitectural side channels, including the state of microarchitectural structures (e.g., caches, branch predictors, the reorder buffer), speculation success and rollback behavior, and contention for execution ports and other timingvisible shared resources. This follows prior secure speculation work that treats attacker-observable microarchitectural state abstractly rather than committing to one specific channel [9]. Our threat model is intentionally stronger than the one captured by the Capability Speculation Contract (CSC): in addition to unauthorized speculative memory accesses, we consider leakage of legitimately accessed secrets through timing-visible microarchitectural effects. We do not consider physical side channels such as power analysis, electromagnetic emanations, or fault injection. Our goal is therefore to enforce relative speculative constant time for victim code (formalized in Theorem 6.2): secrets that are not leaked architecturally do not influence the attacker’s observations. In particular, we seek guarantees stronger than CSC-style speculative sandboxing: speculative execution should preserve confidentiality, not only prevent capability forgery.

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

4

Prior Approaches and Shortcomings

The security property we aim to achieve is that programs that are constant-time in the absence of speculation remain constant-time under speculation. In this section, we show that existing Spectre mitigations on the CHERI architecture do not achieve this goal. We discuss two recently published approaches: Capability Speculation Contracts (CSC) [16] and BLACKOUT [12].

4.1

Capability Speculation Contract

CHERI provides architectural support for software sandboxing [29], and recently Fuchs et al. [16] proposed an approach to preserving these architectural sandboxing properties under speculation. Their Capability Speculation Contract (CSC) expresses that the hardware should not perform speculative violations of security boundaries enforced by capabilities. More precisely, it states: Contract 4.1 (Capability Speculation Contract [16]). All instruction and data-memory accesses issued in speculation must be authorized by capabilities either (1) in the committed register file; or (2) in memory transitively reachable through (1). Hence, CSC has a different goal than our design: it does not aim to prevent side-channel leaks of accessible secrets through speculation. It is straightforward to construct examples of programs that are secure in the absence of speculation and that comply with the CSC contract, but that leak secrets through side channels when speculation is allowed. Consider the following code: 1 2 3 4 5 6 7

f_load_sec: cld a1, 0(csp) # secret data is loaded into a1 ccall g # after executing g, speculation starts ... exploit_gadget: # call to g speculatively returns here cinoffset cs0, csp, a1 # cs0's offset advances by secret a1 cld a1, 0(cs0) # secret is leaked

Here, csp is the stack capability register, cs0 is another capability register, and a1 is a general purpose register. If secret data is stored on the stack referenced by csp, the cld instruction at line 2 loads it in a1. If the called g function speculatively returns to exploit_gadget, the secret is used to set the offset of cs0. Then, the load at line 7 uses the address of cs0, leaking the secret. Note that the secret could also be leaked through other side channels.

4.2

BLACKOUT

BLACKOUT [12] extends CHERI with blinded capabilities for enforcing data-oblivious execution, also during speculation. BLACKOUT incorporates the ideas of oblivious instruction set architectures [13, 31] into the CHERI architecture by repurposing an unused bit in capability metadata to mark a capability as blinded. When data is loaded through a blinded capability into a register, the register is also marked as blinded, and the hardware implements taint-tracking to propagate blindedness. Blinded values are treated as secrets, and the hardware prevents them from affecting timing behavior by faulting on e.g., load/store instructions with blinded addresses or branches with blinded conditions. During speculation, the faults are not raised architecturally, but they still prevent the leakage of blinded data. Building on these primitives, BLACKOUT allows programmers to write data-oblivious code, relying on the hardware to prevent side-channel leakage.

The security argument of BLACKOUT relies on the maintenance of five key invariants, but in order to efficiently support the call stack and register spills, BLACKOUT allows some of these invariants to be violated in a controlled way. One invariant (I3 [12]) says that the bounds of valid blinded and non-blinded capabilities must not simultaneously overlap. However, this complicates storing both blinded and non-blinded data on the stack. Hence, BLACKOUT allows this invariant to be violated for the stack capability: a local blinded variable will architecturally be accessed through a blinded capability that overlaps with the non-blinded stack capability. This is not a problem in the absence of speculation if the software stack can enforce that blinded data is only accessed through the blinded capability. However, this opens the door to speculative execution attacks that can load and leak blinded data through the non-blinded stack capability. Another invariant (I1) says that blinded data cannot be stored into memory using non-blinded capabilities. This is a problem for compiler-induced register spills: blinded registers cannot be spilled to the stack using the non-blinded stack capability. To address this, BLACKOUT repurposes the capability validity tag to indicate blindedness of secret register spills. Specifically, when the program spills a blinded register to the stack (using a csc instruction that stores the whole 16 bytes in the register), the BLACKOUT processor emits a special structure named Blinded Register Record (BRR), which consists of the 8-byte spilled blinded value and an 8-byte marker (see Figure 1). BLACKOUT also sets the capability validity tag corresponding to the spill slot to 1. When restoring the blinded register spill from the stack using the clc instruction, the validity tag and the marker indicate that the restored value is a BRR rather than a capability or other public data on the stack, so the processor can properly mark the loaded data as blinded. 4.2.1 Security Analysis. At its core, BLACKOUT maintains its security guarantees as long as the hardware correctly tracks when secret data is accessed. Data can be marked as secret in two ways: either by setting the blinded bit in its associated capability, or, in the case of spilled registers on the stack, with a Blinded Register Record (BRR), which stores a magic constant right next to the spilled register value in memory. As long as the data is accessed through the blinded capability (in the case of blinded memory) or through a capability load through the stack capability (in the case of BRRs), the processor correctly sets and propagates the taint bit. In the following, we systematically explore how blinded data could be accessed in other ways that deviate from the above, leading to the loaded secret data incorrectly not being tainted, enabling leakage. (1) Improperly accessing a BRR. (a) The blinded bit is only set when BRRs are loaded via full capability load instructions (clc). Accessing a spilled value, potentially speculatively, through a narrower load instruction (e.g., cld) via the stack pointer (or any non-blinded capability) will not set the blinded bit. (b) The blinded bit is only set when the BRR is tagged as a capability and its metadata corresponds to the magic constant. Even if the BRR is accessed via the stack pointer using a capability load, the blinded bit will not be set if the magic value has been corrupted or the capability tag cleared. This corruption could also happen transiently, e.g., as a result of incorrect store-to-load forwarding.

Song et al.

BRR

magic secret

blinded capability

secret

csp

Higher memory

Memory

Here, the victim function spills a secret register (using the capability store instruction csc) onto the stack in a BRR. During speculation, this BRR is accessed via cld, a regular load instruction, which does not check whether the loaded data is a BRR, and as a result, does not set the blinded bit. Consequently, the loaded secret data in a1 can be leaked by using it as an offset for a load. Exploit for Issue 2a. This attack is structurally very similar to the previous one, and is exemplified below: 1 2 3 4

Figure 1: In BLACKOUT, memory referenced by blinded capabilities and Blinded Register Records (BRR) on the stack are considered secret (areas colored in ). However, the nonblinded stack capability (csp) can also access this secret data.

5 6 7 8 9 10 11

(2) Improperly accessing secret data through a non-blinded capability. In BLACKOUT, this is always possible for function-local blinded data, as the stack capability is not blinded (cf. Figure 1). (a) Access through a load via the stack capability, bypassing compiler enforcement of exclusive access through the associated blinded capability. This can easily happen during speculation; e.g., if the stack capability has a different offset than the compiler expects. (b) Stale data on the stack might be accessed by a newly derived capability in a later function call. Even if accesses to uninitialized memory are not allowed by the compiler, this could happen transiently if the store performing the initialization is speculatively skipped before a load. (c) Blinded data could also be accessed through a non-blinded capability derived from the stack pointer during speculation. The CSC (see Section 4.1) does not curb this issue, as it allows all legal capability modifications to be performed speculatively as well. 4.2.2 Concrete Attacks. To provide evidence for the validity of the security concerns, we demonstrate proof-of-concept attacks (released publicly in Appendix A) exploiting Issues 1a and 2a on the BLACKOUT prototype. Issue 2b and Issue 2c can likely be exploited with additional engineering effort. The current prototype lacks the hardware speculation features required to exploit Issue 1b, but we believe it could be exploited on future implementations of BLACKOUT. Below, we briefly describe our exploits for Issues 1a and 2a, which rely on return address speculation [21]: the attacker causes a return instruction from a victim function to transiently jump to attacker code by poisoning the return stack buffer. Exploit for Issue 1a. Consider the following code: 1 2 3 4 5 6 7 8 9 10

f_write_brr: # csp is the stack pointer capability # ca0 contains secret data csc ca0, -16(csp) # spill the secret to the stack as a BRR ccall g # after executing g, speculation starts ... exploit_gadget: # call to g speculatively returns here cld a1, -16(csp) # secret data is loaded to a1 through csp cinoffset cs0, csp, a1 # cs0's offset advances by secret a1 cld a1, 0(cs0) # secret is leaked

f_write_sec: # csp is the stack pointer capability # a0 contains secret data # cs0 is a blinded capability referencing [csp-8, csp) csd a0, 0(cs0) # store secret data to the stack through cs0 ccall g # after executing g, speculation starts ... exploit_gadget: # call to g speculatively returns here cld a1, -8(csp) # secret data is loaded to a1 through csp cinoffset cs0, csp, a1 # cs0's offset advances by secret a1 cld a1, 0(cs0) # secret is leaked

The main difference with the previous attack is that in this case, secret data is stored on the stack using a blinded capability, and the compiler enforces that this data is never accessed using the (non-blinded) stack capability during normal execution. However, this property can be broken during speculation, and the attacker can induce a transient load instruction that accesses the secret data on the stack using the stack pointer. The leakage in CSC and BLACKOUT shows that reasoning jointly about capability safety, speculative execution, and information leakage is subtle and deserves rigorous formal treatment. Next, we present an alternative design addressing these issues, formally verified to provide strong security guarantees under speculation.

5

SCHERI

In this section, we introduce a formal execution model to study the security of CHERI-like architectures under speculative execution. To the best of our knowledge, our design is the first to formalize a taint-tracking mechanism where capabilities keep track of the security level of the data they reference. We treat the stack capability as always tainted and mark spills of public values using a distinguished encoding, which we call Untainted Register Record (URR). URRs set the capability tag and use a reserved marker value in the metadata field. When the capability tag is set, capability-sized loads consult the metadata field to distinguish data from capabilities: if the metadata matches the reserved marker, the value is interpreted as untainted data; otherwise, it is treated as a capability. A key challenge of this design is that using a tainted stack capability would cause restored capabilities to be conservatively classified as secret, unnecessarily constraining speculation. This would cause a performance degradation, especially in constant-time software, where memory access patterns and control flow must be public. To address this, we encode taint information explicitly in capability metadata. Concretely, when a capability 𝑐 with address ℓ, metadata ℓmeta , and taint status 𝑡 is stored to memory, the pair of words (ℓ, packTaint (ℓmeta, 𝑡)) is stored instead, where packTaint (·) is a function that embeds the taint bit into reserved metadata bits. Upon loading, the hardware reconstructs both the original metadata and the taint of the destination register. Our design solves the issues of BLACKOUT identified in the previous section. Since the initial stack capability is tainted, it will

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

remain tainted during execution and there will be no public aliasing capability addressing the same region if public data is always stored in URRs. This prevents speculative accesses to secrets on the stack from producing untainted values. Throughout the remainder of this section, we illustrate SCHERI ’s semantics and the rationale behind its security guarantees through a running example: we formalize the attack based on Issue 2a (Section 4.2) in SCHERI , and use it to contrast SCHERI ’s design with alternative approaches. SCHERI is flexible enough to model realistic capability-specific speculation mechanisms, presented in Section 7.

5.1

Preliminaries

Capabilities. In SCHERI , memory is accessed via capabilities. In turn, capabilities are modeled as pairs CapData ∋ 𝑐 F (ℓ, ℓmeta ), composed of an address ℓ ∈ Addr ≜ N, and metadata ℓmeta = (𝑝, 𝑏, 𝑒, 𝑡), where the permission 𝑝 ∈ Perm constrains memory accesses using the pointer. Permissions1 can either be o (no access), ro (read-only), rw (read-write) or rx (read+execute), with the order: o ⊑ ro

ro ⊑ rw

ro ⊑ rx

The addresses 𝑏 and 𝑒 express the range of memory [𝑏, 𝑒) that is allowed to be accessed by the capability; the bit 𝑡 ∈ Taint is a taint indicating whether data in such range is considered secret (if 𝑡 = T) or not (if 𝑡 = U). Following the usual convention, we assume that Taint is a lattice where U ≤ T. We use the shorthand Meta ≜ Perm × Addr2 × Taint for the set of all capability metadata, and we write CapData ≜ Addr × Meta for the set of capability data. Register files and memories. In SCHERI , total register files are modeled as functions 𝑟 : Reg → Val∗ × Taint × Tag that associate each register to: • a sequence of values; • a taint, which is equal to T if the register content is tainted and to U otherwise; • a capability tag, which is equal to C when the register stores a capability, and to V, otherwise. For simplicity, we leave the set Val of values abstract, and we assume Z ∪ B ∪ Addr ∪ Meta ∪ Instr ∪ Taint ∪ Tag ⊆ Val. Following most CHERI implementations, registers can hold multiple words, and their content is considered a capability only if the capability tag is C, i.e., registers holding values in CapData, but with tag set to V will not be considered capabilities. In our semantics, due to out-of-order execution, the content of some registers may be unavailable. To model this, we introduce partial register files with type 𝑟 : Reg → (Val∗ × Taint × Tag)⊥ . In such register files, a register can now also be mapped to ⊥ when its value is not available. Data memories are modeled as maps from addresses ℓ ∈ Addr to values. We assume that each address designates one word, i.e., one memory cell holds a single value 𝑣 ∈ Val. Analogously, pc increments by one word (corresponding to one instruction) per step. As other CHERI-like systems do, SCHERI keeps track of whether a certain memory region stores a capability in a dedicated tag memory, 1 For simplicity, we only model CHERI permissions that are required for our formal

analysis of speculative constant-time. In particular, we do not model sealing and unsealing.

Exprs ∋ 𝑒 Instr ∋ instr

F F

𝑣 | 𝑥 | op(𝑒 ∗ ) 𝑥 ← 𝑒 | load 𝑥, 𝑒, sz | store 𝑥, 𝑒, sz | jmp 𝑒 | beqz 𝑥, ℓ

Expression Instruction

Figure 2: SCasm syntax.

which we model with a mapping 𝑚 t : Addr → Tag. Recall that each capability 𝑐 ∈ CapData = Addr × Meta occupies 2 words, so 𝑚 t [𝑘] stores the tag that indicates whether the words in the region [2𝑘, 2𝑘 + 2) are considered a capability. Equivalently, whether the word at address ℓ is part of a capability is recorded in 𝑚 t [⌊ℓ/2⌋], which we abbreviate as 𝑚 t [ℓ/2]. When dealing with memories, we will often use the notation 𝑚[ℓ ↦→ 𝑣], to indicate the memory 𝑚 with address ℓ updated to 𝑣, and 𝑚[[ℓ, ℓ + 𝑛) ↦→ 𝑣®] as a shorthand for 𝑚[ℓ ↦→ 𝑣 1 ] [ℓ + 1 ↦→ 𝑣 2 ] . . . [ℓ + 𝑛 − 1 ↦→ 𝑣𝑛 ]. Dually, the notation 𝑚[ℓ, ℓ + 𝑛) denotes the tuple (𝑚(ℓ), . . . , 𝑚(ℓ + 𝑛 − 1)). Running Example. The first step to model Issue 2a in SCHERI is to model the stack capability that is exploited by the attack, which we assume is stored in a dedicated register 𝑠:  𝑟 (𝑠) ≜ (42, (rw, 0, 256, T)), U, C .

(1)

Here 𝑟 maps 𝑠 to a 2-word value, which is a capability (due to tag C) with address 42, permissions rw, and bounds [0, 256). Its outer taint is U, because the capability, seen as a pointer, is public. Its inner taint is instead T in our design, to avoid transient leaks of tainted data (as discussed at the start of this section)—as opposed to BLACKOUT, where it would be U.

5.2

Syntax

We model SCHERI using a simple ISA presented in Figure 2, called SCasm, which extends 𝜇asm [18]. An expression is either a value 𝑣, a register name 𝑥, or an application of an operator op ∈ Op to a sequence of expressions. Each operator has an associated arity, and we only consider expressions where the arity constraints of operators are respected. For the moment we leave the set of operators abstract; it will be further specified in Section 5.3. Instructions include assignments 𝑥 ← 𝑒, which update 𝑥 with the value obtained by evaluating 𝑒, and load instructions load 𝑥, 𝑒, sz, which evaluate 𝑒 to a capability and use it to load sz consecutive memory words into 𝑥, represented as a tuple. For demonstration purposes, we only allow sz ∈ {1, 2}—i.e., memory operations over one or two words—but the model can be extended with arbitrary sizes. For simplicity, we assume that neither assignments nor load instructions can modify the value of a dedicated register pc which holds the program counter capability. The instruction store 𝑥, 𝑒, sz evaluates 𝑒 to a capability and uses it to store the value of 𝑥 in memory. Finally, jmp 𝑒 is an indirect jump that evaluates 𝑒 to an executable capability used as the jump target, while beqz 𝑥, ℓ is a conditional branch that adds the offset ℓ to the program counter if the value of 𝑥 is 0, and increments it by 1 otherwise.

Song et al.

Running Example. Attack 2a can be modeled by the following sequence of SCasm instructions: P ≜ store 𝑥, t, 1; jmp 𝑔;

Q ≜ load 𝑦, s, 1; load 𝑦, s ✚ 𝑦, 1.

(2)

The program P models function f_write_sec: it writes a secret contained in register 𝑥 to memory through the tainted capability in register 𝑡, and jumps to the instruction pointed to by register 𝑔 via the instruction jmp 𝑔. The program Q models the leak gadget exploit_gadget. The first load instruction uses the stack capability in register 𝑠 to load one word of data into register y. The second load instruction performs the secret-dependent memory access by loading one word of data from the capability that is obtained by summing the value of 𝑦 to the stack pointer.

5.3

Expression Semantics

To ensure correct taint and resolution propagation, we assume that each operator is equipped with an interpretation op that is: • monotone with respect to taints, i.e., the taint of the output is greater than or equal to that of the inputs; • eager with respect to ⊥, meaning that if any of the inputs is ⊥, the output must also be ⊥. To enforce monotonicity with respect to capability manipulation, we assume that Op = OpArith ⊎ OpCap consists of • a set of arithmetic operators OpArith, modeling ordinary arithmetical operations and always returning values tagged with V; • a set of capability operators OpCap, modeling capability operations, such as shrinking and address modifications. These operators are the only ones that can return capabilities, i.e., values in CapData with tag C. We require that 𝑛-ary arithmetic operators op ∈ OpArith have 𝑛 → (Val∗ ×Taint×{V}) , and interpretation op : (Val∗ ×Taint×Tag)⊥ ⊥ since these operators do not output capabilities, we do not impose further constraints. To ensure that capability operators op ∈ OpCap are well-behaved, we assume that they have the interpretation op : (Val∗ ×Taint×Tag)⊥, (Val∗ ×Taint×Tag)⊥ → (Val∗ ×Taint×Tag)⊥ . We also require: (1) Capability in first position: the output has tag C iff only the first input has tag C. (2) Monotonicity with respect to capabilities: if the output is a capability, then its permissions and bounds are no greater than those of the input capability. (3) Propagation of capability taint: if the input capability has inner taint 𝑡, then the output capability must also have inner taint 𝑡. Expression semantics. Given a partial register file 𝑟 , the denotational semantics of expressions is a function ⟦·⟧𝑟 : Exprs → (Val∗ × Taint × Tag)⊥ that computes the expression value together with its taint and capability tag, or ⊥ if the value of any of the registers used in the expression is not available. Observe that when an expression evaluates to a capability, it carries two distinct taints: the inner taint, which describes the security level of the data referenced by the capability, and the outer taint, which expresses the security level of the capability itself as a value. The denotational semantics of expressions is defined by structural recursion: ⟦𝑣⟧𝑟 ≜ (𝑣, U, V)

⟦𝑥⟧𝑟 ≜ 𝑟 (𝑥)

⟦op(𝑒 )⟧𝑟 ≜ op(⟦𝑒 ⟧𝑟 ).

For convenience, we assume that the set of operators includes an operator ✚ for adding an integer offset to the pointer of a capability, with the following interpretation:   ⊥      ( (ℓ + 𝑣, ℓmeta ), 𝑡 1 ⊔ 𝑡 2 , C)  ⟦𝑒 1 ✚ 𝑒 2 ⟧𝑟 ≜       (0, U, V) 

if ⟦𝑒 1 ⟧𝑟 = ⊥ ∨ ⟦𝑒 2 ⟧𝑟 = ⊥ if ⟦𝑒 1 ⟧𝑟 = ( (ℓ, ℓmeta ), 𝑡 1 , C) ∧ ⟦𝑒 2 ⟧𝑟 = (𝑣, 𝑡 2 , V) otherwise.

Running Example. We continue on Attack 2a, focusing on the capability s ✚ 𝑦 used for the secret-dependent load. Here, 𝑠 has outer taint U (see (1)) while 𝑦, loaded through the stack capability, is tainted (Section 5.5): ⟦𝑦⟧𝑟 = (𝑣, T, V). Hence ⟦s ✚ 𝑦⟧𝑟 = ((42 + 𝑣, (rw, 0, 256, T)), T, C), whose outer taint is the supremum of the taints of 𝑦 and 𝑠, i.e., T ⊔ U = T. SCHERI therefore blocks the secretdependent load under speculation. In BLACKOUT, by contrast, the inner taint of 𝑠 would be U, giving ⟦𝑦⟧𝑟 = (𝑣, U, V) and ⟦s ✚ 𝑦⟧𝑟 = ((42 + 𝑣, (rw, 0, 256, U)), U, C), so the load would not be prevented.

5.4

Architectural Semantics

In this section, we equip SCHERI with an architectural operational semantics, formalizing its behavior and, in particular, the interpretation of memory operations. Configurations are tuples ((𝑚 d, 𝑚 t ), 𝑟 ), composed by a data memory, a tag memory and a register file, and ranged over by the metavariable 𝑆. Following a common approach in modeling side-channel leaks [2, 4, 18, 19], the architectural semantics of SCHERI is defined 𝑜 as a small-step operational semantics, where transitions 𝑆 0 → − 𝑆1 are labeled with observations 𝑜 revealing the information that the program leaks to the adversaries. Observations are defined by the following BNF: Obs ∋ 𝑜 F () | load (𝑐) | store(𝑣, 𝑐) | br (𝑐) | br (𝑏) where 𝑐 ∈ CapData, 𝑏 ∈ B, and 𝑣 ∈ (Val∗ × Taint × Tag)⊥ . The observation () labels transitions that do not leak. The observations load (𝑐) and store(𝑣, 𝑐) are produced by load and store operations from and to capability 𝑐, respectively. In the store(𝑣, 𝑐) observation, 𝑣 is equal to the stored value when the store targets the public region, and to ⊥ otherwise. This ensures that the resulting trace reveals declassified values or values that are already public. The observation br (𝑐) is produced when an indirect jump to 𝑐 executes, and br (𝑏) is produced by direct branches whose guard evaluates to 𝑏. The branch target does not need to be included in the observation because attackers can derive it from their knowledge of the victim program and the value of 𝑏. The 𝑛-step transition relation is defined as follows: 𝑜

𝑆0 → − 𝑆1 𝜖

𝑂

𝑆1 − → 𝑛 𝑆2

𝑜:𝑂

𝑆→ − 0𝑆

𝑆 0 −−→ 𝑛+1 𝑆 2

Two ISA states 𝑆 0 and 𝑆 1 produce the same leakage (written 𝑆 0 ≡ISA 𝑆 1 ) when for every 𝑛 ∈ N and observation 𝑂 ∈ Obs∗ , we have: 𝑂

𝑂

∃𝑆 0′ .𝑆 0 − → 𝑛 𝑆 0′ ⇔ ∃𝑆 1′ .𝑆 1 − → 𝑛 𝑆 1′ . The semantics is defined in Figure 3. Each rule evaluates the program counter in the register file 𝑟 , and uses the resulting capability to fetch the current instruction. In turn, fetching is performed via

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

Architectural Semantics [isa-assign]

readMemInst (𝑚, ⟦pc⟧𝑟 ) = (𝑥 ← 𝑒 )

⟦𝑒⟧𝑟 = (𝑧, 𝑡, 𝑐 )

()

(𝑚, 𝑟 ) −→ 𝑚, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ ⟦𝑒⟧𝑟 ] [isa-jmp]

readMemInst (𝑚, ⟦pc⟧𝑟 ) = jmp 𝑒

𝑥 ≠ pc 

( (ℓ, ℓmeta ), 𝑡, C) = ⟦𝑒⟧𝑟

br (ℓ,ℓmeta )

(𝑚, 𝑟 ) −−−−−−−→ (𝑚, 𝑟 [pc ↦→ ( (ℓ, ℓmeta ), U, C) ] ) [isa-beqz]

readMemInst (𝑚, ⟦pc⟧𝑟 ) = beqz 𝑥, ℓ ′ (𝑣, _, _) = ⟦𝑥⟧𝑟 ( (ℓ, ℓmeta ), 𝑡, C) = (𝑣 = 0) ? ⟦pc ✚ ℓ ′ ⟧𝑟 : ⟦pc ✚ 1⟧𝑟 br (𝑣=0)

(𝑚, 𝑟 ) −−−−−→ (𝑚, 𝑟 [pc ↦→ ( (ℓ, ℓmeta ), 𝑡, C) ] ) [isa-load]

readMemInst (𝑚, ⟦pc⟧𝑟 ) = load 𝑥, 𝑒, sz readMem(𝑚, ⟦𝑒⟧𝑟 , sz) = (𝑣, 𝑡, 𝑐 ) ⟦𝑒⟧𝑟 = ( (ℓ, ℓmeta ), _, _) load (ℓ,ℓmeta )

(𝑚, 𝑟 ) −−−−−−−−→ 𝑚, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐 ) ]



[isa-store]

writeMem(𝑚, ⟦𝑒⟧𝑟 , sz, ⟦𝑥⟧𝑟 ) = 𝑚 ′ readMemInst (𝑚, ⟦pc⟧𝑟 ) = store 𝑥, 𝑒, sz

𝑣 = (𝑡 = U) ? ⟦𝑥⟧𝑟 : ⊥ ⟦𝑒⟧𝑟 = ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡 ) ), _, _)

store (𝑣,(ℓ,(𝑝,𝑏,𝑒,𝑡 ) ) )

(𝑚, 𝑟 ) −−−−−−−−−−−−−−→ 𝑚 ′ , 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ]



Core rules of memory operations [check-cap]

𝑏 ≤𝑙

𝑙 + sz ≤ 𝑒

𝑝′ ⊑ 𝑝

sz = 2 ⇒ ℓ%2 = 0

(𝑝, 𝑏, 𝑒, 𝑡 ) ≠ 𝑣magic

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡 ) ), sz, 𝑝 ′ ) [read-mem-inst]

𝑚 d (ℓ ) ∈ Instr

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, U) ), 1, rx)

readMemInst ( (𝑚 d , 𝑚 t ), ( (ℓ, (𝑝, 𝑏, 𝑒, U) ), U, C) ) = 𝑚 d (ℓ ) [read-mem]

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ro) processRead (𝑚 d [ℓ, ℓ + sz), 𝑡𝑐 , 𝑚 t [ℓ/2], sz) = (𝑣, 𝑡, 𝑐 ) readMem( (𝑚 d , 𝑚 t ), ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C), sz) = (𝑣, 𝑡, 𝑐 ) [write-mem]

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, rw) processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) = (𝑣 ′ , 𝑐 ′ ) 𝑚 ′ = (𝑚 d [ [ℓ, ℓ + sz) ↦→ 𝑣 ′ ], 𝑚 t [ℓ/2 ↦→ 𝑐 ′ ] ) writeMem( (𝑚 d , 𝑚 t ), ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C), sz, (𝑣, 𝑡, 𝑐 ) ) = 𝑚 ′

Figure 3: SCasm architectural semantics and memory operation semantics.

the readMemInst (𝑚, ⟦pc⟧𝑟 ) function, which is formally defined by Rule [read-mem-inst]. In this rule, the premise on the left ensures that the memory location pointed to by the program counter is an instruction, and the premise on the right validates the capability that is used to fetch the next instruction via the predicate checkCap(·, ·, ·), defined by Rule [check-cap]. Essentially, this rule ensures that the capability offset is in bounds, and it has sufficient permissions. When the size of the load is 2, the rule also ensures that the accessed address is aligned. This requirement is necessary for correct handling of capability tags, as 𝑚 t (ℓ/2) keeps track of the tag associated with the data stored in 𝑚 d [ℓ, ℓ + 2) (see Rules [writemem] and [read-mem]). Consequently, the alignment condition enforces consistency of that data and tag. Finally, the last premise ensures that the metadata field of the capability is not a dedicated

value 𝑣 magic , which marks spills of untainted values; we discuss this mechanism in more detail when we describe the semantics of memory operations in Section 5.5. We now turn to the semantics of instructions. Assignments 𝑥 ← 𝑒 are evaluated via Rule [isa-assign], which evaluates expression 𝑒 and updates 𝑥 accordingly. The pc register is updated to point to the next instruction, while its metadata, taint and capability tag are not changed. Rule [isa-jmp] executes indirect jump instructions jmp 𝑒 by updating the program counter with the jump target ⟦𝑒⟧𝑟 , which is leaked via the observation. Conditional branches beqz 𝑥, ℓ ′ are executed with Rule [isa-beqz], which evaluates the branch condition 𝑥 to a value 𝑣, and sums the offset ℓ ′ to the program counter if 𝑣 = 0, and increments the program counter otherwise. Load instructions load 𝑥, 𝑒, sz are evaluated via Rule [isa-load]. The load capability is obtained by evaluating ⟦𝑒⟧𝑟 . The destination register 𝑥 is then updated with the ISA-level value produced by readMem(𝑚, ⟦𝑒⟧𝑟 , sz), together with its associated taint and capability tag. The value of readMem(𝑚, ⟦𝑒⟧𝑟 , sz) is provided by Rule [read-mem], which is also responsible for ensuring that the load capability has sufficient permissions, and for providing the correct ISA-level representation of unspills and loaded capabilities, given by the helper function processRead (·, ·, ·, ·). Leaving the helper function unspecified allows our semantics to represent different memory designs. The one of SCHERI is described in detail in Section 5.5, the one of BLACKOUT in Section 7.2. Store instructions store 𝑥, 𝑒, sz are handled by Rule [isa-store]. The store capability is obtained by evaluating ⟦𝑒⟧𝑟 , and the value is taken from register 𝑥. Memory is updated via writeMem(𝑚, ⟦𝑒⟧𝑟 , sz, ⟦𝑥⟧𝑟 ), whose value is provided by Rule [write-mem]. This rule is responsible for ensuring that the store capability has sufficient permissions to write and for computing the ISA-level representation (𝑣 ′, 𝑐 ′ ) of the value and capability tag to store, given by the helper function processWrite(·, ·, ·). Again, leaving this helper function unspecified allows our semantics to represent different designs. The updated memory is obtained by writing the value 𝑣 ′ and tag 𝑐 ′ at the addresses spanned by the store capability. Note that when the capability’s inner taint is untainted, Rule [isa-store] leaks the stored value to include declassified values in the leakage trace. Running Example. We illustrate SCHERI ’s architectural semantics on (2), assuming 𝑔 points to a benign, skip-like target at ℓ ′ performing 𝑤 ← 𝑤; this is not the Attack 2a behavior, where Q is reached via misspeculation (covered in Section 5.6). The architectural execution of P ≜ store 𝑥, t, 1; jmp 𝑔 is store (⊥, ⟦𝑡 ⟧𝑟 )

(𝑚, 𝑟 ) −−−−−−−−−−→ 𝑚 ′, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ]



br (⟦𝑔⟧𝑟 )   ′ −−−−−−−→ 𝑚 ′, 𝑟 [pc ↦→ ( ℓ ′, ℓmeta , U, C)]  () ′ −→ 𝑚 ′, 𝑟 [pc ↦→ (ℓ ′ + 1, ℓmeta , U, C)] .

The store executes via Rule [isa-store]: since 𝑡 has inner taint T, the leaked observation carries ⊥ rather than the declassified value, ′ while the secret 𝑟 (𝑥) = (𝑣, T, V) is written to 𝑚, giving  𝑚 . The ′ jump (Rule [isa-jmp]) leaks the target ⟦𝑔⟧𝑟 = ( ℓ ′, ℓmeta , U, C) and transfers control to the benign ℓ ′ . Finally, 𝑤 ← 𝑤 fires Rule [isaassign], producing the empty observation () and advancing the program counter.

Song et al.

5.5

Memory Operation Semantics

At a high level, our memory semantics combines three mechanisms: (1) On reads, the capability used for the access determines the taint of the loaded value. Hence, values stored via a capability to untainted data are treated as untainted by any following load, and thereby declassified. (2) 2-word-wide public values with high-order bits equal to zero are represented in memory by Untainted Register Records (URRs), i.e., pairs (𝑣, 𝑣 magic ) tagged as capabilities, where 𝑣 magic is a magic value that does not collide with any valid capability metadata. This allows URRs to be distinguished from genuine capabilities. Unlike BLACKOUT, where the BRR mechanism only applies to register spills, the URR mechanism applies to every untainted value store in SCHERI . To improve memory efficiency, future work could refine this mechanism. (3) When a capability is written to memory, its outer taint is saved as part of its stored metadata and later recovered when the capability is read back. If this encoded taint is U, the outer taint is restored to U regardless of the taint of the loading capability. This mechanism avoids the performance degradation caused by conservatively tainting originally untainted capabilities. The semantics of memory accesses is defined in Figure 4, via two helper functions that convert between the ISA-level representation of values and their in-memory representation. The function processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) takes as arguments the value (𝑣, 𝑡, 𝑐) being stored—i.e., the ISA-level value 𝑣, together with its taint 𝑡 and capability tag 𝑐—the inner taint 𝑡𝑐 of the capability used to perform the store, and the store size sz; it returns the pair (𝑣 ′, 𝑐 ′ ) to be written respectively to 𝑚 d and 𝑚 t . Dually, the function processRead (𝑣, 𝑡𝑐 , 𝑐, sz) takes as arguments the raw value 𝑣 read from 𝑚 d , the inner taint 𝑡𝑐 of the capability used to perform the load, the tag 𝑐 read from 𝑚 t at the corresponding address, and the load size sz; it returns the ISA-level value, taint, and capability tag. For writes, operations of size sz = 1 are handled by Rule [writedata], which tags them as non-capabilities, since capabilities have size 2. Capability writes are handled via Rule [write-cap], which encodes the taint bit into the metadata via the function packTaint (·) before storing it in memory. This way the capability’s taint can be restored at load time (Rule [read-cap]) via function unpackTaint (·). For generality, we do not further specify these two functions: we only assume that unpackTaint (packTaint (·)) is the identity. Finally, Rule [spill-value] ensures that untainted values are spilled to memory using URRs, as previously discussed. The restriction to pairs whose second element is zero ensures no information loss. For reads, Rule [read-data] specifies that data of size 1, or data not tagged as a capability, is forwarded to the ISA level as a noncapability: its value is unchanged, and its taint is set to 𝑡𝑐 , i.e., to the taint of the capability used for loading, rather than to any taint recorded in memory. If the loaded data is tagged as a capability and the access size is 2, then two cases arise, distinguished by the value 𝑣𝑚 of the second memory word: if 𝑣𝑚 = 𝑣 magic , i.e., the loaded data is a URR, Rule [unspill-value] returns the spilled value and marks it as untainted. Otherwise, Rule [read-cap] applies, and the value is reconstructed as a capability, using unpackTaint (·) to recover its metadata and stored taint from 𝑣𝑚 . The two taints are combined via ⊓, so the resulting capability is untainted if the stored

taint is U—meaning that the capability was untainted before being stored—or if the accessing capability is untainted. Running Example. We illustrate memory operations with two stores. First, when P stores the tainted 𝑥, Rule [write-data] applies, storing the value as-is and tagged as a non-capability; when Q later loads it via load 𝑦, s, 1, the tag in 𝑚 t is V, so Rule [read-data] returns (𝑣, T, V)—the observed taint is entirely determined by that of 𝑠. Second, if 𝑥 holds a public 2-word value (𝑣, 0), it is spilled via Rule [spill-value] as a URR: processWrite(((𝑣, 0), U, V), 𝑡, 2) = ((𝑣, 𝑣 magic ), C), i.e., tagged as a capability in 𝑚 t with second word 𝑣 magic , independently of the storing capability’s taint. When the value is read back the tag is C and the second word is 𝑣 magic , so Rule [unspill-value] applies and the value is read back untainted, independently of the loading capability’s taint.

5.6

Hardware Semantics

Reorder buffers. Following a common approach [4, 9, 19], outof-order and speculative execution are modeled by using a reorder buffer (ROB): a partial map 𝜌 : N ⇀ PInstr, expressing the position of a partially evaluated instruction in the buffer. In turn, partially evaluated instructions are described by the following BNF: SpecTag ∋ 𝜏 := 𝜀 | (ℓ, ℓmeta ) | ((ℓ, ℓmeta ), sz, 𝑧) PInstr ∋ 𝜄 F 𝑥 ← 𝑒@𝜏 | 𝑥 ← (𝑣, 𝑡, 𝑐)@𝜏 | load 𝑥, 𝑒, sz@𝜏 | store 𝑥, 𝑒, sz@𝜏 | store (𝑣, 𝑡, 𝑐), (ℓ, ℓmeta ), sz@𝜏 where 𝑒 ∈ Exprs is an expression that has not yet been evaluated, (𝑣, 𝑡, 𝑐) ∈ Val∗ × Taint × Tag represents an evaluated expression for assignments, or a load/store value, and (ℓ, ℓmeta ) ∈ CapData is an evaluated capability for memory access. In order to resolve speculative choices, partially evaluated instructions are tagged with a speculation tag 𝜏 ∈ SpecTag. In turn, the speculation tag can be: • the constant 𝜀, if the partial execution of the corresponding instruction has so far made no unresolved speculative choice; • the pc capability from which the branch instruction was fetched, in the case of control-flow speculation; • a triple ((ℓ, ℓmeta ), sz, 𝑧) ∈ CapData × {1, 2} × N⊥ , which is used to tag assignments resulting from loads, in the presence of speculative store-to-load forwarding. Precisely, (ℓ, ℓmeta ) is the load capability, sz is the load size, and 𝑧 is the index in the ROB of the aliasing store that forwarded the data, or ⊥ if the value is fetched from the memory. Given a ROB 𝜌 and an index 𝑖, the ROB 𝜌 \𝑖 is pointwise identical to 𝜌, but is undefined on 𝑖. Similarly, the ROB 𝜌 |𝑖 is pointwise identical to 𝜌, but is undefined at indices greater than or equal to 𝑖. More formally, their domains are: dom(𝜌 \ 𝑖) ≜ dom(𝜌) \ {𝑖}, dom(𝜌 |𝑖 ) ≜ dom(𝜌) ∩ {0, . . . , 𝑖 − 1}, and, for every 𝑗 in the respective domain: (𝜌 \ 𝑖)( 𝑗) ≜ 𝜌 ( 𝑗),

(𝜌 |𝑖 )( 𝑗) ≜ 𝜌 ( 𝑗).

Buffer application. Instructions in the ROB cannot be evaluated directly under the architectural register file, as it may be stale with respect to updates performed by earlier buffered instructions. Instead, evaluation must use a register file that reflects the updates

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

[write-data]

[spill-value]

[write-cap]

′ , 𝑐 ′ ) = (𝑣 = 0 ∧ 𝑡 = U) ? (𝑣 (𝑣𝑚 packTaint (𝑣 meta, 𝑡) = 𝑣𝑚 𝑚 magic , C) : (𝑣𝑚 , V) sz = 1 ′ ), 𝑐 ′ ) processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) = (𝑣, V) processWrite(((𝑣, 𝑣 meta ), 𝑡, C), 𝑡𝑐 , 2) = ((𝑣, 𝑣𝑚 ), C) processWrite(((𝑣, 𝑣𝑚 ), 𝑡, V), 𝑡𝑐 , 2) = ((𝑣, 𝑣𝑚

[read-data]

[unspill-value]

[read-cap]

𝑣𝑚 ≠ 𝑣 magic unpackTaint (𝑣𝑚 ) = (𝑣 meta, 𝑡) sz = 1 ∨ 𝑐 = V processRead (𝑣, 𝑡𝑐 , 𝑐, sz) = (𝑣, 𝑡𝑐 , V) processRead ((𝑣, 𝑣𝑚 ), 𝑡𝑐 , C, 2) = ((𝑣, 0), U, V) processRead ((𝑣, 𝑣𝑚 ), 𝑡𝑐 , C, 2) = ((𝑣, 𝑣 meta ), 𝑡 ⊓ 𝑡𝑐 , C) 𝑣𝑚 = 𝑣 magic

Figure 4: SCHERI ’s memory operation semantics. of the preceding portion of the buffer. This is captured by the function apl(𝜌, 𝑟 ), defined as follows: ( 𝑟 dom(𝜌) = ∅ apl(𝜌, 𝑟 ) ≜ apl(𝜌 \ 𝑖, aplinst (𝜌 (𝑖), 𝑟 )) 𝑖 = min dom(𝜌), where: aplinst (𝑥 ← (𝑣, 𝑡, 𝑐)@𝜏, 𝑟 ) ≜ 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)] aplinst (𝑥 ← 𝑒@𝜏, 𝑟 ) ≜ 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ ⊥] aplinst (load 𝑥, 𝑒, sz@𝜏, 𝑟 ) ≜ 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ ⊥] aplinst (store 𝑥, 𝑒, sz@𝜏, 𝑟 ) ≜ 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ]. This function applies the updates of 𝜌 to 𝑟 by processing instructions in program order, starting from the smallest index in the buffer. It also updates the program counter for each instruction. Note that the result of a buffer application in general is a partial register file. Using partial register files is also convenient to model how our design prevents the leak of tainted data during speculative execution. Following [9], this is done by evaluating instructions using exclusively untainted values during speculative execution. We model this via an untainted projection, ·| U , defined as follows: ( 𝑤 if 𝑤 = (𝑣, U, 𝑐) for some 𝑣 ∈ Val∗, 𝑐 ∈ Tag, 𝑤 |U ≜ ⊥ otherwise. The untainted projection is extended pointwise to register files by stipulating: 𝑟 | U (𝑥) ≜ 𝑟 (𝑥)| U . The untainted projection of a register file can be used to sanitize speculatively tainted values, as follows: ( 𝑟 if dom(𝜌) = ∅ aplsan(𝜌, 𝑟 ) = apl(𝜌, 𝑟 )| U otherwise. During out-of-order execution, when the value of an expression at the 𝑖-th entry of a ROB 𝜌 might be leaked through side channels, that expression is evaluated with the register file aplsan(𝜌 |𝑖 , 𝑟 ). The case dom(𝜌 |𝑖 ) = ∅ captures instructions at the very beginning of the ROB: such instructions are non-speculative, evaluated directly against the architectural register file 𝑟 . All other instructions have at least one predecessor in the buffer, and are evaluated against apl(𝜌 |𝑖 , 𝑟 )| U , i.e., against the sanitized register file obtained by applying their predecessors’ updates and discarding any value that is tainted. This prevents leaking tainted values during speculation. For instance, take a ROB 𝜌 where index 0 holds 𝑥 ← (secret, T, 𝑐), and index 1 holds a subsequent instruction 𝜄 that references and leaks 𝑥. When 𝜄 is evaluated, it is evaluated in aplsan(𝜌 | 1, 𝑟 ) = aplsan({0 ↦→ 𝑥 ← (secret, T, 𝑐)}, 𝑟 ). Since 𝜌 | 1 is not empty, this reduces to apl(𝜌 | 1, 𝑟 )| U : first, apl propagates the update to 𝑥; then,

·| U maps the tainted value of 𝑥 to ⊥. Therefore, when 𝜄 is evaluated, 𝑥 is unavailable, preventing its transient leak. Only once the first instruction retires, can 𝜄 access 𝑥’s value. Microarchitectural contexts. Having discussed how our hardware model supports speculative and out-of-order execution, we turn to the speculative semantics itself. Following [9, 19], we model the attacker by including in our configurations a microarchitectural context, which abstracts both the observations available to the adversary and its influence on execution. Definition 5.1 (Microarchitectural context). A microarchitectural context is a structure (Ctx, update(·, ·), predPc(·), next (·)) with nonempty carrier Ctx, where: (1) update(·, ·) : Ctx × 𝐴 → Ctx returns a microarchitectural context that is obtained by updating the input context with information leaked during evaluation, taken from a set 𝐴 which we do not further specify; (2) predPc(·) : Ctx → Addr takes the current microarchitectural context and returns a jump target (address) prediction; (3) next (·) : Ctx → Dir takes the current context and returns a directive from Dir F fetch | exec 𝑖 | commit, where 𝑖 ∈ N. The semantics interacts with the microarchitectural context through the operations update(·, ·), predPc(·) and next (·). First, all observable microarchitectural effects are reported via update(·, ·), ensuring that any information that may leak is reflected in the context. Second, speculative choices are generated using predPc(·), which is invoked to predict the target of jump instructions. Finally, the execution order is governed by next (·), whose output determines whether the processor fetches a new instruction (directive fetch), executes the 𝑖-th instruction in the ROB (directive exec 𝑖), or commits the oldest instruction (commit). In the following, we use a fixed arbitrary microarchitectural context. This ensures that all of our results hold independently of the specific adversary. Hardware semantics. Hardware configurations are tuples of the form (𝑚, 𝑟, 𝜌, 𝜇), where 𝑚 is a pair of a data and a tag memory, 𝑟 is a register file, 𝜌 is a ROB, and 𝜇 is a microarchitectural context. Our hardware-level semantics of SCHERI is a small-step operational semantics. An excerpt of the transition rules is in Figure 5. The complete set of rules is deferred to Appendix C. The hardware semantics proceeds at two levels. Externally, the behavior is described by Rule [step]: at each step, the microarchitectural context is updated with the untainted projection of the ROB, ensuring that scheduling and prediction decisions depend only on untainted information. The context then produces a directive via next (·), which determines the next directive 𝑑, governing an internal transition.

Song et al.

[step]

[fetch-branch-predict-pc]

𝜇 ′ = update (𝜇, 𝜌 | U ) 𝑑 = next (𝜇 ′ ) (𝑚, 𝑟, 𝜌, 𝜇 ′ ) − → (𝑚 ′ , 𝑟 ′ , 𝜌 ′ , 𝜇 ′′ ) 𝑑

(𝑚, 𝑟, 𝜌, 𝜇 ) → − (𝑚 ′ , 𝑟 ′ , 𝜌 ′ , 𝜇 ′′ )

( (ℓ, ℓmeta ), 𝑡, 𝑐 ) = ⟦pc⟧aplsan(𝜌,𝑟 ) instr ∈ {beqz 𝑥, ℓ ′′ , jmp 𝑒 } instr = readMemInst (𝑚, ( (ℓ, ℓmeta ), 𝑡, 𝑐 ) ) 𝑖 = sup dom(𝜌 ) 𝜌 ′ = 𝜌 [𝑖 + 1 ↦→ pc ← ( (predPc (𝜇 ), ℓmeta ), 𝑡, 𝑐 )@(ℓ, ℓmeta ) ]   (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ 𝑚, 𝑟, 𝜌 ′ , update (𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) ) fetch

[fetch-other]

instr = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌,𝑟 ) ) instr ∉ {beqz 𝑥, ℓ ′′ , jmp 𝑒 } 𝜌 ′ = 𝜌 [sup dom(𝜌 ) + 1 ↦→ instr@𝜀 ]   (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ 𝑚, 𝑟, 𝜌 ′ , update (𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) )

[execute-assign]

𝜌 (𝑖 ) = 𝑥 ← 𝑒@𝜀

𝑥 ≠ pc

(𝑣, 𝑡, 𝑐 ) = ⟦𝑒⟧apl (𝜌 |𝑖 ,𝑟 )

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ 𝑥 ← (𝑣, 𝑡, 𝑐 )@𝜀 ], 𝜇 ) exec 𝑖

fetch

[execute-jmp-hazard]

[execute-jmp-ok]

𝜌 (𝑖 ) = pc ← ℓ ′ @(ℓ, ℓmeta ) 𝜌 ′ = 𝜌 [𝑖 ↦→ pc ← ℓ ′ @𝜀 ] ℓ ′ = (ℓ0 , _, C) jmp 𝑒 = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) (ℓ0 , _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 )

𝜌 (𝑖 ) = pc ← (ℓ1 , _, _)@(ℓ, ℓmeta ) jmp 𝑒 = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) ℓ1 ≠ ℓ0 (ℓ0 , _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) 𝜌 ′ = 𝜌 |𝑖+1 [𝑖 ↦→ pc ← (ℓ0 , U, C)@𝜀 ]

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 ′ , update (𝜇, ℓ0 ) )

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 ′ , update (𝜇, ℓ0 ) )

exec 𝑖

exec 𝑖

[execute-load-fwd]

𝜌 (𝑖 ) = load 𝑥, 𝑒, sz@𝜀 𝑥 ≠ pc [ℓ, ℓ + sz) = [ℓ ′ , ℓ ′ + sz ′ ) ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ro)  ′  ′ ′ ′ ( 𝑗 = max{ 𝑗 < 𝑖 : 𝜌 ( 𝑗 ) = store _, ℓ ′ , ℓmeta , sz @𝜏 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ ′ + sz ′ ) ≠ ∅ } ) 𝑡 0 ⊑ 𝑡𝑐 𝜌 ( 𝑗 ) = store (𝑣0 , 𝑡 0 , 𝑐 0 ), ℓ ′ , ℓmeta , sz @𝜏 (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ (𝑥 ← processRead (𝑣0 , 𝑡𝑐 , 𝑐 0 , sz)@( (ℓ, ℓmeta ), sz, 𝑗 ) ) ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) ) exec 𝑖

[execute-store-ok]

𝜌 (𝑖 ) = store 𝑥, 𝑒, sz@𝜀 ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, rw) (𝑣, 𝑡, 𝑐 ) = ⟦𝑥⟧apl (𝜌 |𝑖 ,𝑟 )  ′ ′ (∀ 𝑗 > 𝑖, 𝜌 ( 𝑗 ) = 𝑥 ← 𝑣@( ℓ ′ , ℓmeta , sz , 𝑘 ) ∧ 𝑘 ≠ 𝑖 ⇒ (𝑘 > 𝑖 ∨ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ + sz ′ ) = ∅ ) ) (𝑣 ′ , 𝑐 ′ ) = processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ (store (𝑣 ′ , 𝑡, 𝑐 ′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz)@𝜀 ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) ) exec 𝑖

[commit-assign]

𝑖 = min dom(𝜌 )

𝑥 = pc ⇒ 𝜏 = 𝜀

𝜌 (𝑖 ) = 𝑥 ← 𝑣@𝜏

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−−−→ 𝑚, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ 𝑣 ], 𝜌 \ 𝑖, 𝜇



commit

[commit-store]

𝑖 = min dom(𝜌 )

𝜌 (𝑖 ) = store (𝑣, 𝑡, 𝑐 ), (ℓ, ℓmeta ), sz@𝜀

( (𝑚 d , 𝑚 t ), 𝑟, 𝜌, 𝜇 ) −−−−−→ commit

𝑚 ′ = (𝑚 d [ [ℓ, ℓ + sz) ↦→ 𝑣 ], 𝑚 t [ℓ/2 ↦→ 𝑐 ] ) 

𝑚 ′ , 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ], 𝜌 \ 𝑖, update (𝜇, (ℓ, ℓmeta ) )

Figure 5: Hardware semantics of SCHERI , excerpt. If the directive is fetch, and the program counter points to a branch instruction (instr ∈ {beqz 𝑥, ℓ ′′, jmp 𝑒}), Rule [fetchbranch-predict-pc] applies. It uses predPc(·) to obtain a predicted target, and appends a speculative update of the program counter to the ROB by setting: 𝜌 ′ = 𝜌 [𝑖 + 1 ↦→ pc ← ((predPc(𝜇), ℓmeta ), 𝑡, 𝑐)@(ℓ, ℓmeta )]. Here, the branch instruction is tagged by its pc value, indicating the speculation on its target, which is resolved at execute time. Finally, the rule leaks the current program counter to the microarchitectural context 𝜇 via update(𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) ). If the program counter points to a non-control-flow instruction, Rule [fetch-other] applies. It appends the fetched instruction to the ROB with an empty speculation tag, and leaks the current program counter to the context. When the issued directive is exec 𝑖, different rules can be applied, depending on the 𝑖-th entry of the reorder buffer. Rule [executeassign] evaluates assignment instructions 𝑥 ← 𝑒 in the buffer by computing the value of 𝑒 under the partially applied register file apl(𝜌 |𝑖 , 𝑟 ), and storing it in the buffer. Rule [execute-jmp-ok] resolves indirect jumps. The target 𝑒 is evaluated under the sanitized register file aplsan(𝜌 |𝑖 , 𝑟 ). This ensures that the value of the jump target is not stale and that the leaked jump target depends only on untainted values. If the target resolves as correct, the speculative

annotation is cleared and the resulting target is reported to the context. If instead the resolved target ℓ0 does not match the predicted one, Rule [execute-jmp-hazard] applies: the corresponding entry of the ROB is updated with the correct target ℓ0 (retagged as U), the speculation tag is cleared, and all younger instructions are dropped from the buffer. Conditional branches are executed analogously to indirect jumps by Rules [execute-beqz-ok] and [execute-beqz-hazard], whose details are in Appendix C. Rule [execute-load-fwd] evaluates load instructions. It first checks that the target register is not the program counter, as this can only be updated via beqz and jmp instructions, then it evaluates the load address to a capability ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) . Using aplsan ensures that the address only depends on untainted values in speculative execution. The rule then checks whether the resulting capability has sufficient permissions for performing the load in the checkCap((ℓ, ℓmeta ), sz, ro) premise. Then, if an aliasing evaluated store exists at index 𝑗 < 𝑖, the stored value is forwarded by setting the 𝑖-th entry in the ROB to an assignment of that value to 𝑥. Note that this forwarding is a form of SSB speculation, as the ROB may contain a not-yet-evaluated aliasing store at some index 𝑗 ′ with 𝑗 < 𝑗 ′ < 𝑖. Thus, the speculation tag of the assignment carries the load capability (ℓ, ℓmeta ), the load size sz, and the index 𝑗 in the ROB of the store instruction that forwarded its value.

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

Rule [execute-store-ok] executes store instructions and resolves store-to-load dependency speculation. As for the load rule, the store address is first evaluated under the sanitized register file to ensure that no tainted values are leaked during speculative execution. The resulting capability is then checked for sufficient permissions via checkCap(·, sz, rw). The value to be stored is obtained by evaluating 𝑥 under the partially applied register file apl(𝜌 |𝑖 , 𝑟 ). The value to be written is then computed via processWrite(·), the corresponding entry in the ROB is updated, and the accessed address is leaked to the microarchitectural context. Crucially, the rule additionally enforces consistency with prior store-to-load forwarding. Recall that Rule [execute-load-fwd] may speculatively forward a value from an older store to a younger load. To ensure that such forwarding is sound, the store rule checks that no younger load has speculated on a conflicting store. This is captured by the side condition on indices 𝑗 > 𝑖: if a younger load has been resolved by forwarding from a store at position 𝑘 ≠ 𝑖, then either that store is younger than the current one (𝑘 > 𝑖), or the accessed memory regions do not overlap (otherwise, the pipeline is flushed). Rule [commit-assign] commits assignments by updating the architectural register file with the computed value and removing the instruction from the buffer. This rule explicitly checks that if the target register is pc, the speculation tag has to be 𝜀, to ensure that speculative jumps have been validated at execution time. Rule [commit-store] commits store instructions by updating the data and tag memories, leaking the accessed address to the context a second time and removing the instruction from the buffer. Running example. In Figure 6, we illustrate the execution in SCHERI of the vulnerable program (2), modeling Attack 2a. First, the store store 𝑥, 𝑡, 1 is fetched into entry 1 by Rule [fetch-other] and executed by Rule [execute-store-ok], resolving its store capability to (𝑧, 𝑧 meta ) and value to (𝑣, T, V) without yet updating memory. Then, the jump jmp 𝑔 is fetched by Rule [fetch-branch-predictpc]. Recall that we assume 𝑔 points to a benign, skip-like target at ℓ ′ performing 𝑤 ← 𝑤. Under the correct prediction (top), the predicted target is predPc(𝜇) = ℓ ′ , so entry 2 is speculatively set to pc ← ((ℓ ′, ℓmeta ), U, C), tagged with the branch’s pc value (ℓ, ℓmeta ). Next, the benign 𝑤 ← 𝑤 is fetched into entry 3 by Rule [fetchother]. Finally, exec 2 resolves the jump (Rule [execute-jmp-ok]): the evaluated target matches the prediction, so entry 2’s speculation tag is cleared, and the run reproduces the architectural execution. Under the attacker’s misprediction predPc(𝜇) = Q (bottom), the store and jump execute as above except that entry 2 records the mispredicted target, steering speculation into the gadget Q. The first load load 𝑦, 𝑠, 1 is fetched into entry 3 by Rule [fetchother] and executed by Rule [execute-load-fwd]. The load capability is resolved to (𝑧, 𝑧 meta ), aliasing the store capability at entry 1, so the store value is forwarded to the load, yielding 𝑦 ← (𝑣, T, V). The instruction load 𝑦, 𝑠 ✚ 𝑦, 1 is fetched into entry 4 by Rule [fetch-other], but exec 4 is disabled: its address evaluates to ⟦𝑠 ✚ 𝑦⟧aplsan(𝜌 |4 ,𝑟 ) = ⊥ because aplsan drops the tainted 𝑦.

6

Correctness and Security of SCHERI

In this section, we establish the two main properties of SCHERI , its correctness and security. Correctness ensures that every evaluation that is performed at the hardware level can be simulated at the ISA

level. Security states that SCHERI is relative speculative constant time. Intuitively, this property guarantees that if a program does not leak secret data at the ISA level, it also does not leak secret data under the hardware semantics of SCHERI . For brevity’s sake, proofs and formal definitions are deferred to Appendix D. Correctness of SCHERI. The following result establishes that SCHERI is correct with respect to its ISA-level semantics. Theorem 6.1 (Functional Correctness). Let 𝑆 = (𝑚, 𝑟 ) and 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇) be initial ISA and HW configurations, respectively. For every 𝑛 ∈ N, and configuration 𝐶 ′ = (𝑚 ′, 𝑟 ′, 𝜌 ′, 𝜇 ′ ) such that 𝑂

𝐶→ − 𝑛 𝐶 ′, there exist 𝑡 ∈ N and 𝑂 ∈ Obs∗ such that 𝑆 − → 𝑡 (𝑚 ′, 𝑟 ′ ). The proof of Theorem 6.1 is in Appendix D.2, and is carried out by induction on the length of the execution trace. Security of SCHERI. Our main result shows that the hardware semantics preserves ISA-level security guarantees in the presence of speculation. Formally, we require that hardware configurations that contain the same public data and produce the same ISA-level leakage also produce the same leakage at the hardware level. We first define two ISA states (𝑚 0, 𝑟 0 ) and (𝑚 1, 𝑟 1 ) containing the same public values, written (𝑚 0, 𝑟 0 ) ≃pub (𝑚 1, 𝑟 1 ): untainted values coincide in the two states. Next, we define how two hardware configurations can leak the same data. Since the hardware-level semantics updates the microarchitectural contexts at each transition with leaked values, it suffices to require the identity of the target microarchitectural contexts at each transition step. More formally, we say that two hardware states 𝐶 0 = (𝑚 0, 𝑟 0, 𝜌 0, 𝜇), 𝐶 1 = (𝑚 1, 𝑟 1, 𝜌 1, 𝜇) produce the same leakage traces (written 𝐶 0 ≡HW 𝐶 1 ) whenever for every 𝑛 ∈ N and adversarial context 𝜇 ′ , we have ∃𝑚 0′ , 𝑟 0′ , 𝜌 0′ .𝐶 0 → − 𝑛 (𝑚 0′ , 𝑟 0′ , 𝜌 0′ , 𝜇 ′ ) ⇔ ∃𝑚 1′ , 𝑟 1′ , 𝜌 1′ .𝐶 1 → − 𝑛 (𝑚 1′ , 𝑟 1′ , 𝜌 1′ , 𝜇 ′ ).

Note that this condition imposes the identity of the target microarchitectural states that are updated at each step with microarchitectural leakage. With these definitions at hand, security of SCHERI can now be stated as follows: Theorem 6.2 (Relative Speculative Constant Time). Let 𝑆 0 = (𝑚 0, 𝑟 0 )  𝑆 0′ = 𝑚 0′ , 𝑟 0′

𝐶 0 = (𝑚 0, 𝑟 0, 𝜌 init, 𝜇)  𝐶 0′ = 𝑚 0′ , 𝑟 0′ , 𝜌 init, 𝜇

be initial ISA and HW configurations where dom(𝜌 init ) = ∅. If 𝑆 0 ≃pub 𝑆 0′ and 𝑆 0 ≡ISA 𝑆 0′ , then we have 𝐶 0 ≡HW 𝐶 0′ . The proof is carried out by induction on the length of the execution trace and relies on key invariants. The main one is the preservation of public equivalence at the hardware level, established via the functional correctness of SCHERI (Theorem 6.1), which helps relate leakage at the ISA level and the HW level. Another is the absence of memory regions referenced simultaneously by tainted and untainted capabilities, which prevents using untainted capabilities to access and leak tainted data.

7

Comparison with Other Designs

In this section, we compare SCHERI with different designs. To this end, we start in Section 7.1 by modeling CHERI-Toooba’s design issues outlined in [14–16], and showing that the resulting system

Song et al.

(∅, 𝜇 0 ) −−−→ ( {1 ↦→ store 𝑥, 𝑡, 1@𝜀 }, 𝜇 1 ) −−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀 }, 𝜇2 ) exec 1

fetch

−−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (ℓ ′ , ℓmeta ), U, C)@(ℓ, ℓmeta ) }, 𝜇 3 ) fetch

−−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (ℓ ′ , ℓmeta ), U, C)@(ℓ, ℓmeta ), 3 ↦→ 𝑤 ← 𝑤@𝜀 }, 𝜇4 ) fetch

−−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (ℓ ′ , ℓmeta ), U, C)@𝜀, 3 ↦→ 𝑤 ← 𝑤@𝜀 }, 𝜇5 ) exec 2

( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (Q, ℓmeta ), U, C)@(ℓ, ℓmeta ) }, 𝜈3 ) −−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (Q, ℓmeta ), U, C)@(ℓ, ℓmeta ), 3 ↦→ load 𝑦, 𝑠, 1@𝜀 }, 𝜈4 ) fetch

−−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (Q, ℓmeta ), U, C)@(ℓ, ℓmeta ), 3 ↦→ 𝑦 ← (𝑣, T, V)@( (𝑧, 𝑧 meta ), 1, 1) }, 𝜈5 ) exec 3

−−−→ ( {1 ↦→ store (𝑣, T, V), (𝑧, 𝑧 meta ), 1@𝜀, 2 ↦→ pc ← ( (Q, ℓmeta ), U, C)@(ℓ, ℓmeta ), 3 ↦→ 𝑦 ← (𝑣, T, V)@( (𝑧, 𝑧 meta ), 1, 1), 4 ↦→ load 𝑦, 𝑠 ✚ 𝑦, 1@𝜀 }, 𝜈6 ) fetch

Figure 6: Two hardware executions of (2) from the same initial configuration: execution under a correct prediction (top), which matches the architectural execution of Section 5.4; execution under the attacker’s misprediction reaches Q (bottom). does not satisfy the CSC contract. We then model BLACKOUT [12] formally in Section 7.2, and show that the resulting system can leak secrets through microarchitectural side channels.

7.1

one, thus violating the CSC contract. To model these vulnerabilities, we extend the language of expressions with an additional operator buildCap(·, ·) corresponding to CHERI’s cbuildcap instruction. It has the following interpretation:

Modeling CHERI-Toooba’s CSC Violations

Fuchs et al. [14–16] describe several violations of the CSC contract in CHERI-Toooba. These vulnerabilities fall into two categories: violations caused by control-flow speculation crossing the program counter capability boundaries, and violations arising from capability manipulation, where a modified capability can be used transiently before the corresponding monotonicity checks are enforced. In both cases, transient execution may perform memory accesses that violate capability bounds or permissions. In the following, we demonstrate that our formal semantics can capture these classes of vulnerabilities with only minimal modifications. Speculative control-flow violation. This vulnerability [15] occurs when the branch target predictor predicts a jump target that is not allowed by the current program counter capability, violating the CSC contract. Such a vulnerability can be modeled by modifying Rule [fetch-branch-predict-pc] as follows: [fetch-branch-predict-pc-cap]

((ℓ, ℓmeta ), 𝑡, 𝑐) = ⟦pc⟧aplsan(𝜌,𝑟 ) instr ∈ {beqz 𝑥, ℓ ′′, jmp 𝑒} instr = readMemInst (𝑚, ((ℓ, ℓmeta ), 𝑡, 𝑐)) 𝑖 = sup dom(𝜌) 𝜌 ′ = 𝜌 [𝑖 + 1 ↦→ pc ← predPcCap(𝜇) @(ℓ, ℓmeta )]   (𝑚, 𝑟, 𝜌, 𝜇) −−−→ 𝑚, 𝑟, 𝜌 ′, update(𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) ) fetch

The main difference between this rule and Rule [fetch-branchpredict-pc] lies in the predPcCap(𝜇) function highlighted in red: while in our system, the function predicts the offset in the pc register, here it can predict the whole pc capability, potentially violating the architectural boundaries of the pc, and thereby the CSC contract (and also our security property). Speculative capability manipulation. This class of vulnerability [14, 16] is caused by race conditions in the hardware between capability usage and validation that can occur with operations modifying capability offsets or, more generally, constructing capabilities from existing ones. Specifically, the constructed capability can be used during transient execution before the hardware ensures that such a capability has smaller range and permissions than an existing

  ⊥     (𝑣2 , 𝑡 1 ⊔ 𝑡 2 , C)     ⟦buildCap (𝑒 1 , 𝑒 2 )⟧𝑟 ≜         (0, U, V) 

if ⟦𝑒 1 ⟧𝑟 = ⊥ ∨ ⟦𝑒 2 ⟧𝑟 = ⊥ if ⟦𝑒 1 ⟧𝑟 = (𝑣1 , 𝑡 1 , C) ∧ ⟦𝑒 2 ⟧𝑟 = (𝑣2 , 𝑡 2 , V) ∧𝑣2 ⊑ 𝑣1 otherwise.

The primitive satisfies the axioms for capability operators in Section 5.3. First, the interpretation above satisfies Axiom (1) by construction. Second, Axioms (2) and (3) are satisfied given the following definition of 𝑣 2 ⊑ 𝑣 1 , for 𝑣 1, 𝑣 2 ∈ CapData (Definition D.1): it requires monotonicity of permissions and bounds, as well as equality of the inner taints of 𝑣 1 and 𝑣 2 . The primitive is sufficiently expressive to capture arbitrary monotone capability manipulations: 𝑒 1 is a valid capability derived in accordance with the capability monotonicity requirement, which we call the guarantor capability; 𝑒 2 expresses the arithmetic operations to build the binary representation of the new capability. To model the race conditions that occur in CHERI-Toooba with capability manipulation, we extend the HW semantics with a rule for executing an instruction that builds a capability. [execute-forge]

𝜌 (𝑖) = 𝑥 ← buildCap(𝑒 1, 𝑒 2 )@𝜀 𝑥 ≠ pc (𝑣 1, 𝑡 1, C) = ⟦𝑒 1 ⟧apl (𝜌 |𝑖 ,𝑟 ) (𝑣 2, 𝑡 2, V) = ⟦𝑒 2 ⟧apl (𝜌 |𝑖 ,𝑟 ) (𝑚, 𝑟, 𝜌, 𝜇) −−−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ 𝑥 ← (𝑣 2, 𝑡 1 ⊔ 𝑡 2, C)@(𝑣 1, 𝑣 2 )], 𝜇) exec 𝑖

In this rule, the value of 𝑒 2 is promoted to a capability without enforcing the monotonicity constraints between 𝑒 1 and 𝑒 2 ; these are instead deferred to commit time, as specified by the next rule: [commit-forge]

𝑖 = min dom(𝜌 )

𝑥 ≠ pc

𝜌 (𝑖 ) = 𝑥 ← (𝑣2 , 𝑡, C)@(𝑣1 , 𝑣2 )

𝑣2 ⊑ 𝑣1 

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−−−→ 𝑚, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ (𝑣2 , 𝑡, C) ], 𝜌 \ 𝑖, 𝜇 commit

Here, the rule uses the speculation tag (𝑣 1, 𝑣 2 )—corresponding to the values of the guarantor and the new capability—to check whether the new capability satisfies the monotonicity constraints.

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

By leveraging Rule [execute-forge], an attacker can issue a buildCap() instruction during speculation, and use the resulting capability to perform memory accesses that violate the CSC contract. Such problematic memory accesses would also violate our relative speculative constant time property, since the resulting capability may allow accesses to memory locations containing secrets. These violations also illustrate how much control an attacker has over which secrets are leaked. Because CHERI-Toooba admits transient accesses that violate capability bounds, an attacker can steer speculation towards secrets that are never accessed architecturally, exactly as in classical Spectre attacks on non-capability architectures. A CSC-compliant design reduces this control: the attacker can only dereference capabilities reachable from the committed architectural state and leak the memory they authorize. SCHERI pushes this even further, by preventing leakage of any secret data.

Eager detection of data-obliviousness violations [blackout-write-data]

sz = 1

𝑡 ⊑ 𝑡𝑐

processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) = (𝑣, V) [blackout-read-mem]

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ro) processRead (𝑚 d [ℓ, ℓ + sz), 𝑡𝑐 , 𝑚 t [ℓ/2], sz) = (𝑣, 𝑡, 𝑐 ) readMem( (𝑚 d , 𝑚 t ), ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), U , C), sz) = (𝑣, 𝑡, 𝑐 ) [blackout-write-mem]

checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, rw) processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) = (𝑣 ′ , 𝑐 ′ ) 𝑚 ′ = (𝑚 d [ [ℓ, ℓ + sz) ↦→ 𝑣 ′ ], 𝑚 t [ℓ/2 ↦→ 𝑐 ′ ] ) writeMem( (𝑚 d , 𝑚 t ), ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), U , C), sz, (𝑣, 𝑡, 𝑐 ) ) = 𝑚 ′ Treatment of BRRs [blackout-unspill-value]

7.2

Modeling BLACKOUT

To model BLACKOUT [12], there are two largely orthogonal differences to consider. First, BLACKOUT attempts to eagerly detect violations of the data-oblivious programming model and will generate faults when tainted values are stored to memory through non-blinded capabilities (except in the case of spilling tainted registers into BRRs, see below). In our formal model, this corresponds to an alternative version of the rules in Figure 7. The additional condition 𝑡 ⊑ 𝑡𝑐 in rule [blackout-write-data] (highlighted) will make execution get stuck when a tainted value is written to memory through a non-blinded capability, modeling the exception that BLACKOUT would generate. Additionally, the extra conditions in rules [blackout-read-mem] and [blackout-write-mem] (highlighted) will make execution get stuck when memory is accessed through a tainted capability. This eager detection of data-obliviousness violations is a consequence of the different threat model targeted by both systems. As explained in Section 4.2, BLACKOUT strives to enforce speculative constant time under five assumptions on user code. The check 𝑡 ⊑ 𝑡𝑐 in Figure 7 enforces the first (I1) of these assumptions, while other assumptions only hold for code generated by a trusted compiler. Our threat model is different: for assembly code that is constant time (i.e., data oblivious) in the architectural semantics, we ensure that speculation will not create new leaks and preserves constant-time behavior. As such, we make no attempt to enforce architectural security of the code and restrict ourselves to correctly propagating but not enforcing taint in the architectural semantics. The second important change with respect to BLACKOUT is that we adopt a treatment of the stack that is dual to BLACKOUT’s. We treat the stack capability as always tainted, and mark spills of public values using a distinguished encoding, URR, that sets the capability tag and uses a reserved marker value in the metadata field. Figure 7 shows modified rules that revert to BLACKOUT’s original treatment of BRRs. A consequence of BLACKOUT’s design is that secrets are stored in memory that is accessible through nonblinded capabilities, only guarded by the BRR’s capability tag and magic value. To preserve the tainted status of such values, it is crucial to enforce atomicity of BRR writes (the magic value or tag must not be overwritten without erasing the secret value) and to ensure all reads (including partial reads) of the BRR secret value

𝑣𝑚 = 𝑣magic

𝑐 =C

processRead ( (𝑣, 𝑣𝑚 ), 𝑡𝑐 , 𝑐, 2) = ( (𝑣, 𝑣𝑚 ), T , V) [blackout-spill-value] ′ , 𝑐 ′ ) = (𝑡 = T ) ? (𝑣 (𝑣𝑚 magic , C) : (𝑣𝑚 , 𝑐 ) ′ ), 𝑐 ′ ) processWrite ( ( (𝑣, 𝑣𝑚 ), 𝑡, 𝑐 ), 𝑡𝑐 , 2) = ( (𝑣, 𝑣𝑚

Figure 7: Rules modeling eager detection of dataobliviousness violations and BRRs in BLACKOUT.

taint the result. BLACKOUT does not enforce these two properties in hardware, but instead relies on invariants of software. Section 4 shows that constructing software that satisfies these invariants is highly non-trivial, especially because the invariants must hold in transient execution as well as architecturally. By reversing the treatment of BRRs, atomicity of URR writes becomes less important: if the magic value or the tag is overwritten, then an untainted value will be treated as tainted, which only risks degrading performance. Similarly, if a URR value is read using an instruction (e.g., a partial read) that does not correctly take into account the URR, this will also only result in unnecessarily marking the value that has been read as tainted. Moreover, URRs are unforgeable, just like CHERI’s capabilities. A URR is created only when the hardware sets the capability tag and the magic value while spilling a public value. Since software cannot set capability tags itself (a property inherited from CHERI), writing the magic value alone leaves the tag clear and yields no URR. Conversely, writing a secret over an existing URR clears its tag and thereby destroys it. Hence, SCHERI guarantees that URRs hold public values, and achieves sound taint tracking without any assumption on software, preventing BLACKOUT’s Issue 1. BLACKOUT narrows the attacker’s control on speculative memory accesses compared to CSC-compliant systems, but less than SCHERI . In BLACKOUT, only secrets that are reachable through a non-blinded capability can leak. However, BLACKOUT keeps the stack capability non-blinded, while still allowing programs to create secret stack variables, as long as these variables are accessed only through blinded capabilities during non-speculative execution. As a result, an attacker who controls the stack capability’s offset can speculatively reach a leak gadget (such as the one in

Song et al.

Issue 2a) and leak arbitrary stack secrets. Such leakage is impossible in SCHERI , since it avoids any overlap between blinded and non-blinded capabilities, preventing BLACKOUT’s Issue 2.

8

Related Work

Since CSC [16] and BLACKOUT [12] are discussed extensively throughout the paper, we focus this section on other work. Formal models of transient execution. A large body of prior work develops formal semantics for speculative processors to detect or prove absence of transient leaks. These cover variants [3, 23] such as Spectre-PHT, Spectre-BTB, Spectre-STL, and have enabled tools for leak detection, symbolic analysis, and formal noninterference proofs [2, 4, 5, 8, 10, 18]. Our semantics builds on this line of work, but extends it with capability state, capability provenance, and capability-specific authorization checks that are central in CHERI-like systems. A foundational direction [19] formalizes security guarantees as contracts between hardware and software. Several prior works characterize the attacker-visible behavior of speculative processors and show how software guarantees must be matched to hardware behavior to obtain end-to-end confidentiality. This contract-based view is closely aligned with our approach. Relative security. Prior works [6, 11, 17] introduce general security notions to model speculative execution vulnerabilities, which relate execution traces produced with and without speculation to determine whether speculation introduces additional leakage. We compare our security property (Theorem 6.2) against the state-ofthe-art notion in [11]. First, our threat model is similar to theirs: both include control-flow and memory-access addresses in the attacker’s observation. We additionally leak the value written by a store whose capability has untainted inner taint, i.e., when storing to public memory regions. We adopt a simpler setup, not modeling secret input introduced during execution or interactive attacker actions. Second, we require an additional assumption of public equivalence on initial states, which states that all secrets in the initial state are already properly tracked by taint bits on registers and capabilities. Third, the security property of [11], instantiated to our setting, reads: if the attacker observes a difference between a pair of HW traces, then there must exist a pair of ISA traces that carry the same secrets as the HW traces (respectively) and are also attacker-distinguishable. Theorem 6.2 is a stronger, less general version of this statement: rather than asserting the existence of such ISA traces, we construct them by executing the ISA semantics from the same initial ISA state as the corresponding HW trace. Provably secure speculation for constant time. Several works study how hardware support can maintain the classic constant-time programming model during speculative execution. ProSpeCT [9] develops a processor model with secrecy tracking in the pipeline and proves that constant-time software remains secure under speculative and out-of-order execution across a range of predictors and speculation policies. Importantly, it assumes a fixed partitioning of memory into secret and public regions, which forces the stack to reside entirely in secret or public memory, with complementary efficiency-security trade-offs. SCHERI instead integrates secrecy into CHERI’s capability system: capabilities keep track of the data’s secrecy level, which is propagated through capability derivation,

and preserved across spills and restores. This design choice enables secret and public objects to coexist within the same stack, but requires a new design of capability metadata, stack management (URR), and information-flow issues that do not arise in ProSpeCT. More broadly, taint-based and delayed-transmitter processor designs demonstrate that dynamic secrecy tracking or speculative shadow structures can preserve performance while blocking Spectre-style leaks [7, 9, 25, 30, 32]. Our work shares this goal, but addresses the additional challenges introduced by capabilities, capability-derived pointers, and capability-mediated memory accesses. Relative to this line of work, our approach is closer to BLACKOUT, but BLACKOUT does not provide security proofs. To the best of our knowledge, our work is the first one to provide provably secure speculation for constant-time in CHERI. Machine-level enforcement after compilation. A related line of work observes that source-level security guarantees may be invalidated during compilation, register allocation, and stack layout. Recent work such as SecSep [27] addresses this problem for speculative side-channel defenses by rewriting compiled assembly code to separate secret and public data after compilation, thereby accounting for register spills, stack slots, and other low-level effects that are difficult to control at the source level. SecSep improves how ProSpeCT [9] handles secrets in memory. This perspective closely aligns with our emphasis on machine-level reasoning: in capability systems, speculative leaks may similarly arise from transient interactions with spilled values, stale stack contents, or lowered memory accesses that are invisible in higher-level models. Our work differs in targeting CHERI-like capability machines and providing formal secure-speculation guarantees for capability-aware hardware semantics rather than compiler rewriting alone.

9

Conclusion

Architectural isolation in CHERI does not guarantee confidentiality under speculation. We showed that secure speculation for capability machines requires reasoning jointly about authorization and information flow, and presented SCHERI with formal guarantees for preserving constant-time behavior under speculation. Our work provides a foundation for future capability systems that should remain secure not only architecturally, but also microarchitecturally.

Acknowledgments This research is partially funded by the Air Force Office of Scientific Research (AFOSR) under grant FA9550-22-1-0511, the Internal Funds KU Leuven, the Cybersecurity Research Program Flanders, the Research Foundation – Flanders (FWO) under grant number G081322N, and a European Research Council (ERC) Starting Grant (UniversalContracts; 101040088) funded by the European Union. Views and opinions expressed are those of the authors only and do not necessarily reflect those of the European Union or the European Research Council.

References [1] ARM. 2020. ARM Architecture Reference Manual Supplement Morello for A-profile Architecture. Technical Report. ARM.

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

[2] Gilles Barthe, Sunjay Cauligi, Benjamin Grégoire, Adrien Koutsos, Kevin Liao, Tiago Oliveira, Swarn Priya, Tamara Rezk, and Peter Schwabe. 2021. HighAssurance Cryptography in the Spectre Era. In IEEE S&P. doi:10.1109/SP40001. 2021.00046 [3] Claudio Canella, Jo Van Bulck, Michael Schwarz, Moritz Lipp, Benjamin von Berg, Philipp Ortner, Frank Piessens, Dmitry Evtyushkin, and Daniel Gruss. 2019. A Systematic Evaluation of Transient Execution Attacks and Defenses. In USENIX Security Symposium. [4] Sunjay Cauligi, Craig Disselkoen, Klaus v. Gleissenthall, Dean Tullsen, Deian Stefan, Tamara Rezk, and Gilles Barthe. 2020. Constant-time foundations for the new spectre era. In Proceedings of the 41st ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI 2020). doi:10.1145/ 3385412.3385970 [5] Sunjay Cauligi, Craig Disselkoen, Daniel Moghimi, Gilles Barthe, and Deian Stefan. 2022. SoK: Practical Foundations for Software Spectre Defenses. In IEEE S&P. doi:10.1109/SP46214.2022.9833707 [6] Kevin Cheang, Cameron Rasmussen, Sanjit Seshia, and Pramod Subramanyan. 2019. A formal approach to secure speculation. In Computer Security Foundations Symposium (CSF). [7] Rutvik Choudhary, Jiyong Yu, Christopher W. Fletcher, and Adam Morrison. 2021. Speculative Privacy Tracking (SPT): Leaking Information From Speculative Execution Without Compromising Privacy. In 54th Annual IEEE/ACM International Symposium on Microarchitecture. [8] Lesly-Ann Daniel, Sébastien Bardin, and Tamara Rezk. 2021. Hunting the Haunter - Efficient Relational Symbolic Execution for Spectre with Haunted RelSE. In NDSS. https://www.ndss-symposium.org/ndss-paper/hunting-the-haunterefficient-relational-symbolic-execution-for-spectre-with-haunted-relse/ [9] Lesly-Ann Daniel, Marton Bognar, Job Noorman, Sébastien Bardin, Tamara Rezk, and Frank Piessens. 2023. ProSpeCT: Provably Secure Speculation for the Constant-Time Policy. In USENIX Security Symposium. https://www.usenix.org/ conference/usenixsecurity23/presentation/daniel [10] Hernán Ponce de León and Johannes Kinder. 2022. Cats vs. Spectre: An Axiomatic Approach to Modeling Speculative Execution Attacks. In IEEE S&P. [11] Brijesh Dongol, Matt Griffin, Andrei Popescu, and Jamie Wright. 2024. Relative security: Formally modeling and (dis) proving resilience against semantic optimization vulnerabilities. In Computer Security Foundations Symposium (CSF). [12] Hossam ElAtali, Merve Gülmez, Thomas Nyman, and N. Asokan. 2025. BLACKOUT: Data-Oblivious Computation with Blinded Capabilities. In ACM CCS. doi:10.1145/3719027.3765169 [13] Hossam ElAtali, Lachlan J. Gunn, Hans Liljestrand, and N. Asokan. 2024. BliMe: Verifiably Secure Outsourced Computation with Hardware-Enforced Taint Tracking. In NDSS. [14] Franz Fuchs. 2024. Toward transient-execution attack mitigations on CHERI. Ph. D. Dissertation. Apollo - University of Cambridge Repository. doi:10.17863/CAM. 118739 [15] Franz Anton Fuchs. 2021. Analysis of Transient-Execution Attacks on the outof-order CHERI-RISC-V Microprocessor Toooba. Master’s thesis. KTH, School of Electrical Engineering and Computer Science (EECS). [16] Franz A. Fuchs, Jonathan Woodruff, Peter Rugg, Alexandre Joannou, Jessica Clarke, John Baldwin, Brooks Davis, Peter G. Neumann, Robert N. M. Watson, and Simon W. Moore. 2024. Safe Speculation for Cheri. In IEEE International Conference on Computer Design, ICCD. [17] Roberto Guanciale, Musard Balliu, and Mads Dam. 2020. Inspectre: Breaking and fixing microarchitectural vulnerabilities by formal analysis. In ACM CCS. [18] Marco Guarnieri, Boris Köpf, José F Morales, Jan Reineke, and Andrés Sánchez. 2020. Spectector: Principled detection of speculative information flows. In IEEE S&P. [19] Marco Guarnieri, Boris Köpf, Jan Reineke, and Pepe Vila. 2021. Hardwaresoftware contracts for secure speculation. In IEEE S&P. [20] Paul Kocher, Jann Horn, Anders Fogh, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz, and Yuval Yarom. 2019. Spectre Attacks: Exploiting Speculative Execution. In IEEE S&P. doi:10.1109/SP.2019.00002 [21] Esmaeil Mohammadian Koruyeh, Khaled N Khasawneh, Chengyu Song, and Nael Abu-Ghazaleh. 2018. Spectre returns! speculation attacks using the return stack buffer. In 12th USENIX Workshop on Offensive Technologies (WOOT 18). [22] Moritz Lipp, Michael Schwarz, Daniel Gruss, Thomas Prescher, Werner Haas, Jann Horn, Stefan Mangard, Paul Kocher, Daniel Genkin, Yuval Yarom, Mike Hamburg, and Raoul Strackx. 2020. Meltdown: reading kernel memory from user space. Commun. ACM 63, 6 (2020). [23] Hany Ragab, Enrico Barberis, Herbert Bos, and Cristiano Giuffrida. 2021. Rage Against the Machine Clear: A Systematic Analysis of Machine Clears and Their Implications for Transient Execution Attacks. In USENIX Security Symposium. [24] RISC-V International. 2026. RISC-V Specification for CHERI Extensions. https: //github.com/riscv/riscv-cheri. GitHub repository, accessed 27 April 2026. [25] Michael Schwarz, Moritz Lipp, Claudio Canella, Robert Schilling, Florian Kargl, and Daniel Gruss. 2020. ConTExT: A Generic Approach for Mitigating Spectre. In NDSS.

[26] Shixin Song, Davide Davoli, Elias Storme, Marton Bognar, Dominique Devriese, Frank Piessens, and Tamara Rezk. 2026. SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI. In ACM CCS. doi:10.1145/3830454. 3846718 [27] Shixin Song, Tingzhen Dong, Kosi Nwabueze, Julian Zanders, Andres Erbsen, Adam Chlipala, and Mengjia Yan. 2025. Securing Cryptographic Software via Typed Assembly Language. In ACM CCS. [28] Thomas Van Strydonck, Job Noorman, Jennifer Jackson, Leonardo Alves Dias, Robin Vanderstraeten, David F. Oswald, Frank Piessens, and Dominique Devriese. 2023. CHERI-TrEE: Flexible enclaves on capability machines. In IEEE EuroS&P. [29] Robert N. M. Watson, Jonathan Woodruff, Peter G. Neumann, Simon W. Moore, Jonathan Anderson, David Chisnall, Nirav H. Dave, Brooks Davis, Khilan Gudka, Ben Laurie, Steven J. Murdoch, Robert M. Norton, Michael Roe, Stacey D. Son, and Munraj Vadera. 2015. CHERI: A Hybrid Capability-System Architecture for Scalable Software Compartmentalization. In IEEE S&P. [30] Ofir Weisse, Ian Neal, Kevin Loughlin, Thomas F. Wenisch, and Baris Kasikci. 2019. NDA: Preventing Speculative Execution Attacks at Their Source. In MICRO. [31] Jiyong Yu, Lucas Hsiung, Mohamad El Hajj, and Christopher W. Fletcher. 2019. Data Oblivious ISA Extensions for Side Channel-Resistant and High Performance Computing. In NDSS. [32] Jiyong Yu, Mengjia Yan, Artem Khyzha, Adam Morrison, Josep Torrellas, and Christopher W. Fletcher. 2020. Speculative Taint Tracking (STT): A Comprehensive Protection for Speculatively Accessed Data. IEEE Micro 40, 3 (2020).

A

Open Science

The code of our exploits for the BLACKOUT prototype is released at https://doi.org/10.5281/zenodo.22755930. We additionally reported issues in the hardware,2 allowing BRR writes through unblinded capabilities, and in the compiler,3 which does not set blindedness properly for struct fields.

B

Ethical Considerations

We responsibly disclosed all identified issues to the authors of BLACKOUT prior to submission. As it is a research prototype, no users are directly affected by this research. We believe that our work improves the understanding and security of speculative execution in capability systems and contributes to the design of more secure capability-based processors. These considerations motivated our decision to submit this work.

C

Full hardware semantics

The full set of rules of SCHERI ’s hardware semantics is in Figure 8. Many of the rules were already discussed in Section 5.6, so we only comment on those that were not already described. Rule [fetch-other] evaluates instructions involving the fetch directive when the program counter does not point to a jump instruction. The target configuration extends the input buffer by adding a new entry corresponding to the instruction instr = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌,𝑟 ) ). Note that the target microarchitectural context is updated with the leakage ⟦pc⟧aplsan(𝜌,𝑟 ) , revealing to the attacker the current value of the program counter. Rule [execute-jmp-hazard] is very similar to Rule [executejmp-ok]: the target 𝑒 is evaluated using register file aplsan(𝜌 |𝑖 , 𝑟 ), and the result is compared with the speculation tag, recording the jump target in the speculation tag. This rule applies when the two do not match, and the reorder buffer of the target configuration is set to 𝜌 ′ = 𝜌 |𝑖+1 [𝑖 ↦→ pc ← (ℓ0, U, 𝑐)@𝜀], where the pc is updated 2 https://github.com/blindedcapabilities/blinded-toooba/issues/1 3 https://github.com/blindedcapabilities/blinded-cheri-llvm/issues/1

Song et al.

Step rule [step]

𝜇 ′ = update (𝜇, 𝜌 | U )

𝑑 = next (𝜇 ′ )

(𝑚, 𝑟, 𝜌, 𝜇 ′ ) − → (𝑚 ′ , 𝑟 ′ , 𝜌 ′ , 𝜇 ′′ ) 𝑑

(𝑚, 𝑟, 𝜌, 𝜇 ) → − (𝑚 ′ , 𝑟 ′ , 𝜌 ′ , 𝜇 ′′ ) Rules for fetch steps [fetch-branch-predict-pc]

[fetch-other]

( (ℓ, ℓmeta ), 𝑡, 𝑐 ) = ⟦pc⟧aplsan(𝜌,𝑟 ) instr ∈ {beqz 𝑥, ℓ ′′ , jmp 𝑒 } instr = readMemInst (𝑚, ( (ℓ, ℓmeta ), 𝑡, 𝑐 ) ) 𝑖 = sup dom(𝜌 ) 𝜌 ′ = 𝜌 [𝑖 + 1 ↦→ pc ← ( (predPc (𝜇 ), ℓmeta ), 𝑡, 𝑐 )@(ℓ, ℓmeta ) ]   (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ 𝑚, 𝑟, 𝜌 ′ , update (𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) )

instr = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌,𝑟 ) ) instr ∉ {beqz 𝑥, ℓ ′′ , jmp 𝑒 } 𝜌 ′ = 𝜌 [sup dom(𝜌 ) + 1 ↦→ instr@𝜀 ]   (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ 𝑚, 𝑟, 𝜌 ′ , update (𝜇, ⟦pc⟧aplsan(𝜌,𝑟 ) )

fetch

fetch

Rules for assignments [execute-assign]

𝜌 (𝑖 ) = 𝑥 ← 𝑒@𝜀

𝑥 ≠ pc

(𝑣, 𝑡, 𝑐 ) = ⟦𝑒⟧apl (𝜌 |𝑖 ,𝑟 )

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ 𝑥 ← (𝑣, 𝑡, 𝑐 )@𝜀 ], 𝜇 ) exec 𝑖

[execute-jmp-hazard]

[execute-jmp-ok]

𝜌 (𝑖 ) = pc ← ℓ ′ @(ℓ, ℓmeta ) 𝜌 ′ = 𝜌 [𝑖 ↦→ pc ← ℓ ′ @𝜀 ] ℓ ′ = (ℓ0 , _, C) jmp 𝑒 = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) (ℓ0 , _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 )

𝜌 (𝑖 ) = pc ← (ℓ1 , _, _)@(ℓ, ℓmeta ) jmp 𝑒 = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) ℓ1 ≠ ℓ0 (ℓ0 , _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) 𝜌 ′ = 𝜌 |𝑖+1 [𝑖 ↦→ pc ← (ℓ0 , U, C)@𝜀 ]

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 ′ , update (𝜇, ℓ0 ) )

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 ′ , update (𝜇, ℓ0 ) )

exec 𝑖

exec 𝑖

[execute-beqz-ok]

[execute-beqz-hazard]

beqz 𝑥, ℓ ′′ = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) 𝜌 (𝑖 ) = pc ← ℓ ′ @(ℓ, ℓmeta ) z = (𝑣 = 0) ? ℓ ′′ : 1 ′ (𝑣, 𝑡, 𝑐 ) = ⟦𝑥⟧aplsan(𝜌 |𝑖 ,𝑟 ) ℓ = ⟦pc ✚ z⟧apl (𝜌 |𝑖 ,𝑟 )

beqz 𝑥, ℓ ′′ = readMemInst (𝑚, ⟦pc⟧aplsan(𝜌 |𝑖 ,𝑟 ) ) 𝜌 (𝑖 ) = pc ← ℓ ′ @(ℓ, ℓmeta ) z = (𝑣 = 0) ? ℓ ′′ : 1 ′ (𝑣, 𝑡, 𝑐 ) = ⟦𝑥⟧aplsan(𝜌 |𝑖 ,𝑟 ) ℓ ≠ ℓ0 = ⟦pc ✚ z⟧apl (𝜌 |𝑖 ,𝑟 )

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ pc ← ℓ ′ @𝜀 ], update (𝜇, (𝑣 = 0) ) ) (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 |𝑖+1 [𝑖 ↦→ pc ← ℓ0 @𝜀 ], update (𝜇, (𝑣 = 0) ) ) exec 𝑖

exec 𝑖

Rules for executing loads and stores [execute-load-mem]

𝜌 (𝑖 ) = load 𝑥, 𝑒, sz@𝜀 𝑥 ≠ pc ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ro)  ′ ′ (∀ 𝑗 < 𝑖, 𝜌 ( 𝑗 ) = store 𝑥 ′ , ℓ ′ , ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ ′ + sz ′ ) = ∅ ) (𝑣, 𝑡, 𝑐 ) = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz) ( (𝑚 d , 𝑚 t ), 𝑟, 𝜌, 𝜇 ) −−−→ ( (𝑚 d , 𝑚 t ), 𝑟, 𝜌 [𝑖 ↦→ (𝑥 ← (𝑣, 𝑡, 𝑐 )@( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ⊥) ) ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) ) exec 𝑖

[execute-load-fwd]

𝜌 (𝑖 ) = load 𝑥, 𝑒, sz@𝜀 𝑥 ≠ pc [ℓ, ℓ + sz) = [ℓ ′ , ℓ ′ + sz ′ ) ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, ro)  ′  ′ ′ ′ ( 𝑗 = max{ 𝑗 < 𝑖 : 𝜌 ( 𝑗 ) = store _, ℓ ′ , ℓmeta , sz @𝜏 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ ′ + sz ′ ) ≠ ∅ } ) 𝑡 0 ⊑ 𝑡𝑐 𝜌 ( 𝑗 ) = store (𝑣0 , 𝑡 0 , 𝑐 0 ), ℓ ′ , ℓmeta , sz @𝜏 (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ (𝑥 ← processRead (𝑣0 , 𝑡𝑐 , 𝑐 0 , sz)@( (ℓ, ℓmeta ), sz, 𝑗 ) ) ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) ) exec 𝑖

[execute-store-ok]

𝜌 (𝑖 ) = store 𝑥, 𝑒, sz@𝜀 ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, rw) (𝑣, 𝑡, 𝑐 ) = ⟦𝑥⟧apl (𝜌 |𝑖 ,𝑟 )  ′ ′ (∀ 𝑗 > 𝑖, 𝜌 ( 𝑗 ) = 𝑥 ← 𝑣@( ℓ ′ , ℓmeta , sz , 𝑘 ) ∧ 𝑘 ≠ 𝑖 ⇒ (𝑘 > 𝑖 ∨ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ + sz ′ ) = ∅ ) ) (𝑣 ′ , 𝑐 ′ ) = processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ (𝑚, 𝑟, 𝜌 [𝑖 ↦→ (store (𝑣 ′ , 𝑡, 𝑐 ′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz)@𝜀 ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) ) exec 𝑖

[execute-store-hazard]

𝜌 (𝑖 ) = store 𝑥, 𝑒, sz@𝜀 ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) checkCap ( (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz, rw) (𝑣, 𝑡, 𝑐 ) = ⟦𝑥⟧apl (𝜌 |𝑖 ,𝑟 )  ′ ′ , sz , 𝑘 ) ∧ (𝑘 < 𝑖 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′ , ℓ ′ + sz ′ ) ≠ ∅ ) } ) (𝑣 ′ , 𝑐 ′ ) = processWrite ( (𝑣, 𝑡, 𝑐 ), 𝑡𝑐 , sz) ( 𝑗 = min{ 𝑗 > 𝑖 : 𝜌 ( 𝑗 ) = 𝑥 ← 𝑣@( ℓ ′ , ℓmeta  (𝑚, 𝑟, 𝜌, 𝜇 ) −−−→ 𝑚, 𝑟, 𝜌 | 𝑗 [𝑖 ↦→ (store (𝑣 ′ , 𝑡, 𝑐 ′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ), sz)@𝜀 ], update (𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) ) ) exec 𝑖

Rules for commit steps [commit-assign]

𝑖 = min dom(𝜌 )

𝑥 = pc ⇒ 𝜏 = 𝜀

𝜌 (𝑖 ) = 𝑥 ← 𝑣@𝜏

(𝑚, 𝑟, 𝜌, 𝜇 ) −−−−−→ 𝑚, 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ 𝑣 ], 𝜌 \ 𝑖, 𝜇



commit

[commit-store]

𝑖 = min dom(𝜌 )

𝜌 (𝑖 ) = store (𝑣, 𝑡, 𝑐 ), (ℓ, ℓmeta ), sz@𝜀

( (𝑚 d , 𝑚 t ), 𝑟, 𝜌, 𝜇 ) −−−−−→ commit

𝑚 ′ = (𝑚 d [ [ℓ, ℓ + sz) ↦→ 𝑣 ], 𝑚 t [ℓ/2 ↦→ 𝑐 ] ) 

𝑚 ′ , 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ], 𝜌 \ 𝑖, update (𝜇, (ℓ, ℓmeta ) )

Figure 8: SCHERI hardware semantics

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

with the correct jump target and all the following instructions are dropped. The rules for evaluating conditional branches are similar to those for direct jumps. Rules [execute-beqz-ok] and [execute-beqzhazard] evaluate the branch condition (𝑣, 𝑡, 𝑐) = ⟦𝑥⟧aplsan(𝜌 |𝑖 ,𝑟 ) using the sanitized buffer to prevent transient leakages of secret data. The rules compute the jump offset in 𝑧 and the actual jump target ⟦pc ✚ z⟧apl (𝜌 |𝑖 ,𝑟 ) . If the jump target matches the one stored in the buffer, Rule [execute-beqz-ok] applies and the speculation tag is cleared. If the two targets do not match, Rule [execute-beqzhazard] applies, which sets the correct jump target in the ROB, and drops the stale entries. Both rules leak the branch condition to the microarchitectural context via the update(·) function. Rule [execute-load-mem] is similar to Rule [execute-loadfwd] and describes how load instructions are executed when no aliasing store is found in the ROB. The rule checks that the target register is not the program counter to ensure that such register can only be updated via beqz and jmp instructions. Then the load address ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) is evaluated and capability permissions are checked in the checkCap((ℓ, ℓmeta ), sz, ro) premise. The rule applies when for every 𝑗 < 𝑖, the corresponding entry in the ROB is not an aliasing store. If this is the case, the value is fetched from central memory in premise: (𝑣, 𝑡, 𝑐) = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz) and the speculation tag is updated to record the loaded region, in order to resolve aliasing, when executing store instructions. Rule [execute-store-hazard] applies if store-to-load aliasing is detected when resolving a store instruction. It computes the store capability using the sanitized register file aplsan(𝜌 |𝑖 , 𝑟 ), to ensure that no tainted value can leak to the microarchitectural context, and checks its permission via checkCap(·, sz, rw). The value to be stored is obtained by evaluating 𝑥 under the partially applied register file apl(𝜌 |𝑖 , 𝑟 ), and is recorded in the store buffer. The rule then computes the index of the first aliasing load instruction:  ′ ′ 𝑗 = min{ 𝑗 > 𝑖 : 𝜌 ( 𝑗) = 𝑥 ← 𝑣@( ℓ ′, ℓmeta , sz , 𝑘)∧ (𝑘 < 𝑖 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) ≠ ∅)}, and drops all the entries in the reorder buffer at positions equal to or greater than 𝑗 by setting the buffer of the target configuration to: 𝜌 | 𝑗 [𝑖 ↦→ (store (𝑣 ′, 𝑡, 𝑐 ′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz)@𝜀]. Finally, Rule [commit-store] commits store instructions by updating the memory with the value taken from the ROB.

D

Proofs

This section is devoted to proving the main results presented in this paper, namely functional correctness and security of SCHERI . We start in Appendix D.1, by providing lemmas about architectural (ISA-level) invariants that are helpful to reason about correctness and security of SCHERI ’s HW semantics. First, SCHERI ’s HW semantics does not handle self-modifying code, so we assume that in the initial configuration, there is no overlapping between memory with rx and rw permission. This property will be maintained as an invariant by monotonicity of capability operations. Another key invariant of SCHERI necessary

to its security property is that there is no overlapping between tainted and untainted memory. This is needed since we should not allow using untainted capabilities to access tainted data and leak it, or using tainted capabilities to write secret data to the untainted region while losing protection on it. These two invariants are formalized in Definition D.4 and proved in Lemma D.1. Second, we formally define public equivalence of architectural states (Definition D.6) and prove that it is maintained by the ISA semantics (Lemma D.8). We then turn our attention to proving functional correctness of SCHERI ’s HW semantics, i.e., that SCHERI is correct with respect to its ISA-level semantics. The proof is in Appendix D.2, and is carried out by induction on the number of HW steps being executed. The key insight is to define inductive invariants to constrain well-formedness of reorder buffer 𝜌 (Definition D.7). Specifically, it constrains the expected correlation between each in-flight instruction’s execution outcome and its instruction type/operands. Lemma D.17 proves that the invariant holds for every step. In Appendix D.3, we prove SCHERI satisfies relative speculative constant time. The proof is also done by induction on the number of HW steps being executed. The key inductive invariant is defined via public equivalence of HW configurations (Definition D.10), which requires public equivalence on both architectural states (𝑚, 𝑟 ) and microarchitectural contexts (𝜌, 𝜇). Specifically, proving this requires us to apply Theorem 6.1 (functional correctness of SCHERI ) to correlate ISA leakage traces and HW observation traces.

D.1

ISA-level Proof

D.1.1

ISA Maintains Well-formedness.

Definition D.1 (Capability Monotonicity). We define capability monotonicity as follows: (𝑝, 𝑏, 𝑒, 𝑡) ⊑ (𝑝 ′, 𝑏 ′, 𝑒 ′, 𝑡 ′ ) ≜ 𝑝 ⊑ 𝑝 ′ ∧ [𝑏, 𝑒) ⊆ [𝑏 ′, 𝑒 ′ ) ∧ 𝑡 = 𝑡 ′ . We also employ the notation (ℓ, (𝑝, 𝑏, 𝑒, 𝑡)) ⊑ (ℓ ′, (𝑝 ′, 𝑏 ′, 𝑒 ′, 𝑡 ′ )) to represent the case where (𝑝, 𝑏, 𝑒, 𝑡) ⊑ (𝑝 ′, 𝑏 ′, 𝑒 ′, 𝑡 ′ ). Definition D.2 (Reachable Capabilities). Given a register file 𝑟 and a memory 𝑚 = (𝑚 d, 𝑚 t ), the set of reachable capabilities is defined as follows: [base]

𝑟 (𝑥) = ((ℓ, ℓmeta ), 𝑡, C) (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑥) [ind]

(_, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚, 𝑥) [ℓ ′, ℓ ′ + 2) ⊆ [𝑏, 𝑒) 𝑚 d [ℓ ′, ℓ ′ + 2] = (ℓ, ℓ𝑚 ) 𝑚 t [ℓ ′ /2] = C ℓ ′ %2 = 0 unpackTaint (ℓ𝑚 ) = (ℓmeta, _) ℓ𝑚 ≠ 𝑣 magic (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑥) [shrink]

 ′ ℓ ′, ℓmeta ∈ rc(𝑟, 𝑚, 𝑥)

′ (ℓ, ℓmeta ) ⊑ ℓ ′, ℓmeta



(ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑥) Ð

We also employ the notation rc(𝑟, 𝑚) for

𝑥 ∈Reg rc(𝑟, 𝑚, 𝑥).

Definition D.3 (Reachable Memory). Given a set of capabilities 𝐶, and a taint 𝑡, we define the set of reachable addresses with taint 𝑡

Song et al.

as:

Ø

tAddr (𝐶, 𝑡) ≜

′ By definition of tAddr (·), and ℓmeta ⊑ ℓmeta , we conclude tAddr (𝑟 [𝑥 ↦→ 𝑐], 𝑚, 𝑡) ⊆ tAddr (𝑟, 𝑚, 𝑡). By reiterating this reasoning to also cover the pc update, we prove

[𝑏, 𝑒)

(ℓ,(𝑝,𝑏,𝑒,𝑡 ) ) ∈𝐶

we then define rAddr (𝐶) as the whole set of reachable addresses,

tAddr (𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ ⟦𝑒⟧𝑟 ], 𝑚, 𝑡) ⊆ tAddr (𝑟, 𝑚, 𝑡).

rAddr (𝐶) ≜ tAddr (𝐶, T) ∪ tAddr (𝐶, U). For convenience, we use tAddr (𝑟, 𝑚, 𝑡) as a shorthand for tAddr (rc(𝑟, 𝑚), 𝑡), and rAddr (𝑟, 𝑚) as a shorthand for rAddr (rc(𝑟, 𝑚)). By slight abuse of notation, given a permission 𝑝 ∈ Perm, we also define Ø pAddr (𝐶, 𝑝) ≜ [𝑏, 𝑒) (ℓ,(𝑝,𝑏,𝑒,𝑡 ) ) ∈𝐶

and the notation pAddr (𝑟, 𝑚, 𝑝) in the expected way. Definition D.4 (Well-formed ISA State). ISA state (𝑚, 𝑟 ) is wellformed (denoted as wf (𝑚, 𝑟 )) if its tainted memory and untainted memory do not overlap, i.e., tAddr (𝑟, 𝑚, T) ∩ tAddr (𝑟, 𝑚, U) = ∅ and its writable and executable memory do not overlap, i.e., pAddr (𝑟, 𝑚, rw) ∩ pAddr (𝑟, 𝑚, rx) = ∅.

This establishes the first invariant. The second invariant follows analogously. • If the transition was established by applying Rule [isaload], then we have 𝑚 ′ = 𝑚 and 𝑟 ′ = 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ (𝑤, 𝑡, 𝑐)], where 𝑐 is either C or V. In the former case, it is easy to observe that rc(𝑟 [𝑥 ↦→ (𝑤, 𝑡, 𝑐)], 𝑚) ⊆ rc(𝑟, 𝑚). By definition of tAddr (·), we conclude

tAddr (𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ (𝑤, 𝑡, 𝑐)], 𝑚, 𝑡 ′ ) ⊆ tAddr (𝑟, 𝑚, 𝑡 ′ ), and similarly for pAddr (·). On the other hand, if 𝑐 = C, by introspection of the rules for memory operation, we observe that the capability value must have been loaded via rules [read-mem] and [read-cap]. More precisely, said 𝑚 = (𝑚 d, 𝑚 t ), and ⟦𝑒⟧𝑟 = ((ℓ, ℓmeta ), 𝑡𝑐 , C) we have: (𝑤, 𝑡, C) = readMem((𝑚 d, 𝑚 t ), ⟦𝑒⟧𝑟 , 2)

Lemma D.1 (ISA Model Maintains ISA Well-formedness). For all 𝑚, 𝑟 , 𝑚 ′ , 𝑟 ′ , 𝑜, if 𝑜

(𝑚, 𝑟 ) → − (𝑚 , 𝑟 ),

wf (𝑚, 𝑟 ) then

wf (𝑚 ′, 𝑟 ′ ).

Proof. This lemma is a direct consequence of Lemma D.2, which establishes monotonicity of tAddr (·) and pAddr (·). □ Lemma D.2. For all 𝑚, 𝑟 , 𝑚 ′ , 𝑟 ′ , 𝑜 such that wf (𝑚, 𝑟 ) 𝑜

(𝑚, 𝑟 ) → − (𝑚 ′, 𝑟 ′ ), there should be ∀𝑡,

tAddr (𝑟 ′, 𝑚 ′, 𝑡) ⊆ tAddr (𝑟, 𝑚, 𝑡)

∀𝑝,

pAddr (𝑟 ′, 𝑚 ′, 𝑝) ⊆ pAddr (𝑟, 𝑚, 𝑝).

= processRead (𝑚 d [ℓ, ℓ + 2), 𝑡𝑐 , 𝑚 t [ℓ/2], 2) so we deduce C = 𝑚 t [ℓ/2], and said 𝑚 d [ℓ, ℓ + 2) = (𝑧 0, 𝑧 1 ), and (𝑧 meta, 𝑡𝑧 ) = unpackTaint (𝑧𝑚 ), we deduce that 𝑤 = (𝑧 0, 𝑧 meta ) and 𝑧 meta ≠ 𝑣 magic . Furthermore, ℓ%2 = 0 holds by introspection of Rule [read-mem]. Observe that if we are able to find a second capability in rc(𝑟, 𝑚) such that it spans over [ℓ, ℓ + 2), we can conclude that 𝑤 = (𝑧 0, 𝑧 meta ) ∈ rc(𝑟, 𝑚) by Rule [ind], since all the other premises of the rules can be discharged by using the intermediate observations on (𝑧 0, 𝑧 meta ). The existence of such a capability spanning over [ℓ, ℓ + 2) is guaranteed by Lemma D.5. This proves: rc(𝑟 [𝑥 ↦→ (𝑤, 𝑡, 𝑐)], 𝑚) ⊆ rc(𝑟, 𝑚), and we can conclude this sub-derivation by reasoning identically to the case where the loaded value was not a capability. • If the transition was established by applying Rule [isastore], then we call ⟦𝑒⟧𝑟 = ((ℓ𝑒 , (𝑝𝑒 , 𝑏𝑒 , 𝑔𝑒 , 𝑡𝑒 )), 𝑡, C)

Proof. The proof proceeds by case analysis on the rule used 𝑜 for (𝑚, 𝑟 ) → − (𝑚 ′, 𝑟 ′ ). For each case, the key idea is to utilize the monotonicity of capability operations. We omit the details here. □ Proof. The proof goes by case analysis on our transition relation • If the transition was established by applying Rule [isaassign], then we have 𝑚 ′ = 𝑚 and 𝑟 ′ = 𝑟 [pc ↦→ ⟦pc ✚ 1⟧𝑟 ] [𝑥 ↦→ ⟦𝑒⟧𝑟 ], therefore it suffices to observe that if ⟦𝑒⟧𝑟 evaluates to a capability 𝑐 = (ℓ, ℓmeta ), by Lemma D.5, there exists a  ′ ′ register 𝑧 such that 𝑟 (𝑧) = ( ℓ ′, ℓmeta , 𝑡, C), and ℓmeta ⊑ ℓmeta . Therefore, we have rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚, 𝑥) ⊆ rc(𝑟, 𝑚) ∪ {𝑐}, and thereby, by Lemma D.3, we have rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚) ⊆ rc(𝑟, 𝑚) ∪ {𝑐}.

⟦𝑥⟧𝑟 = (𝑣, 𝑡𝑥 , 𝑐 𝑥 ), and we have 𝑟′ = 𝑟 𝑚 ′ = writeMem(𝑚, ⟦𝑒⟧𝑟 , 𝑠𝑧, ⟦𝑥⟧𝑟 ) = writeMem(𝑚, ⟦𝑒⟧𝑟 , 𝑠𝑧, ⟦𝑥⟧𝑟 ) = (𝑚 d [[ℓ, ℓ + sz) ↦→ 𝑣 ′, 𝑚 t [ℓ/2 ↦→ 𝑐 ′ ]]) where (𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡𝑥 , 𝑐 𝑥 ), 𝑡𝑒 , sz). The proof then proceeds by case analysis. By introspection of the rules for processWrite(), either of the following cases holds: – 𝑐 ′ = V ∨ 𝑐 𝑥 = V: By Lemma D.6, we can easily conclude that rc(𝑟 ′, 𝑚 ′ ) ⊆ rc(𝑟, 𝑚).

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

– 𝑐 𝑥 = C ∧ sz = 2: By rules for memory operations, there should be ℓ%2 = 0. By Lemma D.7, we have    ′ ′ ′ rc(𝑟 , 𝑚 ′ ) ⊆ rc(𝑟, 𝑚) ∪ ℓ ′, ℓmeta : ℓ ′, ℓmeta ⊑𝑣 . By the definition of Lemma D.5, 𝑣 ∈ rc(𝑟, 𝑚).  𝑣 and  ′ ′ Then, by [shrink], ℓ ′, ℓmeta : ℓ ′, ℓmeta ⊑ 𝑣 ⊆ rc(𝑟, 𝑚). Therefore, there should be rc(𝑟 ′, 𝑚 ′ ) ⊆ rc(𝑟, 𝑚). □ Lemma D.3. For an ISA configuration (𝑚, 𝑟 ) and a capability 𝑐, we have rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚) ⊆ rc(𝑟, 𝑚) ∪ rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚, 𝑥) Proof. Direct consequence of

• (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [base]: in this case, the conclusion is trivial. • (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [ind]: By [ind], there must exist (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) such that (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚 ′, 𝑧), and by induction hypothesis, there should also be (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚, 𝑧). By [ind], (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) implies that there exists ℓ ′ such that [ℓ ′, ℓ ′ + 2) ∈ [𝑏, 𝑒)

∀𝑧, rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚, 𝑧) ⊆ rc(𝑟, 𝑚) ∪ rc(𝑟 [𝑥 ↦→ 𝑐], 𝑚, 𝑥), which is established by case analysis on whether 𝑧 = 𝑥.

Proof. It suffices to prove that for every register 𝑧, rc(𝑟, 𝑚 ′, 𝑧) ⊆ rc(𝑟, 𝑚, 𝑧). So it suffices to prove that for all (ℓ, ℓmeta ) such that (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧), there must also be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧). The proof is by induction on the proof derivation. Consider the following cases:

𝑚 d′ [ℓ ′, ℓ ′ + 2] = (ℓ, ℓ𝑚 ) 𝑚 t′ [ℓ ′ /2] = C ℓ ′ %2 = 0

(3)

unpackTaint (ℓ𝑚 ) = (ℓmeta, _)

Lemma D.4. For all 𝑚, 𝑟 , 𝑒 such that ⟦𝑒⟧𝑟 = ((ℓ, ℓmeta ), 𝑡, C), there should be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚).

Furthermore, there should be

Proof. By Lemma D.5, there exists 𝑥 such that  ′ ′ 𝑟 (𝑥) = ( ℓ ′, ℓmeta , _, C) ℓmeta ⊑ ℓmeta .  ′ By Definition D.2, ℓ ′, ℓmeta ∈ rc(𝑟, 𝑚), so there should also be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚) by Rule [shrink]. □

This can be proved by considering the following cases: – sz = 1: by [write-data], 𝑚 t′ [ℓ0 /2] = 𝑐 = V. Note that 𝑚 t′ [ℓ/2] = C and ℓ ′ %2 = 0, so ℓ0 ∉ [ℓ ′, ℓ ′ + 2). Thus, (4) holds. – sz = 2, 𝑣𝑚 = 0 ∧ 𝑡 0 = U: in this case, there should be ℓ0 %2 = 0. Furthermore, by [spill-value], there should be

Lemma D.5 (Monotonicity of expression evaluation). For every register file 𝑟 and expression 𝑒, if ⟦𝑒⟧𝑟 = ((ℓ, ℓmeta ), _, C), there ′ ′ exists a register 𝑥 such that 𝑟 (𝑥) = ( ℓ ′, ℓmeta , _, C), and ℓmeta ⊑ ℓmeta . Proof. The proof is by induction on the syntax of the expression. If the expression is a register, or a value, the conclusion is trivial, and vacuously satisfied, respectively. We now consider the case where the expression is given by a composition of operators. In this case we have 𝑒 = op(𝑒 0, 𝑒 1 ). If the outermost operator is a value operator, the claim is vacuously satisfied. If the outermost operator is a capability operator, then the proof proceeds by case analysis on ⟦𝑒 0 ⟧𝑟 and ⟦𝑒 1 ⟧𝑟 . If they are both tagged as values or as capabilities, then we reach a contradiction because by our assumption on capability operators (Axiom (1) of capability operators) the output ⟦𝑒⟧𝑟 is tagged as a regular value while, according to our assumption, it has to be tagged as a capability. Finally, by inductive hypothesis, if ⟦𝑒𝑖 ⟧𝑟 evaluates to a value and ⟦𝑒 1−𝑖 ⟧𝑟 evaluates to a capability (𝑎, 𝑎 meta ), ′ then there exists a register 𝑥 such that 𝑟 (𝑥) = ( ℓ ′, ℓmeta , _, C), and ′ 𝑎 meta ⊑ ℓmeta . By monotonicity of capability operators (Axiom (2) of capability operators), the resulting capability (ℓ, ℓmeta ) satisfies ′ ℓmeta ⊑ 𝑎 meta ⊑ ℓmeta . Finally, observe that in this case, the inner taint 𝑡 of the resulting capability ℓmeta coincides with the one of 𝑎 meta by assumption on operators by Axiom (3) on capability operators. □ Lemma D.6 (Monotonicity of reachable capabilities after store value). For every register 𝑟 and memories 𝑚 = (𝑚 d, 𝑚 t ), and 𝑚 ′ = (𝑚 d′ , 𝑚 t′ ) = (𝑚 d [[ℓ0, ℓ0 + sz) ↦→ 𝑣], 𝑚 t [ℓ0 /2 ↦→ 𝑐]), where (𝑣, 𝑐) = processWrite(((𝑣 0, 𝑣𝑚 ), 𝑡 0, 𝑐 0 ), 𝑡𝑐 , sz), sz = 2 ⇒ ℓ0 %2 = 0, and 𝑐 = V ∨ 𝑐 0 = V, we have rc(𝑟, 𝑚 ′ ) ⊆ rc(𝑟, 𝑚).

ℓ𝑚 ≠ 𝑣 magic .

[ℓ ′, ℓ ′ + 2) ∩ [ℓ0, ℓ0 + sz) = ∅.

𝑚 d′ [ℓ0, ℓ0 + 2] = (𝑣 0, 𝑣 magic )

(4)

𝑚 t′ [ℓ0 /2] = C.

Note that 𝑚 d′ [ℓ ′, ℓ ′ + 2] = (ℓ, ℓ𝑚 ) and ℓ𝑚 ≠ 𝑣 magic , so ℓ ′ ≠ ℓ0 . Furthermore, ℓ ′ %2 = ℓ0 %2 = 0, so (4) holds. – sz = 2, 𝑣𝑚 ≠ 0 ∨ 𝑡 0 = T: in this case, there should be ℓ0 %2 = 0. By [spill-value], 𝑚 t′ [ℓ0 /2] = 𝑐 = V. Note that 𝑚 t′ [ℓ ′ /2] = C and ℓ ′ %2 = 0, so (4) holds. By (4), 𝑚 d [ℓ ′, ℓ ′ + 2] = 𝑚 d′ [ℓ ′, ℓ ′ + 2] and 𝑚 t [ℓ ′ /2] = 𝑚 t′ [ℓ ′ /2]. This implies that (3) still holds if we replace 𝑚 d′ with 𝑚 d , and 𝑚 t′ with 𝑚 t . Therefore, by [ind], there should be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧). • (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [shrink]: By [shrink],  ′ ′, ℓ ′ ℓ ∈ rc(𝑟, 𝑚 ′, 𝑧) there exists ℓ ′, ℓmeta such that meta  ′ ′ and (ℓ, ℓmeta ) ⊑ ℓ , ℓmeta  . By induction hypothesis, there ′ should also be ℓ ′, ℓmeta ∈ rc(𝑟, 𝑚, 𝑧). Therefore, by [shrink], (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧) holds. □ Lemma D.7 (Monotonicity of reachable capabilities after store capability). For every register 𝑟 and memories 𝑚 = (𝑚 d, 𝑚 t ), and 𝑚 ′ = (𝑚 d′ , 𝑚 t′ ) = (𝑚 d [[ℓ0, ℓ0 + 2) ↦→ 𝑣], 𝑚 t [ℓ0 /2 ↦→ 𝑐]), where ℓ0 %2 = 0 and (𝑣, 𝑐) = processWrite(((𝑣 0, 𝑣 meta ), 𝑡 0, C), 𝑡𝑐 , 2), we have rc(𝑟, 𝑚 ′ ) ⊆ rc(𝑟, 𝑚) ∪ {(ℓ, ℓmeta ) : (ℓ, ℓmeta ) ⊑ (𝑣 0, 𝑣 meta )}. Proof. Denote 𝑅 = {(ℓ, ℓmeta ) : (ℓ, ℓmeta ) ⊑ (𝑣 0, 𝑣 meta )}. It suffices to prove that for every register 𝑧, rc(𝑟, 𝑚 ′, 𝑧) ⊆ rc(𝑟, 𝑚, 𝑧) ∪ 𝑅. So it suffices to prove that for all (ℓ, ℓmeta ) such that (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧), there must also be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧) ∪ 𝑅. The proof is by induction on the proof derivation. Consider the following cases:

Song et al.

• (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [base]: in this case, the conclusion is trivial. • (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [ind]: By [ind], there must exist (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) such that (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚 ′, 𝑧), and by induction hypothesis, there should also be (ℓ1, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚, 𝑧). By [ind], (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) implies that there exists ℓ ′ such that [ℓ ′, ℓ ′ + 2) ∈ [𝑏, 𝑒) 𝑚 d′ [ℓ ′, ℓ ′ + 2] = (ℓ, ℓ𝑚 ) 𝑚 t′ [ℓ ′ /2] = C ℓ ′ %2 = 0 unpackTaint (ℓ𝑚 ) = (ℓmeta, _)

(5)

ℓ𝑚 ≠ 𝑣 magic .

Consider the following cases: – ℓ ′ = ℓ0 : in this case, there should be ((ℓ, ℓ𝑚 ), C) = (𝑣, 𝑐) = processWrite(((𝑣 0, 𝑣 meta ), 𝑡 0, C), 𝑡𝑐 , 2). By [write-cap], this implies that ℓ = 𝑣0

ℓ𝑚 = packTaint (𝑣 meta, 𝑡 0 ).

Hence, (𝑣 meta, 𝑡 0 ) = unpackTaint (ℓ𝑚 ) = (ℓmeta, _). In short, the above analysis shows that (ℓ, ℓmeta ) = (𝑣 0, 𝑣 meta ). Thus, (ℓ, ℓmeta ) ∈ 𝑅 ⊆ rc(𝑟, 𝑚, 𝑧) ∪ 𝑅. – ℓ ′ ≠ ℓ0 : Since ℓ ′ %2 = ℓ0 %2 = 0, then [ℓ ′, ℓ ′ + 2) ∩ [ℓ0, ℓ0 + 2) = ∅. Thus, 𝑚 d [ℓ ′, ℓ ′ + 2] = 𝑚 d′ [ℓ ′, ℓ ′ + 2] and 𝑚 t [ℓ ′ /2] = 𝑚 t′ [ℓ ′ /2]. This implies that (5) still holds if we replace 𝑚 d′ with 𝑚 d , and 𝑚 t′ with 𝑚 t . Therefore, by [ind], there should be (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧) ⊆ rc(𝑟, 𝑚, 𝑧) ∪ 𝑅. • (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚 ′, 𝑧) was proved with [shrink]: By [shrink],  ′ ′, ℓ ′ there exists ℓ ′, ℓmeta such that ℓ ∈ rc(𝑟, 𝑚 ′, 𝑧) meta  ′ ′ and (ℓ, ℓmeta ) ⊑ ℓ , ℓmeta  . By induction hypothesis, there ′ should also be ℓ ′, ℓmeta ∈ rc(𝑟, 𝑚, 𝑧). Therefore, by [shrink], (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚, 𝑧) holds. □ D.1.2

Lemma D.8 (ISA Model Maintains Public Eqivalence). Let 𝑆 0 , 𝑆 0′ be well-formed initial ISA states such that 𝑆 0 ≃pub 𝑆 0′ and 𝑆 0 ≡ISA 𝑆 0′ . Then, for all 𝑛 such that 𝑜1

Definition D.6 (Public Equivalence for ISA States). ISA states 𝑆 = (𝑚, 𝑟 ), 𝑆 ′ = (𝑚 ′, 𝑟 ′ ) are publicly equivalent, i.e., 𝑆 ≃pub 𝑆 ′ , if ∀𝑥 ∈ Reg,

𝑟 (𝑥)| U = 𝑟 ′ (𝑥)| U .

and ∀ℓ, sz ∈ {1, 2}, 𝑡, ((sz = 2 ⇒ ℓ%2 = 0) ∧ [ℓ, ℓ + sz) ⊆ tAddr (𝑟, 𝑚, 𝑡) ∩ tAddr (𝑟 ′, 𝑚 ′, 𝑡)) ⇒

(processRead (𝑚 d [ℓ, ℓ + sz], 𝑡, 𝑚 t [ℓ/2], sz)| U = processRead (𝑚 d′ [ℓ, ℓ + sz], 𝑡, 𝑚 t′ [ℓ/2], sz)| U )

where 𝑚 = (𝑚 d, 𝑚 t ), 𝑚 ′ = (𝑚 d′ , 𝑚 t′ ).

𝑜𝑛

there must exist 𝑆 1′ , . . . , 𝑆𝑛′ such that 𝑜1

𝑜2

𝑜𝑛

𝑆 0′ −→ 𝑆 1 −→ 𝑆 2′ · · · −−→ 𝑆𝑛′ , and 𝑆𝑛 ≃pub 𝑆𝑛′ . Proof. The proof of the inductive claim is by induction on 𝑛. As the base case is trivial, we directly go to the inductive case. Assuming 𝑆𝑛 ≃pub 𝑆𝑛′ , we must prove that the target configurations 𝑜𝑛+1

′ ′ . Recall that we denote 𝑆 −−−→ 𝑆𝑛+1 and 𝑆𝑛+1 satisfy 𝑆𝑛+1 ≃pub 𝑆𝑛+1 𝑛  𝑜 𝑛+1 ′ . We also denote 𝑆 = (𝑚 , 𝑟 ), 𝑆 ′ = 𝑚 ′ , 𝑟 ′ . 𝑆𝑛+1 and 𝑆𝑛′ −−−→ 𝑆𝑛+1 𝑛 𝑛 𝑛 𝑛 𝑛 𝑛 ′ By repeatedly applying Lemma D.1, we can get wf (𝑆𝑛 ) and wf (𝑆𝑛 ). By rules for ISA semantics and [read-mem-inst], register pc should be untainted in both configurations 𝑆𝑛 and 𝑆𝑛′ , i.e.,

⟦pc⟧𝑟𝑛 = (_, U, C)

⟦pc⟧𝑟𝑛′ = (_, U, C).

Since 𝑆𝑛 ≃pub 𝑆𝑛′ , then by Definition D.6 there should be ⟦pc⟧𝑟𝑛 = ⟦pc⟧𝑟𝑛′ . Furthermore, by Lemma D.4, we can get ⟦pc⟧𝑟𝑛′ ∈ rc(𝑟𝑛′ , 𝑚𝑛′ ).

⟦pc⟧𝑟𝑛 ∈ rc(𝑟𝑛 .𝑚𝑛 )

Then, by Lemma D.12, there should be an instr such that instr = readMemInst (𝑚𝑛 , ⟦pc⟧𝑟𝑛 ) = readMemInst (𝑚𝑛′ , ⟦pc⟧𝑟𝑛′ ) The proof proceeds by case analysis on instr. We consider the following cases: Case instr = 𝑥 ← 𝑒. By [isa-assign], the next states are 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ ⟦𝑒⟧𝑟𝑛 ] ′ 𝑟𝑛+1 = 𝑟𝑛′ [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛′ ] [𝑥 ↦→ ⟦𝑒⟧𝑟𝑛′ ]

𝑆𝑛+1 = (𝑚𝑛 , 𝑟𝑛+1 ) ′ ′  𝑆𝑛+1 = 𝑚𝑛′ , 𝑟𝑛+1 .

ISA Maintains Public Equivalence.

Definition D.5 (Untainted Projection). We define untainted projection of a value associated with taint and capability tag, i.e., (𝑣, 𝑡, 𝑐) as ( (𝑣, U, 𝑐) 𝑡 = U (𝑣, 𝑡, 𝑐)| U = ⊥ 𝑡 = T.

𝑜2

𝑆 0 −→ 𝑆 1 −→ 𝑆 2 · · · −−→ 𝑆𝑛 ,

By Definition D.6, 𝑆𝑛 ≃pub 𝑆𝑛′ implies that ⟦𝑒⟧𝑟𝑛 | U = ⟦𝑒⟧𝑟𝑛′ | U

⟦pc ✚ 1⟧𝑟𝑛 | U = ⟦pc ✚ 1⟧𝑟𝑛′ | U .

Hence, ∀𝑥 ∈ Reg,

′ 𝑟𝑛+1 (𝑥)| U = 𝑟𝑛+1 (𝑥)| U .

′ , it Denote 𝑚𝑛 = (𝑚 d, 𝑚 t ), 𝑚𝑛′ = (𝑚 d′ , 𝑚 t′ ). To prove 𝑆𝑛+1 ≃pub 𝑆𝑛+1 suffices to prove that for all ℓ, sz ∈ {1, 2}, 𝑡 such that

(sz = 2 ⇒ ℓ%2 = 0) ′ ∧ [ℓ, ℓ + sz) ⊆ tAddr (𝑟𝑛+1, 𝑚𝑛 , 𝑡) ∩ tAddr (𝑟𝑛+1 , 𝑚𝑛′ , 𝑡),

there must be processRead (𝑚 d [ℓ, ℓ + sz], 𝑡, 𝑚 t [ℓ/2], sz)| U = processRead (𝑚 d′ [ℓ, ℓ + sz], 𝑡, 𝑚 t′ [ℓ/2], sz)| U .

(6)

Fix ℓ, sz, 𝑡. By Lemma D.2, for all 𝑡, ′ [ℓ, ℓ + sz] ⊆ (tAddr (𝑟𝑛+1, 𝑚𝑛 , 𝑡) ∩ tAddr (𝑟𝑛+1 , 𝑚𝑛′ , 𝑡))

⊆ (tAddr (𝑟𝑛 , 𝑚𝑛 , 𝑡) ∩ tAddr (𝑟𝑛′ , 𝑚𝑛′ , 𝑡)) By induction hypothesis, i.e., 𝑆𝑛 ≃pub 𝑆𝑛′ , (6) holds.

(7)

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

Case instr = load 𝑥, 𝑒, sz. Denote ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑛

 ′ ( ℓ , ℓmeta , _, C) = ⟦𝑒⟧𝑟𝑛′ . ′

By [isa-load], there should be ′ 𝑜𝑛+1 = load (ℓ, ℓmeta ) = load (ℓ ′, ℓmeta ).

(𝑣, 𝑡, 𝑐) = readMem(𝑚𝑛 , ((ℓ, ℓmeta ), _, C), sz)  ′ ′ ′ ′ (𝑣 , 𝑡 , 𝑐 ) = readMem(𝑚𝑛′ , ( ℓ ′, ℓmeta , _, C), sz). and get   𝑆𝑛+1 = 𝑚𝑛 , 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)]   ′ 𝑆𝑛+1 = 𝑚𝑛′ , 𝑟𝑛′ [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛′ ] [𝑥 ↦→ (𝑣 ′, 𝑡 ′, 𝑐 ′ )] . Hence, it suffices to prove that (𝑣, 𝑡, 𝑐)| U 𝑚𝑛 = (𝑚 d, 𝑚 t )

U . Denote

𝑚𝑛′ = (𝑚 d′ , 𝑚 t′ )

′ ℓmeta = ℓmeta = (𝑝, 𝑏, 𝑒, 𝑡𝑐 ).

By Lemma D.9 and [check-cap], [ℓ, ℓ + sz) ⊆ tAddr (𝑟𝑛 , 𝑚𝑛 , 𝑡𝑐 ) ∩ tAddr (𝑟𝑛′ , 𝑚𝑛′ , 𝑡𝑐 ). Thus, by Definition D.6 and [read-mem], we have (𝑣, 𝑡, 𝑐)| U = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U = processRead (𝑚 d′ [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t′ [ℓ/2], sz)| U . Case instr = store 𝑥, 𝑒, sz. Denote  ′ ( ℓ ′, ℓmeta , _, C) = ⟦𝑒⟧𝑟𝑛′ ,

′ where ℓmeta = (𝑝, 𝑏, 𝑒, 𝑡𝑐 ) and ℓmeta = (𝑝 ′, 𝑏 ′, 𝑒 ′, 𝑡𝑐′ ). Since the two configurations generate the same observation, then by [isa-store], ′ 𝑜𝑛+1 = store(𝑣, (ℓ, ℓmeta )) = store(𝑣 ′, (ℓ ′, ℓmeta ))

𝑣 = (𝑡𝑐 = U) ? ⟦𝑥⟧𝑟𝑛 : ⊥ 𝑣 ′ = (𝑡𝑐′ = U) ? ⟦𝑥⟧𝑟𝑛′ : ⊥.  ′ Hence, (ℓ, ℓmeta ) = ℓ ′, ℓmeta and 𝑣 = 𝑣 ′ ; in particular, 𝑡𝑐 = 𝑡𝑐′ . Denote ⟦𝑥⟧𝑟𝑛 = (𝑣 𝑥 , 𝑡𝑥 , 𝑐 𝑥 )

𝑚𝑛′ = (𝑚 d′ , 𝑚 t′ )

𝑚𝑛+1 = (𝑚 d1, 𝑚 t1 )

′ ′ ′ 𝑚𝑛+1 = (𝑚 d1 , 𝑚 t1 ).

(sz 0 = 2 ⇒ ℓ0 %2 = 0) ′ ′ ∧ [ℓ0, ℓ0 + sz 0 ) ⊆ tAddr (𝑟𝑛+1, 𝑚𝑛+1, 𝑡 0 ) ∩ tAddr (𝑟𝑛+1 , 𝑚𝑛+1 , 𝑡 0 ).

the following statement holds: processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 )| U

⟦𝑥⟧𝑟𝑛′ = (𝑣 𝑥′ , 𝑡𝑥′ , 𝑐 𝑥′ ).

Then, by discussing the value of 𝑡𝑐 , it is straightforward to derive that (𝑣 𝑥 , 𝑡𝑐 , 𝑐 𝑥 )| U = (𝑣 𝑥′ , 𝑡𝑐 , 𝑐 𝑥′ )| U . By Definition D.6, 𝑆𝑛 ≃pub 𝑆𝑛′ implies that (𝑣 𝑥 , 𝑡𝑥 , 𝑐 𝑥 )| U = (𝑣 𝑥′ , 𝑡𝑥′ , 𝑐 𝑥′ )| U . By [isa-store], the next states can be derived as 𝑚𝑛+1 = writeMem(𝑚𝑛 , ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C), sz, ⟦𝑥⟧𝑟𝑛 ) ′ = writeMem(𝑚𝑛′ , ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C), sz, ⟦𝑥⟧𝑟𝑛′ ) 𝑚𝑛+1   𝑆𝑛+1 = 𝑚𝑛+1, 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ]   ′ ′ 𝑆𝑛+1 = 𝑚𝑛+1 , 𝑟𝑛′ [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛′ ] .

It is straightforward to derive that 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ]| U = 𝑟𝑛′ [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛′ ]| U .

(8)

′ ′ = processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 )| U .

Fix ℓ0 , sz 0 ∈ {1, 2}, 𝑡 0 . By Lemma D.2, for all 𝑡, ′ ′ (tAddr (𝑟𝑛+1, 𝑚𝑛+1, 𝑡) ∩ tAddr (𝑟𝑛+1 , 𝑚𝑛+1 , 𝑡))

(9)

⊆ (tAddr (𝑟𝑛 , 𝑚𝑛 , 𝑡) ∩ tAddr (𝑟𝑛′ , 𝑚𝑛′ , 𝑡)) By inductive hypothesis, processRead (𝑚 d [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t [ℓ0 /2], sz 0 )| U

sz = 2 ⇒ ℓ%2 = 0

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑛

𝑚𝑛 = (𝑚 d, 𝑚 t )

It suffices to prove that for all ℓ0 , sz 0 ∈ {1, 2}, 𝑡 0 where

By [isa-load], we can denote

= (𝑣 ′, 𝑡 ′, 𝑐 ′ )|

Hence, we focus on proving the statement about memory in Defi′ . Denote nition D.6 holds for 𝑚𝑛+1 , 𝑚𝑛+1

= processRead (𝑚 d′ [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t′ [ℓ0 /2], sz 0 )| U .

(10)

Our proof goes by case discussion on ℓ, sz, ℓ0 , and sz 0 . By [checkcap], sz%0 ⇒ ℓ%2 = 0. Consider the following cases. Note that we use ℓ/2 as a shortcut for ⌊ℓ/2⌋. • sz = sz 0 = 2, ℓ = ℓ0 : By assumption, in this case there should be ℓ%2 = ℓ0 %2 = 0. By Lemma D.9, 𝑡 0 = 𝑡𝑐 . By [write-mem], (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑚 t1 [ℓ0 /2]) = (𝑚 d1 [ℓ, ℓ + 2], 𝑚 t1 [ℓ/2]) = processWrite(⟦𝑥⟧𝑟𝑛 , 𝑡𝑐 , 2) = processWrite((𝑣 𝑥 , 𝑡𝑥 , 𝑐 𝑥 ), 𝑡𝑐 , 2). Similarly, ′ ′ (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑚 t1 [ℓ0 /2])

= processWrite((𝑣 𝑥′ , 𝑡𝑥′ , 𝑐 𝑥′ ), 𝑡𝑐 , 2). By Lemma D.11, (8) holds. • sz = sz 0 = 2, ℓ ≠ ℓ0 : Similarly, ℓ%2 = ℓ0 %2 = 0. Then, [ℓ, ℓ + 2) ∩ [ℓ0, ℓ + 2) = ∅ and ℓ/2 ≠ ℓ0 /2. Thus, by [writemem], (10) implies (8). • sz = 1, sz 0 = 2, ℓ ∈ [ℓ0, ℓ0 + 2): Note that in this case, ℓ0 %2 = 0, so ℓ/2 = ℓ0 /2. By [write-mem] and [writedata], (𝑚 d1 [ℓ], 𝑚 t1 [ℓ/2]) = processWrite((𝑣 𝑥 , 𝑡𝑥 , 𝑐 𝑥 ), 𝑡𝑐 , 1) = (𝑣 𝑥 , V). Similarly, ′ ′ (𝑚 d1 [ℓ], 𝑚 t1 [ℓ/2]) = (𝑣 𝑥′ , V).

Then, by [read-data], processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 ) = processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t [ℓ0 /2], sz 0 ) = processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, V, sz 0 ) = (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V).

Song et al.

– ℓ0 ∉ [ℓ, ℓ + 2): In this case,

Similarly, ′ ′ processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 ) ′ = (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V).

= (𝑚 d1 [ℓ0 ], 𝑡 0, V) = (𝑚 d [ℓ0 ], 𝑡 0, V)

It suffices to prove

= processRead (𝑚 d [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t [ℓ0 /2], sz 0 ).

processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 )

Similarly,

′ (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V)| U = (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V)| U

′ ′ processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 )

Since ℓ ∈ [ℓ0, ℓ0 +2), there should be either ℓ = ℓ0 or ℓ = ℓ0 +1. Here we only prove the first case; the second one can be proved similarly. When ℓ = ℓ0 , 𝑚 d1 [ℓ0, ℓ0 + 2] = (𝑣 𝑥 , 𝑚 d [ℓ0 + 1]) ′ 𝑚 d1 [ℓ0, ℓ0 + 2] = (𝑣 𝑥′ , 𝑚 d′ [ℓ0 + 1]).

By inductive hypothesis,

= processRead (𝑚 d′ [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t′ [ℓ0 /2], sz 0 ). Therefore, (10) implies (8). Case instr = jmp 𝑒 or instr = beqz 𝑥, ℓ ′ . The two cases can be proved similarly to the case where instr = 𝑥 ← 𝑒. □ Lemma D.9. For all 𝑚, 𝑟 , 𝑒, sz, 𝑝 ′ such that wf (𝑚, 𝑟 )

processRead (𝑚 d [ℓ0 + 1], 𝑡 0, 𝑚 t [ℓ0 /1], 1)| U

⟦𝑒⟧𝑟 = ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C)

= processRead (𝑚 d′ [ℓ0 + 1], 𝑡 0, 𝑚 t′ [ℓ0 /1], 1)| U .

checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, 𝑝 ′ ),

By [read-data], this implies that (𝑚 d [ℓ0 + 1], 𝑡 0, V)| U = (𝑚 d′ [ℓ0 + 1], 𝑡 0, V)| U . Furthermore, recall that (𝑣 𝑥 , 𝑡𝑐 , 𝑐 𝑥 )| U = (𝑣 𝑥′ , 𝑡𝑐 , 𝑐 𝑥′ )| U . And since ℓ ∈ [ℓ0, ℓ0 + 2), it is straightforward to derive that 𝑡 0 = 𝑡𝑐 using Lemma D.9. Therefore, (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V)| U = ((𝑣 𝑥 , 𝑚 d [ℓ0 + 1]), 𝑡 0, V)| U = ((𝑣 𝑥′ , 𝑚 d′ [ℓ0 + 1]), 𝑡 0, V)| U ′ = (𝑚 d1 [ℓ0, ℓ0 + 2], 𝑡 0, V)| U

• sz = 1, sz 0 = 2, ℓ ∉ [ℓ0, ℓ0 +2): Note that in this case ℓ0 %2 = 0, so ℓ/2 ≠ ℓ0 /2. By [write-mem],

then [ℓ, ℓ + sz) ⊆ tAddr (𝑟, 𝑚, 𝑡𝑐 ). Furthermore, if for some 𝑡, [ℓ, ℓ + sz) ∩ tAddr (𝑟, 𝑚, 𝑡) ≠ ∅, then there must be 𝑡 = 𝑡𝑐 . Proof. By Lemma D.4, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )) ∈ rc(𝑟, 𝑚). Then, by Definition D.3, [𝑏, 𝑒) ⊆ tAddr (𝑟, 𝑚, 𝑡𝑐 ). Furthermore, by [check-cap], checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, 𝑝 ′ ) implies that [ℓ, ℓ + sz) ⊆ [𝑏, 𝑒). Therefore, [ℓ, ℓ + sz) ⊆ tAddr (𝑟, 𝑚, 𝑡𝑐 ). By Definition D.4, there should be 𝑡 = 𝑡𝑐 . □ Lemma D.10. For all 𝑣, 𝑣𝑚 , 𝑡, 𝑐, 𝑡𝑐 , denote ′ ((𝑣 ′, 𝑣𝑚 ), 𝑐 ′ ) = processWrite(((𝑣, 𝑣𝑚 ), 𝑡, 𝑐), 𝑡𝑐 , 2).

processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 ) = processRead (𝑚 d [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t [ℓ0 /2], sz 0 ) ′ ′ processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 )

= processRead (𝑚 d′ [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t′ [ℓ0 /2], sz 0 ). Therefore, (10) implies (8). • sz 0 = 1: By [read-data], processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 ) = (𝑚 d1 [ℓ0 ], 𝑡 0, V) ′ ′ processRead (𝑚 d1 [ℓ0, ℓ0 + sz 0 ], 𝑡 0, 𝑚 t1 [ℓ0 /2], sz 0 ) ′ = (𝑚 d1 [ℓ0 ], 𝑡 0, V).

Consider the following two cases: – ℓ0 ∈ [ℓ, ℓ + 2): Note that by [write-mem], (𝑚 d1 [ℓ, ℓ + 2], 𝑚 t1 [ℓ/2]) = processWrite((𝑣 𝑥 , 𝑡𝑥 , 𝑐 𝑥 ), 𝑡𝑐 , 2) ′ ′ (𝑚 d1 [ℓ, ℓ + 2], 𝑚 t1 [ℓ/2]) = processWrite((𝑣 𝑥′ , 𝑡𝑥′ , 𝑐 𝑥′ ), 𝑡𝑐 , 2).

By Lemma D.11, this implies that ′ ′ (𝑚 d1 [ℓ, ℓ + 2], 𝑡𝑐 , 𝑚 t1 [ℓ/2])| U = (𝑚 d1 [ℓ, ℓ + 2], 𝑡𝑐 , 𝑚 t1 [ℓ/2])| U .

Note that ℓ0 ∈ [ℓ, ℓ +2) and by Lemma D.9 there should ′ [ℓ ], 𝑡 , V)| , be 𝑡 0 = 𝑡𝑐 . Thus, (𝑚 d1 [ℓ0 ], 𝑡 0, V)| U = (𝑚 d1 0 0 U so the statement holds.

Then, ′ ((𝑣, 𝑣𝑚 ), 𝑡 ′, 𝑐) = processRead ((𝑣 ′, 𝑣𝑚 ), 𝑡𝑐 , 𝑐 ′, 2),

where 𝑡′ =

( 𝑡𝑐 𝑡 ⊓ 𝑡𝑐

𝑐 = V ∧ 𝑣𝑚 ≠ 0 ∧ 𝑡 = U Otherwise

Proof. Consider the following cases: • 𝑐 = C: Note that packTaint (𝑣𝑚 , 𝑡) ≠ 𝑣 magic and (𝑣𝑚 , 𝑡) = unpackTaint (packTaint (𝑣𝑚 , 𝑡)). Then, by [write-cap] and [read-cap], ′ processRead ((𝑣 ′, 𝑣𝑚 ), 𝑡𝑐 , 𝑐 ′, 2)

= processRead ((𝑣, packTaint (𝑣𝑚 , 𝑡)), 𝑡𝑐 , C, 2) = ((𝑣, 𝑣𝑚 ), 𝑡 ⊓ 𝑡𝑐 , C) = ((𝑣, 𝑣𝑚 ), 𝑡 ⊓ 𝑡𝑐 , 𝑐). • 𝑐 = V ∧ 𝑣𝑚 = 0 ∧ 𝑡 = U: By [spill-value], ′ ((𝑣 ′, 𝑣𝑚 ), 𝑐 ′ ) = ((𝑣, 𝑣 magic ), C).

Then, by [unspill-value], ′ processRead ((𝑣 ′, 𝑣𝑚 ), 𝑡𝑐 , 𝑐 ′, 2)

= processRead ((𝑣, 𝑣 magic ), 𝑡𝑐 , C, 2) = ((𝑣, 0), U, V) = ((𝑣, 𝑣𝑚 ), 𝑡 ⊓ 𝑡𝑐 , 𝑐).

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

• 𝑐 = V ∧ (𝑣𝑚 ≠ 0 ∨ 𝑡 = T): By [spill-value],

• sz = 2 and 𝑡 = 𝑡 ′ = T: First, we prove (11). If 𝑡𝑐 = T, by the definition of low projection, the statement holds. If 𝑡𝑐 = U, then (𝑣, 𝑡𝑐 , 𝑐)| U = (𝑣 ′, 𝑡𝑐 , 𝑐)| U implies that 𝑣 = 𝑣 ′ and 𝑐 = 𝑐 ′ . Then, 𝑣 1 = 𝑣 1′ , 𝑐 1 = 𝑐 1′ , so (11) holds. Second, we prove (12). By Lemma D.10,

′ ((𝑣 ′, 𝑣𝑚 ), 𝑐 ′ ) = ((𝑣, 𝑣𝑚 ), V).

Then, by [read-data], ′ processRead ((𝑣 ′, 𝑣𝑚 ), 𝑡𝑐 , 𝑐 ′, 2)

(𝑣, 𝑡 ⊓ 𝑡𝑐 , 𝑐) = processRead (𝑣 1, 𝑡𝑐 , 𝑐 1, sz)

= processRead ((𝑣, 𝑣𝑚 ), 𝑡𝑐 , V, 2)

(𝑣 , 𝑡 ′ ⊓ 𝑡𝑐 , 𝑐 ′ ) = processRead (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ , sz).

= ((𝑣, 𝑣𝑚 ), 𝑡𝑐 , V).

Since (𝑣, 𝑡𝑐 , 𝑐)| U = (𝑣 ′, 𝑡𝑐 , 𝑐 ′ )| U and 𝑡 ⊓ 𝑡𝑐 = 𝑡 ′ ⊓ 𝑡𝑐 = T ⊓ 𝑡𝑐 = 𝑡𝑐 , (12) holds.

If 𝑡 = T, then 𝑡𝑐 = 𝑡 ⊓ 𝑡𝑐 , so the statement holds. If 𝑡 = U, then by assumption 𝑣𝑚 ≠ 0, thus the statement also holds. □ Lemma D.11. For all 𝑣, 𝑣 ′, 𝑡, 𝑡 ′, 𝑐, 𝑐 ′, 𝑡𝑐 , sz such that (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U (𝑣, 𝑡𝑐 , 𝑐)| U = (𝑣 ′, 𝑡𝑐 , 𝑐 ′ )| U, denoting

□ Lemma D.12 (Lemma for readMemInst (·)). Let (𝑚, 𝑟 ) and (𝑚 ′, 𝑟 ′ ) be two ISA states such that (𝑚, 𝑟 ) ≃pub (𝑚 ′, 𝑟 ′ ). Let ((ℓ, ℓmeta ), 𝑡, 𝑐) be a capability (associated with taint and capability tag) such that (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚), (ℓ, ℓmeta ) ∈ rc(𝑟 ′, 𝑚 ′ ), and there exists instr such that instr = readMemInst (𝑚, ((ℓ, ℓmeta ), 𝑡, 𝑐)). Then, there must be

(𝑣 1, 𝑐 1 ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz)

instr = readMemInst (𝑚 ′, ((ℓ, ℓmeta ), 𝑡, 𝑐)).

(𝑣 1′ , 𝑐 1′ ) = processWrite((𝑣 ′, 𝑡 ′, 𝑐 ′ ), 𝑡𝑐 , sz), then,

Proof. By [read-mem-inst], there should be

(𝑣 1, 𝑡𝑐 , 𝑐 1 )| U = (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ )| U

(11)

processRead (𝑣 1, 𝑡𝑐 , 𝑐 1, sz)| U = processRead (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ , sz)| U .

(12)

Proof. By Definition D.5, (𝑣, 𝑡, 𝑐)| U 𝑡 = 𝑡 ′ . Consider the following cases:

= (𝑣 ′, 𝑡 ′, 𝑐 ′ )|

U implies that

• sz = 1: By [write-data], (𝑣 1, 𝑐 1 ) = (𝑣, V)

(𝑣 1′ , 𝑐 1′ ) = (𝑣 ′, V).

ℓmeta = (𝑝, 𝑏, 𝑒, U)

checkCap((ℓ, ℓmeta ), 1, rx) By [check-cap], this implies that ℓ ∈ [𝑏, 𝑒). Furthermore, by Definition D.3, (ℓ, ℓmeta ) ∈ rc(𝑟, 𝑚) implies that [𝑏, 𝑒) ⊆ tAddr (𝑟, 𝑚, U), so ℓ ∈ tAddr (𝑟, 𝑚, U). Similarly, since (ℓ, ℓmeta ) ∈ rc(𝑟 ′, 𝑚 ′ ), there should also be ℓ ∈ tAddr (𝑟 ′, 𝑚 ′, U). Denote 𝑚 = (𝑚 d, 𝑚 t ), 𝑚 ′ = (𝑚 d′ , 𝑚 t′ ). Since (𝑚, 𝑟 ) ≃pub (𝑚 ′, 𝑟 ′ ), by Definition D.6, there should be. processRead (𝑚 d [ℓ], U, 𝑚 t [ℓ/2], 1)

Thus, (𝑣, 𝑡𝑐 , 𝑐)| U = (𝑣 ′, 𝑡𝑐 , 𝑐 ′ )| U implies (11). By [read-data], processRead (𝑣 1, 𝑡𝑐 , 𝑐 1, sz) = (𝑣 1, 𝑡𝑐 , V) = (𝑣, 𝑡𝑐 , V) processRead (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ , sz) = (𝑣 1′ , 𝑡𝑐 , V) = (𝑣 ′, 𝑡𝑐 , V). Thus, (12) holds. • sz = 2 and 𝑡 = 𝑡 ′ = U: In this case, (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U implies that 𝑣 = 𝑣 ′ , and 𝑐 = 𝑐 ′ . Then, there should be 𝑣 1 = 𝑣 1′ and 𝑐 1 = 𝑐 1′ , which implies (11). Denote 𝑣 = 𝑣 ′ = (𝑣 0, 𝑣𝑚 ). Consider the following two cases: – 𝑐 = 𝑐 ′ = V and 𝑣𝑚 ≠ 0: By Lemma D.10, (𝑣, 𝑡𝑐 , 𝑐) = processRead (𝑣 1, 𝑡𝑐 , 𝑐 1, sz) (𝑣 ′, 𝑡𝑐 , 𝑐 ′ ) = processRead (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ , sz). Since (𝑣, 𝑡𝑐 , 𝑐)| U = (𝑣 ′, 𝑡𝑐 , 𝑐 ′ )| U , (12) holds. – 𝑐 = 𝑐 ′ = C or 𝑣𝑚 = 0: By Lemma D.10, (𝑣, 𝑡 ⊓ 𝑡𝑐 , 𝑐) = processRead (𝑣 1, 𝑡𝑐 , 𝑐 1, sz) ′

𝑡 =U 𝑐 =C

= processRead (𝑚 d′ [ℓ], U, 𝑚 t′ [ℓ/2], 1). By [read-data], this implies that 𝑚 d [ℓ] = 𝑚 d′ [ℓ]. Therefore, there should be instr = readMemInst (𝑚 ′, ((ℓ, ℓmeta ), 𝑡, 𝑐)). □

D.2

HW Functional Correctness

Definition D.7 (Well-formed HW Configuration). A HW configuration 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇) is well-formed if for all 𝑖 ∈ dom(𝜌), entry 𝜌 (𝑖) is well-formed (denoted as wf (𝑚, 𝑟, 𝜌, 𝑖)) so that the following statement holds: Let 𝑟𝑖 = apl(𝜌 |𝑖 , 𝑟 ). There must exist instr ∈ Instr such that instr = readMemInst (𝑚, ⟦pc⟧𝑟𝑖 ) such that one of the following cases holds: • instr ∉ {beqz 𝑥, ℓ, jmp 𝑒} and 𝜌 (𝑖) = instr@𝜀. • instr = 𝑥 ← 𝑒, 𝑥 ≠ pc, and 𝜌 (𝑖) = 𝑥 ← 𝑣@𝜀 and 𝑣 = ⟦𝑒⟧𝑟𝑖 ≠ ⊥. • instr = load 𝑥, 𝑒, sz, 𝑥 ≠ pc, then

(𝑣 , 𝑡 ′ ⊓ 𝑡𝑐 , 𝑐 ′ ) = processRead (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ , sz).

𝜌 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗)

Since (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U and 𝑡 = 𝑡 ′ = U = 𝑡 ⊓ 𝑡𝑐 = 𝑡 ′ ⊓ 𝑡𝑐 , (12) holds.

checkCap((ℓ, ℓmeta ), sz, ro),

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑖

Song et al.

and either 𝜌 ( 𝑗) = store (𝑣 , 𝑡 , 𝑐 ), ′

 ′ ′ ℓ , ℓmeta , sz @𝜀

Consider the following cases for 𝑑: Case 𝑑 = fetch. By rules for fetch steps, there should be

[ℓ, ℓ + sz) = [ℓ ′, ℓ ′ + sz ′ )

𝑚1 = 𝑚0

(𝑣, 𝑡, 𝑐) = processRead (𝑣 ′, 𝑡𝑐 , 𝑐 ′, sz)

𝜌 1 = 𝜌 0 [sup dom(𝜌 0 ) + 1 ↦→ instr@𝜏].

ℓmeta = (_, _, _, 𝑡𝑐 )  ′ ′ ∀𝑗 < 𝑘 < 𝑖, 𝜌 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅ or 𝑗 = ⊥ ∨ 𝑗 < min dom(𝜌)

Note that dom(𝜌 1 ) = dom(𝜌 0 ) ∪ {sup dom(𝜌 0 ) + 1}. For all 𝑖 ∈ dom(𝜌 0 ) ∩ dom(𝜌 1 ), there should be 𝑖 < sup dom(𝜌 0 ) + 1. Furthermore, for all 𝑗 < 𝑖, 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). By the definition of apl, there should be apl(𝜌 0 |𝑖 , 𝑟 0 ) = apl(𝜌 1 |𝑖 , 𝑟 1 ). Thus, apl(𝜌 0 |𝑖 , 𝑟 0 ) ≺ apl(𝜌 1 |𝑖 , 𝑟 1 ). Case 𝑑 = exec 𝑖. By rules for execute steps, there should be

(𝑣, 𝑡, 𝑐) = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz)

𝑚1 = 𝑚0

ℓmeta = (_, _, _, 𝑡𝑐 ) ′ ∀𝑘 < 𝑖, 𝜌 (𝑘) = store _, ℓ ′, ℓmeta , sz ′ @𝜏 ⇒



[ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅ 𝑚 = (𝑚 d, 𝑚 t ). • instr = store 𝑥, 𝑒, sz, then 𝜌 (𝑖) = store (𝑣 ′, 𝑡, 𝑐 ′ ), (ℓ, ℓmeta ), sz@𝜀 ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑖 checkCap((ℓ, ℓmeta ), sz, rw)

apl(𝜌 0 |𝑖+1, 𝑟 0 ) = aplinst (𝜌 0 (𝑖), apl(𝜌 0 |𝑖 , 𝑟 0 ))

(𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) • instr = jmp 𝑒, 𝜌 (𝑖) = pc ← (ℓ ′, U, C)@𝜏, and 𝜏 = 𝜀 ⇒ ⟦𝑒⟧𝑟𝑖 = (ℓ ′, _, C). • instr = beqz 𝑥, ℓ ′′ , 𝜌 (𝑖) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜏, and 𝜏 = 𝜀 implies that 𝑧 = (𝑣 = 0) ? ℓ ′′ : 1

(𝑣, _, _) = ⟦𝑥⟧𝑟𝑖

(ℓ ′, 𝑡, 𝑐) = ⟦pc ✚ 𝑧⟧𝑟𝑖 .

Definition D.8 (Initial HW State). An initial HW state is of the form (𝑚, 𝑟, 𝜌 init, 𝜇init ) where • 𝑚 and 𝜇 init are arbitrary memory and microarchitectural contexts • 𝜌 init is empty, i.e., dom(𝜌) = ∅ • 𝑟 is a total register map (i.e., for all registers 𝑥, 𝑟 (𝑥) ≠ ⊥), and 𝑟 (pc) = (ep, U, C) where ep is the program capability pointing to the entrypoint of the program. • The state is well-formed in the sense of Definition D.7. Lemma D.13 (apl(𝜌 |𝑖 , 𝑟 ) invariant). Let 𝐶𝑖 = (𝑚𝑖 , 𝑟𝑖 , 𝜌𝑖 , 𝜇𝑖 ), 𝑖 ∈ {0, 1}, be two HW configurations such that 𝐶 0 is well-formed and 𝐶0 → − 𝐶 1 . For all 𝑖 ∈ dom(𝜌 0 ) ∩ dom(𝜌 1 ), denote 𝑟𝑖0 = apl(𝜌 0 |𝑖 , 𝑟 0 ), 𝑟𝑖1 = apl(𝜌 1 |𝑖 , 𝑟 1 ), then ∀𝑥, 𝑟𝑖0 [𝑥] ≠ ⊥ ⇒ 𝑟𝑖0 [𝑥] = 𝑟𝑖1 [𝑥]. For convenience, we denote 𝑟𝑖0 ≺ 𝑟𝑖1 if the above statement holds. Proof. By [step], there should be 𝜇 ′ = update(𝜇0, 𝜌 0 | U ) (𝑚 0, 𝑟 0, 𝜌 0, 𝜇 ′ ) → 𝑑 = next (𝜇 ) − (𝑚 1, 𝑟 1, 𝜌 1, 𝜇1 ) ′

𝑑

(𝑚 0, 𝑟 0, 𝜌 0, 𝜇0 ) → − (𝑚 1, 𝑟 1, 𝜌 1, 𝜇1 ) 𝑑

𝑟1 = 𝑟0 .

We prove a stronger statement: for all 𝑗 ≤ max(dom(𝜌 0 )∩dom(𝜌 1 )), apl(𝜌 0 | 𝑗 , 𝑟 0 ) ≺ apl(𝜌 1 | 𝑗 , 𝑟 1 ). If 𝑗 ≤ 𝑖, then for all 𝑘 < 𝑗 ≤ 𝑖, 𝜌 0 (𝑘) = 𝜌 1 (𝑘). Then, apl(𝜌 0 | 𝑗 , 𝑟 0 ) = apl(𝜌 1 | 𝑗 , 𝑟 1 ). Thus, there should be apl(𝜌 0 | 𝑗 , 𝑟 0 ) ≺ apl(𝜌 1 | 𝑗 , 𝑟 1 ). Next, we use induction to prove that the statement holds for all 𝑗 where 𝑖 < 𝑗 ≤ max(dom(𝜌 0 ) ∩ dom(𝜌 1 )). Base case. Consider 𝑗 = 𝑖 + 1. by rules for execute steps, there should be 𝑖 ∈ dom(𝜌 0 ) and 𝑖 ∈ dom(𝜌 1 ). Then, by unfolding the recursive definition of apl, we can get

(𝑣, 𝑡, 𝑐) = ⟦𝑥⟧𝑟𝑖 ℓmeta = (_, _, _, 𝑡𝑐 ).

𝑟1 = 𝑟0

apl(𝜌 1 |𝑖+1, 𝑟 1 ) = aplinst (𝜌 1 (𝑖), apl(𝜌 1 |𝑖 , 𝑟 1 )). Denote 𝑟𝑖0 = apl(𝜌 0 |𝑖 , 𝑟 0 )

′ 𝑟𝑖0 = apl(𝜌 0 |𝑖+1, 𝑟 0 )

𝑟𝑖1 = apl(𝜌 1 |𝑖 , 𝑟 1 )

′ 𝑟𝑖1 = apl(𝜌 1 |𝑖+1, 𝑟 1 ).

According to what we have proved, 𝑟𝑖0 ≺ 𝑟𝑖1 . We aim to show that ′ ≺ 𝑟′ . 𝑟𝑖0 𝑖1 By rules for execute steps, we just need to consider the following subcases: Subcase 𝜌 0 (𝑖) = 𝑥 ← 𝑒@𝜀 where 𝑥 ≠ pc. By [execute-assign], 𝜌 1 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@𝜀, where (𝑣, 𝑡, 𝑐) = ⟦𝑒⟧apl (𝜌 0 |𝑖 ,𝑟 0 ) . Thus, ′ 𝑟𝑖0 = aplinst (𝜌 0 (𝑖), 𝑟𝑖0 ) = 𝑟𝑖0 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖0 ] [𝑥 ↦→ ⊥] ′ 𝑟𝑖1 = aplinst (𝜌 1 (𝑖), 𝑟𝑖1 ) = 𝑟𝑖1 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖1 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)]. ′ [𝑥 ] ≠ ⊥ ⇒ 𝑟 ′ [𝑥 ] = 𝑟 ′ [𝑥 ]. We aim to prove that for all 𝑥 0 , 𝑟𝑖0 0 𝑖0 0 𝑖1 0 For all registers 𝑥 0 , consider the following cases ′ [pc] = ⟦pc ✚ 1⟧ • 𝑥 0 = pc: Note that 𝑟𝑖0 𝑟𝑖0 = ⊥ iff 𝑟 𝑖0 [pc] = ′ ⟦pc⟧𝑟𝑖0 = ⊥. If 𝑟𝑖0 [pc] ≠ ⊥, then 𝑟𝑖0 [pc] ≠ ⊥. Thus, 𝑟𝑖0 ≺ 𝑟𝑖1 implies that 𝑟𝑖0 [pc] = 𝑟𝑖1 [pc]. Thus, by the definition of ⟦·⟧, ′ [pc] = 𝑟 ′ [pc]. ⟦pc ✚ 1⟧𝑟𝑖0 = ⟦pc ✚ 1⟧𝑟𝑖1 . Therefore, 𝑟𝑖0 𝑖1 ′ [𝑥] = ⊥, so the statement holds. • 𝑥 0 = 𝑥: Note that 𝑟𝑖0 ′ [𝑥 ] = 𝑟 [𝑥 ] and 𝑟 ′ [𝑥 ] = • 𝑥 0 ∉ {pc, 𝑥 }: In this case, 𝑟𝑖0 0 𝑖0 0 𝑖1 0 𝑟𝑖1 [𝑥 0 ]. Note that 𝑟𝑖0 ≺ 𝑟𝑖1 implies that 𝑟𝑖0 [𝑥 0 ] ≠ ⊥ ⇒ 𝑟𝑖0 [𝑥 0 ] = 𝑟𝑖1 [𝑥 0 ]. Thus, the statement holds. ′ ≺ 𝑟′ . Therefore, 𝑟𝑖0 𝑖1 Subcase 𝜌 0 (𝑖) = pc ← ℓ ′ @(ℓ, ℓmeta ). By [execute-jmp-ok], [executejmp-hazard], [execute-beqz-ok], and [execute-beqz-hazard], either of the following cases should hold:

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

• 𝜌 1 = 𝜌 0 [𝑖 ↦→ pc ← ℓ ′ @𝜀] (branch is not mispredicted): in this case, there should be ′ 𝑟𝑖0 = aplinst (𝜌 0 (𝑖), 𝑟𝑖0 ) = 𝑟𝑖0 [pc ↦→ ℓ ′ ] ′ 𝑟𝑖1 = aplinst (𝜌 1 (𝑖), 𝑟𝑖1 ) = 𝑟𝑖1 [pc ↦→ ℓ ′ ]. ′ [𝑥 ] ≠ ⊥ ⇒ 𝑟 ′ [𝑥 ] = We aim to prove that for all 𝑥 0 , 𝑟𝑖0 0 𝑖0 0 ′ 𝑟𝑖1 [𝑥 0 ]. For all registers 𝑥 0 , consider the following cases ′ [pc] = 𝑟 ′ [pc] = ℓ ′ , so the state– 𝑥 0 = pc: Note that 𝑟𝑖0 𝑖1 ment holds. ′ [𝑥 ] = 𝑟 [𝑥 ], 𝑟 ′ [𝑥 ] = 𝑟 [𝑥 ]. – 𝑥 0 ≠ pc: Note that 𝑟𝑖0 0 𝑖0 0 𝑖1 0 𝑖1 0 Furthermore, 𝑟𝑖0 ≺ 𝑟𝑖1 implies that 𝑟𝑖0 [𝑥 0 ] ≠ ⊥ ⇒ 𝑟𝑖0 [𝑥 0 ] = 𝑟𝑖1 [𝑥 0 ]. Thus, the statement holds. • 𝜌 1 = 𝜌 0 |𝑖+1 [𝑖 ↦→ pc ← ℓ0 @𝜀] where ℓ0 ≠ ℓ ′ (branch is mispredicted): Note that in this case, max(dom(𝜌 0 ) ∩ dom(𝜌 1 )) = max dom(𝜌 1 ) = 𝑖 < 𝑗. As we only aim to prove the statement for 𝑖 < 𝑗 ≤ max(dom(𝜌 0 ) ∩ dom(𝜌 1 )), we do not need to reason about this case.

Subcase 𝜌 0 (𝑖) = load 𝑥, 𝑒, sz@𝜀. By [execute-load-fwd] and [execute-load-mem], there always exists (𝑣, 𝑡, 𝑐) and 𝜏 such that 𝜌 1 = 𝜌 0 [𝑖 ↦→ (𝑥 ← (𝑣, 𝑡, 𝑐))@𝜏]. Thus, ′ 𝑟𝑖0 = aplinst (𝜌 0 (𝑖), 𝑟𝑖0 ) = 𝑟𝑖0 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖0 ] [𝑥 ↦→ ⊥] ′ 𝑟𝑖1 = aplinst (𝜌 1 (𝑖), 𝑟𝑖1 ) = 𝑟𝑖1 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖1 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)].

Thus, similar to the subcase for execute an assign instruction, we ′ ≺ 𝑟′ . can derive that 𝑟𝑖0 𝑖1 Subcase 𝜌 0 (𝑖) = store 𝑥, 𝑒, sz@𝜀. By [execute-store-ok] and [execute-store-hazard], either of the following cases will hold:

This implies that 𝑗 ∈ dom(𝜌 0 ) ∧ 𝑗 ≤ min(dom(𝜌 0 ) ∩ dom(𝜌 1 )) iff 𝑗 ∈ dom(𝜌 𝑗 ). Then, if 𝑗 ∈ dom(𝜌 0 ), since we only consider the case where 𝑗 ≤ max(dom(𝜌 0 ) ∩ dom(𝜌 1 )), we have 𝑗 ∈ dom(𝜌 1 ). Similarly, by unfolding the recursive definition of apl, we can get apl(𝜌 0 | 𝑗+1, 𝑟 0 ) = aplinst (𝜌 0 ( 𝑗), apl(𝜌 0 | 𝑗 , 𝑟 0 )) = aplinst (𝜌 0 ( 𝑗), 𝑟 𝑗0 ) apl(𝜌 1 | 𝑗+1, 𝑟 1 ) = aplinst (𝜌 1 ( 𝑗), apl(𝜌 1 | 𝑗 , 𝑟 1 )) = aplinst (𝜌 1 ( 𝑗), 𝑟 𝑗1 ). Since 𝑗 > 𝑖, then by rules for execution steps, 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). By discussing the instruction type of 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗), it is straightforward to derive that apl(𝜌 0 | 𝑗+1, 𝑟 0 ) ≺ apl(𝜌 1 | 𝑗+1, 𝑟 1 ). Case 𝑑 = commit. By rules for commit steps, 𝜌 0 is not empty. Denote 𝑖 = min dom(𝜌 0 ). Then, there should be 𝜌 1 = 𝜌 0 \𝑖, which implies that for all 𝑗, 𝜌 1 | 𝑗 = 𝜌 0 | 𝑗 \𝑖. Furthermore, there should be dom(𝜌 0 ) ∩ dom(𝜌 1 ) = dom(𝜌 0 )\{𝑖} = dom(𝜌 1 ). If dom(𝜌 0 ) ∩ dom(𝜌 1 ) = ∅, the statement automatically hold. We consider the case where dom(𝜌 0 ) ∩ dom(𝜌 1 ) ≠ ∅. It suffices to prove that for all 𝑗 ∈ dom(𝜌 0 ) ∩ dom(𝜌 1 ), apl(𝜌 0 | 𝑗 , 𝑟 0 ) = apl(𝜌 1 | 𝑗 , 𝑟 1 ). Note that there should be 𝑗 > 𝑖, so apl(𝜌 0 | 𝑗 , 𝑟 0 ) = apl(𝜌 0 | 𝑗 \𝑖, aplinst (𝜌 0 (𝑖), 𝑟 0 )) = apl(𝜌 1 | 𝑗 , aplinst (𝜌 0 (𝑖), 𝑟 0 )).

′ 𝑟𝑖0 = aplinst (𝜌 0 (𝑖), 𝑟𝑖0 ) = 𝑟𝑖0 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖0 ]

Hence, we just need to prove that 𝑟 1 = aplinst (𝜌 0 (𝑖), 𝑟 0 ). Depending on the type of the committed instruction, there will be the following two cases: Subcase 𝜌 0 (𝑖) = 𝑥 ← 𝑣@𝜏. By rule [commit-assign] and the definition of aplinst, there should be

′ 𝑟𝑖1 = aplinst (𝜌 1 (𝑖), 𝑟𝑖1 ) = 𝑟𝑖1 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑖1 ].

𝑟 1 = 𝑟 0 [pc ↦→ ⟦pc ✚ 1⟧𝑟 0 ] [𝑥 ↦→ 𝑣] = aplinst (𝜌 0 (𝑖), 𝑟 0 ).

• 𝜌 1 = 𝜌 0 [𝑖 ↦→ (store 𝑣, (ℓ, ℓmeta ), sz)@𝜀] (no load needs to be squashed): in this case, there should be

′ [𝑥 ] ≠ ⊥ ⇒ 𝑟 ′ [𝑥 ] = We aim to prove that for all 𝑥 0 , 𝑟𝑖0 0 𝑖0 0 ′ 𝑟𝑖1 [𝑥 0 ]. For all registers 𝑥 0 , consider the following cases: ′ [pc] = ⟦pc ✚ 1⟧ – 𝑥 0 = pc: Note that 𝑟𝑖0 𝑟𝑖0 = ⊥ iff ′ [pc] ≠ ⊥, then 𝑟 [pc] ≠ ⊥. 𝑟𝑖0 [pc] = ⟦pc⟧𝑟𝑖0 = ⊥. If 𝑟𝑖0 𝑖0

Thus, 𝑟𝑖0 ≺ 𝑟𝑖1 implies that 𝑟𝑖0 [pc] = 𝑟𝑖1 [pc]. Thus, by the definition of ⟦·⟧, ⟦pc ✚ 1⟧𝑟𝑖0 = ⟦pc ✚ 1⟧𝑟𝑖1 . There′ [pc] = 𝑟 ′ [pc]. fore, 𝑟𝑖0 𝑖1 ′ [𝑥 ] = 𝑟 [𝑥 ] and 𝑟 ′ [𝑥 ] = – 𝑥 0 ≠ pc: In this case, 𝑟𝑖0 0 𝑖0 0 𝑖1 0 𝑟𝑖1 [𝑥 0 ]. Note that 𝑟𝑖0 ≺ 𝑟𝑖1 implies that 𝑟𝑖0 [𝑥 0 ] ≠ ⊥ ⇒ 𝑟𝑖0 [𝑥 0 ] = 𝑟𝑖1 [𝑥 0 ]. Thus, the statement holds. • 𝜌 1 = 𝜌 0 | 𝑗 [𝑖 ↦→ (store 𝑣, (ℓ, ℓmeta ), sz)@𝜀] for some 𝑗 > 𝑖 ′ has the same format (some load is squashed): Since 𝑗 > 𝑖, 𝑟𝑖1 as in the last case. Thus, the statement still holds. Induction Step. Consider 𝑗 such that 𝑖 < 𝑗 ≤ max(dom(𝜌 0 ) ∩ dom(𝜌 1 )). Denote 𝑟 𝑗0 = apl(𝜌 0 | 𝑗 , 𝑟 0 ), 𝑟 𝑗1 = apl(𝜌 1 | 𝑗 , 𝑟 1 ) and assume 𝑟 𝑗0 ≺ 𝑟 𝑗1 . We aim to prove that apl(𝜌 0 | 𝑗+1, 𝑟 0 ) ≺ apl(𝜌 1 | 𝑗+1, 𝑟 1 ). If 𝑗 ∉ dom(𝜌 0 ) = dom(𝜌 1 ), then 𝜌 0 | 𝑗+1 = 𝜌 0 | 𝑗 and 𝜌 1 | 𝑗+1 = 𝜌 1 | 𝑗 . Thus, the statement holds. By rules for execute step, there should be • either dom(𝜌 0 ) = dom(𝜌 1 ) (if no hazard) • or there exists 𝑘 such that { 𝑗 : 𝑗 < 𝑘 ∧ 𝑗 ∈ dom(𝜌 0 )} = dom(𝜌 1 ) (if hazard).

Subcase 𝜌 0 (𝑖) = store (𝑣, 𝑡, 𝑐), (ℓ, ℓmeta ), sz@𝜀. By rule [commitstore] and the definition of aplinst, there should be 𝑟 1 = 𝑟 0 [pc ↦→ ⟦pc ✚ 1⟧𝑟 0 ] = aplinst (𝜌 0 (𝑖), 𝑟 0 ). □ Lemma D.14. Let 𝑟 0 and 𝑟 1 be two register files such that 𝑟 0 ≺ 𝑟 1 . For all 𝑒, if ⟦𝑒⟧𝑟 0 ≠ ⊥, then ⟦𝑒⟧𝑟 0 = ⟦𝑒⟧𝑟 1 . Proof. By induction on the syntax of 𝑒.

Lemma D.15. For all 𝜌, 𝑟 , 𝑒 such that ⟦𝑒⟧aplsan(𝜌,𝑟 ) ≠ ⊥, then there must be ⟦𝑒⟧aplsan(𝜌,𝑟 ) = ⟦𝑒⟧apl (𝜌,𝑟 ) . Proof. If dom(𝜌) = ∅, then by definition, aplsan(𝜌, 𝑟 ) = apl(𝜌, 𝑟 ). Thus, the statement holds. If dom(𝜌) ≠ ∅, then aplsan(𝜌, 𝑟 ) = apl(𝜌, 𝑟 )| U . By induction on 𝑒, it is straightforward to derive that ⟦𝑒⟧aplsan(𝜌,𝑟 ) = ⟦𝑒⟧apl (𝜌,𝑟 ) | U . Consider the following cases: • ⟦𝑒⟧apl (𝜌,𝑟 ) = (𝑣, T, 𝑐): Then, ⟦𝑒⟧aplsan(𝜌,𝑟 ) = ⊥, and we do not consider this case. • ⟦𝑒⟧apl (𝜌,𝑟 ) = (𝑣, U, 𝑐): Then, ⟦𝑒⟧aplsan(𝜌,𝑟 ) = (𝑣, U, 𝑐) = ⟦𝑒⟧apl (𝜌,𝑟 ) . • ⟦𝑒⟧apl (𝜌,𝑟 ) = ⊥: Then, ⟦𝑒⟧aplsan(𝜌,𝑟 ) = ⊥, and we do not consider this case.

Song et al.

Therefore, the statement holds.

Lemma D.16 (Well-formed Prefix Buf). For all 𝑚, 𝑟 , 𝜌, 𝑖, wf (𝑚, 𝑟, 𝜌 |𝑖+1, 𝑖)

wf (𝑚, 𝑟, 𝜌, 𝑖)

Proof. It is straightforward to derive this from Definition D.7. □

Case 𝑑 = exec 𝑖. We aim to prove that for all 𝑗 ∈ dom(𝜌 1 ), wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). We discuss based on the range of 𝑗 (𝑗 < 𝑖, 𝑗 = 𝑖, and 𝑗 > 𝑖). Subcase 𝑗 < 𝑖. By rules for execute steps, there should be 𝑚0 = 𝑚1

Proof. Denote 𝐶 1 = (𝑚 1, 𝑟 1, 𝜌 1, 𝜇1 ). By [step], there should be 𝑑 = next (𝜇 ′ )

𝜇 ′ = update(𝜇0, 𝜌 0 | U ) (𝑚 0, 𝑟 0, 𝜌 0, 𝜇 ′ ) → − (𝑚 1, 𝑟 1, 𝜌 1, 𝜇1 ) 𝑑

(𝑚 0, 𝑟 0, 𝜌 0, 𝜇0 ) → − (𝑚 1, 𝑟 1, 𝜌 1, 𝜇1 ) 𝑑

Consider the following cases for 𝑑: Case 𝑑 = fetch. By rules for fetch steps, there must exist instr such that instr = readMemInst (𝑚 0, ⟦pc⟧aplsan(𝜌 0 ,𝑟 0 ) ). Note that this implies that ⟦pc⟧aplsan(𝜌 0 ,𝑟 0 ) ≠ ⊥. By Lemma D.15, there should be instr = readMemInst (𝑚 0, ⟦pc⟧apl (𝜌 0 ,𝑟 0 ) ). Furthermore, there should also be 𝑚 1 = 𝑚 0 , 𝑟 1 = 𝑟 0 . Denote 𝑖 = sup dom(𝜌). Consider the following two cases: Subcase instr ∉ {beqz 𝑥, ℓ ′′, jmp 𝑒}. By [fetch-other], we have 𝜌 1 = 𝜌 0 [𝑖 + 1 ↦→ instr@𝜀]. This implies that 𝜌 0 = 𝜌 1 |𝑖+1 . For all 𝑗 ∈ dom(𝜌 1 ), consider the following two cases: • 𝑗 ≤ 𝑖: By Definition D.7, 𝑆 0 is well-formed implies that wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗). Thus, by the equivalence we have shown above, there should be wf (𝑚 1, 𝑟 1, 𝜌 1 |𝑖+1, 𝑗). By Lemma D.16, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). • 𝑗 = 𝑖: Since 𝜌 1 (𝑖) = instr@𝜀, then by Definition D.7, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖). Subcase instr ∈ {beqz 𝑥, ℓ ′′, jmp 𝑒}. By [fetch-branch-predictpc] and [read-mem-inst], we have ′

𝜌 1 = 𝜌 0 [𝑖 + 1 ↦→ pc ← ((predPc(𝜇 ), ℓmeta ), 𝑡, 𝑐)@(ℓ, ℓmeta )] ((ℓ, ℓmeta ), 𝑡, 𝑐) = ⟦pc⟧aplsan(𝜌 0 ,𝑟 0 ) 𝑡 = U.

𝜌 0 |𝑖 = 𝜌 1 |𝑖 .

Then, for all 𝑗 < 𝑖, 𝑗 ∈ dom(𝑏𝑢 𝑓1 ) (naturally, there should also be 𝑗 ∈ dom(𝜌 0 )), we have wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) ⇔ wf (𝑚 0, 𝑟 0, 𝜌 0 | 𝑗+1, 𝑗) ⇔ wf (𝑚 1, 𝑟 1, 𝜌 1 | 𝑗+1, 𝑗)

Lemma D.17 (HW State Well-formed Invariant). Let 𝐶 0 , 𝐶 1 be two HW configurations such that 𝐶 0 is a well-formed HW config and 𝐶 0 → − 𝐶 1 . Furthermore, denote 𝐶 0 = (𝑚 0, 𝑟 0, 𝜌 0, 𝜇0 ), assume there is also wf (𝑚 0, 𝑟 0 ). Then, 𝐶 1 is also a well-formed HW config.

𝑟0 = 𝑟1

⇔ wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Since 𝑆 0 is well-formed, then wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗). Thus, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase 𝑗 = 𝑖. By rules for execute steps, there should be 𝜌 0 |𝑖 = 𝜌 1 |𝑖

𝑖 ∈ 𝜌 0, 𝜌 1

𝑟0 = 𝑟1

𝑚0 = 𝑚1 .

Hence, we can denote 𝑟𝑖 = apl(𝜌 0 |𝑖 , 𝑟 0 ) = apl(𝜌 1 |𝑖 , 𝑟 1 ). Since 𝑆 0 is well-formed, then wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑖). Hence, there exists instr ∈ Instr such that instr = readMemInst (𝑚 0, ⟦pc⟧𝑟𝑖 ) = readMemInst (𝑚 1, ⟦pc⟧𝑟𝑖 ). To prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗), by rules for execute steps, we just need to consider the following cases: • 𝜌 0 (𝑖) = 𝑥 ← 𝑒@𝜀 and 𝑥 ≠ pc: wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑖) implies that instr = 𝑥 ← 𝑒. By [execute-assign], there should be 𝜌 1 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@𝜀

(𝑣, 𝑡, 𝑐) = ⟦𝑒⟧apl (𝜌 0 |𝑖 ,𝑟 0 ) ≠ ⊥.

Thus, by Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖). • 𝜌 0 (𝑖) = pc ← (ℓ ′, 𝑡 ′, 𝑐 ′ )@(ℓ, ℓmeta ): wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑖) implies that either of the following cases holds: – instr = jmp 𝑒: By [execute-jmp-ok], [execute-jmphazard], and Lemma D.15, there should always be 𝜌 1 (𝑖) = pc ← (ℓ0, U, 𝑐)@𝜀 (ℓ0, 𝑡, 𝑐) = ⟦𝑒⟧aplsan(𝜌 0 |𝑖 ,𝑟 0 ) = ⟦𝑒⟧𝑟𝑖 . By Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖) holds. – instr = beqz 𝑥, ℓ ′′ : By [execute-beqz-ok], [executebeqz-hazard], and Lemma D.15, there should always be 𝜌 1 (𝑖) = pc ← ℓ0 @𝜀

This implies that 𝜌 0 = 𝜌 1 |𝑖+1 . For all 𝑗 ∈ dom(𝜌 1 ), consider the following two cases: • 𝑗 ≤ 𝑖: we can prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗) using the similar reasoning for the non-branch case. • 𝑗 = 𝑖: Note that ′

𝜌 1 (𝑖) = pc ← ((predPc(𝜇 ), ℓmeta ), 𝑡, 𝑐)@(ℓ, ℓmeta ) 𝑡 = U. By Definition D.7, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖).

(𝑣, _, _) = ⟦𝑥⟧aplsan(𝜌 0 |𝑖 ,𝑟 0 ) = ⟦𝑥⟧𝑟𝑖 𝑧 = (𝑣 = 0) ? ℓ ′′ : 1 ℓ0 = ⟦pc ✚ 𝑧⟧𝑟𝑖 . By Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖) holds. • 𝜌 0 (𝑖) = load 𝑥, 𝑒, sz@𝜀 By [execute-load-mem] and [executeload-fwd], either of the following cases should hold: – Load data is read from memory (i.e., [execute-loadmem] applies): In this case, (also by Lemma D.15) there

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

should be 𝜌 1 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥) ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C) = ⟦𝑒⟧aplsan(𝜌 0 |𝑖 ,𝑟 0 ) = ⟦𝑒⟧𝑟𝑖 checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ro) (𝑣, 𝑡, 𝑐) = processRead (𝑚 d [ℓ, ℓ + 𝑠𝑧], 𝑡𝑐 , 𝑚 t [ℓ/2], sz)  ′ ′ ∀𝑗 < 𝑖, 𝜌 0 ( 𝑗) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅

• instr = 𝑥 ← 𝑒, 𝑥 ≠ pc, 𝜌 0 ( 𝑗) = 𝑥 ← 𝑣@𝜀, and 𝑣 = ⟦𝑒⟧𝑟 𝑗 0 ≠ ⊥: By Lemma D.14, ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Furthermore, note that 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). Thus, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). • instr = load 𝑥, 𝑒, sz, 𝑥 ≠ pc: wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) further implies that either of the following cases holds: – Load data is forwarded from an in-flight store: in this case, there should be 𝜌 0 ( 𝑗) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗 ′ )

∀𝑗 < 𝑖, 𝜌 1 ( 𝑗) = 𝜌 0 ( 𝑗)

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0

(𝑚 d, 𝑚 t ) = 𝑚 0 = 𝑚 1

checkCap((ℓ, ℓmeta ), sz, ro)  ′ ′ 𝜌 0 ( 𝑗 ′ ) = store (𝑣 ′, 𝑡 ′, 𝑐 ′ ), ℓ ′, ℓmeta , sz @𝜀

By Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖) holds. – Load data is forwarded from a store (i.e., [executeload-fwd] applies): In this case, (also by Lemma D.15), there should be 𝜌 1 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥) ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C) = ⟦𝑒⟧aplsan(𝜌 0 |𝑖 ,𝑟 0 ) = ⟦𝑒⟧𝑟𝑖 checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ro)  ′ 𝜌 ( 𝑗) = store 𝑣 ′, 𝑡 ′, 𝑐 ′, ℓ ′, ℓmeta , sz@𝜏 [ℓ, ℓ + sz) = [ℓ ′, ℓ ′ + sz ′ ) (𝑣, 𝑡, 𝑐) = processRead (𝑣 ′, 𝑡𝑐 , 𝑐 ′, sz)  ′ ′ 𝑗 = max{ 𝑗 < 𝑖 : 𝜌 ( 𝑗) = store _, ℓ ′, ℓmeta , sz @𝜏 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) ≠ ∅} Note that the last line implies that  ′ ′ ∀𝑗 < 𝑘 < 𝑖, 𝜌 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∧ [ℓ ′, ℓ ′ + sz ′ ) = ∅. By Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖) holds. • 𝜌 0 (𝑖) = store 𝑥, 𝑒, sz@𝜀: By [execute-store-ok], [executestore-hazard], and Lemma D.15, there should always be 𝜌 1 (𝑖) = store 𝑣 ′, 𝑡, 𝑐 ′, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz@𝜀 ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), _, C) = ⟦𝑒⟧aplsan(𝜌 0 |𝑖 ,𝑟 0 ) = ⟦𝑒⟧𝑟𝑖 checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, rw) (𝑣, 𝑡, 𝑐) = ⟦𝑥⟧𝑟𝑖 (𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz). By Definition D.7, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑖) holds. Subcase 𝑗 > 𝑖. By rules for execute steps, 𝑗 > 𝑖 and 𝑗 ∈ dom(𝜌 1 ) implies that 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). Since 𝑆 0 is well-formed, then wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗). Denote 𝑟 𝑗0 = apl(𝜌 0 | 𝑗 , 𝑟 0 )

𝑟 𝑗1 = apl(𝜌 1 | 𝑗 , 𝑟 1 ).

By rules for execute steps, there should also be 𝑚 0 = 𝑚 1 and 𝑟 0 = 𝑟 1 . By Definition D.7, there exists instr such that instr = readMemInst (𝑚 0, ⟦pc⟧𝑟 𝑗 0 ). This implies that ⟦pc⟧𝑟 𝑗 0 ≠ ⊥. Furthermore, by Lemma D.13, 𝑟 𝑗0 ≺ 𝑟 𝑗1 . Thus, by Lemma D.14, ⟦pc⟧𝑟 𝑗 0 = ⟦pc⟧𝑟 𝑗 1 . Therefore, instr = readMemInst (𝑚 1, ⟦pc⟧𝑟 𝑗 1 ). Then, we just need to consider the following cases • instr ∉ {beqz 𝑥, ℓ ′′, jmp 𝑒} and 𝜌 0 ( 𝑗) = instr@𝜀: Since 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗), then wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗).

[ℓ, ℓ + sz) = [ℓ ′, ℓ ′ + sz ′ ) ′

(13) ′

(𝑣, 𝑡, 𝑐) = processRead (𝑣 , 𝑡𝑐 , 𝑐 , sz) ℓmeta = (_, _, _, 𝑡𝑐 )  ′ ′ ∀𝑗 ′ < 𝑘 < 𝑗, 𝜌 0 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅. To prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗), it suffices to prove that (13) still holds if we replace 𝜌 0 with 𝜌 1 , and 𝑟 𝑗0 with 𝑟 𝑗1 . Note that 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). By rules for execute steps, since 𝜌 0 ( 𝑗 ′ ) is an executed store instruction, then 𝑗 ′ ≠ 𝑖. Hence, 𝜌 0 ( 𝑗 ′ ) = 𝜌 1 ( 𝑗 ′ ). Furthermore, by Lemma D.14, there should also be ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Thus, we just need to prove that for all 𝑗 such that 𝑗 ′ < 𝑘 < 𝑗, the following holds.  ′ ′ 𝜌 1 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ (14) [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅. We prove by contradiction. Suppose there exists 𝑘, 𝑗 ′ < 𝑘 < 𝑗 such that (14) does not hold. Then, there must be 𝑘 = 𝑖, otherwise 𝜌 1 (𝑘) = 𝜌 0 (𝑘) and (14) should be implied by (13). Furthermore, by [execute-storeok] or [execute-store-hazard], if there is aliasing between the store at entry 𝑖 and the load at entry 𝑗, then entry 𝑗 must be squashed in 𝜌 1 , i.e., 𝑗 ∉ dom(𝜌 1 ). This contradicts our assumption that 𝑗 ∈ dom(𝜌 1 ). Thus, (14) holds. – Load data is loaded from memory or forwarded from a committed store: in this case, there should be 𝜌 0 ( 𝑗) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗 ′ ) ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0 checkCap((ℓ, ℓmeta ), sz, ro) 𝑗 ′ = ⊥ ∨ 𝑗 ′ < min dom(𝜌 0 ) (𝑣, 𝑡, 𝑐) = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz)

(15)

ℓmeta = (_, _, _, 𝑡𝑐 )  ′ ′ ∀𝑘 < 𝑗, 𝜌 0 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅ 𝑚 0 = (𝑚 d, 𝑚 t ). To prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗), it suffices to prove that the above statement still holds if we replace 𝜌 0 with 𝜌 1 , 𝑟 𝑗0 with 𝑟 𝑗1 , and 𝑚 0 with 𝑚 1 . By rules for execution

Song et al.

steps, there should be 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗), min dom(𝜌 0 ) = min dom(𝜌 1 ), and 𝑚 0 = 𝑚 1 . Furthermore, by Lemma D.14, there should also be ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Thus, we just need to prove that for all 𝑗 such that 𝑘 < 𝑗, the following holds.  ′ ′ 𝜌 1 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒ (16) [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅. Similar to the last case, we prove by contradiction. Suppose there exists 𝑘, 𝑘 < 𝑗 such that (16) does not hold. Then, there must be 𝑘 = 𝑖, otherwise 𝜌 1 (𝑘) = 𝜌 0 (𝑘) and (16) should be implied by (15). Furthermore, by [execute-store-ok] or [execute-store-hazard], if there is aliasing between the store at entry 𝑖 and the load at entry 𝑗, then entry 𝑗 must be squashed in 𝜌 1 , i.e., 𝑗 ∉ dom(𝜌 1 ). This contradicts our assumption that 𝑗 ∈ dom(𝜌 1 ). Thus, (16) holds. • instr = store 𝑥, 𝑒, sz: wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) further implies that 𝜌 0 ( 𝑗) = store (𝑣 ′, 𝑡, 𝑐 ′ ), (ℓ, ℓmeta ), sz@𝜀

Since 𝑆 0 is well-formed, there should be wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗). By Definition D.7, there must exist instr ∈ Instr such that instr = readMemInst (𝑚 0, ⟦pc⟧𝑟 𝑗 0 ). We aim to show that instr = readMemInst (𝑚 1, ⟦pc⟧𝑟 𝑗 1 ). Note that, by Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦pc⟧𝑟 𝑗 0 = ⟦pc⟧𝑟 𝑗 1 . Furthermore, consider the following cases regarding the type of the committed instruction in entry 𝜌 0 (𝑖): • 𝜌 0 (𝑖) = 𝑥 ← 𝑣@𝜏 is not a store instruction: by [commitassign], there should be 𝑚 0 = 𝑚 1 . Thus, instr = readMemInst (𝑚 0, ⟦pc⟧𝑟 𝑗 0 ) = readMemInst (𝑚 1, ⟦pc⟧𝑟 𝑗 1 ) • 𝜌 0 (𝑖) = store (𝑣, 𝑡, 𝑐), (ℓ, ℓmeta ), sz@𝜀 is a store instruction: by [commit-store], there should be 𝑚 0 = (𝑚 d, 𝑚 t ) 𝑚 1 = (𝑚 d [[ℓ, ℓ + sz) ↦→ 𝑣], 𝑚 t [ℓ/2 ↦→ 𝑐])

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0 checkCap((ℓ, ℓmeta ), sz, rw) (𝑣, 𝑡, 𝑐) = ⟦𝑥⟧𝑟 𝑗 0 (𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) ℓmeta = (_, _, _, 𝑡𝑐 ). Note that 𝜌 1 ( 𝑗) = 𝜌 0 ( 𝑗). By Lemma D.14, ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 and ⟦𝑥⟧𝑟 𝑗 0 = ⟦𝑥⟧𝑟 𝑗 1 . Furthermore, 𝜌 1 ( 𝑗) = 𝜌 0 ( 𝑗). Thus, the above statement still holds if we replace 𝜌 0 with 𝜌 1 , and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). • instr = jmp 𝑒: wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) further implies that 𝜌 0 ( 𝑗) = pc ← (ℓ ′, U, 𝑐)@𝜏 𝜏 = 𝜀 ⇒ ⟦𝑒⟧𝑟 𝑗 0 = (ℓ ′, _, 𝑐). By Lemma D.14, ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Furthermore, note that 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗). Thus, the above statement still holds if we replace 𝜌 0 with 𝜌 1 , and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). • instr = beqz 𝑥, ℓ ′′ : wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) further implies that 𝜌 0 ( 𝑗) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜏

store _, _, sz = readMemInst (𝑚 0, ⟦pc⟧𝑟 0 )

(17)

checkCap((ℓ, ℓmeta ), sz, rw).

(18)

 ′ Denote ⟦pc⟧𝑟 0 = ( ℓ ′, ℓmeta , _, _). By [read-mem-inst],  ′ there should be checkCap( ℓ ′, ℓmeta , 1, rx). This implies that [ℓ ′, ℓ ′ + 1) ∈ pAddr (𝑟 0, 𝑚 0, rx). Furthermore, the above statements also implies that [ℓ, ℓ + sz) ∈ pAddr (𝑟 0, 𝑚 0, rw). By Definition D.4, the ISA state (𝑚 0, 𝑟 0 ) is well-formed implies that pAddr (𝑟 0, 𝑚 0, rw) ∩ pAddr (𝑟 0, 𝑚 0, rx) = ∅ Thus, [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + 1) = ∅. Therefore, instr = readMemInst (𝑚 0, ⟦pc⟧𝑟 𝑗 0 ) = readMemInst (𝑚 1, ⟦pc⟧𝑟 𝑗 1 ).

𝜏 =𝜀 ⇒ ((𝑣, _, _) = ⟦𝑥⟧𝑟 𝑗 0 ∧ (𝑧 = (𝑣 = 0) ? ℓ ′ : 1) ∧ (ℓ ′, 𝑡, 𝑐) = ⟦pc ✚ 𝑧⟧𝑟 𝑗 0 ) By Lemma D.14, ⟦𝑥⟧𝑟 𝑗 0 = ⟦𝑥⟧𝑟 𝑗 1 . Furthermore, since 𝜌 0 ( 𝑗) = 𝜌 1 ( 𝑗), then the above statement still holds if we replace 𝜌 0 with 𝜌 1 , and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Case 𝑑 = commit. Denote 𝑖 = min dom(𝜌). By rules for commit steps, there should be 𝜌 1 = 𝜌 0 \𝑖. Furthermore, for all 𝑗 ∈ dom(𝜌 1 ), there should be 𝜌 1 ( 𝑗) = 𝜌 0 ( 𝑗). We aim to prove that wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Let 𝑟 𝑗0 = apl(𝜌 0 | 𝑗 , 𝑟 0 )

Note that 𝑆 0 is well-formed, so wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑖). By Definition D.7, this implies that

𝑟 𝑗1 = apl(𝜌 1 | 𝑗 , 𝑟 1 ).

By Lemma D.13, there should be 𝑟 𝑗0 ≺ 𝑟 𝑗1 .

To prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗), we just need to consider the following cases: Subcase instr ∉ {beqz 𝑥, ℓ ′′, jmp 𝑒}. wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝜌 0 ( 𝑗) = instr@𝜀. Note that 𝜌 1 ( 𝑗) = 𝜌 0 ( 𝑗) = instr@𝜀, so by Definition D.7 there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase instr = 𝑥 ← 𝑒. wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝑥 ≠ pc and 𝜌 0 ( 𝑗) = 𝑥 ← 𝑣@𝜀 where 𝑣 = ⟦𝑒⟧𝑟 𝑗 0 ≠ ⊥. By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Thus, the above statement still holds if we replace 𝜌 0 with 𝜌 1 and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase instr = load 𝑥, 𝑒, sz. wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝑥 ≠ pc and either of the following cases holds:

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

• Load data is forwarded from an in-flight store: in this case there should be

• Load data is loaded from memory or forwarded from a committed store: in this case, there should be 𝜌 0 ( 𝑗) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗 ′ )

𝜌 0 ( 𝑗) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗 ′ )

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0

checkCap((ℓ, ℓmeta ), sz, ro) 𝑗 ′ = ⊥ ∨ 𝑗 ′ < min dom(𝜌 0 )

checkCap((ℓ, ℓmeta ), sz, ro) 𝜌 0 ( 𝑗 ) = store (𝑣 , 𝑡 , 𝑐 ), ′

 ′ ′ ℓ , ℓmeta , sz @𝜀

[ℓ, ℓ + sz) = [ℓ , ℓ + sz )

(19)

(𝑣, 𝑡, 𝑐) = processRead (𝑚 0d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 0t [ℓ/2], sz)  ′ ′ ∀𝑘 < 𝑗, 𝜌 0 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒

(𝑣, 𝑡, 𝑐) = processRead (𝑣 ′, 𝑡𝑐 , 𝑐 ′, sz) ∀𝑗 < 𝑘 < 𝑗, 𝜌 0 (𝑘) = store _, ′

(21)

[ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅

 ′ ′ ℓ , ℓmeta , sz @𝜏 ⇒ ′ ′ ′

𝑚 0 = (𝑚 0d, 𝑚 0t )

[ℓ, ℓ + sz) ∩ [ℓ , ℓ + sz ) = ∅

ℓmeta = (_, _, _, 𝑡𝑐 ).

𝜌 1 ( 𝑗) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗 ′ )

We consider the following cases: – The committed instruction is not a store instruction, i.e., 𝜌 0 (𝑖) = 𝑥 ← 𝑣@𝜏. By [commit-assign], 𝑚 1 = 𝑚 0 . By Lemma D.14, ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Hence, (21) still holds if replacing 𝜌 0 with 𝜌 1 , 𝑚 0 with 𝑚 1 , and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). – The committed instruction is a store instruction, i.e., 𝜌 0 (𝑖) = store _, _, sz@𝜀. By [commit-assign] and (21), there should be

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 1

(𝑣, 𝑡, 𝑐) = processRead (𝑚 0d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 0t [ℓ/2], sz)

By Lemma D.14, ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Consider the following two cases – 𝑗 ′ = 𝑖, i.e., the store where the load data was forwarded from is committed at this step: To prove wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗), we aim to prove the following:

= processRead (𝑚 1d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 1t [ℓ/2], sz).

checkCap((ℓ, ℓmeta ), sz, ro) 𝑗 ′ = ⊥ ∨ 𝑗 ′ < min dom(𝜌 1 ) (𝑣, 𝑡, 𝑐) = processRead (𝑚 1d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 1t [ℓ/2], sz)  ′ ′ ∀𝑘 < 𝑗, 𝜌 1 (𝑘) = store _, ℓ ′, ℓmeta , sz @𝜏 ⇒

(20)

[ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅ 𝑚 1 = (𝑚 1d, 𝑚 1t ) ℓmeta = (_, _, _, 𝑡𝑐 ).

Specifically, (21) implies that the committed store should not affect the load result since there is no aliasing between the committed store at 𝜌 0 (𝑖) and the load at 𝜌 0 ( 𝑗). Thus, the above statement can be deduced by discussing different cases to derive processRead (). Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase instr = store 𝑥, 𝑒, sz. wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝜌 0 (𝑖) = store (𝑣 ′, 𝑡, 𝑐 ′ ), (ℓ, ℓmeta ), sz@𝜀

By [commit-store], as 𝜌 1 = 𝜌 0 \𝑖, then 𝑗 ′ = 𝑖 < min dom(𝜌 1 ) Furthermore, for all 𝑘 ∈ dom(𝜌 1 ), 𝜌 0 (𝑘) = 𝜌 1 (𝑘). By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Furthermore, by [commit-store], we also have

((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟 𝑗 0 checkCap((ℓ, ℓmeta ), sz, rw) (𝑣, 𝑡, 𝑐) = ⟦𝑥⟧𝑟 𝑗 0 (𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) ℓmeta = (_, _, _, 𝑡𝑐 ).

𝑚 1d [ℓ, ℓ + sz] = 𝑣 ′

𝑚 1t [ℓ/2] = 𝑐 ′ .

By the above reasoning, (19) implies (20). Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). – 𝑗 ′ ≠ 𝑖, i.e., the store where the load data was forwarded from is not committed at this step: In this case, since 𝑖 = min dom(𝜌 0 ), there should be 𝑖 < 𝑗 ′ . Hence, for all 𝑘 such that 𝑗 ′ ≤ 𝑘 ≤ 𝑗, there should be 𝜌 0 (𝑘) = 𝜌 1 (𝑘). By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Thus, the above statement still holds if we replace 𝜌 0 with 𝜌 1 and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗).

By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 and ⟦𝑥⟧𝑟 𝑗 0 = ⟦𝑥⟧𝑟 𝑗 1 . Thus, the above statement still holds if we replace 𝜌 0 with 𝜌 1 and 𝑟 𝑗0 with 𝑟 𝑗1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase instr = jmp 𝑒. wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝜌 0 ( 𝑗) = pc ← (ℓ ′, U, 𝑐)@𝜏, and 𝜏 = 𝜀 ⇒ ⟦𝑒⟧𝑟 𝑗 0 = (ℓ ′, _, 𝑐). Furthermore, there should also be ℓ ′ = (_, U, _). If 𝜏 = 𝜀, then ⟦𝑒⟧𝑟 𝑗 0 ≠ ⊥. By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑒⟧𝑟 𝑗 0 = ⟦𝑒⟧𝑟 𝑗 1 . Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗). Subcase instr = beqz 𝑥, ℓ ′′ . wf (𝑚 0, 𝑟 0, 𝜌 0, 𝑗) implies that 𝜌 0 ( 𝑗) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜏, and if 𝜏 = 𝜀, there should be (𝑣, _, _) = ⟦𝑥⟧𝑟 𝑗 0

𝑧 = (𝑣 = 0) ? ℓ ′′ : 1

(ℓ ′, 𝑡, 𝑐) = ⟦pc + 𝑧⟧𝑟 𝑗 0 .

Song et al.

Suppose 𝜏 = 𝜀. Then ⟦𝑥⟧𝑟 𝑗 0 ≠ ⊥ and ⟦pc + 𝑧⟧𝑟 𝑗 0 ≠ ⊥. By Lemma D.14, 𝑟 𝑗0 ≺ 𝑟 𝑗1 implies that ⟦𝑥⟧𝑟 𝑗 0 = ⟦𝑥⟧𝑟 𝑗 1

⟦pc + 𝑧⟧𝑟 𝑗 0 = ⟦pc + 𝑧⟧𝑟 𝑗 1 .

Case 𝑑 = commit. In this case, 𝑘 ′ = 𝑓 ({𝐶 0, . . . , 𝐶𝑛 }) = 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) = 𝑘. So we need to prove that there exists 𝑆𝑘+1 such that

Therefore, there should be wf (𝑚 1, 𝑟 1, 𝜌 1, 𝑗).

𝑜𝑘+1

• 𝑆𝑘 −−−→ 𝑆𝑘+1 and • 𝑆𝑘+1 = (𝑟𝑛+1, 𝑚𝑛+1 ).

□ Theorem D.1 (Functional Correctness). Let 𝑆 0 = {𝑚, 𝑟 } be the initial ISA state and 𝐶 0 = {𝑚, 𝑟, 𝜌 init, 𝜇init } be the initial HW configuration. Assume 𝑆 0 is a well-formed ISA state, and 𝐶 0 is a well-formed HW configuration. Let 𝑜1

By rules for commit steps, dom(𝜌𝑛 ) ≠ ∅. Let 𝑖 = min dom(𝜌𝑛 ). Then, there should be 𝑟𝑛 = apl(𝜌𝑛 |𝑖 , 𝑟𝑛 ). Since 𝐶𝑛 is well-formed, by Definition D.7, there must exist instr ∈ Instr such that instr = readMemInst (𝑚𝑛 , ⟦pc⟧𝑟𝑛 ).

𝑜2

𝑇ISA (𝑆 0 ) = 𝑆 0 −→ 𝑆 1 −→ 𝑆 2 . . . 𝑇𝜇Arch (𝐶 0 ) = 𝐶 0 → − 𝐶1 → − 𝐶2 . . .

Furthermore, by rules for commit steps, the following statements hold: • For instr ∉ {beqz 𝑥, ℓ ′′, jmp 𝑒}, they must be executed, so 𝜌𝑛 (𝑖) = instr ′ @𝜏 and instr ′ ≠ instr, i.e., the first case in Definition D.7 does not hold when the HW configuration performs a commit step. • For instr ∈ {beqz 𝑥, ℓ ′′, jmp 𝑒}, there must be 𝜌𝑛 (𝑖) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜀, i.e., the branch is resolved.

be the ISA and HW execution traces. The HW semantics is functionally correct, i.e., for all 𝑛 ∈ N, 𝑚𝑛 , and 𝑟𝑛 , 𝐶𝑛 = (𝑚𝑛 , 𝑟𝑛 , _, _) ⇒ 𝑆 𝑓 ( {𝐶0 ,𝐶1 ,...,𝐶𝑛−1 } ) = (𝑚𝑛 , 𝑟𝑛 ), where 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) = |{(_, _, 𝜌, 𝜇) ∈ {𝐶 0, . . . , 𝐶𝑛−1 } : next (update(𝜇, 𝜌 | U )) = commit}| counts the number of previous configurations in set {𝐶 0, . . . , 𝐶𝑛−1 } that perform a commit operation. Proof. We prove by induction on 𝑛. For the base case when 𝑛 = 0, the statement automatically holds since 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) = 𝑓 (∅) = 0 𝐶 0 = {𝑚, 𝑟, 𝜌 init, 𝜇init } 𝑆 0 = {𝑚, 𝑟 }. Suppose the statement holds for all 𝑖 ≤ 𝑛, i.e., 𝐶𝑖 = (𝑚𝑖 , 𝑟𝑖 , 𝜌𝑖 , 𝜇𝑖 ) 𝑆 𝑓 ( {𝐶0 ,...,𝐶𝑖 −1 } ) = (𝑚𝑖 , 𝑟𝑖 ). Specifically, we denote 𝑘 = 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) for convenience. We aim to prove that the statement still holds for 𝑛 + 1, i.e., 𝐶𝑛+1 = (𝑚𝑛+1, 𝑟𝑛+1, 𝜌𝑛+1, 𝜇𝑛+1 ) 𝑆𝑘 ′ = (𝑚𝑛+1, 𝑟𝑛+1 )

𝑘 ′ = 𝑓 ({𝐶 0, . . . , 𝐶𝑛 }).

By applying Lemma D.1 repeatedly, we can prove that 𝑆𝑖 is wellformed (i.e., wf (𝑆𝑖 )) for 0 ≤ 𝑖 ≤ 𝑘. Then, by applying Lemma D.17 repeatedly, we can know that 𝐶𝑖 , 𝑖 ≤ 𝑛 is also well-formed. By [step], there should be 𝑑 = next (𝜇 ′ )

𝜇 ′ = update(𝜇𝑛 , 𝜌𝑛 | U ) (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , 𝜇 ′ ) → − (𝑚𝑛+1, 𝑟𝑛+1, 𝜌𝑛+1, 𝜇𝑛+1 ) 𝑑

(𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , 𝜇𝑛 ) → − (𝑚𝑛+1, 𝑟𝑛+1, 𝜌𝑛+1, 𝜇𝑛+1 ) Consider the following cases for 𝑑. Case 𝑑 = fetch or 𝑑 = exec 𝑖. In this case, 𝑘 ′ = 𝑓 ({𝐶 0, . . . , 𝐶𝑛 }) = 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) = 𝑘, so 𝑆𝑘 ′ = 𝑆𝑘 = (𝑚𝑛 , 𝑟𝑛 ). Furthermore, by rules for fetch and execute steps, 𝑚𝑛+1 = 𝑚𝑛 and 𝑟𝑛+1 = 𝑟𝑛 . Thus, (D.2) holds. Case 𝑑 = exec 𝑖. In this case, 𝑘 ′ = 𝑓 ({𝐶 0, . . . , 𝐶𝑛 }) = 𝑓 ({𝐶 0, . . . , 𝐶𝑛−1 }) = 𝑘.

Thus, we only need to consider the following cases: Subcase instr = 𝑥 ← 𝑒. Since 𝐶𝑛 is well-formed, by Definition D.7, there must be 𝑥 ≠ pc and 𝜌𝑛 (𝑖) = 𝑥 ← ⟦𝑒⟧𝑟𝑛 @𝜀. Then, by [commitassign], we have 𝑚𝑛+1 = 𝑚𝑛 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ ⟦𝑒⟧𝑟𝑛 ]. Let   𝑆𝑘+1 = 𝑚𝑛 , 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ ⟦𝑒⟧𝑟𝑛 ] . ()

By [isa-assign], there should be 𝑆𝑘 −→ 𝑆𝑘+1 . Since 𝑥 ≠ pc, there should also be 𝑆𝑘+1 = (𝑚𝑛+1, 𝑟𝑛+1 ). Subcase instr = load 𝑥, 𝑒, sz. Since 𝐶𝑛 is well-formed, by Definition D.7, there must be 𝜌𝑛 (𝑖) = 𝑥 ← (𝑣, 𝑡, 𝑐)@((ℓ, ℓmeta ), sz, 𝑗) ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑛 checkCap((ℓ, ℓmeta ), sz, ro). Definition D.7 also implies that 𝑗 < 𝑖. Recall that by [commitassign], 𝑖 = min dom(𝜌𝑛 ), so there should be (𝑣, 𝑡, 𝑐) = processRead (𝑚 d [ℓ, ℓ + sz], 𝑡𝑐 , 𝑚 t [ℓ/2], sz) 𝑚 = (𝑚 d, 𝑚 t ) ℓmeta = (_, _, _, 𝑡𝑐 ). By [commit-assign], there should be 𝑚𝑛+1 = 𝑚𝑛 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)]. By [read-mem], the above statements also imply that (𝑣, 𝑡, 𝑐) = readMem(𝑚, ((ℓ, ℓmeta ), _, C), sz). Let   𝑆𝑘+1 = 𝑚𝑛 , 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [𝑥 ↦→ (𝑣, 𝑡, 𝑐)] . Since 𝑥 ≠ pc, there should be 𝑆𝑘 = (𝑚𝑛+1, 𝑟𝑛+1 ). Furthermore, by 𝑜𝑘+1 [isa-load], 𝑆𝑘 −−−→ 𝑆𝑘+1 .

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

Subcase instr = store 𝑥, 𝑒, sz. Since 𝐶𝑛 is well-formed, by Definition D.7, there must be 𝜌𝑛 (𝑖) = store (𝑣 ′, 𝑡, 𝑐 ′ ), (ℓ, ℓmeta ), sz@𝜀 ((ℓ, ℓmeta ), _, C) = ⟦𝑒⟧𝑟𝑛 checkCap((ℓ, ℓmeta ), sz, rw)

Untainted projection of instructions in 𝜌 is defined as (pc ← 𝐸)| U (𝑥 ← 𝐸)| U (load 𝑋, 𝐸, sz)| U (store 𝑋, 𝐸, sz)| U

≜ ≜ ≜ ≜

pc ← 𝐸 𝑥 ← 𝐸| U 𝑥 ≠ pc load 𝑋, 𝐸, sz store 𝑋 | U, 𝐸, sz.

Untainted projection of 𝜌 satisfies the following properties: • dom(𝜌 | U ) = dom(𝜌) • ∀𝑖 ∈ dom(𝜌), 𝜌 (𝑖) = instr@𝜏 ⇒ 𝜌 | U (𝑖) = instr | U @𝜏.

(𝑣, 𝑡, 𝑐) = ⟦𝑥⟧𝑟𝑛 (𝑣 ′, 𝑐 ′ ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) ℓmeta = (_, _, _, 𝑡𝑐 ). Denote 𝑚𝑛 = (𝑚 d, 𝑚 t ). By [commit-store], there should be 𝑚𝑛+1 = (𝑚 d [[ℓ, ℓ + sz) ↦→ 𝑣 ′ ], 𝑚 t [[ℓ/2, ⌈(ℓ + sz)/2⌉) ↦→ 𝑐 ′ ]) 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] Let 𝑆𝑘+1 = (writeMem(𝑚𝑛 , ⟦𝑒⟧𝑟𝑛 , sz, ⟦𝑥⟧𝑟𝑛 ), 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ]) By [write-mem], there should be 𝑆𝑘+1 = (𝑚𝑛+1, 𝑟𝑛+1 ), and by [isa𝑜𝑘+1 store], there should be 𝑆𝑘 −−−→ 𝑆𝑘+1 . Subcase instr = jmp 𝑒. According to our previous discussion, there should be 𝜌𝑛 (𝑖) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜀. Since 𝐶𝑛 is well-formed, by Definition D.7, there must be 𝑡 = U and ⟦𝑒⟧𝑟𝑛 = (ℓ ′, _, 𝑐). By [commit-assign], there should be

Definition D.10 (Public Equivalence for HW Configurations). HW configurations 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇), 𝐶 ′ = (𝑚 ′, 𝑟 ′, 𝜌 ′, 𝜇 ′ ) are publicly equivalent, i.e., 𝐶 ≃pub 𝐶 ′ if • (𝑚, 𝑟 ) ≃pub (𝑚 ′, 𝑟 ′ ) • 𝜌 |U = 𝜌 ′ |U • 𝜇 = 𝜇′ Lemma D.18 (apl Capability Monotonicity). Let 𝐶 0 be the initial HW configuration and 𝐶 0 → − 𝐶1 → − ···→ − 𝐶𝑛 be the execution trace. Denote 𝐶𝑛 = (𝑚, 𝑟, 𝜌, 𝜇). For all 𝑖, denote 𝑟𝑖 = apl(𝜌 |𝑖 , 𝑟 ). Then, ∀𝑥, 𝑟𝑖 (𝑥) = ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡)), _, C) ⇒(ℓ, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚)∧ [𝑏, 𝑒) ⊆ tAddr (𝑟, 𝑚, 𝑡)∧ [𝑏, 𝑒) ⊆ pAddr (𝑟, 𝑚, 𝑝).

𝑚𝑛+1 = 𝑚𝑛 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [pc ↦→ (ℓ ′, U, 𝑐)] = 𝑟𝑛 [pc ↦→ (ℓ ′, U, 𝑐)]. Note that the last step holds because the second update on pc overwrites the first one. Let 𝑆𝑘+1 = (𝑚𝑛 , 𝑟𝑛 [pc ↦→ (ℓ ′, U, 𝑐)]). 𝑜𝑘+1

Note that 𝑆𝑘+1 = (𝑚𝑛+1, 𝑟𝑛+1 ). By [isa-jmp], there should be 𝑆𝑘 −−−→ 𝑆𝑘+1 . Subcase instr = beqz 𝑥, ℓ ′ . According to our previous discussion, there should be 𝜌𝑛 (𝑖) = pc ← (ℓ ′, 𝑡, 𝑐)@𝜀. Since 𝐶𝑛 is well-formed, there must be (𝑣, _, _) = ⟦𝑥⟧𝑟𝑛

𝑧 = (𝑣 = 0) ? ℓ ′′ : 1

(ℓ ′, 𝑡, 𝑐) = ⟦pc + 𝑧⟧𝑟𝑛 .

Proof. The proof goes by induction on 𝑛 and applying Lemma D.13. □ Lemma D.19 (Expr Capability Monotonicity). Let 𝐶 0 be the initial HW configuration and 𝐶 0 → − 𝐶1 → − ···→ − 𝐶𝑛 be the execution trace. Denote 𝐶𝑛 = (𝑚, 𝑟, 𝜌, 𝜇). For all 𝑖, denote 𝑟𝑖 = apl(𝜌 |𝑖 , 𝑟 ). Then, ∀𝑒, ⟦𝑒⟧𝑟𝑖 = ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡)), _, C) ⇒(ℓ, (𝑝, 𝑏, 𝑒, 𝑡)) ∈ rc(𝑟, 𝑚)∧ [𝑏, 𝑒) ⊆ tAddr (𝑟, 𝑚, 𝑡)∧ [𝑏, 𝑒) ⊆ pAddr (𝑟, 𝑚, 𝑝).

By [commit-assign], there should be 𝑚𝑛+1 = 𝑚𝑛 𝑟𝑛+1 = 𝑟𝑛 [pc ↦→ ⟦pc ✚ 1⟧𝑟𝑛 ] [pc ↦→ (ℓ ′, 𝑡, 𝑐)] = 𝑟𝑛 [pc ↦→ (ℓ ′, 𝑡, 𝑐)]. Note that the last step holds because the second update on pc overwrites the first one. Let 𝑆𝑘+1 = (𝑚𝑛 , 𝑟𝑛 [pc ↦→ (ℓ ′, 𝑡, 𝑐)]). Note that 𝑆𝑘+1 = (𝑚𝑛+1, 𝑟𝑛+1 ). By [isa-beqz], there should be 𝑜𝑘+1 𝑆𝑘 −−−→ 𝑆𝑘+1 . □

D.3

Relative Speculative Constant Time

Definition D.9 (Untainted Projection of Reorder Buffer). Operands of in-flight instructions in reorder buffer 𝜌 have three formats, and their untainted projection is defined as 𝑒 |U

(𝑣, 𝑡, 𝑐)| U

⊥| U

𝑒(

(𝑣, U, 𝑐) ⊥ ⊥

𝑡 =U 𝑡 =T

Proof. The proof goes by applying Lemma D.18 and Lemma D.5. □ Lemma D.20 (apl Public Eqivalence). Let 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇) and 𝐶 ′ = (𝑚 ′, 𝑟 ′, 𝜌 ′, 𝜇 ′ ) be two HW configurations such that 𝐶 ≃pub 𝐶 ′ . For all 𝑖, denote 𝑟𝑖 = apl(𝜌 |𝑖 , 𝑟 ), 𝑟𝑖′ = apl(𝜌 ′ |𝑖 , 𝑟 ′ ). Then, ∀𝑥, ∃𝑣 ≠ ⊥, 𝑟𝑖 (𝑥) = 𝑣 ⇒ ∃𝑣 ′ ≠ ⊥, 𝑟𝑖′ (𝑥) = 𝑣 ′ ∧ 𝑣 | U = 𝑣 ′ | U .

Proof. The proof goes by induction on 𝑖 and Definition D.10. □ Lemma D.21 (Expr Public Eqivalence). Let 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇) and 𝐶 ′ = (𝑚 ′, 𝑟 ′, 𝜌 ′, 𝜇 ′ ) be two HW configurations such that 𝐶 ≃pub 𝐶 ′ . For all 𝑖, denote 𝑟𝑖 = apl(𝜌 |𝑖 , 𝑟 ), 𝑟𝑖′ = apl(𝜌 ′ |𝑖 , 𝑟 ′ ). Then, ∀𝑒, ∃𝑣 ≠ ⊥, ⟦𝑒⟧𝑟𝑖 = 𝑣 ⇒ ∃𝑣 ′ ≠ ⊥, ⟦𝑒⟧𝑟 ′ = 𝑣 ′ ∧ 𝑣 | U = 𝑣 ′ | U . 𝑖

Proof. The proof goes by applying Lemma D.20.

Song et al.

Lemma D.22 (Auxiliary lemma for aplsan eqivalence). Let 𝜌 and 𝜌 ′ be buffers such that 𝜌 | U = 𝜌 ′ | U , and 𝑟, 𝑟 ′ be two partial register files such that 𝑟 | U = 𝑟 ′ | U . We have apl(𝜌, 𝑟 )| U = apl(𝜌 ′, 𝑟 ′ )| U . Proof. The proof is by induction on the size of the domain of 𝜌. If it is empty, by 𝜌 | U = 𝜌 ′ | U , the domain of 𝜌 ′ must also be empty. In the inductive case, by the inductive hypothesis, it suffices to observe that: aplinst (𝜌 (min dom(𝜌)), 𝑟 )| U = aplinst (𝜌 ′ (min dom(𝜌 ′ )), 𝑟 ′ )| U . The conclusion follows by case analysis on min dom(𝜌).

Lemma D.23 (Expr aplsan Eqivalence). Let 𝐶 = (𝑚, 𝑟, 𝜌, 𝜇) and 𝐶 ′ = (𝑚 ′, 𝑟 ′, 𝜌 ′, 𝜇 ′ ) be two HW configurations such that 𝐶 ≃pub 𝐶 ′ . For all 𝑖, denote 𝑟𝑖 = aplsan(𝜌 |𝑖 , 𝑟 ), 𝑟𝑖′ = aplsan(𝜌 ′ |𝑖 , 𝑟 ′ ). Then, ∀𝑒, ⟦𝑒⟧𝑟𝑖 = (_, U, _) ⇒ ⟦𝑒⟧𝑟𝑖 = ⟦𝑒⟧𝑟 ′ . 𝑖

Proof. Observe that by 𝐶 ≃pub 𝐶 ′ , we deduce that dom(𝜌 ′ |𝑖 ) = dom(𝜌 |𝑖 ). The proof goes by cases on the size of those domains. If such size is 0, both buffers are empty, 𝑟𝑖 = 𝑟 , and 𝑟𝑖′ = 𝑟 ′ . Under these assumptions, we deduce that ⟦𝑒⟧𝑟𝑖 | U = ⟦𝑒⟧𝑟 ′ | U from Lemma D.21. 𝑖 As we have ⟦𝑒⟧𝑟𝑖 ≠ ⊥ and ⟦𝑒⟧𝑟 ′ | U = ⟦𝑒⟧𝑟 ′ . 𝑖 𝑖 If the domain of the buffer is not empty, we are required to establish ⟦𝑒⟧aplsan(𝜌 |𝑖 ,𝑟 ) = ⟦𝑒⟧aplsan(𝜌 ′ |𝑖 ,𝑟 ′ ) . by definition of aplsan, this reduces to proving

By assumption, we know that ⟦𝑒⟧apl (𝜌 |𝑖 ,𝑟 ) |U is an untainted value, and therefore it cannot be ⊥. Since by Lemma D.22, we have apl(𝜌 |𝑖 , 𝑟 )| U = apl(𝜌 |𝑖 , 𝑟 )| U, □

Theorem 6.2 (Relative Speculative Constant Time). Let 𝑆 0 = (𝑚 0, 𝑟 0 )  𝑆 0′ = 𝑚 0′ , 𝑟 0′

𝐶 0 = (𝑚 0, 𝑟 0, 𝜌 init, 𝜇)  𝐶 0′ = 𝑚 0′ , 𝑟 0′ , 𝜌 init, 𝜇

be initial ISA and HW configurations where dom(𝜌 init ) = ∅. If 𝑆 0 ≃pub 𝑆 0′ and 𝑆 0 ≡ISA 𝑆 0′ , then we have 𝐶 0 ≡HW 𝐶 0′ . Proof. Let 𝑜1

𝑜2

𝑆 0 −→ 𝑆 1 −→ 𝑆 2 . . .

𝑜 1′ 𝑜 2′ 𝑆 0′ −→ 𝑆 1′ −→ 𝑆 2′ . . .

be the corresponding ISA execution traces. By assumption, there should be 𝑜 1𝑜 2 · · · = 𝑜 1′ 𝑜 2′ . . . . It suffices to prove that for all 𝑛 ∈ N, if one HW configuration successfully executes for 𝑛 steps, i.e., there exist 𝐶 1, . . . 𝐶𝑛 such that 𝐶0 → − 𝐶1 → − 𝐶2 · · · → − 𝐶𝑛 then the other one can also execute at least 𝑛 steps, i.e., there exist 𝐶 1′ , . . . , 𝐶𝑛′ such that 𝐶 0′ → − 𝐶 1′ → − 𝐶 2′ · · · → − 𝐶𝑛′

∀𝑘 ≤ 𝑛, 𝐶𝑘 ≃pub 𝐶𝑘′ . We prove by induction on 𝑛. For the base case where 𝑛 = 0, since 𝑆 0 ≃pub 𝑆 0′ and dom(𝜌 init ) = ∅, then by Definition D.10 and the definition of 𝐶 0 and 𝐶 0′ , there should be 𝐶 0 ≃pub 𝐶 0′ . Assume the statement holds for 𝑛. We prove it also holds for 𝑛 + 1. Suppose there exists 𝐶𝑛+1 such that 𝐶𝑛 → − 𝐶𝑛+1 . We just ′ ′ need to prove that there exists 𝐶𝑛+1 such that 𝐶𝑛′ → − 𝐶𝑛+1 and ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 By Definition D.7, since 𝐶 0 and 𝐶 0′ have empty reorder buffers, i.e., dom(𝜌 init ) = ∅, then both 𝐶 0 and 𝐶 0′ are well-formed. Then, by Lemma D.17, for all 𝑘 ≤ 𝑛, 𝐶𝑘 and 𝐶𝑘′ are well-formed. Denote 𝐶𝑛 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , 𝜇𝑛 ). By [step], there should be

𝜇 = update(𝜇𝑛 , 𝜌𝑛 | U )

𝑑 = next (𝜇)

(𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , 𝜇) → − 𝐶𝑛+1 𝑑

(𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , 𝜇𝑛 ) → − 𝐶𝑛+1  Denote 𝐶𝑛′ = 𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛′ , 𝜇𝑛′ . Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then 𝜌𝑛 | U = 𝜌𝑛′ | U and 𝜇𝑛 = 𝜇𝑛′ . Denote 𝜇 ′ = update(𝜇𝑛′ , 𝜌𝑛′ | U ). Thus, there should be 𝜇 = 𝜇 ′ and 𝑑 = next (𝜇) = next (𝜇 ′ ). ′ It suffices to prove that there exists 𝐶𝑛+1 such that  ′ ′ ′ ′ ′ ′ 𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 , update(𝜇𝑛 , 𝜌𝑛 | U ) → − 𝐶𝑛+1 (22) 𝑑

and ′ 𝐶𝑛+1 ≃pub 𝐶𝑛+1

(23)

Consider the following cases for 𝑑. Case 𝑑 = fetch. By [fetch-other] and [fetch-branch-predictpc], there exists instr such that instr = readMemInst (𝑚𝑛 , ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) ).

⟦𝑒⟧apl (𝜌 |𝑖 ,𝑟 ) |U = ⟦𝑒⟧apl (𝜌 ′ |𝑖 ,𝑟 ′ ) |U .

the conclusion is trivial.

and

By [read-mem-inst], ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) = ((ℓ, (𝑝, 𝑏, 𝑒, U)), U, C). By Lemma D.23, we have ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) = ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) . Furthermore, by Lemma D.19, there should be ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) ∈ rc(𝑟𝑛 , 𝑚𝑛 ) and ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) ∈ rc(𝑟𝑛′ , 𝑚𝑛′ ). Then, by Lemma D.12, instr = readMemInst (𝑚𝑛′ , ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) ). This indicates that 𝑆𝑛′ also satisfies the condition of → −. 𝑑

Let 𝑖 = sup dom(𝜌𝑛 ) + 1 = sup dom(𝜌𝑛′ ) + 1 (implied by 𝐶𝑛 ≃pub ′ 𝐶𝑛 ). Consider the following cases: • instr ∉ {beqz 𝑥, ℓ ′′, jmp 𝑒}: Let 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 [𝑖 ↦→ instr@𝜀], update(𝜇, ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) )) ′ 𝐶𝑛+1 = (𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛′ [𝑖 ↦→ instr@𝜀], update(𝜇 ′, ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) )). ′ By [fetch-other], there should be 𝐶𝑛′ → − 𝐶𝑛+1 and 𝐶𝑛+1 ≃pub ′ . 𝐶𝑛+1 • instr ∈ {beqz 𝑥, ℓ ′′, jmp 𝑒}: Denote

((ℓ, ℓmeta ), 𝑡, 𝑐) = ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) = ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) . Let 𝜌𝑛+1 = 𝜌𝑛 [𝑖 ↦→ pc ← ((predPc(𝜇), ℓmeta ), 𝑡, 𝑐)@(ℓ, ℓmeta )] 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇, ⟦pc⟧aplsan(𝜌𝑛 ,𝑟𝑛 ) )) ′ 𝜌𝑛+1 = 𝜌𝑛′ [𝑖 ↦→ pc ← ((predPc(𝜇 ′ ), ℓmeta ), 𝑡, 𝑐)@(ℓ, ℓmeta )] ′ ′ 𝐶𝑛+1 = (𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛+1 , update(𝜇 ′, ⟦pc⟧aplsan(𝜌𝑛′ ,𝑟𝑛′ ) )).

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

Note that 𝜇 = 𝜇 ′ . By [fetch-branch-predict-pc], there ′ ′ . should be 𝐶𝑛′ → − 𝐶𝑛+1 and 𝐶𝑛+1 ≃pub 𝐶𝑛+1 Case 𝑑 = exec 𝑖. Consider the following cases: Subcase 𝜌𝑛 (𝑖) = 𝑥 ← 𝑒@𝜀, 𝑥 ≠ pc. Since 𝜌𝑛 | U = 𝜌𝑛′ | U , there should be 𝜌𝑛′ (𝑖) = 𝑥 ← 𝑒@𝜀 = 𝜌𝑛 (𝑖). By [execute-assign], there should be ⟦𝑒⟧apl (𝜌𝑛 |𝑖 ,𝑟𝑛 ) = (𝑣, 𝑡, 𝑐) ≠ ⊥. By Lemma D.21, this implies that there must exist (𝑣 ′, 𝑡 ′, 𝑐 ′ ) such that ⟦𝑒⟧apl (𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) = (𝑣 ′, 𝑡 ′, 𝑐 ′ ) ≠ ⊥, and (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U . Let 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 [𝑖 ↦→ 𝑥 ← (𝑣, 𝑡, 𝑐)@𝜀], 𝜇)  ′ 𝐶𝑛+1 = 𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛′ [𝑖 ↦→ 𝑥 ← (𝑣 ′, 𝑡 ′, 𝑐 ′ )@𝜀], 𝜇 ′ . ′ . By Definition D.9, By [execute-assign], there should be 𝐶𝑛′ → − 𝐶𝑛+1 ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 Subcase 𝜌𝑛 (𝑖) = pc ← ℓ ′ @(ℓ, ℓmeta ). By introspection of the rules for assignments, one among rules [execute-jmp-ok], [executejmp-hazard], [execute-beqz-ok] and [execute-beqz-hazard] was applied, There should be readMemInst (𝑚𝑛 , ⟦pc⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) ) = jmp 𝑒 or beqz 𝑥, ℓ. The two cases can be proved similarly, so we only prove the case for jmp 𝑒 here and omit the proof for the other. By Definitions D.9 and D.10, 𝐶𝑛 ≃pub 𝐶𝑛′ implies that 𝜌𝑛 (𝑖) = ′ 𝜌𝑛 (𝑖) = pc ← ℓ ′ @(ℓ, ℓmeta ). By [read-mem-inst], ⟦pc⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) = ((ℓ, (𝑝, 𝑏, 𝑒, U)), U, C). By Lemma D.23, we have ⟦pc⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) = ⟦pc⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) . Furthermore, by Lemma D.19, there should be ⟦pc⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) ∈ rc(𝑟𝑛 , 𝑚𝑛 ) and ⟦pc⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) ∈ rc(𝑟𝑛′ , 𝑚𝑛′ ). Then, by Lemma D.12,

jmp 𝑒 = readMemInst (𝑚𝑛 , ⟦pc⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) ) = readMemInst (𝑚𝑛′ , ⟦pc⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) ) Next, denote (ℓ0, 𝑡, 𝑐) = ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 )

(ℓ0′, 𝑡 ′, 𝑐 ′ ) = ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) .

Both [execute-jmp-ok] and [execute-jmp-hazard] require the resolved jump target to be a capability, so 𝑐 = C. We aim to prove that ℓ0 = ℓ0′ and 𝑐 ′ = C by discussing the taint status of the result. • 𝑡 = U: By Lemma D.23, there should be ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) = ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) , hence ℓ0 = ℓ0′ and 𝑐 ′ = 𝑐 = C. • 𝑡 = T: By the definition of aplsan, this is only possible if dom(𝜌𝑛 |𝑖 ) = ∅. Then, there should be aplsan(𝜌𝑛 |𝑖 , 𝑟𝑛 ) = 𝑟𝑛 . Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then there is also dom(𝜌𝑛′ |𝑖 ) = ∅, thereby aplsan(𝜌𝑛′ |𝑖 , 𝑟𝑛′ ) = 𝑟𝑛′ . By [isa-jmp], there should exist (𝑚𝑛+1, 𝑟𝑛+1 ) such that

• [execute-jmp-ok] applies, i.e., ℓ ′ = (ℓ0, _, C): Let 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 [𝑖 ↦→ pc ← ℓ ′ @𝜀], update(𝜇, ℓ0 ))  ′ 𝐶𝑛+1 = 𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛′ [𝑖 ↦→ pc ← ℓ ′ @𝜀], update(𝜇 ′, ℓ0′ ) . ′ . By Definition D.10, By [execute-jmp-ok], 𝐶𝑛′ → − 𝐶𝑛+1 ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 • [execute-jmp-hazard] applies, i.e., ℓ ′ = (ℓ1, _, _) with ℓ1 ≠ ℓ0 : Let

𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛 |𝑖 [𝑖 ↦→ pc ← (ℓ0, U, C)@𝜀], update(𝜇, ℓ0 )) ′ 𝐶𝑛+1 = (𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛′ |𝑖 [𝑖 ↦→ pc ← (ℓ0, U, C)@𝜀], update(𝜇 ′, ℓ0′ )). ′ . By Definition D.10, By [execute-jmp-hazard], 𝐶𝑛′ → − 𝐶𝑛+1 ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1

Subcase 𝜌𝑛 (𝑖) = load 𝑥, 𝑒, sz, 𝑥 ≠ pc. Since 𝜌𝑛 | U = 𝜌𝑛′ | U , then 𝜌𝑛′ (𝑖) = load 𝑥, 𝑒, sz = 𝜌𝑛 (𝑖). By [execute-load-mem] and [execute-load-fwd], there should be ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), 𝑡 0, C) = ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ro)

𝑥 ≠ pc

 ′ Denote ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) = ( ℓ ′, ℓmeta , _, 𝑐 ′ ). We aim to prove  ′ that ℓ ′, ℓmeta = (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )) and 𝑐 ′ = C by discussing the taint status of the result. • 𝑡 0 = U: By Lemma D.23, there should be ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) = ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) , so the statement holds. • 𝑡 0 = T: By the definition of aplsan, this is only possible if dom(𝜌𝑛 |𝑖 ) = ∅. Then, there should be aplsan(𝜌𝑛 |𝑖 , 𝑟𝑛 ) = 𝑟𝑛 . Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then there is also dom(𝜌𝑛′ |𝑖 ) = ∅, thereby aplsan(𝜌𝑛′ |𝑖 , 𝑟𝑛′ ) = 𝑟𝑛′ . Note that the premises of [isa-load] are satisfied: since 𝐶𝑛 is well-formed, there should be load 𝑥, 𝑒, sz = readMemInst (𝑚𝑛 , ⟦pc⟧𝑟𝑛 ); furthermore, aplsan(𝜌𝑛 |𝑖 , 𝑟𝑛 ) = 𝑟𝑛 implies that ⟦𝑒⟧𝑟𝑛 = ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), 𝑡 0, C), so checkCap(⟦𝑒⟧𝑟𝑛 , sz, ro) holds and, by [read-mem], readMem(𝑚𝑛 , ⟦𝑒⟧𝑟𝑛 , sz) is defined. Hence, by [isa-load], there should exist (𝑚𝑛+1, 𝑟𝑛+1 ) such that load ( (ℓ,(𝑝,𝑏,𝑒,𝑡𝑐 ) ) )

(𝑚𝑛 , 𝑟𝑛 ) −−−−−−−−−−−−−−→ (𝑚𝑛+1, 𝑟𝑛+1 ). ′ ,𝑟′ ) Since 𝑆 0 ≡ISA 𝑆 0′ , then there should also exist (𝑚𝑛+1 𝑛+1 such that

br (ℓ0 )

(𝑚𝑛 , 𝑟𝑛 ) −−−−→ (𝑚𝑛+1, 𝑟𝑛+1 ). ′ ,𝑟′ Since 𝑆 0 ≡ISA 𝑆 0′ , then there should also exist 𝑚𝑛+1 𝑛+1 such that  br (ℓ0′ ) ′ ′  𝑚𝑛′ , 𝑟𝑛′ −−−−→ 𝑚𝑛+1 , 𝑟𝑛+1

likewise for [execute-jmp-hazard], so the same rule applies to 𝐶𝑛 and 𝐶𝑛′ . Consider the following cases:



br (ℓ0 ) = br (ℓ0′ ). Therefore, ℓ0 = ℓ0′ . Moreover, [isa-jmp] also requires the jump target to be a capability, so the ISA step of 𝑚𝑛′ , 𝑟𝑛′ gives 𝑐 ′ = C. Since 𝜌𝑛 (𝑖) = 𝜌𝑛′ (𝑖), the two executions agree on ℓ ′ , and we have just established that ℓ0 = ℓ0′ and 𝑐 = 𝑐 ′ = C. Hence the premises of [execute-jmp-ok] hold for 𝐶𝑛 if and only if they hold for 𝐶𝑛′ , and

′ load ( ( ℓ ′ ,ℓmeta )) ′ ′ (𝑚𝑛′ , 𝑟𝑛′ ) −−−−−−−−−−−−→ (𝑚𝑛+1 , 𝑟𝑛+1 )

 ′ load ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ))) = load ( ℓ ′, ℓmeta )  ′ Thus, ℓ ′, ℓmeta = (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )). 𝑐 ′ = C can also be implied ′ load ( ( ℓ ′ ,ℓmeta )) ′ , 𝑟 ′ ) by [isa-load] from (𝑚𝑛′ , 𝑟𝑛′ ) −−−−−−−−−−−−→ (𝑚𝑛+1 𝑛+1 and [read-mem]. Next, we analyze the remaining part of rules to determine whether the load data comes from memory or from a previous store. The key insight is to leverage 𝜌𝑛 | U = 𝜌𝑛′ | U . Specifically, for all 𝑗, if ′ 𝜌𝑛 ( 𝑗) = store 𝑣 0, ℓ ′, ℓmeta , sz@𝜏, then 𝜌𝑛 | U = 𝜌𝑛′ | U implies that

Song et al.

 ′ 𝜌𝑛′ ( 𝑗) = store 𝑣 0′ , ℓ ′, ℓmeta , sz@𝜏 where 𝑣 0 | U = 𝑣 0′ | U . Consider the following two cases: • [execute-load-mem] is applied, i.e.,

Furthermore, since 𝐶𝑛 and 𝐶𝑛′ are well-formed and 𝐶𝑛 ≃pub 𝐶𝑛′ , there should be  ′ ′ checkCap( ℓ ′, ℓmeta , sz , rw) ′ ℓmeta = (_, _, _, 𝑡 store )

 ′ ′ ∀𝑗 < 𝑖, 𝜌𝑛 ( 𝑗) = store 𝑥 ′, ℓ ′, ℓmeta , sz @𝜏 ⇒ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) = ∅. According to the above analysis, the statement still holds if we replace 𝜌𝑛 with 𝜌𝑛′ . Let 𝑚𝑛 = (𝑚 d, 𝑚 t )

𝑣 0 = 𝑚 d [ℓ, ℓ + sz]

𝑐 0 = 𝑚 t [ℓ/2]

𝑚𝑛′ = (𝑚 d′ , 𝑚 t′ )

𝑣 0′ = 𝑚 d′ [ℓ, ℓ + sz]

𝑐 0′ = 𝑚 t′ [ℓ/2].

Recall that checkCap(((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ro)), so sz = 2 ⇒ ℓ%2 = 0. By Lemma D.19, there should be [𝑏, 𝑒) ⊆ tAddr (𝑟𝑛 , 𝑚𝑛 , 𝑡𝑐 ) [𝑏, 𝑒) ⊆ tAddr (𝑟𝑛′ , 𝑚𝑛′ , 𝑡𝑐 ).

store 𝑥, 𝑒, sz = readMemInst (𝑚𝑛 , ⟦pc⟧apl (𝜌𝑛 | 𝑗 ,𝑟𝑛 ) ) = readMemInst (𝑚𝑛′ , ⟦pc⟧apl (𝜌𝑛′ | 𝑗 ,𝑟𝑛′ ) ) (𝑣 1, 𝑡 0, 𝑐 1 ) = ⟦𝑥⟧apl (𝜌𝑛 | 𝑗 ,𝑟𝑛 ) (𝑣 0, 𝑐 0 ) = processWrite((𝑣 1, 𝑡 0, 𝑐 1 ), 𝑡 store, sz) (𝑣 1′ , 𝑡 0′ , 𝑐 1′ ) = ⟦𝑥⟧apl (𝜌𝑛′ | 𝑗 ,𝑟𝑛′ ) (𝑣 0′ , 𝑐 0′ ) = processWrite((𝑣 1′ , 𝑡 0′ , 𝑐 1′ ), 𝑡 store, sz). Note that the load and the store capabilities are aliasing and both pass checkCap(·). Following the proof idea for Lemma D.9, we can also derive that 𝑡𝑐 = 𝑡 store . Furthermore, by Lemma D.21, (𝑣 1, 𝑡 0, 𝑐 1 )| U = (𝑣 1′ , 𝑡 0′ , 𝑐 1′ )| U . Then, 𝑡 0 ⊑ 𝑡𝑐 implies that (𝑣 1, 𝑡𝑐 , 𝑐 1 )| U = (𝑣 1′ , 𝑡𝑐 , 𝑐 1′ )| U . By Lemma D.11, there should be processRead (𝑣 0, 𝑡𝑐 , 𝑐 0, sz)| U = processRead (𝑣 0′ , 𝑡𝑐 , 𝑐 0′ , sz)| U .

By [check-cap], there should be [ℓ, ℓ + sz) ⊆ [𝑏, 𝑒). Thus,

Let

[ℓ, ℓ + sz) ⊆ tAddr (𝑟𝑛 , 𝑚𝑛 , 𝑡𝑐 ) ∩ tAddr (𝑟𝑛′ , 𝑚𝑛′ , 𝑡𝑐 ).

𝜌𝑛+1 = 𝜌𝑛 [𝑖 ↦→ (𝑥 ← processRead (𝑣 0, 𝑡𝑐 , 𝑐 0, sz)| U )

 Note that 𝐶𝑛 ≃pub 𝐶𝑛′ implies that (𝑚𝑛 , 𝑟𝑛 ) ≃pub 𝑚𝑛′ , 𝑟𝑛′ . Thus, by Definition D.6, the above conditions imply that processRead (𝑣 0, 𝑡𝑐 , 𝑐 0, sz)| U = processRead (𝑣 0′ , 𝑡𝑐 , 𝑐 0′ , sz)| U .

@((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥)] 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))) ′ = 𝜌𝑛′ [𝑖 ↦→ (𝑥 ← processRead (𝑣 0′ , 𝑡𝑐 , 𝑐 0′ , sz)| U ) 𝜌𝑛+1

@((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥)] ′ ′ 𝐶𝑛+1 = (𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛+1 , update(𝜇 ′, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))).

Let 𝜌𝑛+1 = 𝜌𝑛 [𝑖 ↦→ (𝑥 ← processRead (𝑣 0, 𝑡𝑐 , 𝑐 0, sz)| U ) @((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥)] 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))) ′ 𝜌𝑛+1 = 𝜌𝑛′ [𝑖 ↦→ (𝑥 ← processRead (𝑣 0′ , 𝑡𝑐 , 𝑐 0′ , sz)| U )

@((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, ⊥)] ′ ′ ′ ′ 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇 ′, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))).

By [execute-load-mem], 𝐶𝑛 → − 𝐶𝑛+1 . By Definition D.10, ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 Subcase 𝜌𝑛 (𝑖) = store 𝑥, 𝑒, sz. Since 𝜌𝑛 | U = 𝜌𝑛′ | U , then 𝜌𝑛′ (𝑖) = store 𝑥, 𝑒, sz = 𝜌𝑛 (𝑖). By [execute-store-ok] and [execute-store-hazard], there should be

((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), 𝑡 0, C) = ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) checkCap((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz, rw)

By [execute-load-mem], 𝐶𝑛 → − 𝐶𝑛+1 . By Definition D.10, ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 • [execute-load-fwd] is applied, i.e., ′′  𝑗 = max{ 𝑗 < 𝑖 : 𝜌𝑛 ( 𝑗) = store _, ℓ ′′, ℓmeta , sz ′′ @𝜏

∧ [ℓ, ℓ + sz) ∩ [ℓ ′′, ℓ ′′ + sz ′′ ) ≠ ∅}  ′ ′ 𝜌𝑛 ( 𝑗) = store (𝑣 0, 𝑡 0, 𝑐 0 ), ℓ ′, ℓmeta , sz @𝜏 [ℓ, ℓ + sz) = [ℓ ′, ℓ ′ + sz ′ )

𝑡 0 ⊑ 𝑡𝑐 .

By the above analysis based on 𝜌𝑛 | U = 𝜌𝑛′ | U , similar statements hold when replacing 𝜌𝑛 with 𝜌𝑛′ , i.e., ′′  𝑗 = max{ 𝑗 < 𝑖 : 𝜌𝑛′ ( 𝑗) = store _, ℓ ′′, ℓmeta , sz ′′ @𝜏 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′′, ℓ ′′ + sz ′′ ) ≠ ∅}  ′ 𝜌𝑛′ ( 𝑗) = store (𝑣 0′ , 𝑡 0′ , 𝑐 0′ ), ℓ ′, ℓmeta , sz@𝜏 𝑡 0′ ⊑ 𝑡𝑐 .

(𝑣, 𝑡, 𝑐) = ⟦𝑥⟧apl (𝜌𝑛 |𝑖 ,𝑟𝑛 ) (𝑣 0, 𝑐 0 ) = processWrite((𝑣, 𝑡, 𝑐), 𝑡𝑐 , sz) By Lemma D.21, there should exist (𝑣 ′, 𝑡 ′, 𝑐 ′ ) such that (𝑣 ′, 𝑡 ′, 𝑐 ′ ) = ⟦𝑥⟧apl (𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) and (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U .  ′′ Denote ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) = ( ℓ ′′, ℓmeta , _, 𝑐 ′′ ). We aim to prove  ′′ ′′ ′′ that ℓ , ℓmeta = (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )) and 𝑐 = C by discussing the taint status of the result. • 𝑡 0 = U: By Lemma D.23, there should be ⟦𝑒⟧aplsan(𝜌𝑛 |𝑖 ,𝑟𝑛 ) = ⟦𝑒⟧aplsan(𝜌𝑛′ |𝑖 ,𝑟𝑛′ ) , so the statement holds. • 𝑡 0 = T: By the definition of aplsan, this is only possible if dom(𝜌𝑛 |𝑖 ) = ∅. Then, there should be aplsan(𝜌𝑛 |𝑖 , 𝑟𝑛 ) = 𝑟𝑛 . Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then there is also dom(𝜌𝑛′ |𝑖 ) = ∅, thereby aplsan(𝜌𝑛′ |𝑖 , 𝑟𝑛′ ) = 𝑟𝑛′ . Note that the premises of [isa-store] are satisfied: since 𝐶𝑛 is well-formed, there should be store 𝑥, 𝑒, sz = readMemInst (𝑚𝑛 , ⟦pc⟧𝑟𝑛 );

SCHERI: Provably Secure Speculation Under the Constant-Time Policy for CHERI (Extended Version)

furthermore, aplsan(𝜌𝑛 |𝑖 , 𝑟𝑛 ) = 𝑟𝑛 implies that ⟦𝑒⟧𝑟𝑛 = ((ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), 𝑡 0, C), so checkCap(⟦𝑒⟧𝑟𝑛 , sz, rw) holds and, by [write-mem], writeMem(𝑚𝑛 , ⟦𝑒⟧𝑟𝑛 , sz, ⟦𝑥⟧𝑟𝑛 ) is defined. Hence, by [isa-store], there should exist 𝑣𝑜 and (𝑚𝑛+1, 𝑟𝑛+1 ) such that store (𝑣𝑜 ,(ℓ,(𝑝,𝑏,𝑒,𝑡𝑐 ) ) )

(𝑚𝑛 , 𝑟𝑛 ) −−−−−−−−−−−−−−−−−→ (𝑚𝑛+1, 𝑟𝑛+1 ). ′ ,𝑟′ ) Since 𝑆 0 ≡ISA 𝑆 0′ , then there should also exist 𝑣𝑜′ and (𝑚𝑛+1 𝑛+1 such that ′′ store (𝑣𝑜′ , ( ℓ ′′ ,ℓmeta )) ′ ′ (𝑚𝑛′ , 𝑟𝑛′ ) −−−−−−−−−−−−−−−→ (𝑚𝑛+1 , 𝑟𝑛+1 ) ′′  ). store(𝑣𝑜 , (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ))) = store(𝑣𝑜′ , ℓ ′′, ℓmeta  ′′ = (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )). 𝑐 ′′ = C can also be imThus, ℓ ′′, ℓmeta ′′ store (𝑣𝑜′ , ( ℓ ′′ ,ℓmeta )) ′ , 𝑟 ′ ) by [isaplied from (𝑚𝑛′ , 𝑟𝑛′ ) −−−−−−−−−−−−−−−→ (𝑚𝑛+1 𝑛+1 store] and [write-mem]. Let (𝑣 0′ , 𝑐 0′ ) = processWrite((𝑣 ′, 𝑡 ′, 𝑐 ′ ), 𝑡𝑐 , sz). By the definition of processWrite(), there should also be (𝑣 0, 𝑡, 𝑐 0 )| U = (𝑣 0′ , 𝑡 ′, 𝑐 0′ )| U . Next, we analyze the load/store aliasing checking in [executestore-ok] and [execute-store-hazard]. The key insight is to leverage 𝜌𝑛 | U = 𝜌𝑛′ | U . Specifically, for all 𝑗, if  ′ ′ 𝜌𝑛 ( 𝑗) = 𝑥 ← 𝑣@( ℓ ′, ℓmeta , sz , 𝑘),

then 𝜌𝑛 | U = 𝜌𝑛′ | U implies that  ′ ′ 𝜌𝑛′ ( 𝑗) = 𝑥 ← 𝑣 ′ @( ℓ ′, ℓmeta , sz , 𝑘) ′ 𝑣 |U = 𝑣 |U . Consider the following two cases: • [execute-store-ok] is applied, i.e.,  ′ ′ ∀𝑗 > 𝑖, 𝜌𝑛 ( 𝑗) = 𝑥 ← 𝑣@( ℓ ′, ℓmeta , sz , 𝑘) ∧ 𝑘 ≠ 𝑖 ⇒ (𝑘 > 𝑖 ∨ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ + sz ′ ) = ∅). According to the above analysis, the statement still hold if we replace 𝜌𝑛 with 𝜌𝑛′ . Let 𝜌𝑛+1 = 𝜌𝑛 [𝑖 ↦→ store (𝑣 0, 𝑡, 𝑐 0 ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz] 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))) ′ 𝜌𝑛+1 = 𝜌𝑛′ [𝑖 ↦→ store (𝑣 0′ , 𝑡 ′, 𝑐 0′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz]  ′ ′ 𝐶𝑛+1 = 𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛+1 , update(𝜇 ′, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ))) . ′ . By Definition D.10, By [execute-store-ok], 𝐶𝑛′ → − 𝐶𝑛+1 ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1 • [execute-store-hazard] is applied, i.e.,  ′ ′ 𝑗 = min{ 𝑗 > 𝑖 : 𝜌𝑛 ( 𝑗) = 𝑥 ← 𝑣@( ℓ ′, ℓmeta , sz , 𝑘)

∧ (𝑘 < 𝑖 ∧ [ℓ, ℓ + sz) ∩ [ℓ ′, ℓ ′ + sz ′ ) ≠ ∅)} According to the above analysis, the right-hand side does not change if we replace 𝜌𝑛 with 𝜌𝑛′ . Let 𝜌𝑛+1 = 𝜌𝑛 |𝑖 [𝑖 ↦→ store (𝑣 0, 𝑡, 𝑐 0 ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz] 𝐶𝑛+1 = (𝑚𝑛 , 𝑟𝑛 , 𝜌𝑛+1, update(𝜇, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )))) ′ 𝜌𝑛+1 = 𝜌𝑛′ |𝑖 [𝑖 ↦→ store (𝑣 0′ , 𝑡 ′, 𝑐 0′ ), (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 )), sz]  ′ ′ 𝐶𝑛+1 = 𝑚𝑛′ , 𝑟𝑛′ , 𝜌𝑛+1 , update(𝜇 ′, (ℓ, (𝑝, 𝑏, 𝑒, 𝑡𝑐 ))) . ′ . By Definition D.10, By [execute-store-hazard], 𝐶𝑛′ → − 𝐶𝑛+1 ′ . 𝐶𝑛+1 ≃pub 𝐶𝑛+1

Case 𝑑 = commit. Let 𝑖 = min dom(𝜌𝑛 ) = min dom(𝜌𝑛′ ) (the equivalence is implied from 𝐶𝑛 ≃pub 𝐶𝑛′ ). Consider the following two cases: • 𝜌𝑛 (𝑖) = 𝑥 ← 𝑣@𝜏: Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then 𝜌𝑛 | U = 𝜌𝑛′ | U . This implies that there must exist 𝑣 ′ , 𝜏 ′ such that 𝜌𝑛′ (𝑖) = 𝑥 ← 𝑣 ′ @𝜏 ′ and 𝑣 | U = 𝑣 ′ | U . Thus, there should be 𝐶𝑛+1 = (𝑚𝑛+1, 𝑟𝑛+1, 𝜌𝑛 \𝑖, 𝜇)  ′ ′ ′ ′ ′ ′ 𝑟𝑛+1 = 𝑟𝑛 [𝑥 ↦→ 𝑣 ] 𝐶𝑛+1 = 𝑚𝑛+1 , 𝑟𝑛+1 , 𝜌𝑛′ \𝑖, 𝜇 ′ , ′ . and 𝐶𝑛 → − 𝐶𝑛+1 , 𝐶𝑛′ → − 𝐶𝑛+1

𝑚𝑛+1 = 𝑚𝑛

𝑟𝑛+1 = 𝑟𝑛 [𝑥 ↦→ 𝑣]

′ 𝑚𝑛+1 = 𝑚𝑛′

• 𝜌𝑛 (𝑖) = store (𝑣, 𝑡, 𝑐), (ℓ, ℓmeta ), sz@𝜀: Since 𝐶𝑛 ≃pub 𝐶𝑛′ , then 𝜌𝑛 | U = 𝜌𝑛′ | U . By the definition of untainted projection for reorder buffer and store instruction, there must be 𝜌𝑛′ (𝑖) = store (𝑣 ′, 𝑡 ′, 𝑐 ′ ), (ℓ, ℓmeta ), sz@𝜀 for some (𝑣 ′, 𝑡 ′, 𝑐 ′ ) where (𝑣, 𝑡, 𝑐)| U = (𝑣 ′, 𝑡 ′, 𝑐 ′ )| U . Let (𝑚 d, 𝑚 t ) = 𝑚𝑛 , and (𝑚 d′ , 𝑚 t′ ) = 𝑚𝑛′ . Thus, there should be 𝑚𝑛+1 = (𝑚 d [[ℓ, ℓ + sz) ↦→ 𝑣], 𝑚 t [ℓ/2 ↦→ 𝑐]) 𝑟𝑛+1 = 𝑟𝑛

𝐶𝑛+1 = (𝑚𝑛+1, 𝑟𝑛+1, 𝜌𝑛 \𝑖, update(𝜇, (ℓ, ℓmeta )))

′ 𝑚𝑛+1 = (𝑚 d′ [[ℓ, ℓ + sz) ↦→ 𝑣 ′ ], 𝑚 t [ℓ/2 ↦→ 𝑐 ′ ]) ′ 𝑟𝑛+1 = 𝑟𝑛′

 ′ ′ ′ 𝐶𝑛+1 = 𝑚𝑛+1 , 𝑟𝑛+1 , 𝜌𝑛′ \𝑖, update(𝜇 ′, (ℓ, ℓmeta )) ,

′ . and 𝐶𝑛 → − 𝐶𝑛+1 , 𝐶𝑛′ → − 𝐶𝑛+1

By Theorem D.1, for both cases, there should be 𝑆 𝑓 ( {𝐶0 ,𝐶1 ,...,𝐶𝑛 } ) = (𝑚𝑛+1, 𝑟𝑛+1 ) ′ ′  𝑆 𝑓′ ( 𝐶 ′ ,𝐶 ′ ,...,𝐶 ′ ) = 𝑚𝑛+1 , 𝑟𝑛+1 { 0 1 𝑛} where 𝑓 is defined in Theorem D.1. By the definition  of 𝑓 , since for all 𝑘 ≤ 𝑛, 𝐶𝑘 ≃pub 𝐶𝑘′ , then 𝑓 ({𝐶 0, 𝐶 1, . . . , 𝐶𝑛 }) = 𝑓 ( 𝐶 0′ , 𝐶 1′ , . . . , 𝐶𝑛′ ). Furthermore, by Lemma D.8, 𝑆 𝑓 ( {𝐶0 ,𝐶1 ,...,𝐶𝑛 } ) ≃pub 𝑆 𝑓′ ( {𝐶0 ,𝐶1 ,...,𝐶𝑛 } ) .  ′ ,𝑟′ Thus, (𝑚𝑛+1, 𝑟𝑛+1 ) ≃pub 𝑚𝑛+1 𝑛+1 . Furthermore, note that 𝜌𝑛 | U = 𝜌𝑛′ | U and 𝜇 = 𝜇 ′ . Therefore, for ′ . both cases, there should be 𝐶𝑛+1 ≃pub 𝐶𝑛+1 □

Record · ID 919237 · SHA-256 f00927380dc67270
Retrieved via Conceptio — every document is proof-bundled with source, license, and retrieval metadata.