eliminates certain denial-of-service vulnerabilities related
to CPU errata.
The fourth problem is control flow integrity, ensuring that all control transfers in the program text target an
instruction identified during disassembly. For each direct
branch, we statically compute the target and confirm it is
a valid instruction as per constraint C6. Our technique for
indirect branches combines 80386 segmented memory
with a simplified sandboxing sequence. As per constraints
C2 and C4, we use the CS segment to constrain executable
text to a zero-based address range, sized to a multiple of
4KB. With the text range constrained by segmented memory, a simple constant mask is adequate to ensure that the
target of an indirect branch is aligned mod 32, as per constraints C3 and C5:
and %eax, 0xffffffe0
jmp *%eax
We will refer to this special two instruction sequence as
a nacljmp. Encoded as a 3-byte and and a 2-byte jmp it
compares favorably to previous implementations of CISC
sandboxing.
16, 23 Without segmented memory or zero-based
text, sandboxed control flow typically requires two six-byte
instructions (an and and an or) for a total of 14 bytes.
Note that this analysis covers explicit, synchronous control flow only. Exceptions are discussed in Section 3. 2.
If the validator were excessively slow it might discourage people from using the system. We find our validator
can check code at approximately 30MB/second ( 35.7MB
in 1. 2 seconds, measured on a MacBook Pro with MacOS
10. 5, 2. 4 GHz Core 2 Duo CPU, warm file-system cache). At
this speed, the compute time for validation will typically be
small compared to download time, and so is not a performance issue.
We believe this inner sandbox needs to be extremely
robust. We have tested it for decoding defects using random instruction generation as well as exhaustive enumeration of valid x86 instructions. We also have used “fuzzing”
tests to randomly modify test executables. Initially these
tests exposed critical implementation defects, although as
testing continues no defects have been found in the recent
past. We have also tested on various x86 microprocessor
implementations, concerned that processor errata might
lead to exploitable defects.
14 We did find evidence of CPU
defects that lead to a system “hang” requiring a power-cycle
to revive the machine. This occurred with an earlier version
of the validator that allowed relatively unconstrained use
of x86 prefix bytes, and since constraining it to only allow
known useful prefixes, we have not been able to reproduce
such problems.
3. 2. exceptions
Hardware exceptions (segmentation faults, floating point
exceptions) and external interrupts are not allowed, due
in part to distinct and incompatible exception models in
Linux, MacOS, and Windows. Both Linux and Windows
rely on the x86 stack via %esp for delivery of these events.
Regrettably, since NaCl modifies the %ss segment register, the stack appears to be invalid to the operating system,
such that it cannot deliver the event and the corresponding process is immediately terminated. The use of x86
segmentation for data sandboxing effectively precludes
recovery from these types of exceptions. As a consequence,
NaCl untrusted modules apply a failsafe policy to exceptions. Each NaCl module runs in its own OS process, for the
purpose of exception isolation. NaCl modules cannot use
exception handling to recover from hardware exceptions
and must be correct with respect to such error conditions
or risk abrupt termination. In a way, this is convenient,
as there are very challenging security issues in delivering
these events safely to untrusted code.
Although we cannot currently support hardware exceptions, Native Client does support C++ exceptions.
24 As
these are synchronous and can be implemented entirely
at user level there are no implementation issues. Windows
Structured Exception Handling18 requires nonportable
operating system support and is therefore not supported.
3. 3. service runtime
Conceptually, the service runtime is a container for hosting Native Client modules. In our research system, the service runtime is implemented as an NPAPI plugin, together
with a native executable that corresponds to the process
container for the untrusted module. It supports a variety
of Web browsers on Windows, MacOS, and Linux. It implements the dynamic enforcement that maintains the integrity of the inner sandbox and provides resource abstractions
to isolate the NaCl application from host resources and
operating system interface. It contains trusted code and
data that, while sharing a process with the contained NaCl
module, are accessible only through a controlled interface.
The service runtime prevents untrusted code from inappropriate memory accesses through a combination of x86
memory segment and page protection.
When a NaCl module is loaded, it is placed in a segment-isolated 256MB region within the service runtime’s address
space. The first 128KB of the NaCl module’s address space
(NaCl “user” address space) is reserved for initialization by
the service runtime. The first 64KB of this 128KB region is
read and write protected to detect NULL pointers and to
provide for defense-in-depth against unintended 16-bit
address calculations. The remaining 64KB contains trusted
code that implements our “trampoline” call gate and
“springboard” return gate. Untrusted NaCl module text is
loaded immediately after this reserved 128KB region. The
%cs segment is set to constrain control transfers from the
zero base to the end of the NaCl module text. The other
segment registers are set to constrain data accesses to the
256MB NaCl module address space.
Because it originates from and is installed by the trusted
service runtime, trampoline and springboard code is
allowed to contain instructions that are forbidden elsewhere in untrusted executable text. This code, patched
at runtime as part of the NaCl module loading process,
uses segment register manipulation instructions and the
far call instruction to enable control transfers between