为了账号安全,请及时绑定邮箱和手机立即绑定

大话闲聊之沙箱

沙箱跟容器其实是有点血缘关系的,要做容器肯定要实现隔离,而沙箱就是专门做隔离的。之所以把他们两个分开介绍是因为沙箱本身是一个很复杂的方向,有很多的种类,而容器只是使用了沙箱技术中的某几种。

沙箱技术大致可以被分为两类,其中第一类是基于隔离的沙箱,该类型的沙箱将应用的执行环境从操作系统中隔离出来,形成一个独立的执行环境。第二类是基于规则的沙箱,该类型的沙箱并不是完全关注于对于应用程序的隔离上,而是用规则的方式控制每个应用的权限,基于规则的沙箱之间可以分享操作系统的逻辑资源。

我读的论文主要都是第一类的沙箱,其中主要的技术是 capabilities, system call interposition, software-based fault isolation 等。

上面的链接是一个综述性质的文章,主要阐述了 Linux 平台上可以用来实现沙箱的内核 feature。它对沙箱的定义和功能介绍的比较简单易懂,不妨一读。

系统调用拦截(System Call Interposition)

System Call Interposition,顾名思义,就是拦截和过滤系统调用的技术。在沙箱的实现过程中,系统调用是一个很关键的部分。如何能够保证应用只能进行被授权的系统调用,是这个方向的研究做的事情。

Janus

链接中的第一篇论文是 1996 年发表的,是 system call interposition 方向上最经典的论文,它提出了一个系统,Janus。这个工具可以根据用户定义的 policy 来对应用的请求调用进行过滤,后面的两篇都是后续的关于 Janus 的论文。

在那个时候,互联网很流行,在互联网上获得的内容,可以直接用本地的 helper application 打开。因为内容本身是不可信的,所以这样的一种模式连带着 helper application 也是不可信的,因此 Janus 希望对这样的应用进行限制和隔离,使得应用具有最小的特权,当其被恶意程序攻击后,不会影响整个操作系统。

Janus 的目标有三点,第一个是安全不多说,第二个是灵活,就是要求对系统调用的限制可以达到参数级别,比如 open 在某些参数时可以被调用,其他就不可以,还有就是可配置,允许为不同应用配置不同的策略。

它的实现比较简单,是借助了内核中的 ptrace,然后实现了一个 kernel module 以及一个用户态的 engine。在开始的时候,Janus 会先读取 policy 文件,然后会 fork 子进程,然后父进程会 select 关于子进程的各种事件。子进程会执行 helper app 的逻辑,当有了一个系统调用,会先给 Janus 的内核模块处理,内核模块会跟用户态的 engine 交互,确定请求是不是合法的,合法会交由内核去处理,否则会 deny 掉请求。

下面的两篇论文中提到了一些关于 Janus 的缺点,包括 ptrace 和 Janus 本身的缺点,主要是涉及一些 system call 的监控问题,以及 deny 的方式问题,就不再说了,已经很长了。

Ostia

Ostia 是在 Janus 等等那一溜论文后面发表的,因此引用了 Janus 中提到的那三篇论文。它最大的贡献,在于提出了一种新的架构,然后解决了之前的基于 filter 的架构不能解决的问题。

它跟 Janus 其实是一挂的工具,都是要在内核态修改一些东西来做的。但是两者的不同在于实现的架构,Janus 是在内核中要有一个负责 tracing 的模块,比如 ptrace。然后在用户态有一个 policy engine,两者会交互,其中是否 deny 请求的逻辑在 policy engine 中,而内核中的模块主要是做 process monitor 的。

Ostia 的实现在我看来参考了虚拟化的一些思想,当一个系统调用到内核的时候,会回调在调用者用户态内存空间中的一个 handler,然后会转发给 agent,然后 agent 会负责校验权限和访问。具体的实现还在看。

软件故障隔离(Software-based Fault Isolation)

SFI

这篇文章是在 1993 年发表的,也正是这篇文章最早提出了 “sandboxing” 一词。这篇文章主要是介绍了用软件方式来实现隔离的方法。传统的隔离是在操作系统这一层来做的,以前进程之间可以通过 RPC 的方式进行,而隔离是通过虚拟内存来实现的。但是这样做的 overhead 特别大,因此这篇文章想实现在同一内存空间内的错误隔离。SFI 的实现,使用了处理器的段寄存器。段寄存器最初是为了解决 Intel 8086 处理器体系架构中数据总线与地址总线的宽度不一致而引入的,因为不一致导致寻址不能在单个指令周期内完成,因此 Intel 引入了段寄存器,将整个内存空间分为了4个段,段寄存器存放每个段的前 N 位的地址,使用段内偏移量,而不是全部的物理地址来描述内存地址,解决了这一问题。在目前处理器的架构中,不再存在地址宽度不统一的问题,因此内存分段成为了可选的特性。而 SFI 通过对段寄存器的访问限制,将程序的控制流严格地限制在一个 Fault Domain 的代码段中。在进行控制的跳转时,会强制使用段寄存器进行检测,如果访问的地址的前 N 位与段基地址不一致,则会触发异常,说明应用程序正在尝试逃出自己的 Fault Domain。

Google Native Client

这篇论文是在 CSP 课上读的, 因为需要做分享,所以读的相比于其他论文要仔细一些,之前在阅读的时候就写了一些阅读笔记,比较冗长,这里写一些这篇论文的大概 idea 的介绍,以及一些自己的评论。

Google Native Client(NaCl),简单来说是一个在浏览器里跑 Native 代码的技术。类比技术是微软臭名昭著的 ActiveX。相比于 ActiveX 那种毫无安全性可言的实现,NaCl 使用了一些自己改良过的 Software Fault Isolation(SFI) 的技术,结合了 ptrace 这样的 System Call Interception 的工具,来实现了在浏览器里安全运行 Native 代码的功能。从实现角度来看,是先对代码进行静态检查,保证代码符合 NaCl 制定的一些规则,然后再把程序运行在一个沙箱内,Native 代码所有跟外界的通讯,包括系统调用,都会被封装或者拦截,使用这样的方式来实现了对 Native 代码的安全隔离。在 2009 年的时候,Google 组织了一个 Native Client Security Contest,鼓励开发者寻找 NaCl 的漏洞,最终发现了 20 多个漏洞但是没有一个可以从根本上破坏 NaCl 的保护。目前 Google Chrome 浏览器仍然支持以这样的方式来运行 Native 代码,只不过好像没有多少人在用的样子。Demo 很容易运行,感兴趣可以试一下,很简单就可以实现从 CPP 代码到 Javascript 代码的通信。

为了提高浏览器段代码运行的效率,还有另外一个流派的做法,那就是 asm.js,它的实现思路跟 NaCl 完全不一样,并不会在浏览器里执行 Native 代码,因此不会有这么多安全方面的问题需要考虑,而是通过修改 LLVM 的那一套工具链,把 Native 代码编译成 Javascript 的一个子集,运行这个子集的 Javascript 代码。这样的实现最高可以只比 Native 应用慢一倍,虽然不如 NaCl 媲美原生应用,但是也可以接受了。这是 Firefox 浏览器的路子。

目前业界有了相对统一的思路,WebAssembly。WebAssembly 跟 asm.js 是相同的路子,得到了各种公司的支持。WebAssembly 由 asm.js 的团队和 NaCl 的团队一起开发,NaCl 的团队更多关注在安全上,这也是他们的特长。因此可以说,Native Client 这个 feature 大概是被抛弃了,但是一些安全的实现,还是由原班人马在为 WebAssembly 做贡献。其实这篇论文的主题也不是说 Native Client 的实现有多好,而是强调它是怎么做到安全的,讲道理这样的实现方式,我觉得是 asm.js 那个流派比较好,因为侵入性小一点。

Language-Independent Sandboxing

// TODO Add the notes
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消