我们正在获得PCI PA-DSS认证,其中一项要求是避免将干净的PAN(卡号)写入磁盘。应用程序不会将此类信息写入磁盘,但如果操作系统(在本例中为Windows)需要交换,则将内存内容写入页面文件。因此,应用程序必须清理内存以防止RAM捕获器服务读取敏感数据。
有三种情况需要处理:
memset
清理该区域memset
清理该区域例如:
void test()
{
char card_number[17];
strcpy(card_number, "4000000000000000");
}
在执行测试之后,存储器仍然包含card_number信息。
一条指令可以在测试结束时将变量card_number归零,但这应该适用于程序中的所有函数。
memset(card_number, 0, sizeof(card_number));
有没有办法在某个时刻清理堆栈,就像程序结束前一样?
程序完成后立即清理堆栈可能为时已晚,它可能已经在运行时的任何时候被换出。你应该只在VirtualLock锁定的内存中保存你的特定数据,这样它就不会被换掉。这必须在读取所述敏感数据之前发生。
你可以锁定这么多的内存有一个小的限制,所以你可以没有锁定整个堆栈,应该避免将敏感数据存储在堆栈上。
我假设你想摆脱下面这种情况:
#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 ->
您可以执行类似清理堆上的内存或静态变量的操作。显然,我们假设卡号来自动态源而不是硬编码的东西......
和是:明确回答你问题的标题:堆栈不会自动清理......你必须自己清理它。
我认为这是必要的,但这只是问题的一半。
这里有两个问题:
VirtualLock
,在linux上使用mlock
。memset
。这也适用于全局和动态分配的内存。我强烈建议你看看cryptopp SecureWipeBuffer
。通常,您应该避免手动执行,因为这是一个容易出错的过程。相反,请考虑使用自定义分配器或自定义类模板来获取可在析构函数中释放的安全数据。
通过移动堆栈指针来清理堆栈,而不是通过实际弹出堆栈指针来清除堆栈。唯一的机制是将返回弹出到适当的寄存器中。您必须手动完成所有操作。另外 - volatile可以帮助您避免基于每个变量的优化。你可以手动弹出堆栈干净,但是 - 你需要汇编程序来做 - 并且开始操作堆栈并不是那么简单 - 它实际上不是你的资源 - 编译器拥有它就你而言。