是否有必要清理堆栈内容?

时间:2017-06-01 18:43:00

标签: c++ c pci-dss

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

有三种情况需要处理:

  • 堆分配(malloc):在释放内存之前,可以使用memset清除区域
  • 静态或全局数据:使用后,可以使用memset
  • 清理该区域
  • 本地数据(函数成员):数据放在堆栈上,在函数完成后无法访问

例如:

void test()
{
  char card_number[17];

  strcpy(card_number, "4000000000000000");
}

执行测试后,内存仍包含card_number信息。

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

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

有没有办法在某个时刻清理堆栈,就像程序结束前一样?

4 个答案:

答案 0 :(得分:4)

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

你可以锁定这么多的内存有一个小的限制,所以你可能没有锁定整个堆栈,应该避免将敏感数据存储在堆栈上。

答案 1 :(得分:2)

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

#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 ->

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

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

答案 2 :(得分:2)

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

这里有两个问题:

  1. 原则上,在您仍在使用数据时,没有什么可以阻止操作系统交换数据。正如在另一个答案中指出的那样,您希望在Windows上使用VirtualLock并在Linux上使用mlock

  2. 您需要阻止优化程序优化memset。这也适用于全局和动态分配的内存。我强烈建议你看看cryptopp SecureWipeBuffer

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

答案 3 :(得分:1)

通过移动堆栈指针来清除堆栈,而不是通过实际弹出堆栈指针来清除堆栈。唯一的机制是将返回弹出到适当的寄存器中。您必须手动完成所有操作。另外 - volatile可以帮助您避免基于每个变量的优化。你可以手动弹出堆栈干净,但是 - 你需要汇编程序来做 - 并且开始操作堆栈并不是那么简单 - 它实际上不是你的资源 - 编译器拥有它就你而言