{{+bindTo:partials.standard_nacl_article}}

NaCl SFI model on x86-64 systems

Summary

This document addresses the details of the Software Fault Isolation (SFI) model for executable code that can be run in Native Client on an x86-64 system. An overview of this model can be found in the paper: Adapting Software Fault Isolation to Contemporary CPU Architectures. The primary focus of the SFI model is a Windows x86-64 system but the same techniques can be applied to run identical x86-64 binaries on other x86-64 systems such as Linux, Mac, FreeBSD, etc, so the description of the SFI model tries to abstract away system dependencies when possible.

Please note: throughout this document we use the AT&T notation for assembler syntax, in which the target operand appears last, e.g. mov src, dst.

Binary Format

The format of Native Client executable binaries is identical to the x86-64 ELF binary format ([0], [1], [2], [3]) for Linux or BSD with a few extra requirements. The additional rules that a Native Client ELF binary must follow are:

Runtime Invariants

To ensure fault isolation at runtime, the system must maintain a number of runtime invariants across the lifetime of the running program. Both the Validator and the Service Runtime are responsible for maintaining the invariants. See the paper for the rationale for the invariants:

  • Exception: RSP and RBP are allowed to be in the range of 0..4GiB inside pseudo-instructions: naclrestbp, naclrestsp, naclspadj, naclasp, naclssp.
  • R15-40GiB..R15 and R15+4GIB..R15+44GiB are buffer zones with PROT_NONE flags.
  • The 4GB safe zone has pages with either PROT_WRITE or PROT_EXEC but must not have PROT_WRITE+PROT_EXEC pages.
  • All executable code in PROT_EXEC pages is validatable and guaranteed to obey the invariant.

Text Segment Rules

  • any privileged instructions
  • mov to/from segment registers
  • int
  • pusha/popa (not dangerous but not needed for GCC)
  • must point to a valid instruction boundary
  • must not point into a pseudo-instruction
  • must not point between a restricted register (see below for definition) producer instruction and its corresponding restricted register consumer instruction.
  • Exception to this rule: string instructions are allowed if used in following sequences (the sequences should not cross bundle boundaries; segment overrides are disallowed):

     mov %edi, %edi
     lea (%rZP,%rdi),%rdi
     [rep] stos  ; other string instructions can be used here
    

    Note: this is identical to the pseudo-instruction: [rep] stos %?ax, %nacl:(%rdi),%rZP

 ; any 32-bit register can be used here; the first operand is
 ; unrestricted but often is the same register
 mov ..., %eXX
 mov %rbp, %rsp
 mov %rsp, %rbp
 mov ..., %ebp
 ; restoration of %RBP from memory, register or stack - keeps the
 ; invariant intact
 add %rZP, %rbp
 mov ..., %esp
 ; restoration of %RSP from memory, register or stack - keeps the
 ; invariant intact
 add %rZP, %rsp
 lea xxx(%rbp), %esp
 add %rZP, %rsp  ; restoration of %RSP from %RBP with adjust
 sub ..., %esp
 add %rZP, %rsp  ; stack space allocation
 add ..., %esp
 add %rZP, %rsp  ; stack space deallocation
 and $XX, %rsp  ; alignment; XX must be between -128 and -1
 pushq ...
 popq ...  ; except pop %RSP, pop %RBP

List of Pseudo-instructions

Pseudo-instructions were introduced to let the compiler maintain the invariants without needing to know the code alignment rules. The assembler guarantees 32-bit alignment for all pseudo-instructions in the table below. In addition, to the pseudo-instructions, one pseudo-operand prefix is introduced: %nacl. Presence of the %nacl operand prefix ensures that:

For example, the instruction:

mov %eax,%nacl:(%r15,%rdi,2)

is translated by the assembler to:

mov %edi,%edi
mov %eax,(%r15,%rdi,2)

The complete list of introduced pseudo-instructions is as follows:

Pseudo-instruction Is translated to
[rep] cmps %nacl:(%rsi),%nacl:(%rdi),%rZP
(sandboxed cmps)
mov %esi,%esi
lea (%rZP,%rsi,1),%rsi
mov %edi,%edi
lea (%rZP,%rdi,1),%rdi
[rep] cmps (%rsi),(%rdi)
[rep] movs %nacl:(%rsi),%nacl:(%rdi),%rZP
(sandboxed movs)
mov %esi,%esi
lea (%rZP,%rsi,1),%rsi
mov %edi,%edi
lea (%rZP,%rdi,1),%rdi
[rep] movs (%rsi),(%rdi)
naclasp ...,%rZP
(sandboxed stack increment)
add ...,%esp
add %rZP,%rsp
naclcall %eXX,%rZP
(sandboxed indirect call)
and $-32, %eXX
add %rZP, %rXX
call *%rXX
Note: the assembler ensures all calls (including naclcall) will end at the bundle boundary.
nacljmp %eXX,%rZP
(sandboxed indirect jump)
and $-32,%eXX
add %rZP,%rXX
jmp *%rXX
naclrestbp ...,%rZP
(sandboxed %ebp/rbp restore)
mov ...,%ebp
add %rZP,%rbp
naclrestsp ...,%rZP (sandboxed %esp/rsp restore) mov ...,%esp
add %rZP,%rsp
naclrestsp_noflags ...,%rZP (sandboxed %esp/rsp restore) mov ...,%esp
lea (%rsp,%rZP,1),%rsp
naclspadj $N,%rZP
(sandboxed %esp/rsp restore from %rbp; incudes $N offset)
lea N(%rbp),%esp
add %rZP,%rsp
naclssp ...,%rZP
(sandboxed stack decrement)
sub ...,%esp
add %rZP,%rsp
[rep] scas %nacl:(%rdi),%?ax,%rZP
(sandboxed stos)
mov %edi,%edi
lea (%rZP,%rdi,1),%rdi
[rep] scas (%rdi),%?ax
[rep] stos %?ax,%nacl:(%rdi),%rZP
(sandboxed stos)
mov %edi,%edi
lea (%rZP,%rdi,1),%rdi
[rep] stos %?ax,(%rdi)
{{/partials.standard_nacl_article}}