如何确保编译器优化不会带来安全风险?

时间:2010-09-24 08:23:38

标签: c++ security optimization memory compiler-construction

我必须编写一个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');
}

现在我关心的是编译器优化。在这里,编译器可能会检测到密码即将被删除,并且此时更改其值是无用的,只需删除该调用即可。

我不希望我的编译器关心未来未引用内存的值。

我的担忧是否合法?我怎么能确定这样的代码不会被优化出来?

6 个答案:

答案 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