是否需要清理堆栈内容?

Is it necessary to clean up stack contents?

我们正在接受 PCI PA-DSS 认证,其要求之一是避免将干净的 PAN(卡号)写入磁盘。应用程序不会将此类信息写入磁盘,但如果操作系统(Windows,在本例中)需要交换,内存内容将写入页面文件。因此,应用程序必须清理内存以防止 RAM 捕获程序服务读取敏感数据。

分三种情况处理:

例如:

void test()
{
  char card_number[17];

  strcpy(card_number, "4000000000000000");
}

测试执行后,内存中仍然有card_number信息

一条指令可以在测试结束时将变量 card_number 归零,但这应该适用于程序中的所有函数。

memset(card_number, 0, sizeof(card_number));

有没有办法在某个时候清理堆栈,比如在程序结束之前?

我假设您想摆脱以下这种情况:

#include <iostream>

using namespace std;

void test()
{
    char card_number[17];
    strcpy(card_number, "1234567890123456");
    cout << "test() -> " << card_number << endl;
}

void test_trash()
{
    // don't initialize, so get the trash from previous call to test()
    char card_number[17];
    cout << "trash from previous function -> " << card_number << endl;
}

int main(int argc, const char * argv[])
{
    test();
    test_trash();
    return 0;
}

输出:

test() -> 1234567890123456
trash from previous function -> 1234567890123456

你可以这样做:

#include <iostream>

using namespace std;

class CardNumber
{
    char card_number[17];

public:
    CardNumber(const char * value)
    {
        strncpy(card_number, value, sizeof(card_number));
    }

    virtual ~CardNumber()
    {
        // as suggested by @piedar, memset_s(), so the compiler
        // doesn't optimize it away.
        memset_s(card_number, sizeof(card_number), 0, sizeof(card_number));
    }

    const char * operator()()
    {
        return card_number;
    }
};

void test()
{
    CardNumber cardNumber("1234567890123456");
    cout << "test() -> " << cardNumber() << endl;
}

void test_trash()
{
    // don't initialize, so get the trash from previous call to test()
    char card_number[17];
    cout << "trash from previous function -> " << card_number << endl;
}

int main(int argc, const char * argv[])
{
    test();
    test_trash();
    return 0;
}

输出:

test() -> 1234567890123456
trash from previous function ->

您可以执行类似清理堆内存或静态变量的操作。 显然,我们假设卡号将来自动态源而不是 hard-coded 东西...

是:明确回答你的问题标题:堆栈不会自动清理......你必须自己清理它。

在程序完成时立即清理堆栈可能为时已晚,它可能已经在其运行时的任何时候被换出。您应该将您的敏感数据仅保存在使用 VirtualLock 锁定的内存中,这样它就不会被换出。这必须发生在之前 说读取敏感数据。

像这样可以锁定多少内存有一个小限制,因此您最好不要锁定整个堆栈,并且应该完全避免在堆栈上存储敏感数据。

我认为这是必要的,但这只是问题的一半。

这里有两个问题:

  1. 原则上,没有什么能阻止 OS 在您仍在使用数据时交换您的数据。正如另一个答案中指出的那样,您希望 windows 上的 VirtualLock 和 linux 上的 mlock

  2. 您需要防止优化器优化掉 memset。这也适用于全局和动态分配的内存。我强烈建议看一下 cryptopp SecureWipeBuffer

一般来说,您应该避免手动操作,因为这是一个容易出错的过程。相反,考虑使用自定义分配器或自定义 class 模板来获取可在析构函数中释放的安全数据。

通过移动堆栈指针来清理堆栈,而不是从中实际弹出值。唯一的机制是将 return 弹出到适当的寄存器中。您必须全部手动完成。此外 - volatile 可以帮助您避免在每个变量的基础上进行优化。您可以手动将堆栈弹出干净,但是——您需要汇编器来执行此操作——并且开始操作堆栈并不是那么简单——它实际上不是您的资源——就您而言,编译器拥有它.