【发布时间】:2015-02-13 00:46:29
【问题描述】:
在编写处理敏感数据的c 代码时,我听说攻击者可能会在处理敏感数据的函数返回后尝试读取堆栈中剩余的内存。在函数返回之前将所有堆栈内存归零似乎是一个艰苦的过程。它还需要使用-O0 来确保编译器不会优化归零代码。有没有办法自动擦除堆栈内存?可能是编译器标志、定义,或者可能是读取堆栈大小并将其归零的汇编语言内联函数?
【问题讨论】:
在编写处理敏感数据的c 代码时,我听说攻击者可能会在处理敏感数据的函数返回后尝试读取堆栈中剩余的内存。在函数返回之前将所有堆栈内存归零似乎是一个艰苦的过程。它还需要使用-O0 来确保编译器不会优化归零代码。有没有办法自动擦除堆栈内存?可能是编译器标志、定义,或者可能是读取堆栈大小并将其归零的汇编语言内联函数?
【问题讨论】:
没有通用的方法(据我所知)可以便携。
您可以做(并且应该工作)只是简单地使用堆栈分配的数组调用另一个函数,该数组大到足以覆盖上一次调用的敏感区域。
之后,memset 表示函数中的数组。将数组声明为volatile 可能是个好主意,这样memset 就不会被优化掉。
C99 示例(因为它使用可变长度数组):
void clear_stack(size_t sz)
{
// you can try using `alloca` or a fixed-size array if not using C99
// make sure `sz` is small enough to avoid stack overflows
volatile char arr[sz];
memset(arr, 0, sz);
}
【讨论】:
static,然后公开调用敏感函数的安全版本,然后立即调用清除堆栈函数。那么只有在同一个文件中的函数调用需要小心。
memset(例如 Windows 中的 SecureZeroMemory())。
您需要使用堆栈变量吗?你可以使用堆变量来控制内存吗?
您可以分配足够大的堆内存块并为 vars 分配类型化的指针,然后在释放块之前将其 memset 为零。
【讨论】:
我认为没有完全可移植的解决方案。我之前曾尝试用通用解决方案来回答这个问题,但意识到编译器对局部变量的重组使得当前正在执行的函数的堆栈几乎不可能归零。
但是,我确实找到了在函数返回后将堆栈内存归零的解决方案,尽管它依赖于操作系统的支持。在这种情况下,FreeRTOS,虽然我确信它可以适应其他简单的 RTOS。这个解决方案有点像 Tim Čas 的建议,但不是希望用适当大小的数组覆盖内存,而是擦除所有内容,因为它知道堆栈的确切位置。
将以下代码添加到task.c:
void *pvGetCurrentTaskStackStart(void) {
return (void *)pxCurrentTCB->pxStack;
}
这是一个函数,用于擦除当前任务的堆栈上所有未使用(但脏)的内存:
void wipe_task_stack(void)
{
int i;
register uint32_t sp asm ("sp");
uint32_t stack_start = (uint32_t) pvGetCurrentTaskStackStart();
uint32_t size = sp - stack_start;
/* Do not use memset or similar function, it would wipe its own stack! */
for (i = i; i < size; i++) {
((volatile uint8_t *) stack_start)[i] = 0;
}
}
这里有一些很好的文章,说明为什么将内存归零很困难:
http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html
http://www.daemonology.net/blog/2014-09-05-erratum.html
http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html
【讨论】: