Paper Sharing: SmashEX
Tert-Butyllithium

TL;DR

这篇工作可以干掉Intel SGX上跑的任意enclave。作者提出,由于原子性的缺失,异步异常的处理过程中存在漏洞。

具体而言,由于Intel SGX的异常处理需要反复跨世界的上下文切换(即,首先给host OS,再转发给enclave)。转发之后的第一步是上下文切换,就是从从上次保存的寄存器中restore。然而若此时再来一个中断,enclave需要保存当前的寄存器状态以备后续使用。但这时寄存器还没有从上一次保存的完全恢复,他的值还是从host OS来的。这样,一个可能的恶意的寄存器就被当做正常的值保存了下来。

Background

这个攻击得以成立的基础是SGX中exception和interrupt的处理机制。

SGX架构图

如上图所示,EENTER, EEXITERESUME都是不安全的host OS与enclave交互的指令。当一个(Enclave中的)异步异常(AEX, Asynchronous EXception)产生时,会首先交给OS kernel。随后,经过数次控制权的转换(host OS到enclave),OS kernel的处理函数会传递给Enclave的真正处理函数。

一句话总结:在SGX中,异常处理不能直接给enclave,而是要在host OS中绕一圈

上图展示了这处理的具体流程,已经用①~⑥标出。其核心要点在于:SGX为了正确执行跨世界切换,需要在切入和切出的每次都做好上下文(例如,各种寄存器)的恢复。例如,在①的过程中,一个divide of zero的exception,各个寄存器的内容随即会被存入SSA region中。随后,在③~④的时候,这个enclave会被再次进入,恢复上次保存在SSA的各种寄存器,这也包括rsp,即栈顶地址。

漏洞:非原子性的上下文恢复

上图展示了上下文恢复的全过程,以及整个攻击的图景。上面虚线框住的$A\rightarrow B \rightarrow C$ 本意是在一次EENTER过程中把当前的寄存器清理掉,然后恢复上一次保存的栈。但是,如果这个过程本身因为非原子性的缺失,导致可以被中断。如同每一次中断的过程一样,CPU会把当前的寄存器状态保存到SSA中,以待后续使用。

但是,由于上下文的切换没有完成,此时保存的值是直接在host OS中传过去的。所以,如果恶意的host OS精心制造了一个栈,并把它的地址写到第一次EENTER(Ocall impl moves values to regs)之前的rsp。enclave就会在第二次EENTER(Interrupt handler)将这个恶意的栈地址恢复。好了,有了栈,攻击完成。

上图展示了一种利用POP运行memcpy,把enclave里面的secret偷出来的方法。作者在SGX SDK里面找到了几个gadgets,然后就能把栈上的值弹到寄存器里面了。最后就是调用memcpy。

一些个人思考

看得出来,这是一个特别精妙的攻击:在整个exception的的处理链条中,如果有任何一个实现不是如此,那么攻击就不会成功。例如:

  • Exception的处理居然依赖从host OS绕一圈。是不是能让整个系统的异常处理优先通过SGX,然后再转交给OS(想法来源——medeleg in RISC-V)
  • 中断的处理和enclave正常运行共用一个栈。用一个单独的栈就可以解决问题(想法来源——本论文讨论,作者另外指出这样就没法嵌套exception,虽然我对嵌套的必要性表示怀疑)
  • 原子性。上下文恢复的原子性缺失是整个攻击得以发生的直接原因,在上下文切换中加入原子化的设计也会使得整个攻击不会发生。