figure 2. The hypothetical application of figure 1 with a trusted
storage service.
Browser
imglib.nexe
User interface
HTML and
JavaScript
SRPC
NPAPI
IMC
SRPC
IMC
Storage
service
Service runtime
SRPC or whether it uses shared memory. Note that the storage service must assume that the image library is untrusted.
The service is responsible for ensuring that it only services
requests consistent with the implied contract with the user.
For example, it might enforce a limit on total disk used by
the photo application and might further restrict operations
to only reference a particular directory.
The Native Client architecture was designed to support pure computation. It is not appropriate for modules
requiring process creation, direct file system access, or
unrestricted access to the network. Trusted facilities such
as storage should generally be implemented outside of
Native Client, encouraging simplicity and robustness of
the individual components and enforcing stricter isolation
and scrutiny of all components. This design choice echoes
microkernel operating system design.
1, 4, 12
With this example in mind we will now describe the
design of key NaCl system components in more detail.
2. 1. The inner sandbox
Native Client is built around an x86-specific intra-process
“inner sandbox.” We believe that the inner sandbox is
robust; regardless, to provide defense in depth,
5, 8 we have
also developed a second “outer sandbox” that mediates
system calls at the process boundary. The outer sandbox is
substantially similar to prior structures11, 20 and we will not
discuss it in detail here.
The inner sandbox uses static analysis to detect security
defects in untrusted x86 code. Previously, such analysis has
been challenging for arbitrary x86 code due to such practices as self-modifying code and overlapping instructions.
In Native Client we disallow such practices through a set of
alignment and structural rules that, when observed, ensure
that the native code module can be disassembled reliably,
such that all reachable instructions are identified during
disassembly. With reliable disassembly as a tool, our validator can then ensure that the executable includes only the
subset of legal instructions, disallowing unsafe machine
instructions.
The inner sandbox further uses x86 segmented memory
to constrain both data and instruction memory references.
Leveraging existing hardware to implement these range
checks greatly simplifies the runtime checks required to
constrain memory references, in turn reducing the performance impact of safety mechanisms.
This inner sandbox is used to create a security sub-domain within a native operating system process. With
this organization we can place a trusted service runtime
subsystem within the same process as the untrusted application module, with a secure trampoline/springboard
mechanism to allow safe transfer of control from trusted
to untrusted code and vice versa. Although in some cases
a process boundary could effectively contain memory and
system-call side effects, we believe the inner sandbox can
provide better security, as it effectively isolates the native
system call interface from untrusted code, thereby removing it from the attack surface. We generally assume that
the operating system is not defect free, such that this
interface might have exploitable defects. The inner sandbox further isolates any resources that the native operating system might deliberately map into all processes, as
commonly occurs in Microsoft Windows. In effect, our
inner sandbox not only isolates the system from the native
module, but also helps to isolate the native module from
the operating system.
2. 2. Runtime facilities
The sandboxes prevent unwanted side effects, but some
side effects are often necessary to make a native module
useful. For interprocess communications, Native Client
provides a reliable datagram abstraction, the “
Inter-Module Communications” service or IMC. The IMC allows
trusted and untrusted modules to send/receive datagrams
consisting of untyped byte arrays along with optional
“NaCl Resource Descriptors” to facilitate sharing of files,
shared memory objects, communication channels, etc.,
across process boundaries. The IMC can be used by trusted
or untrusted modules, and is the basis for two higher-level
abstractions. The first of these, the SRPC facility, provides
convenient syntax for defining and using subroutines
across NaCl module boundaries, including calls to NaCl
code from JavaScript in the browser. The second, NPAPI,
provides a familiar interface to interact with browser state,
including opening URLs and accessing the DOM, that conforms to existing constraints for content safety. Either of
these mechanisms can be used for general interaction with
conventional browser content, including content modifications, handling mouse and keyboard activity, and fetching additional site content, substantially all the resources
commonly available to JavaScript.
As indicated above, the service runtime is responsible
for providing the container through which NaCl modules
interact with each other and the browser. The service runtime provides a set of system services commonly associated
with an application programming environment. It provides
sysbrk() and mmap() system calls, primitives to support
a malloc()/free() interface or other memory allocation abstractions. It provides a subset of the POSIX threads
interface, with some NaCl extensions, for thread creation
and destruction, condition variables, mutexes, semaphores,
and thread-local storage. Our thread support is sufficiently
complete to allow a port of Intel’s Thread Building Blocks21
to Native Client. The service runtime also provides the common POSIX file I/O interface, used for operations on communications channels as well as Web-based read-only content.
As the name space of the local file system is not accessible to
these interfaces, local side effects are not possible.