Home · ASPLOS 2023 · Operating Systems & Virtualization

Going beyond the Limits of SFI: Flexible and Secure Hardware-Assisted In-Process Isolation with HFI

Shravan Narayan, Tal Garfinkel, Mohammadkazem Taram, Joey Rudek, Daniel Moghimi, Evan Johnson and 6 others

Conference
ASPLOS 2023
Published
2023
Pages
266-281
Citations
27

WebAssembly made in-process sandboxing ubiquitous by doing isolation in software — but the software pays 40% runtime overhead, burns 8 GiB of virtual address space per sandbox, cannot contain unmodified native binaries, and is structurally open to Spectre attacks; HFI is a deliberately minimal processor extension (8 instructions, a few dozen registers, one 32-bit comparator, zero new pipeline stages) whose hardware region checks run in parallel with the TLB lookup so isolation costs nothing at steady state, keeps on-chip state only for the RUNNING sandbox so concurrency is unlimited, sandboxes native code untouched, and blocks Spectre by finishing every check before the microarchitecture can leak — turning a 9–42% software Spectre tax into 0–2%.

The argument in one glance

  1. Problem

    Isolation moved into the process because hardware could not keep up

    Modern systems need to isolate MANY small, mutually distrusting pieces of code inside one process: a browser sandboxing its font and image libraries, an edge platform running thousands of tenants' functions. Hardware isolation — processes, containers, VMs — is far too heavy: a hardware context switch costs orders of magnitude more than a function call, and spinning up a container takes tens to hundreds of milliseconds where serverless platforms need microseconds. WebAssembly answered with isolation enforced in SOFTWARE: context switches in tens of cycles, instance creation in 30 microseconds — fast enough that Fastly, Cloudflare, and Akamai built businesses on it.

  2. What people did

    Software fault isolation, with the MMU as a crutch

    Software fault isolation (SFI) instruments compiled code so it cannot reach outside its box. Naive per-access bounds checks cost 2×; address masking is faster but silently converts out-of-bounds bugs into random corruption. So production Wasm uses a cleverer trick: give each sandbox a 4 GiB space followed by 4 GiB of unmapped guard pages, constrain pointer arithmetic to 33 bits, and let the MMU trap anything that strays — a poor man's segmentation.

  3. Why it fell short

    Four walls, all load-bearing

    Performance — even the fastest Wasm engines impose around 40% overhead, from restricted instruction formats, register pressure (base and bounds live in general-purpose registers), and software control-flow integrity. Scaling — 8 GiB of virtual address space per sandbox means a 128 TiB address space holds at most about 16 thousand instances, and tearing sandboxes down triggers TLB shootdowns. Compatibility — only code compiled through the Wasm toolchain can be sandboxed: no unmodified native libraries, no assembly, no SIMD intrinsics, no JIT-generated code, and only 32-bit address spaces. Spectre — processors speculate straight past software checks; hardening Wasm against Spectre in software costs an ADDITIONAL 62–100%. All four walls are consequences of one fact: the isolation the software wants does not exist as a hardware primitive.

  4. This paper's idea

    Build the missing primitive — and make it almost free

    HFI (UC San Diego, Intel, and collaborators) is an instruction-set extension co-designed with CPU architects to be adoptable: 8 instructions, a few dozen internal registers, one 32-bit comparator, four AND gates — bytes of on-chip state, no new pipeline stages, zero impact on non-sandboxed code. Sandboxed execution is constrained by REGIONS — base/bound/permission ranges checked in hardware, in parallel with the TLB lookup and instruction decode, so steady-state isolation costs nothing. On-chip state describes only the CURRENTLY RUNNING sandbox, so a process can host unlimited sandboxes. Unmodified native binaries run in native sandboxes with system calls converted into jumps to a user-space handler; Wasm runs in hybrid sandboxes that never need to exit. And because every check — speculative or not — completes before the memory system or decoder can act, Spectre is blocked by construction: FaaS workloads pay 0–2% tail latency for Spectre safety where the best software mitigation pays 9–42%. Wasm with HFI outruns stock Wasm (image rendering in Firefox 14–37% faster), and one process can hold 256,000 one-GiB sandboxes.

Why this problem is worth solving

Every price in this story is paid billions of times a day.

SFI — sandboxing enforced by the compiler rather than the hardware: every memory access and control transfer in the untrusted code is instrumented or constrained so it cannot leave its assigned range, and all interaction with the outside world funnels through checked entry points. WebAssembly is SFI’s mainstream form: a portable bytecode with a verified, restricted instruction set, compiled to native code whose isolation is guaranteed by construction. Its virtue is deployability — no hardware or OS changes — and its costs are the instrumentation itself and everything the restricted model cannot express
is what lets a browser put a crusty font-parsing library in a box, and what lets an edge platform pack thousands of tenants into one process where containers would allow dozens. The 40% runtime tax is throughput lost on every request; the 8 GiB virtual-footprint-per-sandbox is the scaling wall of an entire product category; the native-code incompatibility is why most of the world’s C libraries still run unsandboxed in the address spaces of the applications they can compromise.

And then there is

the family of attacks exploiting speculative execution: a processor predicts a branch and executes ahead under the prediction, and even though mispredicted work is discarded architecturally, its traces remain in caches and predictors — a side channel through which sandboxed code can read memory its checks would never allow it to touch. Software isolation is structurally exposed, because the checks it inserts are exactly the branches the processor speculates past; hardening Wasm in software (Swivel, the state of the art) costs 62–100% overhead, so mainstream engines simply run unprotected
, which converts the isolation story from expensive to quietly incomplete: the sandbox holds architecturally and leaks speculatively. The paper’s premise is that all four walls have a single cause — the absence of a hardware primitive shaped like what modern software actually does — and that the primitive is buildable for the cost of a rounding error in a modern core.

What people did before, and where it broke

  • Pure software (masking, bounds checks, Wasm’s guard pages). Deployable everywhere, and the source of all four limits above. Guard pages specifically trade virtual address space for speed — 8 GiB per sandbox — and turn teardown into TLB-shootdown territory.
  • Page-based hardware (processes, sandboxed threads, per-page sandbox IDs, ARM domains). Inherits the MMU’s economics: kernel calls to change permissions or switch sandboxes, TLB flushes and shootdowns to stay consistent across cores, and hard limits (4096 address-space IDs on Intel).
  • Intel MPK. User-space domain switching — the right instinct — but only 15 usable domains, page granularity, and system-call interposition must be bolted on separately (Seccomp-bpf); scaling past 15 falls back to the page machinery it was escaping.
  • x86 segmentation and Intel MPX. Segmentation enabled the fast 32-bit-era SFI systems (Vx32, NaCl) — and x86-64 dropped it. MPX’s multiple dependent range checks per access made it as slow as software.
  • CHERI capabilities. The most powerful model — byte-granular, unlimited regions, object-level memory safety — at the cost of 128-bit fat pointers and changes to the OS, ABI, compiler, and every layer of hardware: the opposite end of the adoptability spectrum from what a CPU vendor will ship incrementally.

How HFI works

Regions checked in parallel, state for one sandbox only

  1. HFI mode and the two sandbox types new

    A core enters HFI mode via hfi_enter and leaves via hfi_exit — pure user-space instructions, no kernel, no ring transitions. NATIVE sandboxes hold untrusted, unmodified binaries: region registers lock at entry, and every system call is converted (a one-cycle microcode check in decode) into a jump to a user-space exit handler — complete mediation for free. HYBRID sandboxes hold code from a trusted compiler (Wasm engines): the runtime inside may adjust regions and make system calls directly, so it never needs to exit at all.

  2. Implicit regions: sandboxing code nobody recompiled new

    Four data regions and two code regions are checked on EVERY access, first-match, by prefix comparison: mask off low bits, compare the remainder to a base prefix. Power-of-two size and alignment is the price of a check this cheap (AND gate plus equality). Code regions bound the program counter the same way — in parallel with decode, so out-of-bounds instructions never enter the pipeline, speculatively or otherwise.

  3. Explicit regions: byte-granular, register-relative new

    Four regions accessed via the hmov instruction family — x86 mov semantics with the base operand replaced by the region's base register, negative offsets and overflow trapped. Large regions (up to 256 TiB, 64K-aligned) fit Wasm heaps, which grow in 64K increments; small regions (up to 4 GiB, byte-granular) share individual buffers in place. The shape restrictions let one 32-bit comparator do all bounds checking where naive designs need pairs of 64-bit ones.

  4. Zero-cost checking, by placement new

    All region checks execute in PARALLEL with work the pipeline already does: data checks alongside the dTLB lookup and cache indexing, code checks alongside decode. No new pipeline stages, no added latency on any critical path, no effect on non-sandboxed code — the three constraints CPU architects insisted on. Total additions: 8 instructions, 22 internal registers (22 more for an optional extension), one 32-bit comparator, four AND gates, four equality checks, five small muxes.

  5. Spectre safety by construction — and at the boundary new

    Region checks apply to speculative accesses identically, and complete BEFORE the physical address resolves — so an out-of-bounds speculative load can never update cache state, and out-of-bounds code never decodes into the pipeline. The remaining risk is the boundary: speculating past hfi_enter/hfi_exit itself. Option one: serialize both (30–60 cycles). Option two — switch-on-exit: the trusted runtime runs in its own serialized hybrid sandbox, and child sandboxes' exits atomically SWITCH back to the runtime's region registers instead of disabling HFI — so no per-entry serialization, and speculation can never escape the sandbox set.

  6. Scalability from a working-set decision new

    HFI stores on-chip state for exactly one sandbox — the running one. Switching sandboxes swaps a handful of registers (software-managed, so Wasm's zero-cost transition tricks still apply); the OS saves HFI registers in xsave/xrstor with one new flag. No per-sandbox state lives in the TLB or any cache, so there is no 15-domain or 4096-ASID ceiling: sandbox count is limited by memory, not silicon.

Evaluated twice: a gem5 cycle-accurate Skylake-like simulation for microarchitectural fidelity, and compiler-based emulation for real workloads (validated against gem5 on the Sightglass suite: emulated overheads within 98–108% of simulated, 1.62% geomean difference). Integrated into Wasm2c (Firefox's ahead-of-time sandboxing compiler) and Wasmtime (Fastly's JIT).

The idea

Give hardware the shape software already wants. SFI’s four limits are all workarounds for a missing primitive: guard pages fake segmentation with the MMU, bounds checks fake it with arithmetic, and Spectre slips past both because neither is visible to the speculation machinery. HFI’s regions are that primitive built directly — base, bound, permissions, checked on every access including speculative ones — and the four walls fall together: no instrumentation (no 40% tax), no guard pages (no 8 GiB footprint), no recompilation requirement (implicit regions bound anything, even JIT output), and no speculative bypass (the check IS the hardware’s own path to memory). The evaluation’s sharpest line: Wasm with HFI is strictly FASTER than stock Wasm — isolation became cheaper than the software that was approximating it.

Constraints purchased the hardware. Every HFI restriction that looks arbitrary is a comparator saved: implicit regions are power-of-two because prefix-matching is an AND plus an equality; large explicit regions align to 64K and small ones cannot cross 4 GiB boundaries so that one 32-bit comparator — not multiple 64-bit ones — checks all bounds; hmov traps negative offsets so base violations need no second check. The paper is explicit that these shapes were chosen with vendor architects against three hard constraints: no new pipeline stages, no critical-path timing, no cost to non-sandboxed code. It is the constraints-as-gifts pattern in silicon — restrict the primitive’s generality exactly where software does not need it, and the mechanism becomes cheap enough to actually ship.

Keep state only for who is running. Prior hardware isolation hit ceilings because it kept state for every sandbox — MPK’s 15 domains, tagged-TLB designs’ fixed capacities, the OS’s 4096 address-space IDs. HFI applies the working-set principle to protection state itself: the chip describes the running sandbox only, in registers; everything else lives in memory and swaps in at software-managed switch time. Concurrency becomes unbounded, and context switches stay as fast as the software can make them — including Wasm’s zero-cost transitions, which page-based schemes structurally cannot support because they cannot distinguish stack from heap protection.

A FaaS request, sandboxed by hardware

One request through an HFI-enabled edge platform

The paper's motivating scenario: a trusted runtime multiplexing thousands of tenant sandboxes — Wasm and native — in a single process.

Step 1 of 5

Setup in nanoseconds, not milliseconds

A request arrives. The runtime points region registers at the tenant's code, heap, and input buffer (hfi_set_region — plain register writes), picks the sandbox type, and executes hfi_enter. No mmap of 8 GiB, no mprotect, no kernel involvement at all. For Wasm tenants it chooses hybrid: the Wasm runtime inside will manage its own heap region and make its own system calls, never exiting until the work is done.

sandbox creation, resizing, teardown: a few register updates — near-zero cost by design
growing a Wasm heap to 4 GiB in 64K steps: 10.92 s via mprotect, 370 ms via HFI — about 30×

Why it works

  • The isolation checks ride pipeline stages that already exist (dTLB lookup, decode) → steady-state enforcement adds zero latency and zero instructions → sandboxed code runs at native speed, and Wasm sheds its instrumentation tax to run FASTER than its software self.
  • Region shapes are restricted exactly where software does not need generality (power-of-two implicit, 64K-aligned large, 4 GiB-bounded small) → all bounds checking fits in one 32-bit comparator and a few gates → the extension is cheap enough for vendors to consider actually shipping.
  • Checks apply to speculative and committed execution identically, and complete before microarchitectural state can change → out-of-bounds data never touches the cache, out-of-bounds code never decodes → Spectre containment is a property of the datapath, not a tax on the program.
  • On-chip state describes only the running sandbox → no per-sandbox hardware residency, no domain ceiling → concurrency scales with memory, and context switching stays software-managed and as fast as the runtime can engineer.
  • System calls become jumps when a native sandbox runs → complete mediation of the OS interface without kernel filters or ring crossings → unmodified binaries are contained with interposition cheaper than Seccomp-bpf.

What it costs

  • It is a proposal, not silicon. All results come from gem5 simulation plus validated compiler-based emulation; the design was refined with vendor architects, and Intel co-authorship signals intent, but no shipping CPU implements HFI.
  • Boundary serialization. Full Spectre protection serializes hfi_enter/hfi_exit (30–60 cycles) unless switch-on-exit applies; workloads with extremely frequent transitions and no shared-trust structure pay it.
  • Finite regions. Six implicit and four explicit regions per sandbox — enough for heaps, stacks, code, and shared buffers in practice, but runtimes juggling many multi-memories must multiplex registers themselves.
  • Partial integrations in the evaluation. The Wasm2c port secures the heap but not the stack, indirect tables, or code section (needed for complete Spectre mitigation); MPK remains slightly faster for few-domain native sandboxing.
  • Holistic Spectre caveats remain. Sandboxed code still influences shared predictor state, and a runtime must ensure hfi_enter itself cannot be speculatively bypassed — HFI is a primitive, not a complete policy.

The evaluation

Cycle-accurate gem5 simulation cross-validated with compiler-based emulation (98–108% agreement), driven through SPEC 2006, Firefox library sandboxing, a FaaS lifecycle benchmark, NGINX native sandboxing, and Spectre attack suites.

Three routes to in-process isolation

PropertySFI / Wasm (guard pages, bounds checks)Existing hardware (MPK, page-based, MPX, CHERI)HFI
Steady-state performanceMPK fast per-switch but page-granular; MPX as slow as software (dependent range checks); CHERI pays fat-pointer footprint
Scalability (sandboxes per process)
Unmodified native codeImpossible — Wasm toolchain only; no assembly, SIMD, or JIT outputMPK/page-based: yes, with separate syscall filtering; CHERI: requires whole-stack porting
Spectre protectionNot addressed by MPK/MPX; CPU-redesign proposals too invasive to deploy
Adoption costZero — that is why it wonMPK ships but is capped; CHERI changes OS, ABI, compiler, and pointers everywhere
Sandbox lifecycle wins: heap growth ~30× faster than mprotect-based Wasm; batched teardown 23.1 μs vs 25.7 μs stock (batching without guard-page elision is WORSE at 31.1 μs). All artifacts open-sourced.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. MPK supports 15 domains; Intel ASIDs cap at 4096; tagged-TLB isolation designs hit fixed capacities. HFI claims UNLIMITED concurrent sandboxes. What design decision makes that possible, and what does it cost?

  2. 2. HFI's region checks add zero latency to sandboxed execution — SPEC actually runs 3.25% faster than guard-page Wasm. Where did the overhead go, and what did the odd-looking region restrictions (power-of-two, 64K alignment, no 4 GiB crossings) buy?

  3. 3. Software Spectre mitigation for Wasm costs 62–100%; HFI's costs 0–2% tail latency. Explain why HFI's protection is structural rather than additive — and where the residual risk lives.

  4. 4. HFI has two sandbox types: native (region registers locked, syscalls redirected) and hybrid (registers unlocked, direct syscalls). Predict why the hybrid type exists — and what trust distinction licenses its extra freedom.

Sources

Related papers on Operating Systems & Virtualization

See all 170 →