【问题标题】:Options to wipe stack memory before function return在函数返回之前擦除堆栈内存的选项
【发布时间】:2015-02-13 00:46:29
【问题描述】:

在编写处理敏感数据的c 代码时,我听说攻击者可能会在处理敏感数据的函数返回后尝试读取堆栈中剩余的内存。在函数返回之前将所有堆栈内存归零似乎是一个艰苦的过程。它还需要使用-O0 来确保编译器不会优化归零代码。有没有办法自动擦除堆栈内存?可能是编译器标志、定义,或者可能是读取堆栈大小并将其归零的汇编语言内联函数?

【问题讨论】:

    标签: c security stack


    【解决方案1】:

    没有通用的方法(据我所知)可以便携。

    您可以做(并且应该工作)只是简单地使用堆栈分配的数组调用另一个函数,该数组大到足以覆盖上一次调用的敏感区域。

    之后,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);
    }
    

    【讨论】:

    • 栈,当在一个函数中包含(倒序调用参数时,调用者PC寄存器为call语句之后的指令,被调用的函数自动变量,倒序。由于上述原因,关于最好的办法是让被调用函数将每个传递的参数和自动变量设置为 0
    • 不错的主意,它要求调用者记住在每个安全相关函数之后调用 clear_stack 函数。为了缓解这个问题,我们可以将这些敏感函数设为static,然后公开调用敏感函数的安全版本,然后立即调用清除堆栈函数。那么只有在同一个文件中的函数调用需要小心。
    • @satur9nine:确实,这是一种很好的方法。我只是在描述内存清理,而不是如何使用。当然,这并不是完全可移植的原则上(因为机器可能根本没有堆栈),但应该在实践中。如果安全性真的很重要,则应该在每个单独的系统上/为每个单独的系统审核此代码(任何,真的 - 但特别是此代码)。在某些系统中,您可以使用专用函数来代替 memset(例如 Windows 中的 SecureZeroMemory())。
    【解决方案2】:

    您需要使用堆栈变量吗?你可以使用堆变量来控制内存吗?

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

    【讨论】:

    • 这可能会使应用程序显着变慢。此外,该函数可能会下降到本身使用堆栈的调用;所以这将要求所有可能处理敏感数据的函数都不会使用堆栈
    • 使用堆栈非常方便,但是擦拭区域更容易。
    【解决方案3】:

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

    但是,我确实找到了在函数返回后将堆栈内存归零的解决方案,尽管它依赖于操作系统的支持。在这种情况下,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

    【讨论】:

      猜你喜欢
      • 2022-01-14
      • 2017-02-24
      • 2012-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-05
      • 2019-08-19
      • 2014-09-06
      相关资源
      最近更新 更多