PaaS Sandbox 实现原理分析

GaRY | 2012-09-12 14:55

一、

云计算很火,各种云的实现方式也分为很多个流派。但是无论怎么变,基本类型是有的,主要分为SaaS,PaaS,IaaS。而平时大家接触较多的PaaS(也就是GAE,SAE等类似在线开发平台),是一般开发人员、安全人员第一能接触到的。而大家也对于其支持各种语言sandbox实现方式却并不了解,只是模糊的知道,需要修改语言源代码来做限制。至于怎么去限制,是不是只修改这些就够了,大家都不太清楚。所以无论是做沙盒突破,还是在PaaS上编写自己的程序,经常因为一些莫名其妙的限制而绕了大半钟头却不知所以然。于是本文的目的就是为了将这些看起来比较神秘的内容根据笔者的经验给大家做个介绍,权当科普。

本文介绍的所有PaaS环境搭建在Linux或者类似系统上。Win平台暂不涉及(也没见过用win搭建PaaS的吧?)

二、

首先需要明确,对于一个PaaS,什么是需要做限制的。这一点我在之前的《AppEngine安全测试思路》中做过介绍。云平台既然卖的就是资源,因此对于资源使用率和使用方式是非常敏感的。任何计算、存储、网络资源都是一个好的Sandbox需要关注的。除此之外,作为云平台,有很多不同程序的代码托管在之上。因此sandbox也许要保证这些托管代码不会对平台自身、以及其他不同的代码造成危害。因此总结下来sandbox就有两个任务:

1、资源限制

2、边界控制(或者叫安全控制,whatever)

明白了这一点,我们再来看看这两者需要如何实现。

PaaS搭建的是一个包含操作系统、网络环境、Webserver、CGI脚本解析器的整体环境,其结构大致如下:

[CGI语言]->[Webserver]->[POSIX 环境]->[系统内核]->[网络]->[其他资源(数据库等)]

可以看到,这基本是一层套一层的。因此其实在任何一层做好资源控制都可(到系统内核这一层为止。后续的两个只能控制自身有关的资源),但是大多数情况下多数PaaS的sandbox限制是做在CGI脚本语言这一层的。原因有二,一个是目标明确,如果你在其他底层做限制,可能会误伤到其他进程,牵扯太广。另一个是在语言层面实现的细粒度更全,出现疏漏,定位容易也更安全。毕竟是和用户交互的第一层。

但是如果将sandbox做在这一层,为了做资源限制,很多时候会改变CGI语言原本的api。例如用php可能需要disable一些function,用python可能取消去掉某些使用多线程的模块(实际上还是很多的)。这些会大大影响一些用户的使用习惯,某些开源程序也因此无法使用。

所以也有不少云提供商使用另一种sandbox模型。将大部分控制都做在POSIX环境以及系统内核这一层上。目前主要是国外的一些云厂商是这么做的,例如appfog,zend云等(都架构在亚马逊的IaaS上)。做在这一层的特点是学习成本少,不需要改用户习惯。但是坏处也很明显:因为将安全控制退居二线到操作系统层,因此他的每个app的边界也就扩大了,经常只能一个虚拟机只能跑一个app,否则就可能互相影响。因此这带来的成本就会很大(一个app一个虚拟机,或许还会更多)

在第二种模型下,我更觉得像是IaaS而不是PaaS。因此本文既然探讨PaaS的沙盒实现,则重点主要讨论第一种实现方式。也是国内目前众多云厂商所采用的方式。

三、

既然知道是修改CGI脚本语言这一层做的实现,那么,又是如何实现的呢?

这个基本就是要拼你对语言本身的理解和熟悉程度了。有的语言本身就考虑到了类似场景,例如php的disable_function和safe_mode,例如Java的securityManager,例如bash的"-r"等等。有的语言则需要你亲自动手去阉割一些内容。例如python。当然,合理并用应用两者才是比较好的实现方式。

这些语言实现看起来繁多,其实还是有一定脉络可寻的。一般的脚本语言源代码,通常分为三大部分:

一个是语言本体的语法解析,内存结构

一个是语言自身内部底层api

另一个则是语言自身携带的编程标准库。

拿php源码做例子,zend目录下主要都是语言结构等部分,main目录下是一些内部api,而ext目录下则是PHP自带标准库的内容;Python源代码中,Grammar、Parser和Objects分别是语法解析和内存结构,Python目录下是内部api,Modules和Lib分别是C和Py写的标准库。

而通常情况下所有和资源相关的操作,都不可能存在于语言结构和语法解析部分。而集中于标准库的内容中。而标准库通常在底层又集中调用语言内部api。所以通常来说,只需要修改内部api,就可以达到控制资源访问的目的。

不过由于开源软件可能有些不太规范,标准库还是可能自行去读写文件、操作资源的。而如果你的资源比较特定,比如mysql、memcache的访问控制,这些语言无关的内容,都需要去单独的标准库源代码去做修改控制。

需要修改和注意哪些内容呢?

1、资源限制:posix api调用、文件读写、网络读写、线程进程创建、内存cpu占用等等

2、边界控制:特权代码加载,引入,修改、共享区域代码控制、内存结构可访问性、初始化代码防护等

针对第一点,对不同的资源有不同的限制方法:

1、posix api调用。也就是基本常见的系统调用。通常语言都外包了一层提供在标准库中。这些需要坚决的禁用掉。例如exec,system,pcntl_*,pthread_*, dl之类。关闭这些内容主要是防止用户自己去通过调用这些基本api来绕过我们的资源控制。

2、文件读写。这些调用实际上在posix禁止的时候已经去掉大部分了。但是通常情况下语言自身也会需要访问文件。这些代码基本都在语言的底层内部api中。对于这些就不能一味禁止了,大大影响易用性不说,还可能产生意想不到的问题。最好的处理方式是找到统一的底层api,在这里做一些路径和权限限制,只允许读写制定路径下的内容。例如只允许加载标准库中的py,只允许将session临时文件写入在tmp目录下,所有fopen操作默认添加用户主目录前缀等。

3、网络访问。对于网络访问来说,最主要的控制内容是协议、来源、目的这三元组。也即只允许发送访问tcp/http协议(php的stream等需要阉割),不允许伪造ip(不允许rawsocket),目标也需要控制范围,不能是内网,不能是本机,只能是外网(判断目标ip)。有时可能还需要加入端口范围的控制。这里的控制方案,除了阉割必要的标准库之外,一般都是配合统一代理实现。通过代理有个好处是,我只需要修改语言访问网络的统一代码,默认加入代理,这样在代理上我就能做统一的访问控制(例如内网控制),同时还能有所有内容的日志记录,访问量大了,要扩展做成集群化也比较方便。

另外还有一系列网络资源的限制,如各种数据库、分布式系统、远程rpc,原理也是如此。修改统一底层api,统一到一个代理中进行分发、日志记录。既能做到透明,同时又有可伸缩性,可审计性也大大增加。

4、进程线程创建。这个没什么好说的,在posix部分已经限制住了。

5、内存cpu的占用。这个其实是个难点。幸好linux系统本身支持对单个进程进行控制,例如在每个fastcgi或者httpd进程启动时,利用setrlimit系列api在初始化的时候就对自身进程做cpu,内存占用的最大界限。但是实际上这个并不能解决单一进程多个用户公用时,某一个用户占用过多对其他用户资源占用的影响。如果要做到更深入细致的控制,后续估计只能通过修改操作系统内核来实现。目前多数paas没有做到这一层。

针对第二点,其实说穿了就一句话:注意对于共享的代码或者进程空间内容的控制。不允许任何人能够进行修改。

例如在每个请求初始化的时候能够修改初始化代码、或者php配置,例如能够修改paas默认在每个用户都会执行的共享库代码,例如用户可以加载一个c模块或者dll文件进入当前进程空间,之间绕过所有语言级别限制来执行特权代码。所有这些,都需要在源代码,或者系统级别进行控制。

是否做了这些就足够了?基本上sandbox是差不多了,但是实际上任何控制都是不嫌多的。一层控制或许能够抵得了今天,但是永远不保证能够抵得了明天。PaaS的安全其实是建立于底层更基础系统之上的。因此系统安全、集群网络访问控制一系列,都需要逐渐去完善。

参考文献:SAE架构ppt、BAE源码、Cloudfoundry源码