To prevent unintended network access, network system
calls such as connect() and accept() are simply omitted. NaCl modules can access the network via JavaScript in
the browser. This access is subject to the same constraints
that apply to other JavaScript access, with no net effect on
network security.
The NaCl development environment is largely based
on Linux open source systems and will be familiar to most
Linux and Unix developers. We have found that porting
existing Linux libraries is generally straightforward, with
large libraries often requiring no source changes.
2. 3. Attack surface
Overall, we recognize the following as the system components that a would-be attacker might attempt to exploit:
• Browser integration interface
• Inner sandbox: binary validation
• Outer sandbox: OS system-call interception
• Service runtime binary module loader
• Service runtime trampoline interfaces
• IMC communications interface
• NPAPI interface
• Data integrity: no loads or stores outside of data
sandbox
• Reliable disassembly
• No unsafe instructions
• Control flow integrity
To solve these problems, NaCl builds on previous work
on CISC fault isolation. Our system combines 80386 segmented memory6 with previous techniques for CISC software fault isolation.
15 We use 80386 segments to constrain
data references to a contiguous subrange of the virtual
32-bit address space. This allows us to effectively implement a data sandbox without requiring sandboxing of load
and store instructions. VX3210 implements its data sandbox
in a similar fashion. Note that NaCl modules are 32-bit x86
executables. Support for the more recent 64-bit executable
model is an area of our ongoing development.
Table 1 lists the constraints Native Client requires of
untrusted binaries. Together, constraints C1 and C6 make
disassembly reliable. With reliable disassembly as a tool,
detection of unsafe instructions is straightforward. A partial list of opcodes disallowed by Native Client includes:
In addition to the inner and outer sandbox, the system
design also incorporates CPU and content blacklists.
These mechanisms will allow us to incorporate layers of
protection based on our confidence in the robustness of
the various components and our understanding of how to
achieve the best balance between performance, flexibility,
and security.
In the next section we argue that secure implementations of these facilities are possible and that the specific
choices made in our own implementation are sound.
• syscall and int. Untrusted code cannot invoke the
operating system directly.
• All instructions that modify x86 segment state, including lds, far calls, etc.
• ret. Returns are implemented with a sandboxing
sequence that ends with a register-indirect jump.
3. nATiVe clien T imPlemen TATion
Apart from facilitating control sandboxing, excluding ret
also prevents a vulnerability due to a race condition if the
return address were checked on the stack. A similar argument requires that we disallow memory addressing modes
on indirect jmp and call instructions. Native Client does
allow the hlt instruction. It should never be executed by
a correct instruction stream and will cause the module to
be terminated immediately. As a matter of hygiene, we disallow all other privileged/ring-0 instructions, as they are
never required in a correct user-mode instruction stream.
We also constrain x86 prefix usage to only allow known
useful instructions. Empirically we have found that this
3. 1. inner sandbox
In this section, we explain how NaCl implements software
fault isolation. The design is limited to explicit control
flow, expressed with calls and jumps in machine code.
Other types of control flow (e.g. exceptions) are managed in
the NaCl service runtime, external to the untrusted code, as
described with the NaCl runtime implementation below.
Our inner sandbox uses a set of rules for reliable disassembly, a modified compilation tool chain that observes
these rules, and a static analyzer that confirms that the rules
have been followed. This design allows for a small trusted
code base (TCB),
26 with the compilation tools outside the
TCB, and a validator that is small enough to permit thorough
review and testing. Our validator implementation requires
less than 600 C statements (semicolons), including an x86
decoder and cpuid decoding. This compiles into about
6000 bytes of executable code (Linux optimized build) of
which about 900 bytes are the cpuid implementation, 1700
bytes the decoder, and 3400 bytes the validator logic.
To eliminate side effects the validator must address four
subproblems:
C2
C4
C6
Table 1. constraints for nacl binaries.
C1 once loaded into the memory, the text segment is not writable,
enforced by os-level protection mechanisms during execution.
The binary is statically linked at a start address of zero, with the
first byte of text at 128Kb.
C3 All indirect control transfers use a nacljmp pseudo-instruction
(defined below).
The text segment is padded up to the nearest page with at least
one hlt instruction (0xf4).
C5 The text segment contains no instructions or pseudo-instructions
overlapping a 32-byte boundary.
All valid instruction addresses are reachable by a fall-through
disassembly that starts at the load (base) address.