VAULT:Reducing Paging Overheads in SGX with Efficient Integrity Verification Structures

VAULT是基于SGX的改进工作,主要关注于隔离内存产生的额外开销。

Introduction

SGX是Intel提出的TEE解决方案,可以保护敏感信息的CIA(Confidentiality, Integrity, Authentication),在内存保护方面,SGX可以保护敏感页内存的安全性。SGX讲内存划分为两个区域,EPC(Enclave Page Cache)区域存放敏感页内存数据,实施保护机制,而以外的non-EPC区域不做保护。SGX方案需要硬件上提供EPC区域,这是一段片上的内存区域,具有物理大小的限制,论文中提及是128MB。

那么SGX针对内存的保护主要会产生两种额外开销,时间开销和空间开销。时间开销分为两个方面,一方面是访问敏感内存的时候需要经过校验数据完整性并解密使用。另一方面当敏感数据占满了EPC区域后,需要将敏感信息加密后存储到非EPC区域,SGX在触发EPC miss(需要的内容不在EPC中,包含需要换入换出的情况)时通过page fault的方式进行替换,这带来了上下文切换,数据拷贝,加解密的额外时间开销。空间开销主要是需要对数据进行保护,需要存储额外的信息。

本文工作目标就是降低这两种额外开销。

BackGround

Threat Model

在TEE方案中,一般来说除了硬件本身以外都不属于可信基,应用层软件可能是攻击者运行,操作系统可能被攻击者操纵,甚至设备实体也可以被攻击者物理接触。在这种威胁模型下,要保证敏感信息的安全性非常困难,只考虑内存的安全性,通常我们认为片内的内存是不易被攻击者获取的,相对安全,防护的重点在于从片外内存中的数据。

常见的针对内存数据的攻击有:

  • 攻击者通过恶意的操作系统或软件窃取不具备访问权限的敏感信息

  • 攻击者通过物理手段读取甚至修改片外内存中的数据(TEE方案通常支持多个飞地的建立,当EPC内存不足以存储所有敏感信息时就会发生换入换出,再次读取换出内容时就不能再信任内存提供的数据)

这要求TEE方案做好严格的权限控制,确保敏感信息不被泄露,同时又不能信任内存提供的数据。SGX中在EPC中划分出了区域存储页的元数据,来存储访问页的权限信息,实现了权限控制。比较难的问题是如何信任内存中保存的数据。

Integrity

完整性的保护是TEE内存方案中最为棘手的部分,从原理上看我们需要在有限的可信安全空间内(如上文提到的128MBEPC)对庞大的内存空间做完整性保护,保护其内容不受篡改,由于片上的空间一定更少,一定存储的是特征信息(即不可能做到非概率意义上的安全保证),通过运算验证,以时间代价换取保护。

最基本的想法是利用MAC(Message Authentication Code)来对不可信内存中提供的数据进行校验。如果我们能对每个数据块(这里指一个基本的数据单元)生成一个MAC,同时利用片上产生的可信的私钥进行加密,存储到不可信内存中,待到读取数据时先进行解密,同时校验MAC,就可以确保数据的完整性。(这里MAC是存储在片上的可信数据,存储在不可信内存中则需要考虑存放的位置,以及MAC的保护问题)

Merkle Tree

只使用MAC是不够的,这会带来重放攻击的风险,即攻击者返回其他未经篡改的区块数据,仍然可以通过MAC校验,但是数据的正确性无法得到保障。Merkle Tree的思路是对所有的数据建树,叶子节点为数据块的MAC,非叶子节点的值是所有子节点的哈希校验值,这样就可以通过根哈希值保护整棵树所覆盖地址范围的完整性。而只需要把根哈希值,或者前两层的节点哈希值储存在片内,就可以校验数据不受篡改。在校验时,各层级可以并行计算,同时验证从根出发的一条路径上的所有哈希值是否正确。

Merkle Tree的问题是维护的代价比较大,出于性能考虑一个节点存储的数据最好是一个cache line大小,举例来说,在64B的cache line中只能存储8个64位的校验值,因此一个非叶子节点最多有8个子节点,若是要维护16GB地址范围的数据完整性,则需要10层树深,这带来了大量的空间开销和访问带宽开销(需要并行访问11个地址的数据)。另外,每一个对数据的写入操作都会导致一条到根的路径的哈希值更新,也会成为性能的瓶颈。

Bonsai Merkle Tree

解决重放攻击最直观的思路是nounce,即增加一个计数器,以抵御重放攻击,考虑对每个数据块维护一个计数器记录写入修改的次数,在换出加密时将计数器作为密钥的一部分,这样在验证时就可以证明是该数据块的最新数据,避免重放攻击。因此Bonsai Merkle Tree在Merkle Tree的基础上加以改进,将要校验的数据从哈希值改为了计数器,这样可以增加子节点的数量,因为计数器具有更少的位数,从而在同样保护范围内降低树深,减少开销。

仍以64B举例,每个节点可以包含64个8位计数器,即可以有64个子节点。这时面临的问题是性能和安全性的取舍,如果计数器的位数较少,那么当修改次数超过了\(2^{8}\)后counter就会循环,产生重放攻击的风险,但是越多的子节点数量就意味着更小的开销。

进一步地,还可以通过global counter来进行优化,改为64个7位计数器和一个64位的全局计数器,可以理解为采用了64个71(7+64)位的计数器以保证安全性,同时这64个计数器共用了全局计数器作为公共的前缀,每当一个计数器溢出时便更改全局计数器,以此同时提高了安全性和性能,71位的计数器已经可以应对绝大多数的飞地应用场景生命周期了。

SGX baseline

Intel SGX是现行的工业界实现,在硬件上拥有128MB的片上安全空间,其中的32MB用来存储metadata。当安全空间用完需要换入换出时,会以页为单位将换出的数据归入一个non-EPC区域的完整性树管理,以保证对非安全区域的数据篡改会被发现,non-EPC区域的完整性树可以通过根哈希等方式由安全区域保护。SGX通过维护ELRANGE来确定EPC区域的范围。

SGX也实现了对访问安全数据的权限控制,维护了数据结构EPCM(Enclave Page Cache Map),在数据页被分配时记录了该页对应的虚拟地址,所属的飞地以及相应的权限。考虑到操作系统可能是被攻击的,具有操作TLB的权限,在TLB将虚拟地址翻译为物理地址后,处理器发现该物理地址对应EPC区域后,需要用物理地址去查找相应的EPCM表项,检查所访问的虚拟地址是否与预先记录的匹配,是否属于同一飞地,进行权限校验。

在SGX中,如果飞地访问的页不在EPC区域中,会触发缺页异常,退出飞地转交给OS将缺页拷贝到EPC中(还有可能的换入换出操作),这带来了较大的访问开销。

SGX使用SGX Integrity Tree(SIT)来维护数据的完整性,类似于Bonsai Merkle Tree也采用了counter的形式,每个block采用56bit的counter记录(无global counter),每个节点包含8个counter和64bit哈希(实际为56位哈希),哈希值由节点自身的8个counter和父节点对应的counter共同计算以在父子节点间建立联系,根节点的counter保护在片内。

Proposed Techniques

Unifying the EPC and non-EPC Regions

本文提出不再像SGX一样区分安全区域与非安全区域,即对全部的地址维护EPCM。在访问内存时通过物理地址查找到对应的表项,如果不是SENSITVE数据则正常读写,如果是SENSITIVE数据,类似地通过表项比对虚拟地址的映射,飞地的权限和所属,在通过检查后允许访问。

这一设想简化了EPC的机制设计,在地址转换过程中无需比较EPC范围寄存器。但是代价是EPCM表的大小增加,对于每一个页,都需要存储映射的虚拟地址,以及权限信息,以16Byte计算,需要\(\frac{16B}{4KB}=0.04\%\)的额外地址空间。类似于TLB表,EPCM表在这一机制下也需要参与到每一次地址转换过程中,可以采用类似的缓存机制进行加速。

Variable Arity Unified enctypted-LeafTree(VAULT)

VAUT(Variable Arity Unified Tree)在Bonsai Merkle Tree的基础上做了进一步的优化。在Bonsai结构中,一次写入过程中会从叶节点出发向上更改计数器,直到命中了片上缓存的节点停止,那么显然层级越高的节点counter改变的越频繁,每当一个local counter溢出时,需要将节点的全部counter置为0,对应的就是需要重新计算所有子节点的MAC值。

基于此,VAUT允许每一层的counter位宽不同,越高的层级counter宽度越长。仍然按照一个节点采取一个64B cache line的大小进行设计,每个节点固定拥有一个64bit-HASH用来保护一致性,64bit global counter记录溢出次数,还剩下448bit可控使用。在叶子节点,分配为64个6bit-counter,在倒数第二层分配为32个12bit-counter,再上层为16个24bit-counter。

相比于BMT,在非叶子节点也采用了计数器,相比于SIT,可变的counter长度以及global counter使用让VAUT在使用同样大小空间保护更大的安全区域,并且具有更小的刷新代价。以64GB地址空间为例,采用BMT需要9层树深,SIT需要10层树深,而VAUT只需要7层,需要的时间代价和空间代价都小于二者。

VAUT看着很美好,同样的空间内容纳更多的counter并且也减少了刷新带来的重加密开销,但是代价是作为叶子节点,每个block只能分到6bit的计数器,会导致叶节点的刷新次数增大,这是不可避免的问题,计数器更宽对应更少的刷新和更小的覆盖面积。为了缓解这一问题,VAULT在VAUT的基础上引入了对叶子结点的加密,即对叶子节点采用父节点对应的64+12bit计数器作为密钥加密,从而省去叶子结点的HASH值建立其父子节点间的关系,省去的HASH值平分到计数器的宽度上,变为7bit计数器,减少了一倍的溢出可能。但是显然这会带来额外的时间开销,访问叶子节点数据还需要先访问父节点的数据,因此这一方法本质上是在牺牲动态时间换静态时间

VAULT的改动整体是围绕着counter的宽度和数量进行设计的,作为宽度的取舍,VAULT可以轻易地覆盖较大范围的地址空间,但是会有相对更多的计数器溢出刷新次数(变宽计数器设计只能一定程度上缓解这一问题),而叶子结点的加密也引入了访问内存时新的关键路径。

如果仅仅看这一设计,保护同样大小空间的空间代价和时间代价肯定都是更小的,层数更少带来较大的优势,但是同样需要考虑减少计数器宽度对刷新代价造成的影响,具体的提升还需要实验去验证。也正是因为VAULT的优势是保护更大的空间,才有上一节对于EPC空间概念泛化的讨论。

MAC Allocation

最后一个问题,既然VAULT泛化了全部的内存空间为安全空间,那么对应的MAC大小将会暴增,如何存储MAC校验值成为了新的问题。文中提出了两个改进方案。

第一点是Shared MAC with Compression(SMC),对于数据块进行压缩,将MAC存进里面,我个人觉得这是有些强行的,首先块不一定能压缩,虽然大部分块是可以压缩的(这里提到了Base-Delta-Immediate,看了一下大部分内存中存在许多没有被利用的前导0,就可以进行压缩),其次这引入了额外的解码代价,对压缩数据还原可能会成为关键路径。另一方面SMC也提出了共享MAC的思路,让多个块共享同一MAC,这也是用时间换空间的替代方案,校验时需要将多个数据块一同取出,如果利用上空间一致性可能不会带来过大的overhead。

第二点是On-Demand MAC Allocation(ODMA),也就是按需分配MAC,不敏感不关键的数据就不要产生MAC,聊胜于无的补充。