在函数 return 之前擦除堆栈内存的选项

Options to wipe stack memory before function return

在编写处理敏感数据的 c 代码时,我听说攻击者可能会在处理敏感数据 returns 的函数之后尝试读取堆栈中剩余的内存。在函数 returns 之前将所有堆栈内存归零似乎是一个艰苦的过程。它还需要使用 -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);
}

您需要使用堆栈变量吗?您可以改用堆变量并以这种方式控制内存吗?

您可以分配足够大的堆内存块并将类型化指针分配给变量,然后在释放块之前将其 memset 为零。

我认为没有完全可移植的解决方案。我之前曾尝试用通用解决方案来回答这个问题,但意识到编译器对局部变量的重组使得几乎不可能将当前正在执行的函数的堆栈归零。

然而,我确实找到了一个函数后零堆栈内存的解决方案returns,尽管它依赖于操作系统的支持。在本例中为 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