是否需要清理堆栈内容?
Is it necessary to clean up stack contents?
我们正在接受 PCI PA-DSS 认证,其要求之一是避免将干净的 PAN(卡号)写入磁盘。应用程序不会将此类信息写入磁盘,但如果操作系统(Windows,在本例中)需要交换,内存内容将写入页面文件。因此,应用程序必须清理内存以防止 RAM 捕获程序服务读取敏感数据。
分三种情况处理:
- 堆分配(malloc):在释放内存之前,可以使用
memset
清理该区域
- static or global data: 使用后,可以使用
memset
清理该区域
- 本地数据(函数成员):数据入栈,函数结束后不可访问
例如:
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 锁定的内存中,这样它就不会被换出。这必须发生在之前 说读取敏感数据。
像这样可以锁定多少内存有一个小限制,因此您最好不要锁定整个堆栈,并且应该完全避免在堆栈上存储敏感数据。
我认为这是必要的,但这只是问题的一半。
这里有两个问题:
原则上,没有什么能阻止 OS 在您仍在使用数据时交换您的数据。正如另一个答案中指出的那样,您希望 windows 上的 VirtualLock
和 linux 上的 mlock
。
您需要防止优化器优化掉 memset
。这也适用于全局和动态分配的内存。我强烈建议看一下 cryptopp SecureWipeBuffer
。
一般来说,您应该避免手动操作,因为这是一个容易出错的过程。相反,考虑使用自定义分配器或自定义 class 模板来获取可在析构函数中释放的安全数据。
通过移动堆栈指针来清理堆栈,而不是从中实际弹出值。唯一的机制是将 return 弹出到适当的寄存器中。您必须全部手动完成。此外 - volatile 可以帮助您避免在每个变量的基础上进行优化。您可以手动将堆栈弹出干净,但是——您需要汇编器来执行此操作——并且开始操作堆栈并不是那么简单——它实际上不是您的资源——就您而言,编译器拥有它.
我们正在接受 PCI PA-DSS 认证,其要求之一是避免将干净的 PAN(卡号)写入磁盘。应用程序不会将此类信息写入磁盘,但如果操作系统(Windows,在本例中)需要交换,内存内容将写入页面文件。因此,应用程序必须清理内存以防止 RAM 捕获程序服务读取敏感数据。
分三种情况处理:
- 堆分配(malloc):在释放内存之前,可以使用
memset
清理该区域
- static or global data: 使用后,可以使用
memset
清理该区域
- 本地数据(函数成员):数据入栈,函数结束后不可访问
例如:
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 锁定的内存中,这样它就不会被换出。这必须发生在之前 说读取敏感数据。
像这样可以锁定多少内存有一个小限制,因此您最好不要锁定整个堆栈,并且应该完全避免在堆栈上存储敏感数据。
我认为这是必要的,但这只是问题的一半。
这里有两个问题:
原则上,没有什么能阻止 OS 在您仍在使用数据时交换您的数据。正如另一个答案中指出的那样,您希望 windows 上的
VirtualLock
和 linux 上的mlock
。您需要防止优化器优化掉
memset
。这也适用于全局和动态分配的内存。我强烈建议看一下 cryptoppSecureWipeBuffer
。
一般来说,您应该避免手动操作,因为这是一个容易出错的过程。相反,考虑使用自定义分配器或自定义 class 模板来获取可在析构函数中释放的安全数据。
通过移动堆栈指针来清理堆栈,而不是从中实际弹出值。唯一的机制是将 return 弹出到适当的寄存器中。您必须全部手动完成。此外 - volatile 可以帮助您避免在每个变量的基础上进行优化。您可以手动将堆栈弹出干净,但是——您需要汇编器来执行此操作——并且开始操作堆栈并不是那么简单——它实际上不是您的资源——就您而言,编译器拥有它.