19.7 Dune: Safe User-level Access to Privileged CPU Features

今天要讨论的论文利用了上一节介绍的硬件对于虚拟机的支持,但是却将其用作其他的用途,这是这篇论文的有趣之处,它利用了这种完全是为了虚拟机而设计的硬件,但是却用来做一些与虚拟机完全无关的事情。从一个全局的视角来看这篇论文的内容,它想要实现的是普通的进程。所以现在我们的场景是在一个Linux而不是VMM中,但是我们又用到了硬件中的VT-x。我们将会在Linux中加载Dune可加载模块,所以Dune作为kernel的一部分运行在Supervisor mode(注,又或者叫做kernel mode),除此之外,内核的大部分还是原本的Linux。

因为这里运行的是Linux进程,所以我们期望Dune可以支持进程,以及包括系统调用在内的各种Linux进程可以做的事情。不过现在我们想要使用VT-x硬件来使得普通的Linux进程可以做一些额外的事情。Dune会运行一些进程,或者说允许一个进程切换到Dune模式,这意味着,之前的进程只是被Page Table保护和隔离,现在这个进程完全被VT-x机制隔离开了。现在进程有了一套完整的虚拟控制寄存器,例如CR3寄存器,并且这些进程可以运行在non-root Supervisor mode,所以它可以在VT-x管理的虚拟状态信息上直接执行所有的privileged指令。

基于上面的描述,Dune管理的进程可以通过属于自己的CR3寄存器,设置好属于自己的Page Table。当然Dune也会控制属于这个进程的EPT,EPT会被设置的只包含这个进程相关的内存Page。所以进程可以向CR3寄存器写入任意的Page Table地址,但是因为MMU会在翻译完正常的Page Table之后再将地址送到EPT去翻译,所以进程不能从分配给它的内存中逃逸。所以进程并不能修改其他进程或者kernel的内存,它只是有了一种更灵活的设置自己内存的方式。

Dune管理的进程也可以拥有Guest Supervisor mode和Guest User mode,就像一个小的虚拟机一样,并且可以保护运行在Supervisor mode的代码,不受运行在User mode的代码影响。

论文中提到了可以基于Dune做的两件事情:

首先,Dune能够在硬件层面支持进程同时拥有Guest Supervisor mode和Guest User mode,这样进程可以在自己的User mode中运行未被信任的插件代码。这里的主进程或许是一个网页浏览器,你可以为浏览器下载并运行各种各样的插件,或许是一个新的视频解码器,一个新的广告拦截插件等等。但是我们并不能完全信任这个插件,所以我们希望能够在权限受控的前提下运行它。虽然一个普通的Linux也可以达到这个目的,但是会比较麻烦。通过Dune,我们可以在Guest User mode下运行插件,同时让网页浏览器运行在进程的Guest Supervisor mode下。因为现在可以修改CR3寄存器,所以可以为Guest User mode配置一个不同的Page Table。这样,即使插件是恶意的,进程也可以安全的运行这里的未被信任的插件代码,因为插件代码现在不能任意的读写主浏览器的内存,只能访问网页浏览器指定的某些内存Page。进程的Guest User代码可能会执行系统调用,但是这些系统调用会通过trap走到进程的Guest Supervisor mode,而不是Linux内核,所以这里的插件代码或许会认为自己调用了fork/read/write等系统调用,但是实际上这里尝试运行的系统调用通过trap走到了进程对应的网页浏览器,而网页浏览器可以做任意的事情,它可以选择执行或者不执行系统调用。所以现在网页浏览器对于插件代码有了完全的控制能力。

公平的说,这里提到的隔离效果可以通过Linux中一些非常不一样的技术来实现,但是Dune通过使用VT-x硬件,为你可以提供一个特别优雅且有效的实现方式。

进程可以做的另一个事情是:通过Dune,进程的垃圾回收(Garbage Collect,GC)变得更快了。在这个场景中,没有了Guest Supervisor mode和Guest User mode。假设我们在运行任意一种带有GC的编程语言,比如说Java或者Python。GC可能会很慢,并且本身有着非常非常多的技术可以使得GC变快。许多GC都会扫描并找到位于内存中仍然在使用的对象,扫描会从寄存器中保存的对象指针开始,依次找到所有正在使用对象的所有指针。如果在扫描之后没能找到某个对象,那说明这个对象不被任何指针引用,那么它就可以被释放了。许多GC会同时在主程序的一个线程中运行,所以GC会从寄存器中保存的指针开始,根据指针之间的树或者图的关系,扫描一个个的对象。

但是因为GC与程序本身是并行的在运行,所以程序可能会修改GC已经扫描过的对象,这很糟糕,因为这样的话,GC在扫描完成之后确定的要释放和不能释放的对象清单可能就不再正确了。Dune使用了Page Table Entry中的一个特性来帮助GC检测这样的修改。Dune管理的进程首先会设置好由VT-x提供的虚拟CR3寄存器,指向属于自己的Page Table,其中的PTE都是有效的。每一条PTE的dirty位,表明对于对应的Page存在写操作。所以如果程序在GC的过程中修改了某些对象,那么对应PTE的dirty位会被设置为1。当GC查找完所有的对象之后,它会查看所有PTE中的dirty位,找到包含了可能修改过的对象的内存Page,然后再重新扫描这些对象。

实际中,获取PTE dirty位的过程在普通的Linux中既困难又慢,我甚至都不确定Linux是否支持这个操作,在一些其他操作系统中你可以通过系统调用来查询PTE的dirty位。但是如果你使用Dune和 VT-x,进程可以很快的使用普通的load和store指令获取PTE,进而获取dirty位。所以这里,Dune使得某些需要频繁触发GC的程序明显变得更快。

学生提问:如果Guest User mode中的插件程序想要运行自己的GC会怎样?

Robert教授:现在我们使用了Dune,并且有一个进程是被Dune管理的。这个进程通过VT-x实现了Supervisor mode和User mode,我们在User mode运行了一个插件,并且插件也是由带GC的编程语言写的,所以它有属于自己的Page Table,并且其中的PTE也包含了dirty位。但是刚刚说的GC加速在这不能工作,因为Dune会将插件运行在Guest User mode,而就像普通的User mode一样,Guest User mode不允许使用CR3寄存器。所以在Guest User mode,我们不能快速的访问PTE的dirty位。只有在Guest Supervisor mode,才能通过CR3寄存器访问Page Table。所以,并不能同时使用以上Dune提供的两种功能。

学生提问:如果某人基于Dune写了个浏览器,那么对于不支持Dune的计算机来说就很难使用这样的浏览器,对吗?就像很难直接让Chrome使用Dune,因为不是所有的计算机都有这个内核模块。

Robert教授:首先,这里提到的内容需要运行在支持VT-x的计算机上,也就是说底层的计算机必须支持VT-x,所以需要VT-x来运行Dune。其次Dune需要被加载来运行浏览器以利用前面说到的特性。所以是的,你需要将所有的东西都设置好。并且Dune是一个研究项目,它的目标是使得人们去思考可以部署在真实世界,并且有足够的价值的一些东西。就像Linux一样,Linux有成千上万个功能,如果某人决定将Dune添加到Linux中作为一个标准功能,那么我们就可以依赖这个功能,并且Chrome也可以直接用它了。

学生提问:所以从整体来看,这里就像是创建了一个VM,但是实际上运行的又是一个进程?

Robert教授:你可以这么描述。这里主要是对进程进行抽象,但是这里没有用Page Table硬件来时先进程间的隔离(注,其实也使用了,但是主要不依赖Page Table硬件),这里使用的是CPU上的硬件来支持进程,这里说的CPU上的硬件就是VT-x,它包含了一些额外的功能,例如设置好属于进程的Page Table。

学生提问:论文里提到了,如果Dune管理的一个进程fork了,那就会变成一个不被Dune管理的进程,这不会是一个安全漏洞吗?比如说你通过Dune运行了一个进程,并且认为它现在是安全的。但是fork之后的进程因为不被管理所以可能会逃逸。

Robert教授:Dune管理的进程的Guest Supervisor mode中,不存在安全的问题。这部分代码已经拥有了相应的权限,通过fork也不会获得更多的权限。但是另一方面,Dune的Guest User mode代码中,我们有未被信任的代码,如果让它在没有Dune管理的情况下运行会有一定的风险。所以这部分代码不能fork,如果它尝试执行fork系统调用,会通过trap走到进程的Guest Supervisor mode。

假设进程的Guest Supervisor mode部分代码写的非常的小心,并且不会被欺骗,那么它不会执行fork,所以这时fork不能工作。如果Supervisor mode的代码允许fork,它会调用Linux的fork系统调用,并得到一个fork进程包含了与原进程有相同的内存镜像,所以我们在新进程中包含可能是恶意的插件代码。如果新进程没有意识到Dune已经被关闭了,那么原来的Supervisor mode中的privileged指令会是非法的。所以我们需要假设Dune管理的进程里面的Supervisor mode部分代码能够足够的小心且足够的聪明,来阻止User mode中的插件代码执行fork。

学生:被Dune管理的进程拥有Supervisor mode并没有不安全,因为它实际上是non-root mode下的Supervisor mode,就像是Guest操作系统中的Supervisor mode一样,你可以让它做任何事情,因为VT-x的存在,进程就像是一个虚拟机一样,并不能伤害到真正的操作系统。

Robert教授:是的,进程不能逃逸出来,因为存在EPT,而EPT会限制进程的地址空间。

学生提问:在VT-x的方案中,当我们访问Page Table时,因为我们需要通过EPT进行第二层翻译,将Guest物理内存地址翻译到Host物理内存地址,这样从Page Table返回的延时是不是增加了?

Robert教授:这里可能会花费更多的时间让硬件MMU来翻译内存地址。在最坏的情况下,比如在RISC-V中,会有多层Page Table,MMU需要一层一层的去查找PTE,x86下同样也有多层Page Table,所以在x86中首先会查找主Page Table,如果要访问更多的内存地址,每一次内存地址的访问都需要再次走到EPT,而EPT也是一个多层的Page Table。所以我并不知道最差情况下需要访问Page Table多少次才能完成翻译,但是很明显在VT-x下会比普通情况下差得多。不过实际中会有cache所以通常不会走到最坏的情况。

学生提问:今天的虚拟机还是普遍会更慢吗?如果是的话,AWS是怎么工作的,因为看起来还挺快的,并且工作的也很好。

Robert教授:我认为他们使用了硬件上的VT-x支持,并且使用了我们讨论过的一些功能,这样使得AWS虚拟机比较快,或者并不比真实的计算机慢多少。

学生提问:我对于Trap and Emulate中的Shadow Page Table有个问题,每次都会创建Shadow Page Table吗?难道不能记住上次的Shadow Page Table吗?

Robert教授:VMM需要创建新的Shadow Page Table以供真实的硬件使用。当然在很多时候都可以增加缓存,对于一个聪明的VMM,它可以注意到Guest更新了一个PTE,VMM可以做相应的有限的工作来更新Shadow Page Table。如果机器是在多个虚拟机上分时复用的,VMM会为还在运行的虚拟机保存Shadow Page Table,这样这些虚拟机可以在恢复时直接重用。

学生提问:这难道不是意味着VMM为每个虚拟机中的每个进程都保存了Shadow Page Table的拷贝?

Robert教授:是的,虚拟机里面有很多很多个Page Table,所以维护Shadow Page Table需要大量的工作。而类似于VT-x的硬件支持使得这部分工作更加的容易了,因为EPT表示你不用构建Shadow Page Table了。

学生提问:我有个问题有关GC的,如果有dirty位的话需要重新扫描对象,那么有没有可能会无限扫描?

Robert教授:是的,这有个问题,如果一直有对象在更新,扫描能正常结束吗?实际中,GC会先扫描一次,之后它会冻结除了GC线程以外的其他线程,所以这期间不可能再发生任何其他的变更。之后GC才会查看所有PTE的dirty位,但是因为其他所有线程都冻结了,所以不可能会有更多的dirty位了,所以GC查看了所有的dirty位,之后结束GC会结束扫描并创建需要释放对象的列表,最后再恢复所有之前冻结的线程的执行。GC是一个复杂的流程,Dune的论文中并没有足够的篇幅讨论它。

Last updated