Linux沙箱技术

Linux沙箱技术介绍

在计算机安全领域,沙箱(Sandbox)是一种程序的隔离运行机制,其目的是限制不可信进程的权限。沙箱技术经常被用于执行未经测试的或不可信的客户程序。为了避免不可信程序可能破坏其它程序的运行,沙箱技术通过为不可信客户程序提供虚拟化的磁盘、内存以及网络资源,而这种虚拟化手段对客户程序来说是透明的。由于沙箱里的资源被虚拟化(或被间接化),所以沙箱里的不可信程序的恶意行为往往会被限制在沙箱中。

沙箱技术一直是系统安全领域的挑战,不存在说哪一种方案是足够安全的。沙箱技术方案通常是需要结合多种系统安全技术来实现,采用防御纵深(Defence in Depth)的设计原则,筑建多道防御屏障,尽可能地将安全风险将为最低。下面我们主要讨论如何利用Linux kernel所提供的安全功能来建立有效的沙箱技术。

在讨论之前,我们简单回顾一下Linux安全模型相关的内容(假设读者已经非常熟悉):

(1) 每个进程都有自己的地址空间;

(2) MMU硬件机制来保证地址空间的隔离;

(3) Kernel是系统的TCB(Trusted Computing Base),是安全策略的制定者和执行者;

(4) 进程是最小的权限边界;

(5) root具有最高权限,它能控制一切;

(6) 其它用户受DAC(Discretionary Access Control)限制,如文件系统的UGO权限控制。

进程是最小的权限边界,其根本原因是MMU能保证进程地址空间的隔离。

Linux Kernel还提供了与进程降权(drop privilege)相关的一些功能:

  1. setuid
  2. POSIX.1e capability
  3. chroot jail
  4. Quota control (eg, cgroup, namespace)
  5. Linux Container
  6. Linux Security Module (LSM)

下面我们会介绍如何在实践中利用这些诀窍来构建一个有效的sandbox.

setuid sandbox

Setuid Sandbox主要是基于Linux Kernel所提供的安全机制(eg,DAC)来实现。简单地说就是利用 random uid/gid + chroot() + capability的组合出击来达到目标。其实现非常简单,无需修改Kernel。下面分别悉数之:

  1. Linux中每个进程都会有一个uid,uid=0则为root用户进程(privileged),uid>0则为普通用户进程(unprivileged)。不同uid进程之间(不包括root进程)是相互隔离的,各自都有自己独立的权限,互不干扰。而root进程具有特权,它能干任何事情。Linux uid/gid机制主要是用于进程的权限隔离。如果你打算执行不可信的程序,那么你可以在启动该程序时为其分配一个random uid。一个可能的执行流程如下:fork() -> setuid() -> {设置相关的进程资源限制, eg, RLIMIT_NPROC (0,0)} -> execv()。注意,setuid()只能由root权限(或拥有 CAP_SETUID capability)才能成功调用,所以这个执行流程需要借助某个拥有root权限的helper。比如,将helper程序设置为setuid root。

  2. Chroot是Linux kernel提供的另一个安全功能,它用于修改进程的根目录。比如执行chroot(“/tmp/sandbox/1/“)则可以设置当前进程的根目录为”/tmp/sandbox/1/“,那么该进程的文件操作将被限制在”/tmp/sandbox/1/“中。注意,chroot()只能由root权限(或拥有CAP_SYS_CHROOT capability)才能成功调用。也许你马上会想到:在前面的执行流程中,先让具有root权限的helper去执行”chroot()”后再调用setuid() -> {…} -> execve(),但这样做是行不通的,因为execve()本要执行的binary文件已经不可用了(进程的根目录已经被重定位了)。Google的一篇文章里给出了一个解决此问题的简单方法:

    (1) Helper创建一个子进程H,注意要用clone()和CLONE_FS,使得Helper和H可以共享根目录、当前目录、等等;

    (2) Helper降权后执行execve(“Worker”);

    (3) Worker(原Helper进程)请求H去执行chroot();

    (4) H执行chroot(),新的根目录会对H和Worker同时生效。

    (5) H退出。

    这个方法听起来不错,前提是Helper需要设置RLIMIT_NOFILE为(0,0),并且对于不可信的Worker进程来说,在执行第4步之前应是可控的。

    另外,对于Helper程序来说,由于它是以root身份运行,那么就可能会成为攻击点,比如confused deputy问题。下面我们介绍如何用capability机制来解这个问题。

  3. Linux Capability 主要是解决 confused deputy problem. 这类问题的典型代表之一是 CSRF(cross-site request forgery).给一个简单的例子来描述: 假如你刚刚用浏览器访问过你的网上银行,而同时又在逛水木BBS。这时BBS上的某个坏蛋可能正好猜到你在访问网上银行,于是那个坏蛋就编写一个在你的网上银行站点进行转帐的form提交的链接,并将该链接作为他在BBS上传的图片的tag。如果此时你点击了他的图片,并且你的网上银行在cookie中保存的授权信息还没有过期,那么你就倒霉了。此时的你就被称为”confused deputy”,因为你糊里糊涂地就授权了那个坏蛋所诱导的这次交易事务。

    Linux支持Capability的主要目的是细化root的特权,以避免confused deputy problem. 比如拿ping程序来说,它需要使用raw_sockets所以需要root特权才能运行;如果有了Capability机制,由于该程序只需要一个CAP_NET_RAW的Capability即可运行,那么根据最小权限原则,该程序运行时可以丢弃所有多余的Capability,以防止被误用或被攻击。所以,Capability机制可以将root特权进行很好的细分,当前kernel(2.6.18)已支持30多种不同的Capability。注意在之前的kernel实现中,Capability只能由root进程持有,非root进程是不能保持任何Capability的。但是在2.6.24及以上的kernel版本中一个普通用户进程也将可以持有capability。

小结:可以看出,setuid sandbox实现是简单易行。在一定程度上,它可以用于隔离不可信的程序。由于它完全依赖于kernel所提供的安全机制,除非攻击者能找到kernel的0-day漏洞并通过攻击获得root权限,否则setuid sandbox所提供的安全隔离是可以保证的。不可信代码的隔离一直都是操作系统安全领域的挑战之一,面对这种挑战,我们应当采用防御纵深(in depth)的方法来解决。而最近,我们发现setuid sandbox已被Google用作Chromium系统的第一道隔离屏障。

seccomp sandbox

Seccomp(secure computing)是Linux kernel (自从2.6.23版本之后)所支持的一种简洁的sandboxing机制。它能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用4种系统调用(system calls),即read(), write(), exit()和sigreturn(),否则进程便会被终止。

Seccomp是Andrea Arcangeli在2005年设计的,其目的是解决grid computing中的安全问题,比如你打算出租你的CPU资源,但又担心不可信的代码会破坏你的系统。那么,Seccomp则可以为“不可信的纯计算型代码”提供一个“安全(SAFE, not SECURE)”的运行环境,以保护你的系统和应用程序的正常运行不受不可信代码的干扰。

据说Google Chrome浏览器的开发人员曾经考虑过使用Seccomp来建立Chrome Sandbox,但考虑到Seccomp的一些不足而“另辟蹊径”—— Native Client 。『据了解,Google Chrome 4 在今年 CanSecWest Applied Security 大会上是唯一没有被当场攻破的浏览器,而IE8 on Windows7/Vista/XP, Mozilla Firefox 3 和 Apple Safari 4 都相继被攻破。』

简洁、优美是Seccomp的优点,但只能支持“纯计算型”代码却使得其应用受到很大限制。比如,Seccomp模式的进程不能动态分配内存、不能与其它进程使用共享内存、不能使用新的文件描述符、等等。如果要支持具有丰富功能的应用程序,则需要另外的方法来截获并处理其它系统调用。

有人提议对seccomp进行改进使其支持对系统调用提供更细粒度的控制。比如,对seccomp增加一个新的mode,使用一个bitmap来精确描述哪些系统调用是可以被访问的,而哪些是被禁止的。而进程自己还可以丢弃(但不能重新获取)它所具有的访问哪些系统调用的能力(这种工作方法有点像Linux capability安全机制,尽管这两者是完全正交的)。但是到今天为止,还没有看到相关的进展,据说是因为ftrace也支持类似的功能,如何裁定还需要进一步讨论。

通过截获系统调用来实现sandbox是一贯的做法,它假设Kernel是好人,而User不一定是好人。在现代操作系统中,User和Kernel的空间是隔离的,一个进程只能通过系统调用才能从user空间进入kernel空间。如果一个进程不需要执行系统调用(即不需要kernel提供的丰富功能),那么我们的系统被攻击的风险就小。当然假设进程不使用系统调用是不切实际的,但对今天的多数系统来说,为用户程序提供的丰富功能是以牺牲安全性为代价的。但是,“仅仅通过截获并处理系统调用的方法去实现sandbox”是否是正确的技术方向呢?我们知道,当系统调用的参数保存在用户空间的时候,要想验证该参数是否“安全”是非常困难的,比如TOC2TOU问题便是一个挑战:一个恶意进程可能会在“参数被安全检查”之后、而在“实际使用参数”之前将该参数换掉,这便使截获系统调用时所做的参数检查变得没有意义。要解决这个问题,我们也许不应当只将目光锁定在系统调用的入口处。

sandboxing一直以来都是一个大难题,对于今天的COTS OS来说还不存在一个通用的安全方案。如何去做满足自己需要的sandbox,则需要量体裁衣。

ptrace sandbox

暂无

vm sandbox

暂无

参考链接: