清除堆栈上的变量
Clear variable on the stack
代码片段:
int secret_foo(void)
{
int key = get_secret();
/* use the key to do highly privileged stuff */
....
/* Need to clear the value of key on the stack before exit */
key = 0;
/* Any half decent compiler would probably optimize out the statement above */
/* How can I convince it not to do that? */
return result;
}
我需要在 return
ing 之前从堆栈中清除变量 key
的值(如代码所示)。
如果您好奇的话,这是一个实际的客户需求(嵌入式域)。
您可以使用 volatile
(强调我的):
Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access.
volatile int key = get_secret();
如果您使用动态分配,您可以控制擦除该内存,而不受系统对堆栈的操作的限制。
int secret_foo(void)
{
int *key = malloc(sizeof(int));
*key = get_secret();
memset(key, 0, sizeof(int));
// other magical things...
return result;
}
一种解决方案是对您不想优化的代码部分禁用编译器优化:
int secret_foo(void) {
int key = get_secret();
#pragma GCC push_options
#pragma GCC optimize ("O0")
key = 0;
#pragma GCC pop_options
return result;
}
volatile
有时可能有点矫枉过正,因为它还会影响变量的所有其他用途。
使用memset_s
(C11 起):http://en.cppreference.com/w/c/string/byte/memset
memset may be optimized away (under the as-if rules) if the object modified by this function is not accessed again for the rest of its lifetime. For that reason, this function cannot be used to scrub memory (e.g. to fill an array that stored a password with zeroes). This optimization is prohibited for memset_s: it is guaranteed to perform the memory write.
int secret_foo(void)
{
int key = get_secret();
/* use the key to do highly privileged stuff */
....
memset_s(&key, sizeof(int), 0, sizeof(int));
return result;
}
您可以在此处找到各种 platforms/C 标准的其他解决方案:https://www.securecoding.cert.org/confluence/display/c/MSC06-C.+Beware+of+compiler+optimizations
附录: 看看这篇文章 Zeroing buffer is insufficient 它指出了其他问题(除了将实际缓冲区归零):
With a bit of care and a cooperative compiler, we can zero a buffer — but that's not what we need. What we need to do is zero every location where sensitive data might be stored. Remember, the whole reason we had sensitive information in memory in the first place was so that we could use it; and that usage almost certainly resulted in sensitive data being copied onto the stack and into registers.
您的 key
值可能已被编译器复制到另一个位置(如寄存器或临时 stack/memory 位置),您没有任何控制权来清除该位置。
代码片段:
int secret_foo(void)
{
int key = get_secret();
/* use the key to do highly privileged stuff */
....
/* Need to clear the value of key on the stack before exit */
key = 0;
/* Any half decent compiler would probably optimize out the statement above */
/* How can I convince it not to do that? */
return result;
}
我需要在 return
ing 之前从堆栈中清除变量 key
的值(如代码所示)。
如果您好奇的话,这是一个实际的客户需求(嵌入式域)。
您可以使用 volatile
(强调我的):
Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access.
volatile int key = get_secret();
如果您使用动态分配,您可以控制擦除该内存,而不受系统对堆栈的操作的限制。
int secret_foo(void)
{
int *key = malloc(sizeof(int));
*key = get_secret();
memset(key, 0, sizeof(int));
// other magical things...
return result;
}
一种解决方案是对您不想优化的代码部分禁用编译器优化:
int secret_foo(void) {
int key = get_secret();
#pragma GCC push_options
#pragma GCC optimize ("O0")
key = 0;
#pragma GCC pop_options
return result;
}
volatile
有时可能有点矫枉过正,因为它还会影响变量的所有其他用途。
使用memset_s
(C11 起):http://en.cppreference.com/w/c/string/byte/memset
memset may be optimized away (under the as-if rules) if the object modified by this function is not accessed again for the rest of its lifetime. For that reason, this function cannot be used to scrub memory (e.g. to fill an array that stored a password with zeroes). This optimization is prohibited for memset_s: it is guaranteed to perform the memory write.
int secret_foo(void)
{
int key = get_secret();
/* use the key to do highly privileged stuff */
....
memset_s(&key, sizeof(int), 0, sizeof(int));
return result;
}
您可以在此处找到各种 platforms/C 标准的其他解决方案:https://www.securecoding.cert.org/confluence/display/c/MSC06-C.+Beware+of+compiler+optimizations
附录: 看看这篇文章 Zeroing buffer is insufficient 它指出了其他问题(除了将实际缓冲区归零):
With a bit of care and a cooperative compiler, we can zero a buffer — but that's not what we need. What we need to do is zero every location where sensitive data might be stored. Remember, the whole reason we had sensitive information in memory in the first place was so that we could use it; and that usage almost certainly resulted in sensitive data being copied onto the stack and into registers.
您的 key
值可能已被编译器复制到另一个位置(如寄存器或临时 stack/memory 位置),您没有任何控制权来清除该位置。