Intel® Software Guard Extensions (SGX) SW Development Guidance for Potential Bounds Check Bypass (CVE-2017-5753) Side Channel Exploits 参考中译文
White Paper/Revision 1.1/March 2018 版权属于Intel
2018年1月3日,Google Project Zero的安全团队发现了三个变种的side channel攻击。Intel Security Advisory记录了这三个攻击。Spectre指变种1和变种2,Meltdown指的是变种3。
Intel SGX程序也受到这个攻击的影响。Intel发布的指南中说明了通过软件方式缓解这些攻击也是重要的组成部分。
这份文档中我们解释Intel SGX SDK为了缓解这些攻击所做的改动,并且提供了一份安全编程指南,帮助开发人员写出安全的代码。
为了修复Bound Check Bypass [CVE-2017-5753], Intel SGX SDK做了相应更新。做法是通过插入LFENCE指令,在该情况下停止speculative execution。SDK的修改如下表:
SDK修改 | 防止的Bypass | 受影响的SDK组件 |
---|---|---|
在查ECALL function table时停止推测执行。 | 外界攻击者可以控制ECALL function table的index。这个index的检查可以被bypass。 | tRTS |
在enclave操作secret memory(即使不被考虑为secret)时停止推测执行。 | 使用Intel SGX SDK生成的enclave中,某些经由指针传入enclave的缓冲区在传入时被检查是否不在enclave地址空间中。这些检查可以被bypass。 | Edger8r和tRTS |
在第一次enclave call时停止推测执行。第一次enclave call是enclave装载的一部分,并不执行任何Edger8r生成的代码。 | 第一次enclave call传递一个指针,这个指针会被作为enclave外的指针进行处理。对于这个指针的检查可以被bypass。 | tRTS |
对于可能溢出sealed data数据结构的操作,停止推测执行。 | sgx_sealed_data_t数据结构包括用于计算指针的长度域。对于这些值的检查可以被bypass。 | tSeal |
在trusted key exchange库中停止推测执行。使用这个库会给依赖于这个库的enclave增加额外的ECALL: sgx_ra_get_msg3_trusted,这个ECALL包括一个user_check指针。 | 对于user_check指针的检查(检查这个buffer是否位于enclave外)的操作可以被bypass。由于这是一个user_check指针,所有其他的SDK更新对此无效。 | tkey_exchange |
在trusted key exchange库中停止推测执行。使用这个库会给依赖于这个库的enclave增加额外的ECALL。这些ECALL引入了一个context参数,这个参数会被用作session数组的index。 | 外界攻击者可以控制这个context参数。对于这个context参数的检查(是否越界访问)可以被bypass。 | tkey_exchange |
为了利用本次SDK更新,开发人员应该使用新版SDK重新编译生成SGX enclave。这里的新版SDK指的是:Intel® SGX SDK for Windows* OS Version 1.9.106 和 Intel® SGX SDK for Linux OS Version 2.1.102。根据Intel SGX开发手册,开发人员可以选择自增SGX enclave的ISVSVN,以体现这次安全升级。因此,在某些情况下,在需要引用到ISVSVN建立信任和provision secret时,需要更新对应的ISVSVN,以对应于这次安全更新。
但是,使用更新后的SDK重新编译很可能不解决所有问题。并不是所有的Bounds Check Bypass攻击都可以被重新编译直接缓解。所有攻击者可干涉的内存读取操作都应该被人工审计分析。这意味着enclave开发人员需要仔细分析SDK没有处理过的enclave输入路径。以下章节描述了几种enclave输入数据的方法,以及每种方法开发人员应采取的缓解措施。
以下三种Enclave输入模式可以导致被攻击的风险:
- 输入被解释为地址/指针
- 输入值参与地址计算
- 输入指向的内容(递归包含)被解释为地址,或参与地址运算
产生攻击的原因是:攻击者可以篡改Enclave的输入参数,使其直接或间接计算生成的地址指向所泄露的secret。此时,由于输入会经由一个“输入指针是否在enclave外”的判断,于是符合了spectre攻击的条件,触发推测执行,导致side channel attack风险。
即使enclave所对应的EDL文件并没有声明任何此类参数,一个enclave仍然会包含此类参数。这是因为在编译过程中,Edger8r会为把每个ECALL的参数包装成一个“Marshaling structure”,并使用一个指针指向这个结构体并作为参数进行传递。并且,Edger8r所生成代码和tRTS的部分代码一同维护一个“ecall table”的数据结构,用于将开发者定义的ECALL转化为一个indirect call。针对这个地址转换进行的攻击,以及上述“Marshaling structure”指针的攻击,在本次SDK更新中得到缓解。
此外,更新后的SDK在EDL文件中定义的地址类参数在处理中自动插入缓解指令,只要满足以下条件:
- EDL文件中此参数被定义为指针类型
- 没有使用user_check关键字或者sizefunc属性来修饰此参数。注:某一个"sizefunc input"是否可以被exploit,依赖于这个特定的sizefunc函数。这个特定的sizefunc函数是开发者自己编写的。
- 这个参数的“指针性”(原文是“pointer-ness”,意为这个参数是否本质上具有指针的语义)没有被typedef所掩盖。注:这种情况下,EDL文件中的"isptr"属性可以被用于标识这个参数具有指针性,会被当做指针对待。
然而,即使一个输入的指针参数符合了以上标准,一旦这个指针所指向的数据结构包含了任何一个可以用来计算地址/指针的域,那么,开发者有责任去分析这个enclave代码中所有使用这个“嵌套指针”数据结构的代码,以去除安全隐患。更新后的SDK对此类情况没有帮助,只有一个情况除外:一个使用了sealing库的enclave会引入一个指针,指向seal过的blob,作为输入。sealing库的代码会将这个blob解析为一个含有offset的数据结构。在此次更新的SDK中加入了对应的缓解,以防止产生危险。
在真实世界中,处理可变长参数是开发人员经常遇到的情况,并且需要开发人员仔细分析。可变长参数的头部通常包含一个长度域,常见的做法是借用这个传入的长度作为边界来读取参数的值。但是这样的实现就会导致mis-predict,进一步导致推测执行机制执行本不该执行的代码。
// EDL定义
public uint32_t enclave_function([in, size = alloc_size] tlv_t* varlen_input,
uint32_t alloc_size);
// 头文件定义
typedef struct {
unsigned type;
unsigned length;
void* payload;
} tlv_t;
上图所示的EDL文件中,SDK代码会检查一段长度为alloc_size的内存是否位于enclave之外,本次更新后的SDK加入了缓解指令,避免危险的side channel attack。然而,开发者编写的代码中仍然可能具有根据tlv_t结构中length域对payload进行访问的代码。这样的内存访问所产生的side channel attack风险是不会被SDK的更新所缓解的。例如,开发者往往会先比较varlen_input.length和alloc_size的大小关系,再做相应的数据读取操作。这个比较操作就可以被攻击者利用,导致预测器的mis-predict,进而造成spectre攻击风险。开发人员有责任在确定长度合法之后插入一条LFENCE指令,来防止推测执行机制访问到真实数据。如果性能允许,开发者可以简单的在每个分支之后插入LFENCE指令,避免繁重的路径分析工作,如:
// make sure payload is outside enclave
If (varlen_input.length > alloc_size) {
// error code
... }
Else {
_mm_lfence();
//
// valid length path
//
...
}
其中_mm_lfence是Intel编译器中LFENCE指令对应的函数。其他的编译器使用__builtin_ia32_lfence。
EDL属性中的user_check属性告诉SDK其修饰的指针不需要被检查(检查缓冲区是否位于enclave地址空间内)。SDK代码会把user_check修饰的参数作为整数对待。因此一旦开发者使用user_check对指针参数进行修饰,那么开发者需要负责分析所有直接、间接使用这个指针参数的enclave代码。如果参数是user_check指针并且指向enclave外的内存地址,那么以下的代码模式会直接导致side channel攻击风险。在此例子中加入LFENCE指令来缓解这种风险,其导致的性能下降是可以接受的。
// EDL
public uint32_t enclave_function([user_check]const uint8_t* user_check_input,
uint32_t user_check_size);
uint32_t enclave_function(const uint8_t* user_check_input,
uint32_t user_check_size)
{
...
//
// make sure input buffer is outside enclave
//
int SGXAPI sgx_is_outside_enclave(const void *addr, size_t
size);
if (!sgx_is_outside_enclave(user_check_input,
user_check_size)) {
// error code
...
}
else {
_mm_lfence();
...
}
...
}
sgx_is_outside_enclave 是SDK默认提供的函数,其意自明。这个函数检查一个由起始地址和长度定义的buffer是否完全处于enclave地址空间之外。
如果一个user_check输入本身指向理应处于enclave地址空间内的数据结构,攻击者可以篡改这个指针,使其指向enclave内的错误位置。开发人员应当更加仔细的检查此类输入参数。
首先来看以下这段漏洞代码:
void victim_function(size_t x) {
if (x < array1_size) {
temp &= array2[array1[x] * 512];
}
}
如果victim_function是EDL指定的受信任的enclave函数,那么SDK的代码完全无法缓解这段代码中含有的side channel attack风险,因为SDK并不知道输入会被用于参与地址计算。开发者应当插入对应的检查代码来缓解风险,例如:
void victim_function(size_t x) {
if (x < array1_size) {
_mm_lfence();
temp &= array2[array1[x] * 512];
}
}
EDL属性中的sizefunc使得在EDL进行函数定义时,开发人员可以指定一个函数,这个函数用于计算函数参数的长度。这个sizefunc所修饰的函数是运行在enclave内的。但由于其负责计算输入的长度,因此在进行常规的“输入参数是否位于enclave之外”这一检查之前,输入参数就会被访问到(例如计算一个包含长度域的不定长buffer的sizefunc函数)。这个过程容易引起side channel攻击风险。因此sizefunc函数应当被马上从EDL定义中移除。开发人员应该使用更安全的size属性来确定输入参数的长度,从而保证在读取输入参数之前就使得index不会越界。
size_t tcalc_size(const tlv_t* varlen_input, size_t maxlen)
{
size_t len = sizeof(tlv_t);
if (len > maxlen)
{
len = 0;
} else {
_mm_lfence(); //fence after check (CVE-2017-5753)
len = varlen_input->length;
if (len > maxlen)
{
len = 0;
}
}
_mm_lfence(); //fence after check (CVE-2017-5753)
return len;
}
void ecall_no_sizefunc(tlv_t* varlen_input, size_t len)
{
// make sure code won't go past input
// before processing
if (tcalc_size(varlen_input, len) == 0)
{
//record the error
return;
}
//tcalc_size performs fence
//process varlen_input
return;
}
Microsoft 在2018年1月15日发布了一篇blog,描述了一个新的编译器开关/Qspectre,用于缓解Spectre。Intel SGX开发者可以使用提供了这个开关的编译器重新生成SGX enclave。
译者补充:LLVM 6.0 加入了 -mretpoline 开关用于抵抗 type-2 的 spectre 攻击。但是目前只能通过 clang 前端启用。