《SGXBOUNDS: Memory Safety for Shielded Execution(EuroSys‘17)》笔记

简介

在检查Enclave的内存安全问题时,SGXBounds利用SGX Enclave内指针高32地址位空闲的特点,将Tag放置其中表示内存对象的Upper Bound值,内存对象后紧跟其Lower Bound值,后续图5所示。SGXBounds相较于基于Intel MPX或ASAN的方案,具有更低的性能(1.3倍)、内存(忽略不计)开销(但在非SGX场景下相较其它方案并无优势)。

基于Intel MPX或ASAN的方案存在可用性问题。Enclave任务量达到100+单位时,Inter MPX方案就由于虚拟内存耗尽(57倍)而崩溃了。ASAN方案在一定任务量时开始出现EPC Page Evict(也称EPC Paging、EPC页交换等等)现象,导致性能开销大幅增加,在任务量1200单位时虚拟内存耗尽(3.1倍)导致崩溃,同时额外性能开销3.1倍。
在这里插入图片描述
EPC解密提取到CPU Cache及EPC Page Evict产生的开销如图所示
在这里插入图片描述
SGXBounds的优点还包括:

  1. 多线程应用场景中,SGXBounds无需同步机制。(似乎也是所有Tag方案的优势)
  2. 基于Failure-Oblivious的计算模式能够一定程度容忍越界访问(而非遇到故障就停止的计算模式),以增加方案可用性
  3. 提供管理对象元数据的通用API,以支持新用例。

相关工作

作者总结了现有防御方案的特点,觉得还是从内存安全角度入手进行防御比较好。内存安全问题和OS对Enclave的威胁是两个纬度的事情,前者关注OOB和Dangling,后者关注OS对Enclave的输入及其它如页表、线程机制的影响。
在这里插入图片描述

现有(主要是空间)内存安全检查工作

  • ASAN如下图3a所示,通过库①初始化影子内存所使用的区域并②替换内存管理函数,③通过Pass插桩对目标程序进行加固(设置内存对象的影子内存及redzone并在解引用时验证),如图4a,b所示。
  • Intel MPX通过硬件①记录所有指针的边界并②在解引用时验证,如图4a,c所示,但是其中需要对数组成员指针的边界也进行拷贝。MPX的缺点在于边界寄存器数量较少,会导致边界寄存器里的内容需要频繁地从边界表(图3b示意了基于指针地址索引的两级边界表,有点类似页表)中提取和存放。
    (如何理解MPX需要对数组成员指针边界进行拷贝,而ASAN不需要?ASAN中影子内存记录了数组所有元素的地址的状态,可以直接拿着数组成员的地址去影子内存查询;MPX中记录了数组变量的边界,检查变量传播计算后是否越界,此后重要的是,数组成员能够索引到数组成员指针指向的内存及其边界,这些元数据需要随着保存该指针的数组成员的变化而进行拷贝,也就是元数据的传播)
  • 基于Tag检查边界的工作包括Baggy Bounds和Low Fat Pointer。(当前并没有工作在SGX场景使用该类方案)
    在这里插入图片描述
    在这里插入图片描述

内存安全检查工作引入到SGX中

  • 非SGX场景下
    • MPX引入2.5倍性能开销
    • ASAN引入2.1倍性能开销
  • SGX场景下(如图1所示)
    • MPX维护的边界表很容易大到消耗完Enclave虚拟地址空间(ELRANGE,大小约为 2 32 ∼ 36 2^{32\sim36} 23236字节)导致崩溃。针对SQLiteI in SCONE Enclave,当约100个 工作集 时,指针边界表消耗的内存为:
      800 ∼ 900 ( 个 边 界 表 ) × 4 ( M B / 个 ) ≈ 3 ∼ 4 ( G B ) 800 \sim 900(个边界表) \times 4(MB/个) \approx 3 \sim 4(GB) 800900()×4(MB/)34(GB)
    • ASAN在大 工作集 时引入3.1倍性能开销(与EPC Thrashing有关)。它所使用的512MB影子内存及Redzone等区域增加了Enclave虚拟内存消耗,小 工作集 时也将导致所用内存超过可用EPC物理内存大小(约90MB),导致EPC Page Evict(类似于Kernel Page Swap,但源与目的介质不一样)产生(该现象被本文称为EPC Thrashing),进一步导致元数据访问造成巨大性能开销。
    • 本文SGXBounds引入30~35%性能开销,几乎没有内存开销。利用了Enclave虚存地址实际使用位数有限的特点。

SGXBounds

SGXBounds基于三个发现:

  1. Shielded APP(如APP in SCONE Enclave)考虑到有限的EPC尺寸而做的较小,这与常规内存安全措施考虑的场景相异。
  2. APP花费许多时间遍历数组,更好的元数据布局可以显著降低边界检查的开销。
    • (前两点说明将Per-object Metadata与Tagged Pointer结合能有效减少内存开销)
  3. Enclave(特别提醒:SGXBounds针对SCONE Enclave)内的所有代码均是静态链接的,消除了对兼容性和模块化的需求。
    • 静态链接起来的整体,避免了对已插桩代码和未插桩代码互操作性的影响。

SGXBounds将Tag放置在SGX Enclave内指针高32空闲地址位,以表示内存对象Upper Bound值(内存对象小端对齐,从低地址开始存储),内存对象Upper Bound地址后存储Lower Bound值(换个角度就是Upper Bound能够指向Low Bound值),如图5所示。这样做的好处包括:

  • 最小化元数据的内存占用
  • 递增遍历数组时无需(关于元数据查询的)额外访存操作
  • 减轻了多线程和内存布局变化场景对Fat Pointer带来的问题

在这里插入图片描述
上图4d显示了SGXBounds的插桩。SGXBounds不需要像ASAN那样用Redzone包裹对象,不需要使用影子内存;不需要像MPX那样维护边界表,不需要显式地关联指针和它的元数据,新创建的指针能隐式继承元数据,无需元数据传播。

设计细节

指针创建

全局变量和栈变量在程序初始化阶段设置边界(新的函数栈帧的边界应该在函数调用时配置?)。

void* specify_bounds(void *p, void *UB):
	LBaddr = UB
	*LBaddr = p
	tagged = (UB << 32) | p
	return tagged

针对堆变量,需要对内存管理函数进行调整,对新创建的对象及其指针进行设置。(free函数无需调整,如加4,因为释放堆对象时会从堆对象元数据确定该堆对象长度,这也是free函数不需要指定长度的原因)

void* malloc(int size):
	void *p = malloc_real(size + 4)
	return specify_bounds(p, p + size)

运行时边界检查

对Load/Store访存操作及原子操作插桩运行时边界检查。检查包括两个步骤:

  1. 提取原始指针及边界信息extract()
void* extract_p(void* tagged):
	return tagged & 0xFFFFFFFF
void* extract_UB(void* tagged):
	return tagged >> 32
void* extract_LB(void* UB):
	return *UB
  1. 检查指针越界
bool bounds_violated(void* p, void* LB, void* UB):
	if (p < LB or p >= UB):
		return true

指针运算

插桩指针运算避免运算结果影响高32位的Tag。每次指针运算插桩后效果如下:

UB = extract_UB(si)
si = s + i
si = (UB << 32) | extract_p(si)

类型转换

以往技术处理类型转换会导致程序破坏、性能及安全性下降。

SGXBounds不对类型转换插桩,并假设Tag指针转为整数时高32位不被篡改,使得后续再转为指针时不受影响。(此文由此号称对强制类型转换免疫)

函数调用

Enclave内,开发者代码和库是静态链接的,唯一不被插桩的只有标准C库libc,不过对libc构建了wrapper(有些实现简单,有些复杂)以兼容Tag指针,因此总体而言,Tag指针及其元数据能够在开发者代码和库间通用、传递、兼容。

SGXBounds的优势

多线程支持

已有方案在多线程下的问题

  • ASAN方案涉及访问影子内存,与内存对象地址差距大,额外的数据结构访问会导致缓存替换,从而导致 缓存局部性 被破坏(与内存对象不存在一块的边界表应该也有这个问题?)。
  • MPX方案存在漏报和误报的情况(当指针及其元数据未原子性更新的情况下)。
  • Fat Pointer不存在 缓存局部性 问题,但是和Disjoint-metadata(包括MPX)一样存在指针及其元数据更新的事项,这需要同步机制保障(MPX未使用同步机制),但同步机制又会引起巨大的性能开销

而SGXBounds未分离指针及其元数据Upper Bound的存储,同时Lower Bound在初始化时写死,因此没有上述问题。

使用Boundless Memory来容忍错误发生

这里有个概念叫做Failure-oblivious Computing,当故障发生时通过某种机制让程序并非被终止执行,而是改正错误并正常继续执行。一种机制就是Boundless Memory,被SGXBounds也拿来用了。SGX中的中止页面语义也是实现Failure-oblivious Computing的机制。

SGXBounds中Boundless Memory的构建和使用如图6所示。当发生越界(越界这个行为由前面提到的Bound信息来确定)写时,肯定不能将内容写到越界的地方,而是将其存在LRU Cache(LRU算法指当Cache满时如达到1MB阈值,会保留最近使用的项,剔除不用的项,以利用时间局部性原理)中;当越界读时,从LRU Cache找找有没有对应的项,如果找不到就默认读取0。LRU Cache内存是按需分配的,上限1MB,每一项(Chunk)是1KB。
在这里插入图片描述

元数据管理

针对元数据管理的时机可以引入三个API,①on_create在对象创建后配置元数据(如specify_bounds),②on_access在访存(读/写)时使用元数据检查越界等安全问题(如bounds_violated),③on_delete在释放对象前清除元数据【全局变量生命周期为整个程序执行期间,无需关注;栈变量销毁过程无法跟踪,此例中Lower Bound会随栈顶指针调整自动标记为无效;堆变量释放可以插桩,此例中由于元数据和堆变量存储在同一个堆对象中,堆变量销毁时元数据会被一起释放】
在这里插入图片描述
三个API中部署额外的元数据实现额外的防御功能。

优化

  • 不对安全的内存访问插桩以提升性能(20%)。
  • 递增循环场景中无需提取Lower Bound

(本文的核心思想就到此为止了。)

实施

SGXBounds实施

  • 用到了Link-Time Optimization插桩SGXBounds运行时以避免修改程序构建过程。

编译器支持

  • 针对内联汇编检查所有涉及指针的边界或禁用内联汇编。
  • 选择性地插桩C++库以增加对C++的支持,但尚不支持C++异常处理句柄。

运行时支持

  • 辅助功能Boundless Memory中读操作/更新操作需要同步造成性能开销,好在越界这种触发情况较少。
  • Libc函数包装(Wrapper)中出现错误时不采用Failure-oblivious,而是直接返回错误码方便程序排除攻击请求。
  • 为了支持Tag指针模式,需要vm.mmap_min_addr=0让Enclave从0地址开始映射,但这会导致Null-Pointer Dereference危害提升并威胁内核

在Enclave内集成ASAN、MPX

难点

  1. SCONE内不允许动态链接,ASAN和MPX需要静态编译进Enclave
  2. SCONE Enclave虚拟地址空间限制为32位
  3. OS无法观察Enclave地址布局

集成ASAN进Enclave

存在难点①和②:

  1. 静态链接libc导致函数重复定义的编译错误。对此可以针对libc函数别名(libc函数的别名标记了真正的函数)进行包装。
  2. ASAN64位需要占用16TB虚存用于存放影子内存,占用内存过大。对此可以只开启32位模式,此时影子内存只占用512MB(仍然巨大)。同时关闭导致SCONE崩溃的“泄漏检测”功能。

集成MPX进Enclave

存在难点②和③:

  1. MPX64位模式边界目录表占据2GB虚存,4MB大小的边界表按需分配,占用内存过大。而改用32位模式,12位地址索引边界目录表,边界目录表尺寸缩减至32KB。
  2. MPX按需分配的边界表项需要触发异常让操作系统完成,而SGX中不存在内核。对此,将边界表分配逻辑移入MPX运行时,当触发异常时,内核自动将异常转发给Enclave内的MPX运行时来完成,Enclave内会进行双重检查确保内核转发的异常无害。(边界表分配由于较少触发性能开销有限,异常转发开销忽略不计)

评估

评估目标

  1. SGXBounds对比ASAN、MPX的性能及内存开销
  2. 逐渐增加的 工作集 如何影响性能
  3. 多线程对性能的影响
  4. 额外的优化功能对性能的提升
  5. 依据RIPE标准,SGXBounds的安全性如何
  6. 非SGX场景下,SGXBounds的性能如何

内存度量

Linux未提供Resident Set Size的统计,改用保留的虚拟内存大小进行内存度量,使用 工作集 指代所用内存。

①内存性能开销(图7)

方案内存性能开销特点
MPX针对指针密集型程序如pca,性能开销激增;针对指针密集型程序如bodyfluiddedup(内存占用过大导致崩溃)、swap,会导致指针边界元数据占用大量内存(为什么没有重合?)
ASAN平均性能开销51%,kmeans工作集 被膨胀131倍,导致性能开销增大;ASAN的影子内存固定占用512MB内存,Redzone及释放内存隔离区可能导致50到100倍的内存开销
SGXBounds平均性能开销17%,平均内存开销0.1%。比MPX对指针密集型程序更友好,比ASAN内存开销低,没有极端现象

在这里插入图片描述

②不断增加工作集时的性能开销(图8)

kmeans中,XS、S、M工作集下的MPX(w.r.t SGXBounds)占用大量内存(边界表),导致EPC Evict(表3),性能开销大。L、XL工作集下所有方案均出现EPC Evict,因此这些方案性能开销比值(w.r.t SGXBounds)变小。

matrixmul中,MPX仅需为每个矩阵分配一个边界表项,且边界信息不会从寄存器换出,因此性能开销与SGXBounds相当。矩阵乘法中顺序访存使得其不受EPC Evict影响,仅受缓存未命中影响,因此ASAN额外访问影子内存导致性能开销在XL下激增。
在这里插入图片描述

在这里插入图片描述

③多线程影响

MPX无多线程同步支持,存在安全问题,若引入多线程同步支持,性能将进一步恶化(8线程性能开销数据见图8,图9仅显示1和4线程数据)。

两者大体相当,但是ASAN的Redzone会破坏内存布局,访问影子内存会破坏缓存局部性,导致mmulswap中ASAN-4线程性能开销巨大。
在这里插入图片描述

④额外优化功能的效果

性能优化仅2%,不如预想的好。kmmmul等个别案例性能有提升。
在这里插入图片描述

⑤安全评估

在RIPE Security Benchmark下,MPX只检查出Stack Smashing问题。而ASAN和SGXBounds无法检查出结构体内缓冲区溢出问题。
在这里插入图片描述

SGX内/外SPEC 2006的性能开销(前面用的Phoenix和PARSEC)

有自定内存管理或会操纵地址高位的程序会导致SGXBounds误报。

在SGX环境下,ASAN会破坏Cache和EPC局部性,导致性能开销较大。MPX表项相对ASAN好一些,但某些指针密集型程序会使边界表过大导致程序崩溃。
在这里插入图片描述
在非SGX环境下,尽管SGXBounds对内存开销忽略不计,但由于所利用的缓存局部性优势已不复存在,因此性能开销没有优势。
在这里插入图片描述

各方案在实际应用上的表现

MPX由于边界表占用内存大,在Memcached上性能表现极差,在Apache上多客户端时表现开始变差。(ASAN大部分内存占用是因为影子内存,与MPX有一些差异)。

三者均能检测对应应用的著名漏洞,但SGXBounds凭借Boundless Memory,实现Failure-oblivious。
在这里插入图片描述

总结

  • SGXBounds基于Enclave理论只使用36位地址,且实际只使用32位地址的特性,在空闲地址位存放Tag。这种元数据管理能有效利用缓存和EPC局部性(没有影子内存、Bound Table等额外数据结构),无需同步机制即可支持多线程。
  • 由于利用了缓存和EPC局部性,SGXBounds在SGX环境下较ASAN和MPX更优,且所需内存开销(4B的Lower Bound)忽略不计。
  • SGXBounds相较MPX对指针密集型程序相比MPX和ASAN有极大的性能优势。
  • 扩展实现了Boundless Memory带来的Failure-oblivious(当故障发生时通过某种机制让程序并非被终止执行,而是改正错误并正常继续执行。SGX中的中止页面语义也达到Failure-oblivious的功能)的优势。
  • 但是SGXBounds是对象粒度边界保护,无法检查对象内溢出。
  • 讨论了SGX采用的静态链接与(假设的)动态链接的优劣
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值