我必须编写一个Windows服务,在某些时候处理机密数据(例如PIN码,密码等)。这些信息需要很短的时间:通常它们几乎立即发送到智能卡读卡器。
让我们考虑一下这段代码:
{
std::string password = getPassword(); // Get the password from the user
writePasswordToSmartCard(password);
// Okay, here we don't need password anymore.
// We set it all to '\0' so it doesn't stay in memory.
std::fill(password.begin(), password.end(), '\0');
}
现在我关心的是编译器优化。在这里,编译器可能会检测到密码即将被删除,并且此时更改其值是无用的,只需删除该调用即可。
我不希望我的编译器关心未来未引用内存的值。
我的担忧是否合法?我怎么能确定这样的代码不会被优化出来?
答案 0 :(得分:34)
是的,您的担忧是合法的。您需要使用专门设计的函数,如SecureZeroMemory(),以防止优化修改您的代码行为。
不要忘记字符串类应该是专门为处理密码而设计的。例如,如果类重新分配缓冲区以保存更长的字符串,则必须先擦除缓冲区,然后再将其重新调整到内存分配器。我不确定,但很可能std::string
没有这样做(至少在默认情况下)。使用不合适的字符串处理类会使您的所有关注变得毫无价值 - 您甚至可以在程序存储器中复制密码。
答案 1 :(得分:9)
这是有问题的,但另一个原因。谁说std::string password = getPassword();
不会在记忆中留下另一份副本? (可能你需要在“destruct”或“deallocate”上为零写一个“安全”的分配器类)
在您的代码安静中,您可以通过获取指向字符串数据的易失性指针(我不知道您是否可以以标准方式执行)来避免优化,然后将数据归零。
答案 2 :(得分:8)
不要使用std::string
作为密码,因为它在进行重新分配或销毁时不会将其内存归零 - 而是设计自己的ConfidentialString
类。在设计该类时,您可能希望利用CryptProtectMemory ...并且在需要使用解密版本时非常非常小心,尤其是在调用外部代码时。
答案 3 :(得分:1)
在这个特定的例子中,如果编译器可以优化掉一个明显有副作用的方法调用,我会感到非常惊讶。或者是std :: fill inline所以编译器可以看到实现? (我不是C ++程序员)。
话虽如此,这种事情一般都是一个问题。但是你需要考虑利用它是多么容易。要读取另一个进程的内存,攻击者需要一定级别的管理员访问权限(如果没有,为什么要使用该操作系统)。如果机器被泄露到那个级别,你就已经输了。
答案 4 :(得分:0)
为什么不禁用相关代码的优化?
#pragma optimize( "", off )
// Code, not to optimize goes here
#pragma optimize( "", on )
此#pragma optimize示例特定于MSVC,但其他编译器也支持它。
答案 5 :(得分:-1)
声明密码volatile,以防止编译器对删除显式读取或写入做出任何假设。
volatile std::string password = getPassword(); // Get the password from the user