玩转Fuchsia操作系统

badmonkey 2021年07月03日 1,015次浏览

玩转Fuchsia操作系统

代码分析部分没有做,可以参考一下原文https://blog.quarkslab.com/playing-around-with-the-fuchsia-operating-system.html

介绍

Fuchsia是谷歌开发的新型操作系统,面向AArch64和X86_64架构。尽管uchsia的未来用途还不是很明朗,但是它的口号是旨在取代Android和Chrome OS。对于这个可能运行在数百万台设备上的OS,我们还是很感兴趣的,并决定简单的了解一下Fuchsia的内部设计,安全特性,优缺点等等,并尝试找到攻击它的方式。

概览

宏内核vs微内核

如今大多数内核都采用了宏内核的设计,比如Linux和BSD的内核都是宏内核,基于Linux的Android和Chrome OS也都采用了宏内核。

宏内核通常来说都比较"大",嵌入了所有的设备驱动,网络协议栈,和数百个系统调用。简单来说提供了所有的系统功能。

尽管不同的宏内核之间有所不同,但是大体的布局还是一致的,如下图所示。

monolithic.png

宏内核一个显著的安全问题就是如果系统内部某个组件存在漏洞,那么将会影响到整个系统。比如,在上述的模式下,USB驱动存在一个可以利用的内存破坏漏洞,由于驱动运行在内核空间,攻击者可以利用USB驱动的漏洞获得整个内核的控制权。

而Fuchsia并没有使用宏内核设计,而是采用了微内核。

微内核是一种体积较小的内核,只实现有限的几个核心功能,比如调度,异常处理,内存管理,几个设备驱动和几个系统调用。剩下的组件都被移到用户空间,而不在内核空间实现。下图是一个微内核设计的例子:

micro.png

这里的VFS层,socket层,network stack,文件系统,设备驱动都被移到了专属于用户进程的用户空间,彼此通过IPC通信(socket,channel,fifo)。

比如,一个FTP客户端从网络协议栈中抓取数据并将其存储在USB key中,只需要通过用户空间的进程的通信就可以做到,而不需要内核的干预。内核只是用来保证权限分离和进程间的隔离。

微内核的设计带来了有意思的安全特性。比如USB 驱动存在一个漏洞,那么攻击者可以控制USB 对应的进程即Sys Process 6,但是攻击者没有机会获得其他进程或者内核的权限,而仅仅具有Sys Process 6所具有的权限。

所以攻击者必须结合其他的漏洞才能有实质性的危害,在安全性上相比于宏内核这是一个很大的提升。

Zircon 微内核

Fuchsia的微内核叫做Zircon,使用C++编写。在这里简单介绍一下这个内核中的一些内部结构。

组件

Fuchsia是通过一系列用户空间的组件(Components)而组织起来的。比如,网络协议栈,是一个用户空间的组件。USB驱动同样的也是用户空间的一个组件。组件之间通过IPC互相通信。

components.png

组件可以使用不同的语言编写,比如go,rust,c++等等。

其中设备驱动组件存放在devhosts 进程中。devhost 是用来保存同一个驱动栈(driver stack)的几个层的进程,如下图所示:

devhosts.png

这里有三个devhosts进程。其中 Devhost Process 3包含了AHCI 驱动和SATA驱动,以及MinFS和BlobFS文件系统。所有的这些组件都存活在同一个进程中。

这其实有点弱化了分割模型(segmentation model),因为几个组件在一个进程中,所以如果其中的一个组件存在漏洞就会影响到同进程中的其他组件。但是devhosts的组织方式是一个进程中仅能有一个驱动栈,也就是说USB驱动和SATA驱动不可能同时存在于同一个devhost中。因此分割模型的优点依然存在。

进程隔离

Zircon通过CPU的MMU保护进程的内存,采用的方式是,每个进程都有自己的地址空间,地址空间的上下文切换由Zircon控制。

不同于其他的OS,IOMMU(Input-Ouput MMU)在Zircon中十分重要,它由内核控制,每个devhost进程只能在其自己的地址空间执行DMA(direct memory acess)操作。

因此IOMMU和MMU同等重要,如果没有IOMMU,devhost进程就可以对内核页进行DMA操作,进而改写其内容。

此外,在x86上,TSS(task state segment) I/O Bitmap 用于限制I/O端口的访问权限。

命名空间

Fuchsia中,对于用户进程来说没有统一的文件系统。每个用户进程都有一个自己的虚拟文件系统,称之为命名空间。命名空间中包含对象,对象可以是文件,服务,设备。这些对象通过层级关系排序,并通过open()函数访问。

几个路径需要注意一下。一个是/svc/文件夹,包含了一些服务对象,比如/svc/fuchsia.hardware.usb.device,这些是可以进行IPC的对象。也就是说,组件可以通过/svc/中的服务对象将IPC暴露出来,其他的组件就可以通过访问对应命名空间中的服务对象访问对应的IPC。

命名空间是在进程创建时建立的。可以通过manifest文件指明那些对象/路径可以出现在命名空间的层级中,因此可以实现沙盒机制,控制进程可以访问的IPC。

需要注意的是,命名空间是存在于用户进程的概念,内核并不知道命名空间。对于开发者来说,命名空间是一个方便处理对象的接口,对于内核来说仅仅知道句柄(handles)。

句柄

Zircon通过句柄管理对组件的访问权限,类似于Unix中的文件描述符,或者access token。

命名空间中的对象基本上由句柄支持,通常其中的一个路径对应一个句柄。前文说过内核仅仅知道句柄而不知道名字空间。命名空间存在于用户空间,可以被视作句柄们的封装。

句柄具有类型和权限。大多数情况下,Zircon的系统调用依靠句柄管理权限。特定的系统调用需要句柄的类型和权限匹配,不匹配则无法执行。

同时需要注意的是,相较于Unix系统,Zircon并没有User的概念。一切都归结于为句柄的概念,从安全的角度看十分有趣,因为攻击者会尝试获取更好的句柄以便进行攻击。

系统调用

官方文档十分清楚,也没有什么需要强调的。主要是记录了不同的系统调用需要的句柄的类型。

安全措施

在防护上,Fuchsia采用了ASLR(用户空间强制使用),DEP, SafeStack, ShadowCallStack, AutoVarInit

Zircon内核在编译时默认采用以上的防护措施。

在安全实践方面,可以注意到Fuchsia的很多(所有?)组件都有相关的测试单元和fuzzers。fuzzing是通过 libfuzzer去fuzz组件内部的结构体的,并通过syzkaller fuzz暴露给用户的系统调用。同时还支持ASanUBSan sanitizers,但是 MSanTSan暂时还不支持。

之前提到过组件可是使用不同的编程语言实现,但是C是最容易出现"编程错误"的语言。对C来说,组件通常会重载几个操作符以进行合法检查。比如,[]操作符会被重载当使用索引访问数组的时候会检查是否出现数组越界。因此,即使对一些容易"出错"的语言,一些安全措施也是到位的。

Where we at?

让我们从安全的角度,总结一下目前提到过的设计理念:

  • Fuchsia 使用微内核,其受到攻击的面受到了天然的限制:入口点少,逻辑简单
  • 系统由用户空间的组件构成,带来了良好的分割性质:一个组件的漏洞仅会危害其对应的进程。
  • 组件可以使用安全的语言编写,如Rust
  • 组件具有自己的虚拟文件系统,且完全处于用户态,内核无法得知。
  • 访问组件和系统调用都是基于句柄的,句柄充当了内核唯一认可的token。他们被抽象为命名空间中的对象。
  • 内核默认的防护措施还不错
  • 组件和内核以一种看似系统的方式进行了模糊测试和单元测试。

整体上来说,Fuchsia的内核设计要比Linux的要安全,同时防护和安全措施也要比目前Linux上使用的要好。

但是也存在两个缺点:

  • Fuchsia还不支持CFIPAC防护,PAC是比较有力的防护措施
  • devhosts中一个进程包含多个组件会弱化分割模型尤其是对于设备驱动。