最近没干劲,已经有不少人向我推荐越狱,恩恩,暂时逃避下现实吧。
如果日常而又无意义,但不得不做的事是监狱,毕竟这是责任和义务么。那么完成那些私人计划的有意义的事就是挖开监狱的通道。也如同主人公一般,各种事让越狱变得很不顺利,让人烦躁。
现在的软件一些构成了地狱,而其他的就是照向地狱阳光。追逐阳光,直到象Linus所说的那样,我是这个世界的上帝。于是我将发动洪水,把火种留在诺亚方舟上.......
哈哈,开个玩笑,发泄下怨念。不过对GNU和Debian之类的Hack的浪子般的不负责的开发软件表示极度的BS。
2009年3月8日星期日
| [+/-] |
越狱 |
2009年1月27日星期二
2008年9月12日星期五
| [+/-] |
新的Linux包管理方式与目录结构 |
特性:
模块化
“热插拔”
稳健性
读、写、执行分离
易于访问控制
模块化
整个系统如同拼图版一样拼成。并且呈现一定的层次结构:
微核:由内核+initrd构成,其中initrd的要求是启动、恢复系统的最小集合:
initrd不能依赖除内核、自身以外
能够恢复系统——恢复核心环境(C库、包管理系统...)
含klibc、busybox、必要的模块
核心环境:具备一定完整功能的最小集合:
C库
包管理系统
init
...
其他环境
一个示例的目录结构:
PuzzleFS
PuzzleFS是假象中的实现“热插拔”功能的用户态文件系统(FUSE)。PuzzleFS还是用来实现访问控制的文件系统。
PuzzleFS上的目录结构指定,类似示例目录结构。这样,它和主源的映射关系就比较简单。所不同的是,PuzzleFS是所有源的混合,其某个文件夹下的内容是多个源对应文件夹的并集。
当新的包发现时(新源挂载,或者包下载复制到对应目录——依据配置在执行域中),启动“热插拔”历程。
“热插拔”时,首先读取/验证新包的meta信息。meta信息包括以下:
- 数字签名
- 依赖关系(例如冲突...)
- 访问控制规则(基于路径的正则表达式匹配,可以更复杂地制定主体。当然这意味着对建链接的额外关注)
- 包的所属环境
- 包的分类
- 图标
- ...
熟悉Singularity的朋友,可以想象依赖关系和访问控制规则是其相应风格的(MBP...)?
另外,这个方式符合“现代”的安全实现方式:在进行动作前,基于某种描述方式来对动作的影响进行描述,之后验证该动作不违背先前描述。因此,在系统辅助不产生冲突的情形下,软件的安全规则由软件开发者提供(没有人比软件开发者更了解软件的正常行为)
“热插拔”的第二阶段,是可选,为解包阶段。即包如果是压缩,或者需要安装时刻动作(比如编译...),则进行相应动作。包解包后存放的实际地址可以指定源。
第三阶段,为新包分配相关“资源”。包的内容是只读的,并且只有指定目录下的文件可以有可执行的权限(bin、lib)。因此,比如为包分配一个影子etc目录(在System下某个目录中或者用户目录下的某个目录下),当有程序改写etc目录中的内容时,则执行一个COW(Copy On Write),结果在影子etc目录下)
第四阶段,包的注册阶段。包的某些内容写入数据库(Sytem某个目录下)。这样做是因为:
- 比如,包中lib是公开的,则其下文件等内容数据库索引。对bin下的内容也是,加快相应操作
- 包的meta中的信息,比如实现分类,方便用户分类搜索浏览
- 包的源来分类浏览
- 包是封装的,任何其他包不能访问包中的非公开文件(配置文件),则在数据库中注册(通用接口的)配置信息,即这是类似注册表或者GConf的。
PuzzleFS实现了“热插拔”、访问控制、包封装、搜索、通用接口的配置功能。
访问控制机制是通过PuzzleFS是对多个原生文件系统统一封装的用户态文件系统的自然结果。
包的封装是指对包的访问,(在访问控制规则许可下)限制只可对bin、lib以及通用配置信息的访问。其他,比如外包不能访问非本包的etc下文件。
搜索。是由于PuzzleFS背后有数据库支持的结果。用于比如加快输入命令,链接库等的查找,分类视图的作用。这是对之前硬盘搜索的自然嵌入(想像微软未曾经计划的WinFS、苹果的spotlight搜索、Novell的beagle...)
通用接口的配置。使得PuzzleFS扮演了注册表(GConf)的角色,用于包配置信息的导出共享等。相比注册表,这里的实现方式更加稳健:所有PuzzleFS数据库中的信息只是一个缓冲,出现问题后可以方便重建的。
2008年9月5日星期五
| [+/-] |
项目 |
- 一个高性能程序编写框架:
- 异步:
- 使用I/O设备、使用其他CPU(主线模型)
- 信号机制
- 调度(优先级)
- 对OpenCL等的封装,易于使用
- 管线对象:
- 一系列串行例程不需要用信号机制连接
- 流水线两个阶段约定数据传递方式(可以是Unix的shell风格)
- 由依赖图生成管线(并被缓冲,参见缓冲对象)
- 期待代替Gstreamer和LLVM编译器驱动
- 缓冲对象:
- 缓冲对象未必在内存中,可以在磁盘...(发布对象?)
- 内存的缓冲(SLAB风格的内存分配)
- 管线缓冲(缓冲由依赖图和状态动态生成的管线)
- 其他...对若干操作得到的结果(立体的)——一个操作就能得到结果(平面化,命中的情形下)
- 与系统资源监视服务互动,调整缓冲大小
- Detective Conan:
- 嵌入在调用中。
- 在收集I/O结果、错误、性能参数。报告某监视进程。
- 用例:web服务器、下载器、启动器<缓冲、日志、收集结果>。
- 非MVC的UI编写框架
- 界面和模型参数的传递是“量子”的而非经典的(需要和控制器无限交互)
- 由ASL库启发
- 解析器等依赖LLVM,依赖图依赖上一框架
- LLVM整合?
- 基于结构化、模式匹配的限制(contrain)模型
- 用于窗口系统对窗口“权限”的限制
- 所见即所得编辑器缺陷的弥补:附加在文字编辑器的模板,对某一域限制的图形化显示,违背限制的图形化显示。比如论文的索引,鼠标移到之上显示某些下拉菜单等的组合,移出恢复”所见所得“模式
- 易用的访问控制模型。结合系统包管理方式
- 包管理方式:
- 分布式<没有中心/usr...,热插拔>、文件系统布局、特性通知(忘了configure...)、访问控制<封装>
- X windows的移除
- 分离功能,一些功能分离到EGL、OpenGL中
- 关注窗口树结构,DOM风格(事件传递、处理)
- wayland now
- LLVM SVA操作系统的设计
- Singularity Like
- ...
将在某个CMS中实践部分
2008年6月29日星期日
| [+/-] |
缓冲溢出漏洞的防御 |
缓冲溢出通常发生在C或者C++编程语言中。在2002年,针对缓冲漏洞的修正占了全部安全修正的22.5%1,而基于此的开发攻击所占的比重更高(2003年为75%)。
防御手段1——“金丝雀”(Canaries)法
通过在缓冲区和控制数据之间放置“金丝雀2”(或叫做“金丝雀”数据),来监视缓冲区是否溢出。当缓冲区溢出时,第一个损坏的数据就是“金丝雀”,之后对“金丝雀”数据进行验证就可预警发生了缓冲溢出。
Stack-Smashing Protector(SSP)是GCC编译器的对“金丝雀”法的实现。SSP在缓冲区与控制数据之间放置“特殊符号”(已知符号,随机符号或随机符号与控制信息异或产物),之后检查这些符号来侦测溢出。(同时,SSP还把数组变量移向栈底来增加溢出的难度)
“金丝雀”法的防御是有限的,比如不能防御通过溢出来改变结构体中的函数指针的攻击。
防御手段2——对内存页保护增加执行位
最近的CPU,都开始支持在内存的页保护中加入执行位(比如Intel和AMD的NX(No
eXecute )技术),从而阻止不在“可执行”页中的代码。这样,当溢出使得控制流跳转到恶意代码起始时,由于恶意代码所在页不具备执行位,故会产生页异常导致程序退出。
通过增加可执行位能够对缓冲溢出攻击提供较为全面的保护。但仍有不如意之处(以主流x86处理器为例):
需要CPU支持
需要启用PAE3扩展(这是由于传统页保护字段中没有多余位可用)。启用PAE会带来大约6%的性能上开销
不能完全抵御缓冲漏洞的攻击。比如改写返回地址到system ()函数上,此操作并未对当前进程中注入新的可执行代码,从而绕过保护机制。
防御手段3——段限制法
使用段限制,即只有进程虚拟地址的前N个M字节可执行。N值由操作系统来选定。
操作系统来保证所有程序的代码在段限制长度之内(图4左部),而数据,栈则在虚拟内存高地址处(图4右部)。当缓冲溢出执行恶意代码时,会触发段错误而导致程序退出。
段限制法在大多数程序中可行,但在某些非常少见的情形下(比如负责Linux图形的XFree86TM服务端)也许不是最好保护方式。此外,段限制法要求CPU支持分段机制。在不支持分段的情形下(比如CPU不支持或x86-64处理器运行于纯64位模式4)不能使用。不过此种情形下,处理器一般支持页保护字段中的“执行位”。
防御手段4——随机化内存布局
随机化内存布局基于一个观察:即攻击者只有知道缓冲区大体位置的才能实施攻击。在防御手段2中提到的如改写返回地址到system ()函数的攻击方法,也需要知道system函数的位置。这些可以通过攻击者在其电脑上运行相似的系统来观察到。通过随机化内存布局,使得攻击者不能猜测相关的地址,从而抵御攻击。
例如,随机化程序以下部分的内存地址:
栈
共享库
程序堆
随机化内存布局需要编译器支持,并且重新构筑关键的程序。
防御手段2、3和4,即构成了RedHat ExecShield技术,并且融合到了Linux内核2.6.25版。
1数据源自http://cve.mitre.org/board/archives/2002-10/msg00005.html
2金丝雀对有毒气体敏感,在煤矿中,被用来预警有毒气体。
3PAE允许32位CPU访问大于4GB的内存。参见Intel® 64 and IA-32 Architectures Software Developer's Manual,Volume 3A: System Programming Guide, Part 1,4.13 PAGE-LEVEL PROTECTION AND EXECUTE-DISABLE BIT
4参见Intel® 64 and IA-32 Architectures Software Developer's Manual,Volume 3A: System Programming Guide, Part 1,3.2.4 Segmentation in IA-32e Mode
| [+/-] |
强制访问控制技术 |
SELinux(Security Enhanced Linux)
强制访问控制技术是对自主访问控制(discretionary access control:DAC)信任模型改进。在自主访问控制中,用户不全是可信的,而资源的所有者指定信任哪些用户(从而这些用户可以访问资源)。自主访问控制的缺陷在于其不能区别人类用户和计算机程序,或者说,在自主访问控制的信任模型中,用户对计算机程序充分信任,即认为程序是无缺陷的、无恶意的。这显然与现实不相符合。
类型强制(Type Enforcement:TE)
在SELinux,进程是访问控制的主体,联合某一安全上下文。客体是系统所有安全对象,包括进程(比如A进程向B进程发送信号时,B进程就作为客体)。SELinux的核心是类型强制(Type Enforcement:TE)。系统根据主体的类型(type),客体的类型(type)与规则作出访问控制的决策。
进程的类型在进程创建时确定且基本不变(一个例外是当进程有dyntransition权限时,可以在执行时改变其类型,这是个不安全的特性,故只允许进入对应许可集为原类型的子集的类型)。SELinux对系统中的所有需要保护对象的访问方式加以罗列,定义为对象类(object class)。之后依据主客类型,许可主体对客体的部分访问权限(即客体所属对象类的所有方法的某个子集)。SELinux实行白名单方式的访问控制,规则中未许可的访问将被拒绝。如图5:
基于角色的访问控制
SELinux中,基于角色的访问控制构筑在类型强制(TE)之上。一个SELinux对象安全上下文如下:
用户:角色:类型
在该上下文中,一个用户可能拥有多个角色,而每个角色则与多个类型相关联。
一个用户的进程(主体)的最初始的安全上下文在登录时获得。用户登录时确定用户的角色(从该用户拥有的多个角色中择一)。在登录后,运行新程序的进程的类型由如下决定:
可执行文件的类型
父进程的类型
规则库中,相关类型转换规则。如
type_transition user_t passwd_exec_t process passwd_t;
图6展示了通过类型转换来确定新进程的类型的全过程。
Bell-La Padula模型的实现
对BLP模型的支持是通过可选的MSL(multilevel security)机制实现的。为了支持MSL,SELinux对对象的安全上下文进行扩充:
用户:角色:类型:安全级范围(低安全级-高安全级)
其中,一个安全级有两个部分组成:敏感级(即密级)和附在本敏感级上的(0个以上)分类。敏感级之间是层次的,而分类则不是。因此,安全级范围的两端要求:
高安全级中的敏感级 > 低安全级中的敏感级
高安全级中的所属分类集合 包含 低安全级中的所属分类集合
例如,为了防止高密级的数据流向低密级的数据(保证机密性:向下读,向上写):
mlsconstrain file write ( L1 domby L2 );
mlsconstrain file { read getattr execute } ( L1 dom L2 );
在第一条规则中,L1指主体的(安全级范围中的)低安全级,L2指客体(安全级范围中的)低安全级。这样,写某个文件必须保证,主体的安全级不得高于客体的安全级。
在第二条规则中,L1与L2分别代表主客体(安全级范围中的)的低安全级。这样,读取某个文件必须保证,主体的安全级不得低于客体安全级。
SELinux的访问控制过程
在启用SELinux的Linux发行版中,访问控制的过程如下:
进行DAC检查
进行TE检查
进行MLS检查
访问控制是每次访问时进行的。例如DAC仅在打开文件时进行访问控制,而SELinux则在每次对文件进行读(或其他操作)进行。这样作的好处是使得对许可规则的改变可以迅速反映出来。
SELinux的结构
对内核对象提供访问控制
通过访问向量缓冲(Access Vector Cache:AVC),来减小访问控制带来的性能开销
通过伪文件系统SELinux(就像许多Linux的伪文件系统一样)来导出管理接口
PMS本身是个用户空间的对象管理器,负责创建代表策略资源的对象类,并对这些策略资源实施细粒度的访问控制。通过PMS,可以允许用户管理工具不用改变TE许可规则,增加用户和赋予角色。更大的好处,可以仅授权数据库服务器改变与之相关的对象类(object class)和TE规则。
PMS把系统策略分离为内核与用户两部分,分别把他们载入内核安全服务器与用户空间安全服务器(userspace security server:USSS)。由于PMS是一个运行中的服务器,可以扩展其接口来允许远程的网络访问,从而支持分布式策略管理。PMS与USSS允许运行时刻注册对象类(object class)。
总结
SELinux的限制:
复杂性:用户需要定义类型(Type)和(极为繁多的)规则。SELinux全面涵盖了各式各样的安全威胁,这也是它复杂的一个原因。为了降低其复杂性,SELinux的策略可模块化,并适时载入。
性能:复杂性带来性能上的开销,这在桌面或者服务器系统上也许并不重要,但是对于消费电子设备来说是不可接受的。因此需要进行相应的优化。在08年的嵌入式Linux会议(Embedded Linux Conference:ELC)上,Yuichi Nakamura宣称通过削减策略(保留适合嵌入式环境的策略——一个令人生畏的工作,但通过创建更简单的策略语言和策略编辑器,Nakamura做到了:4.6M大小的策略文件削减为60K)、移除多余许可检查、优化读写路径和转换缓冲静态分配为动态分配,使得在其SuperH平台上的性能提升了10倍,内存节省了250K2。
动态最小权限原则:SELinux的主体(进程)被赋予某一类型(Type)后,在其生命周期内基本不能再改变(一个例外是有dyntransition权限时,允许其改变为某些权限更小的类型上)。在基于工作流的模型中,在工作流的各个阶段,可以指定不同的最小权限,即理想下,贯穿于工作流各个阶段的进程应在执行中改变类型——动态最小权限。
粒度:SELinux的访问控制的粒度是对象,或者具体的说来,是在当前安全上下文下,决策是否允许对客体对象方法的访问。而对于某些复杂的方法——整合多个功能、参数之间相互依赖,组合形成不同的访问方式,SELinux便无法控制了。
对于复杂性,需要程序的开发者负责开发,实现对应程序运行时最小权限的策略(程序的开发者应该更加了解程序运行的最小权限是什么)。用户信任并接受相应策略,并在安装时刻随程序一起安装到系统中(相应策略应与系统已有策略一致)。这样用户就无须为策略的编写而担心。事实上,可以把对程序的强制访问控制策略看成是程序的正确性断言,通过信任断言的有效性(断言失败,程序便被终结而不会造成更大的破坏),来信任程序。故用户可以回到原来的DAC世界中。
对于动态最小权限原则,可以考虑缩小主体的粒度,比如为一个代码段。不同的代码段完成不同的工作阶段,一个进程则由多个不同类型(Type)代码段构成。实现起来需要考虑的一个问题是相应代码段的安全标签的存储问题:主流操作系统支持的外存的存储粒度是文件,而多个代码段则可能被存储到一个库文件中,这样各个代码段安全标签存储、代码段的界定值得考虑。也许需要SELinux引入新的信任(比如信任编译器/链接器)。
强制访问控制技术——SMACK
针对SELinux的复杂性带来的实际应用部署上的困难,SMACK3(The Simplified Mandatory Access Control Kernel)通过简单的方式,来解决SELinux所针对安全威胁的某个子集。
SMACK访问控制方式相当简单:
标签与标签名一致
标签之间没有隐含的关系
主客体有各自的标签
客体的标签源自创建其主体的标签
直接指明访问控制许可
比如:sub obj r,就许可了有标签sub的主体对标签为obj的客体有读权限。所有的许可(信任)关系必须被直接指明。
Bell-LaPadula模型的实现
需要指明所有级别的相互许可关系,比如有三个秘密度依此上升的安全级:unclass、 secret、 topsecret,实现向下读,需要以下规则:
secret unclass rx(读、执行权限)
topsecret secret rx
topsecret unclass rx
强制访问控制:基于路径 vs 基于标签
在SUSE Linux,引入了名为AppArmor的基于路径的访问控制。基于路径的访问控制方式,相当灵活,例如:
/home/*/public_html/**.html r
许可了各个用户的public_html目录下.html文件的读操作。与之相比,在SELinux和SMACK中需要对上述每个文件打上相应标签。
对基于路径的访问控制最大的批评是这种方式很容易被绕过。比如可以建立一个指向目标文件,从而获得一条不同的路径,绕过对原路径的进行的访问控制。
基于路径的访问控制比较贴近管理逻辑(即高层),而访问控制机制只有在底层实现才能确保不被绕过,因此可以考虑在比如SELinux管理工具中基于路径,之后管理工具对相应文件贴上标签。这个过程中管理工具的行为类似一个编译器:把高层的逻辑翻译成底层的实现。
其他可以考虑的方式,是对文件系统的名空间给予保护,从而防止用户随便增加新的名指向被保护名(代表的文件),来确保基于路径的访问控制不被绕过。
总体来看,MAC更加倾向反应对程序的正确性的强制(断言),而非用户的意志。因此需要DAC(反应用户的意志来进行补充)。可以如下表达:MAC与程序紧密联系,而DAC则与用户联系密切些。因此,终端用户定制安全环境,应该更多地从DAC入手。
当用户的角色成为MAC确定最小权限的因素时(变量),MAC与DAC可能会存在逻辑上的联系,需要定义/导出公认的、一致的角色。
1这个项目在:http://sepolicy-server.sourceforge.net
2LWN.net Weekly Edition 2008.4.24 http://lwn.net/Articles/278492/
3SMACK已经融入了Linux内核2.6.25中
| [+/-] |
虚拟机技术 |
在SELinux中,可信基包括:内核、对象管理器、安全服务器及相关工具。这是相当大的一个可信基,特别是Linux内核并非微内核设计,是个庞大的部分。在这个大的可信基中,难免不出现导致保护措施失败的Bug。
因此,基于纵深防御原则,引入虚拟机技术。
虚拟机技术有各种实现方式。比如操作系统级的虚拟化,通过引入IPC命名空间1、对进程调度器引入调度实体2实现二级调度及内存容器3等,把一组相关的进程放入一个容器,构成一个虚拟环境,共享硬件资源。每个虚拟环境有一个虚拟的IP,从而虚拟环境之间构成虚拟网络进行通信。当某个虚拟环境崩溃时,其他虚拟环境不受影响。
关于虚拟机技术的总览,参见http://www.ibm.com/developerworks/cn/linux/l-linuxvirt/。下面回顾下Xen虚拟技术。
Xen有两种工作模式:全虚拟化(full virtualization)和泛虚拟化(paravirtualization)模式。前者不需要操作系统修改,借助CPU支持实现同一时刻运行多个操作系统。后者需要修改操作系统驱动,及其他相关代码,来实现同一时刻运行多操作系统。
Hypervisor提供对客户机的页表进行修改(泛虚拟化),或者使用影子页表(全虚拟化),来把客户机的虚拟地址到客户机物理地址的映像(客户机页表)进行重新映射。
Hypervisor本身并不负责I/O,而是把设备专有地配给某个虚拟域或者转发来自其他虚拟域的(前端驱动的)请求到虚拟域0,由虚拟域0的后端驱动接收请求后,交由其自生的原生驱动处理后应答。
每个客户机可以配置成拥有任意数量的虚拟CPU,Hypervisor以虚拟CPU为调度单位进行调度。
Hypervisor和各个虚拟域之间的内部通信:
通过事件通道进行通知
通过环形缓冲区放置请求和应答
通过授权表来允许别的虚拟域访问本虚拟域的内存页。比如可以交换内存页来实现内存零拷贝。
在外存上的隔离,每个虚拟机使用各自镜像文件。不过,可以通过使用写时拷贝(Copy On Write:COW)特性文件系统,比如ZFS5或者Btrfs6,来使得各个虚拟机共享某些文件。当某一虚拟机写共享文件时,该文件被拷贝并被改写。
在Xen虚拟机技术中,某一虚拟域上的客户操作系统的失败不会影响其他虚拟域上的操作系统。Xen虚拟技术的可信基只有Hypervisor本身,比较容易确保其正确性。
Xen虚拟机技术的应用中,如在同一服务器硬件上一个虚拟机上中运行web服务器,一个虚拟机上运行邮件服务器。使得之一服务器的崩溃对另一服务器无影响,同时也提高了硬件的利用率。
4Intel® 64 and IA-32 Architectures Software Developer's Manual,Volume 3B: System Programming Guide, Part 2,CHAPTER 20 VIRTUAL-MACHINE CONTROL STRUCTURES
| [+/-] |
概念分布式操作系统——Singularity |
singularity是微软概念分布式操作系统,展示了一个“把可靠性和可信度 作为首要的设计目标”,从头来设计的软件平台。
singularity的设计者认为,通过更为可靠的技术和设计方法是可以在很大程度上提高系统的可靠性的。因此,singularity的可靠性与安全的实现依赖了许多其他方面的技术:
通过安全的设计语言消除可以预防的缺陷,如缓冲溢出。singularity主要使用C#语言的扩展——Sing#来编写。
通过健全的验证工具在开发周期中侦测程序的错误。
通过一个改进的系统构架,在明晰定义的边界处,阻止运行时刻错误的传播。
软件隔离的进程(Software-Isolated Processes:SIP)
和传统的操作系统中进程的概念类似,SIP是作为处理资源的载体,为程序执行提供了上下文:
SIP关联一组包含数据和代码的内存页面。
SIP以某一安全ID执行,关联OS的安全属性。
通过内核ABI(Application Binary Interface),SIP来使用内核函数。
含有一个以上的线程。
每个SIP是自治的,含有自己的数据布局、运行时刻系统和垃圾收集器
与传统的进程不同的是:
SIP的隔离是通过软件而非硬件(如MMU单元等)实现的。
SIP之间不共享数据。通过在channels上交换消息进行通信。
SIP中的代码是密封的,不能动态载入和创建代码。
软件隔离
软件隔离通过篇基于语言的类型和内存访问检查来实现。类型检查保证值或对象被正确解释和操作,内存访问检查保证引用的内存在有效对象的边界内。软件隔离能够比硬件隔离更加细粒度。硬件隔离中,进程可以按照页保护字段的规定,随意访问自己的内存空间(栈和堆),而在软件隔离中,访问的方式和范围被限制,对粒度为变量的区域提供了保护(封装)。
借助软件隔离,所有的SIP和内核可以运行于单一的环0的地址空间中,从而进程的创建,系统调用,调度与通信等的开销减小了。
另外,类型和内存安检确保了函数的执行的完整性,从而可以在安检后,在SIP中运行的信任函数中使用特权指令。例如,访问硬件I/O的特权指令可以在安装阶段,安全地内联到设备驱动中。这也促进了性能上的提升。
-
开销(单位:CPU周期)
API调用
线程产出
消息反馈
进程创建
Singularity
80
365
1,040
388,000
FreeBSD
878
911
13,300
1,030,000
Linux
437
906
5,800
719,000
Windows
627
753
6,340
5,380,000
表1:进程基本开销(硬件平台:AMD Athlon 64 3000+(1.8GHz)CPU,NVIDIA nForce4 Ultra芯片组。
表1数据来源Singularity: Rethinking the Software Stack1,并未指明操作系统的版本与配置。在An Overview of the Singularity Project2类似测试中,指明了操作系统版本与配置,且数据与此表相似,故可能是同一测试平台(硬件:AMD Athlon 64 3000+ (1.8 GHz) CPU,NVIDIA nForce4 Ultra 芯片组,1GB内存,Western Digital WD2500JD 250GB 7200RPM SATA硬盘(不含指令重排(command queuing)),网卡为nForce4 Ultra native Gigabit NIC(不含硬件TCP加速)。操作系统为FreeBSD 5.3,Red Hat Fedora Core 4(内核版本2.6.11- 1.1369_FC4与Windows XP (SP2))据此,对照的测试OS是较老的版本,且存在配置上的问题(比如使用性能低下的pthreads),数据未必精确,但Singularity的性能优势较明显,具备参考意义。
软件隔离所带给SIP的低系统开销,也拓广了进程这一传统概念的应用范围。比如,对微内核OS最大的批评是其低下的性能,从单一内核中分离出的功能运行在各自进程中,来提高系统的稳定性和安全性。但这样作也造成了核与系统进程,系统进程与系统进程之间紧密的通信,不得不通过穿越硬件保护边界——造成了性能上巨大的开销。因此,实用的微内核操作系统总是在微内核设计上加以妥协,比如Windows NT系列系统就把NT内核与NT执行体就被合并到了单个可执行映像了。
软件隔离的不同于传统的构筑安全的思维:在传统的思维中,安全隔离应当在底层、外围实施,从而避免引不必要的依赖(这些依赖可能带来新的安全隐患),因此安全应当是专门独立出来。而在singularity中,安全并不单一存在,而是各种技术混合的结果。从最小权限原则上来说,只有越贴近应用逻辑(高层),才能实现细粒度的控制,这对开发安全的程序也是有利的——错误发现的越早就越容易排除。(后续章节将解释,singularity如何解决实施安全,带来新的依赖的问题)
在SELinux一节中,表达了这样一个观点,即强制访问控制是在操作系统支持粒度下,对程序正确的行为进行断言。而软件隔离则在语言层次上,对语言的正确使用作出断言(由编译器或者解释器负责)。(纯粹的)软件隔离同时也意味着对硬件访问接口的再次封装,即只有通过安全的编程语言才能使用硬件资源。这也是在SELinux一节中的得出的结论,明晰合理的接口相当于细化了安全检查的粒度。
可以依照纵深防御原则,把软件隔离与硬件隔离相结合,比如把单独内核运行在环0的地址空间中,其他SIP运行在环3的各自的地址空间(类似微内核那样),或者经过数字签名,信任的软件于内核一起运作在环0的地址空间中,其他未签名的不受信任的软件运作在环3的各自地址空间中。当然,还可以考虑,如把不安全的语言编写的程序运行在独立的地址空间中,而安全语言编写的程序与内核共处一地址空间,从而兼容以有程序。
这些配置也为观察硬件保护的开销带来了机会,下图源自Singularity: Rethinking the Software Stack。
图10,各种隔离措施所带来的系统性能上的开销。
图中,采用是WebFiles(基于SPECweb99的I/O密集型测试)在6种不同的配置上进行测试,并以第2个配置方案为标准进行衡量。测试采用了3个SIP:
客户端(发出对文件随机读请求)
文件系统
驱动
6个配置方案分别为:
No runtime checks:不进行任何安全隔离,用于对照开启软件隔离的开销(图中,4.7%)
Physical Memory:3个SIPs与内核运行于同一地址空间和同一特权级。禁用分页。
Add 4KB Pages:在上一配置的基础上,开启分页(页大小:4KB)与TLB
Add Separate Domain:在上一配置的基础上,把客户端SIP单独移到一个地址空间中(特权级维持为环0)
Add Ring3:在上一配置的基础上,客户端SIP的特权级改变为环3。
Full Microkernel:把每个SIP移到各自环3的地址空间中。
可见软件隔离所带来的开销非常小(<5%),与之相比较,硬件隔离的开销非常大。
软件隔离的另一个好处是,脱离了对MMU的依赖,从而SIP可以运行在一些特殊的处理器上(或者说可编程I/O设备),比如GPU(Graphics Process Unit,时下渐热的流计算的基本硬件),从而支持非均一处理器组成的分布式计算的网络。
内存管理——SIP之间不共享数据
在SIP中的指针必须指向自己的内存或者交换堆中所拥有的内存。
SIP通过ABI调用内核页管理器获得新的、独占的页。这些页不定与之前分配的内存页相邻,连续的页可以通过大的对象或者数组获得。ABI不传递对象指针。
SIP的栈是链式的,调用内核ABI时,寄存器等的现场环境被保留到栈中一特殊结构中,区分SIP与内核空间。中断处理使用专用的栈。
SIP间传递的数据必须驻留在交换堆中,交换堆中的指针只能指向其自身,对于交换堆,有线性(linearity)属性:任何时候,SIP至多只有一个指向交换堆中一块的指针(由此可推,交换堆中的块任一时刻至多属于一个线程)。只能通过向另一SIP(通过channel)发送消息来转移块的所有权,发送后,原SIP不能在访问该块(SIP可能含有指向交互堆的尾随指针(dangling pointer,指向此SIP不再拥有的块),静态的检查确保SIP不通过尾随指针访问内存)。
每个SIP有其自己的收集器来回收其对象空间中的对象。
代码密封
SIPs含有的代码在运行时刻是密封的,不能动态载入和创建代码。动态载入往往用在诸如载入软件插件的环境中。糟糕的插件可能导致程序崩溃。动态创建代码往往用于某些语言的自省3(reflection)机制。
原需要动态载入的代码,现需要运行在子SIP,通过channel使用父SIP的服务而不是调用函数。Singularity中动态创建代码的自省机制被移除,取而代之的是编译时刻的自省(Compile-Time Reflection:CTR)
密封代码提供了如下好处:
使得程序分析工具能够静态侦测程序的缺陷
通过代码内容来标记一个SIP
在运行时刻执行环境中,不需要复制OS风格的访问控制。
基于Contract的Channel
在singularity中,Channel是唯一支持的IPC手段。Channel提供无损、有序的消息队列。Channel是双向的带有两个终点的队列管道。
终点有权能,比如只有接收了来自另一SIP指向文件系统的终点当前SIP才能访问文件。Channel终点或者在程序启动是获得(由基于清单(manifest)的配置文件获得),或者通过已有的channels中某个消息获得。因为传递的消息必须在交换堆中,故终点驻留在交换堆。这意味着每次终点只明确的属于一个线程(SIP至多只有一个指向交换堆中一块的指针)。每个终点有一个接收队列,向一个终点发送消息,就是把该消息放入该终点的接收队列中。
Contract是描述channel上的通信。其由消息申明(规定参数的数目和类型,及(可选的)消息的传递方向)和一组命名的协议状态组成。借助Contract,SIP之间的通信是有效、可分析的。编译器可以静态地验证发送和接收操作,确保channel不进入错误的协议状态。
Contract可以看成对通信这种工作模式的程序正确性的断言,也可看作基于任务访问控制(Task-Based Access Control)中对工作流的限定。
Channel遵循有限(finiteness)属性:contract的每个状态中,至少含有一对接收和发送操作——终点不能不等待消息而发送无限量的数据。这样作允许在终点中静态分配接收缓冲队列。
Channel的另一个好处隔离了消息传递的实现,比如在同一特权级同一地址空间中的两个SIP之间,实现成零拷贝:发送者通过在接收终点中,放入指向消息(交换堆中)的指针,来传递交换堆中的消息的所有者关系。不同硬件保护域之间的通信,可以实现为数据拷贝或者内存页的COW映射。
基于清单的程序(Manifest-Based Programs)
MBP是一个程序:通过静态的清单(manifest)定义。在Singularity中,执行程序实际上是调用清单而非调用可执行文件。
清单是对程序的描述,是一个机器可检查的,对MBP期望行为的说明(描述了一个MBP代码资源,需要的系统资源,期望的行为能力和对其他程序的依赖)
清单用来进行静态/动态验证MBP的属性。验证包括类型和内存安全、不含特权模式指令、与channel contracts一致(申明contracts的用法)、正确的对应版本ABI的用法。
MBP安装到系统后,清单用来标识MBP代码、验证其符合系统的安全需求,确保MBP对系统的所有依赖得到满足,防止MBP的安装干扰先前安装的MBP的执行。执行前,清单被用来发现MBP相关参数的配置,并对参数进行限制。MBP执行时,清单指导代码载入SIP执行、新的SIP与其他SIP之间的Channel、对系统资源访问的许可。
清单可以内联到程序中,也可分离出来(被多个程序所共享)。
清单(一部分)类似SELinux中的规则,在操作系统的粒度下,对程序进行了限制。可以看在是在OS粒度下程序正确性的断言。
清单另外还对MBP正常运行所需的依赖关系检查,确保了MBP的可靠性。在Windows环境中这部分工作由安装程序所完成,在Linux中,则由各自发行版的包管理程序负责。清单对程序相关参数的配置的检查,这一般由执行程序负责的。之所以独立出上述两点,可以明确的保证程序设计时考虑到相关的检查,使得程序结构更加清晰。
如果比较当前普遍的基于杀毒软件构筑的安全体系,杀毒软件负责对将运行的程序进行验证——不含有病毒库中的特征,且没有触发启发式规则报警。为了让杀毒软件更加有效,可以对待验证的程序要求含有“证据”表明其清白。在Singularity中,这种“证据”即是包含在高级语言Sing#中,而“病毒库”则对应了清单。
Singularity内核
Singularity内核是个微内核,提供基本抽象——SIP、Contract、MBP ,负责调度、对硬件资源的特权访问4、系统内存、线程和线程栈,创建/销毁SIP和Channel。
内核通过ABI向SIP提供内存服务。默认状态下,SIP只能操作其自身的状态、停止/开始子SIP(ABI调用只影响调用方的状态,如SIP同步对象不能够跨越SIP访问)。
ABI 强制标明版本,语义明确。没有ioctl或者CreateFile之类的模糊语义的函数。在SELinux一节的分析中得到的结论:明晰的接口有益于细化访问控制的粒度。
SIP通过channels而非ABI函数来获得高级系统服务(如访问文件、发送和接受网络包)。
ABI调用开销比函数调用开销大些:他们必须区分SIP的垃圾收集空间和内核垃圾收集空间。
抽象内核对象(如互斥、线程)的导出为强类型化的不透明的的句柄(handle表中各槽)。当SIP终结时,相应的handle表中的槽被回收。
Singularity的安全
安装时刻的安全机制
在singularity的安全模型中,核心是程序。Singularity为系统中的所有的服务(如设备驱动)、文件系统提供了单一的、树状的名空间。对程序发布者的信任体现在名上。例如,系统策略可以指定,只有由Microsoft签发的程序可以占有某个Microsoft专用的名空间,例如所有微软签发的程序位于/app/microsoft/下5。名空间也可按照对程序的信任度(根据系统策略)来使用。
可以看到,上述的安全策略基于路径。由于有安全策略对名空间进行了必要的保护,故此处的安全的。
某些安全强制在安装时刻进行。Singularity对资源的访问通过Channel完成,故系统安装器可以通过静态管理程序的Channel来实现对资源的控制。每个程序的需求在静态的清单中指明。系统安装器通过提供给程序的一个配置,从而在运行时刻实例化Channel,来解决程序清单中所有未绑定的Channel。这种静态的检查有时能用来实现最小特权的安全环境,例如,如果某个程序仅负责在本地处理,安装器将不提供对网络的直接的Channel。
动态访问控制
Singularity程序在运行时刻以一个或多个进程的形式实例化。每个进程由于调用而拥有一个不可更改的标识。
进程创建所有channel终点对,用其标识来初始化终点对。当channel的一个终点传递给另一进程是,接收进程便获得另一方进程的标识。当对共享资源进行动态的访问控制决策时,这个标识与促使该请求进程执行的调用链中的程序的标识,组成访问控制决策中的主体。(在某些情形下,如使用自有认证功能的服务器程序,将忽视调用链)调用历史是以进程为粒度追踪的。
在这个环境中,用户表现为程序的角色。认证用户的程序(比如通过密码或者证书)参与标识的合成。因此,远程登录的用户登录后的标识与本地通过智能卡认证后的标识不同。
Singularity的合成标识由文本字符串表示,例如:
/sys/login@/users/fred + /apps/ms/word
这个字符串可能代表了系统登录程序(用户“fred”通过密码登录)调用Microsoft word程序。
与SELinux相比,在SELinux中,用户角色对登录程序的类型作出贡献,而之后登录程序类型影响其运行的程序:或继承登录程序的类型(默认情形下)或依据转换规则转换到新的类型下。
在访问控制列表处,Singularity使用访问控制表达式(access control expressions:ACEs)来定义匹配标识的模式。这些表达式可以非常灵活。例如,可以指定只有Word能读某个模式保护的文件或者“fred”运行的任何Microsoft的程序能够访问它。
可以设想,通过定义可被继承的策略规则,大量的不同的ACEs将被一小组规则代替。这特性将在结构化的环境中,如文件系统中极为有效。
更多的运行时刻安全机制
Channel contract可以导出子类6,来指定终点持有者能发送哪些消息。例如,TcpConnectionContract的一个子类可以只描述某个主体许可的方法,来只允许该主体监听而非连接。这样,一个子类对应一组权限。
如上所描述,在默认的进程调用情形下,新的进程的标识是一个合成的标识,以调用者 + 被调用者的形式。进程调用外,至少在两种场景下,进程也许把其部分标识“借给”其他进程:
进程许可其合作进程,给合作进程以联合的标识
系统策略许可新的服务来决策对某个已有服务的访问,这个负责访问控制决策的服务需要代理原始客户。
在上述两种情形下,通过专门地“赐予”Channel终点,来支持标识继承。一个“被赐予”的终点允许在某些受限的上下文中,接收者继承合作者的标识.
持有多个标识的进程可能令人混乱,造成误用。故大多数情形下,进程尽可能只使用一个标识。
Singularity的可信基
回来之前的一个问题上来,Singularity的安全是多方面技术合作的结果,这样便引入多方面的依赖,是否也随之引入了安全隐患呢?答案是否定的。在Singularity中可信基由4部分组成:
硬件抽象层
内核部分(使用非安全编程语言编写)
运行时刻系统部分
编译器
其中编译器是个很庞大的部分,将来通过引入类型化的汇编语言(typed assembly language:TAL),来使用一个相对较小的验证器来验证编译器的输出。从而庞大的编译器部分由较小的验证器部分替代,减小了可信基。
总结
通过对硬件接口再次封装(通过安全的编程语言接口),限制了编程语言对内存的访问。这与缓冲溢出防御手段2、3类似(限制执行任意地址其的数据),但限制更多。
得益于这种封装,便于实现软件隔离,在性能上获得收益。开销小的安全手段适应更多的应用场合,从而提升系统平台的安全性能。
Singularity中强调了程序的可验证性:
通过安全的编程语言确保对内存正确访问的可验证性。
Contract给予了进程间通信的可验证性。
清单从系统角度,给予了程序正确行为的可验证性。
SIP代码密封等其他简化手段有助于验证的进行。
这种可验证性源自各种形式的断言。这些断言可能是语言级的,通信级的,系统级的——断言的粒度和时刻(编译时刻、链接时刻、安装时刻及运行时刻)的灵活性使得其最小特权原则得以顺利贯彻。
断言在高层的逻辑上进行有助于最小特权,以及友好的开发环境,但也造成安全的实现依赖面太广,使得安全本身的可靠性降低。Singularity通过一个小的可信基来验证其他安全的组成部分,从而确保了安全本身的可靠7。
1Singularity: Rethinking the Software Stack,作者:Galen C. Hunt 、James R. Larus, http://www.research.microsoft.com/os/singularity/publications/OSR2007_RethinkingSoftwareStack.pdf
2An Overview of the Singularity Project,作者:Galen Hunt、James Larus、Martín Abadi、Mark Aiken、Paul Barham、Manuel Fähndrich、Chris Hawblitzel、Orion Hodson、Steven Levi、Nick Murphy、Bjarne Steensgaard、David Tarditi、Ted Wobber、Brian Zill,ftp://ftp.research.microsoft.com/pub/tr/TR-2005-135.pdf
3自省(reflection),程序根据某些变量,改变自身的代码。
4Singularity中的特权指令的执行:不像一般操作系统中只能放到内核中执行,Singularity可以在安检后,放置特权指令到运行在SIP中的可信赖的函数中。(通过这种方式来优化channel通信、运行时刻性能和垃圾收集)
5这个想法有点类似Mac OS X的finder视图
6在SELinux,通过对象类枚举对象的所有方法,然后对不同的主体类型与客体类型(安全上下文)给出此时允许访问的方法(对象类所有方法的一个子集)
7这个思想有点类似merkle tree,上一层验证下一层,通过层层验证,验证整个环境








