翻译 并理解Native Client: A Sandbox for Portable, Untrusted x86 Native Code

可以参考 http://www.cnblogs.com/phinecos/archive/2008/12/12/1353816.html

 

摘要:
本文章主要描述 Native Client(一个运行跨平台、不可信x86平台代码的沙盒环境)的设计思想、实现原理以及相应评价。Native Client的目的,是让基于浏览器的APP,在不用牺牲安全性的前提下,有着与native app相同的性能。Native Client利用软件故障隔离技术和一个secure runtime来隔离直接的系统调用,所带来的副作用就是必须通过Native Client提供的接口来进行调用。Native Client提供二进制码的操作系统可移植性,支持一些在脱离Web app编程环境下性能类的特性,比如线程支持、指令集扩展比如SSE(Streaming SIMD Extensions,单指令多数据流扩展),同时可以使用编译器内联函数和手工编写的汇编程序(hand-coded assembler)。我们把这些特性结合到一个开源的架构上,这样可以鼓励community review以及第三方工具的出现。

 

1. 介绍

作为一个APP平台,现代的web浏览器汇集了一个很好的资源集合为了图像展现和用户交互,包括无缝访问因特网资源,高效的编程语言如JavaScript,以及丰富的Document Object Model (DOM) 。这些优势将浏览器放在被选择的前沿,作为新APP开发的目标平台,它在一个关键点上有缺陷:computational performance。感谢摩尔定律和硬件社区的热忱,尽管有上述障碍,许多有趣的应用在浏览器中仍然获得了足够多的性能。但是由于性能限制,对于基于浏览器的应用来说依旧有一些不可行的大计算。例如:  牛顿物理学的模拟,流体动力学的计算和高分辨率场景渲染。目前的环境趋向于排除利用大段的高效的代码除了JavaScript。

 现代Web浏览器提供了可扩展机制,比如ActiveX和NPAPI来运行本地代码可像web app一样被载入及运行。这些架构可能作为备选方案,他们允许plugins去绕过原本会应用于Web内容的安全机制,同时给了plugins全部的访问权限。鉴于这种组 织和缺失有效的技术来限制这些plugins,浏览器应用希望利用本地代码必须依赖非科技的措施来保证安全;比如,通过弹出框手动设置信任关系,或者手动 安装一个控制台应用。从历史看来,这些非技术性的措施已不足以抵挡恶意本地代码的执行,并导致了不方便和经济上的危害。因此我们相信对于基于 browser的应用的本地代码的扩展存在广泛的偏见。

 承认了当前的结合了native-code的web app系统的不安全性,我们也观察到没有一个实质性理由可以解释为什么native code是不安全的。在Native Client中,我们将安全的本地执行问题从被信任的扩展模块中分割出来,让各自独自管理。概念上,Native Client被分割成两块:一块是被限制的执行环境提供给native code,避免意外的影响,另一块是一个运行时,用来托管这些native code的扩展,通过这个,允许发生的side effects可能会产生的更少一点。

主要的工作如下:

1)  一个提供给 OS和 browser-portable 沙盒 x86字节模块 的基础设施

2)  支持一些先进的性能, 比如 threads、SSE架构、Compiler Intrinsics(固有的编译?)及硬编码汇编

3) 一个开发的系统,设计给简单的重定向和新的编译器及语言

4) CISC(Complex Instruction Set Computer)复杂指令系统计算机软件缺陷隔离技术的改良,利用x86的分割块来改善简化性和降低系统开销。

 

我们将这些特性结合在一个基础架构上,支持安全的副作用和本地通信。总的来说,Native Client提供了沙盒安全的native code的执行,以及操作系统间的可移植性,为浏览器提供了本地代码的性能。

 

本文的结构如下。1.1节描述了我们的模型。第 2章研究了一些NaCl系统架构的基本概念和编程模型。第3章介绍了一些额外的实现细节,主要是关于重要的系统组件。第4章提供了定量的系统评估,利用更 多现实的APP和APP组件。第5章,我们讨论这项工作的一些含义。第6章讨论相关先前的及当代的系统。第7章总结。

 

1.1 Threat Model

 

Native Client必须处理一些 来自任何站点的不被信任的模块,并要保证有比得上被接受的系统如JavaScript的安全性。当展现给系统时,一个不被信任的模块可能包含随意的代码和数据。所以NaCl runtime必须能够确认模块遵守我们的合法性规则(具体下面描述)。不遵守的模块会被系统拒绝执行。

 

一旦一个遵守规则的NaCl模块被接受执行 了,NaCl runtime必须限制他的行为,防止无意识的副作用,比如可能通过不加节制的访问,获得到native操作系统的系统调用接口。NaCl模块可能随意的 结合了整个多种多样的,被NaCl执行环境批准的行为,来试图损害系统。它可能会执行任何可获得的指示来阻碍生效的text segment(代码段)。它可能用尽方法来运用NaCl App的字节接口访问runtime services,比如传递非法参数等。它也可能通过我们的模块间的通信接口,来传递任意的数据,这个通信每次回应都会验证输入。NaCl模块也会根据资 源的限制来分配内存和spawn线程。它可能试图利用race condition来破坏系统。

 

我们在下面的文章里,表明了我们的架构和代码验证规则,在没有沙盒的情况下是怎么样限制NaCl模块的。

 

2. System Architecture

 

一个NaCl App由一组可信及不可信的模块组成。图一展示了一个假设的,基于NaCl的,用来管理和分享图片的App的架构。它包括了两个组件:一个用户接口,用JavaScript实现在web浏览器中执行,以及一个图片进程库(imglib.nexe), 实现成一个NaCl模块。在这个假定的情景中,用户接口和图片进程库是这个app的一部分,因此是不可信任的。浏览器组件被浏览器执行环境所限制,图片进程库被NaCl容器所限制。这两个组件都是可以跨操作系统和浏览器,可移植的,native代码由Native Client提供移植性。在运行app之前,用户必须装上Native Client作为浏览器的插件。注意,NaCl browser plugin自身并不是跨平台的,它是和操作系统和浏览器有关的。再次注意,这个插件是被信任的,它有着访问OS系统调用接口的全部权限,用户为了不被伤害而信任它。

 

当用户访问到有这个图片App的网站时,浏览器加载并执行App的JS组件。JS依次调用NaCl浏览器插件,来加载图片进程库到NaCl容器中。注意到native code模块是静静的载入的,没有弹出框窗口询问批准。Native Client负责限制不被信任的模块的行为。

 

每个模块跑在它私有的地址块内。内部组件通信是基于Native Client的可靠的数据包服务, IMC(Inter-Module Communications). 浏览器和NaCl模块之间的通信,Native Client提供了两个选择:一个简单的RPC工具,以及NPAPI(Netscape Plugin Application Programming Interface)。两者都是基于IMC实现的。IMC同时也提供了共享内存块和共享同步体,试图来避免在高容量和高频率的通信下的消息的系统开销。

 

NaCl模块也可以访问“service runtime”接口,这个接口提供了内存管理操作,进程创建和其他系统服务。这个接口和一个常见的操作系统的系统调用接口很相似。

 

本文,我们利用“NaCl module”来描述不可信的native code。注意到,然而App可以用到多个NaCl module, 可信和不可信的组件都可以利用IMC。比如利用photo App的用户可以选择利用一个假设的可信任的NaCl service,做本地图片存储,如图2所示。因为它需要权限访问local disk, 这个存储service必须安装成一个native browser插件;它不可以被实现成NaCl module。假定这个photo app被设计成可用这个稳定的存储service;用户接口就会在初始化过程中,检查

这个plugin。如果它检测到storage service plugin, 用户接口就会为它建立一个IMC通信channel, 传递一个channel的描述给image库,使得image库和storage service可以直接通过基于IMC的service(SRPC【simple RPC】、共享内存等)通信。这样的话,NaCl module通常就会把提供访问storage service程序接口的库链接进来,这个过程屏蔽了IMC级别的通信细节,比如到底是用了SRPC还是共享内存的方法。注意,storage service必须假定image库是不可信的。storage service负责确保它唯一的服务请求,要和用户的默认约定保持一致。比如,它可能对图片App所用的整个磁盘实行限制,也可能进一步限制到只能操作一个特定的目录。

 

Native Client是一个很理想的架构用来提供给需要纯计算的APP组件。它并不适合以下需求的模块:创建进程、直接访问文件系统以及自由的访问网络。被信任的设备比如存储设备必须在Native Client之外实现,这样可以使得独立的模块更加简易和健壮,同时也可以加强各组件的隔离性和易观察性。该设计模仿了Microkernel 操作系统的设计。

 

由以上的例子,我们将要更加细致的描述NaCl系统的关键的设计部分。

 

2.1 The Inner Sandbox

Native Client 是建立在一个X86内部进程“inner sandbox" 之上的。我们虽然相信“the inner sandbox”是健壮的,无论如何,为了弥补深度的不足,我们也开发了第二个“outer sandbox",that mediates system calls at the process boundary. "outer sandbox" 大体上近似于之前的架构,所以我们将不会在这篇文章里讨论它。

 

内部沙盒利用静态分析来检测不被信任的x86代码中的安全问题。之前这种对于任意的x86代码进行的分析是被人们质疑的,因为self-modifying code和overlapping instructions。在Native Client中,我们通过一系列队列和结构上的规则禁止了这些,当然保证了native code可以被完全反编译,这样所以的指令在反编译的过程中可以被识别。将反编译作为工具,我们的验证器可以确保可执行文件只包含了合法指令的子集,禁止了不安全的机器码。

 

inner sandbox进一步利用了x86 分段存储(segmented memory)的方法来限制数据和指令的内存引用。促进目前的硬件去实现这些检查,就可以极大的简化需要限制内存引用的运行时检测,相应的也减少了安全机制的消耗。

 

inner sandbox用来创建一个包含一个native操作系统进程的安全的子域。有了这个,我们可以将一个trusted service runtime子系统放置在和untrusted app module相同的进程中, 同时有一个安全的trampoline/springboard 机制用来允许安全的从trusted到untrusted以及反向的转化。尽管有时候,一个process boundary可以有效的克制内存及系统调用的副作用,我们相信inner sandbox可以提供更多的安全性。我们通常认为操作系统不是完美无缺的,比如进程屏障可能有缺陷,更深一层,操作系统可能故意将共享库映射到所有进程的地址空间上,像Microsoft windows上发生的一样。事实上,我们的inner sandbox不仅仅从native module中隔离中系统,也从操作系统中隔离出了native module。

 

2.2. Runtime Facilities

 

沙盒是用来阻止有害的副作用的,但是有的副作用经常对native module有用的。对于进程中的通信,Native Client提供了一个可信的报文抽象,“Inter-Module Communications” service or IMC。IMC允许trusted和untrusted模块去收发报文,报文由隐式的字节数组以及可选的“NaCl 资源叙词”组成,可以增强跨越进程边界的文件的共享,内存共享以及通信信道等。IMC 

可以被可信和不可信的两种模块使用,并且它是两种更高级别的抽象基础。第一个是Simple Remote Procedure Call(SRPC),提供了方便的语法来定义和在NaCl boundaries用子程序?,包括从浏览器中的JS调用到NaCl的代码。第二个,NPAPI,提供一个被大家熟知的接口与浏览器 state 互动, 包括公开URLS和访问DOM,这都符合了已存在的为了内容安全限制。其中的任一个机制都可以被用来与常规的浏览器内容做交互,包括content修改、处理鼠标键盘事件以及获取额外的site content; 实质上,所有的一般性的资源对于JavaScript都是开放的。

 

综上所述,service runtime是负责提供container, NaCl modules可以通过它与其他module及浏览器进行交互。service runtime提供了一系列的system services, 且通常与一个应用程序的环境有关联。它提供了sysbark()、mmap()系统调用,基本物体去支持malloc()/free()接口及其他内存分配的抽象。它提供了 一个POSIX线程接口的子集,包括一些NaCl的扩展,线程的创建和销毁,条件变量,互斥器,管道和信号灯和本地线程存储。我们的线程技术已经完成的允许在Native Client上的Intel's Thread Building Blocks(TBB)。service runtime也提供了POSIX文件I/O接口,用来操作通信信道和基于web的只读内容。因为本地文件系统的名空间不可以被这些接口访问,所以本地的漏洞攻击是不可能的。

 

为了阻止非故意的网络访问,网络系统调用如connect() accept()之类的是被简单忽略的。NaCl模块可以通过JaveScript在浏览器中访问网络。这个的访问限制和其他JaveScript的一样,也不会对网络安全产生威胁。

 

NaCl开发环境很大程度上是基于Linux开源系统,这对于大多数的Linux/Unix开发者是很熟悉的。我们发现移植已有的Linux库是很干脆的,因为大多数的库都不需要做改变。

 

2.2. Attack Surface

 

总体来说,我们发现下列作为系统组件是有可能被攻击者所利用:

 

  • inner sandbox: binary validation 二进制验证
  • outer sandbox: OS system-call interception 操作系统调用拦截
  • service runtime binary module loader 二进制模块加载
  • service runtime trampoline interfaces 
  • IMC communications interface 
  • NPAPI interface
 
除了inner和outer沙盒之外, 系统设计也包含了CPU和NaCl模块的黑名单。这些机制可以允许我们合入保护层,保护层是基于我们对于various组件健壮信的自信心和我们对于怎么去平衡性能、灵活性及安全性的理解。
 
在下一节,我们希望展示 实现 这些机制安全性的可能,同时你会从我们的实现工作中看到一个特别的选择。

 

 

 

3. Native Client Implementation

 

3.1. Inner Sandbox

 

在这一节中,我们会解释NaCl是怎么实现software fault isolation(软件错误隔离)的。该设计受限于明确的控制流,控制流用机器码中的call和jump来表达。其他形式的控制流(比如异常)是在NaCl service runtime中管理的,这对于untrusted code来说是外部的,这在下面介绍NaCl runtime的实现时会讲到。

 

我们的inner sandbox利用一系列的规则来使得反编译更可信,包括一个更改后遵循这些规则的编译工具链,一个静态分析器用来确定这些规则是否被遵循。该设计为一个小的trusted code base(TCB)考虑到,在TCB外有一个的编译工具,还有一个验证器,它足够小到可以允许彻底的review和测试。我们的验证器实现仅需要少于600 C代码:包括一个x86的解码器和cpuid decoding。这些被编译成6000字节的可执行代码(Linux优化编译),其中900字节是cpuid的实现,1700字节是decoder,还有3400自己是验证器的逻辑。

 

为了消除验证器的 side effects, 下面是4个子问题:

 

  • Data integrity: no loads or stores outside of data sandbox 数据健全:不在data sandbox的外部载入和保存
  • Reliable disassembly 可信的反汇编
  • No unsafe instructions 安全的指令
  • Control flow integrity 控制流的健全

为了解决这些问题,NaCl在之前工作的基础上关注到CISCComplex Instruction Set Computer fault isolation。为了CISC software fault isolation我们的系统将80386段存储与之前技术结合。【We use 80386 segments to constrain data references to a contiguous subrange of the virtual 32-bit address space.】我们利用80386段来限制在虚拟32位地址空间中的连续子段中的数据引用。这使得我们有效的实现了一个data sandbox,这样就不需要沙盒的载入和存储指令了VX32用一个类似的方式实现了它的data sandbox。注意,NaCl模块是32字节 x86的可执行文件。64字节的可执行模式暂时还不支持。

 

NaCl模块二进制代码规则:

C1:一旦被加载到内存中,二进制文件就不可写,这在整个执行过程中都是被OS基本的保护机制所强制限制的。

C2 :二进制是静态的链接到起始地址为了0的,第一个字节在64K的地址空间。

C3  : 所有的indirect control传输,利用的是一个 nacl jmp 假的指令(defined below).

C4  : The binary is padded up to the nearest page with at least one hlt instruction (0xf4). 二进制被填充到最近的page, 该page至少有一个hlt指令。

C5  :不包含指令或者包含假指令的二进制overlapping a 32-byte boundary

C6:所有合法的指令的地址,可以被一个下落特性的反汇编从load(base)地址开始访问。

 

Table 1列出了Native Client对untrusted二进制文件的限制。C1和C6使得反汇编变得可信。有了一个可信的反汇编工具,检测不安全的指令就变得很容易了。在NaCl Client中被禁止的一部分运算码包括:

 

  • syscall and int. 不可信的代码不可以直接调用操作系统。 
  • all instructions that modify x86 segment state, including lds, far calls, etc. 所有可以更改x86段状态的指令,包括lds,far calls等。
  • ret. Returns are implemented with a sandboxing sequence that ends with an indirect jump.
除了推 进控制沙盒之外,排除ret也阻止了一个可被攻击的地方,即如果return address在堆中被检查的话会有race condition。一个类似的争议需要我们禁止,在间接jmp和call指令上的内存寻址方式【memory addressing modes】。Native Client允许hlt指令。但是hlt指令不应该在一个正确的指令流中被执行,这会导致模块立即总结。作为保护的关键,我们不允许所有的特权为 ring0指令,因为他们在一个正确的用户模式的指令流中是不被需要的。我们同时也限制了x86前缀的用法,仅允许已知的有用的指令。经验上的看我们发 现,这样能排除掉和cpu勘误【cpu errata】相关的,特定的denial-of-service(Dos)脆弱点。
 
第4个问题是关于控制流的健全,保证了 在反汇编的过程中,程序中所有的,在程序中文本里转移的control ,都确定好了一个目标指令。 对于每一个direct branches 来说,我们静态的计算出target然后确认它是一个合法的指令,这和限制C6说的一样。我们的indirect branches also known as a computed jumpindirect jump and register-indirect jump 技术,就是80386段存储加上一个简单的沙盒序列【sandboxing sequence 】。就像C2及C4说的,我们利用CS段来限制可执行的文本去访问一个基于0的地址范围,以4K字节为倍数。有段内存限制了文本范围,一个简单的constant mask 就可以足够保证了 indirect branches的target被调整成mod 32, 这就是C3和C5说的。
 
and     % eax, 0xffffffe0
jmp     * % eax ,
我们将这两个特别的指令序列看出nacl jmp。

你可能感兴趣的:(client)