如何确保编译器优化不会引入安全风险?
我必须编写一个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'); }
现在我关心的是编译器优化。 在这里编译器可能会检测到密码即将被删除,并且在这一点上改变它的值是无用的,只需要删除这个呼叫。
我不希望我的编译器关心未来未被引用的内存的价值。
我的担心是否合法? 我怎样才能确定这样一段代码不会被优化?
是的,你的担心是合法的。 您需要使用专门devise的function(如SecureZeroMemory())来防止优化来修改您的代码行为。
不要忘记,string类应该是专门为处理密码而devise的。 例如,如果类重新分配缓冲区以保存更长的string,则必须先擦除缓冲区,然后再将其重新分配给内存分配程序。 我不确定,但可能std::string
不会这样做(至less在默认情况下)。 使用不适合的string处理类会使得所有的问题变得毫无价值 – 即使您知道,您也将密码复制到整个程序内存中。
这是有问题的,但另一个原因。 谁说std::string password = getPassword();
不会在内存中留下另一个副本? (可能你需要写一个“安全的”分配器类,这个“零”或“释放”的零内存)
在你的代码中,你可以避免通过获得一个指向string数据的volatile指针来优化(我不知道你是否可以用标准的方式来做),然后将数据清零。
不要使用std::string
作为密码,因为它在重新分配或销毁时不会将内存清零 – 而是devise自己的ConfidentialString
类。 在devise这个类的时候,你可能想要利用CryptProtectMemory …并且在需要使用解密版本的时候非常小心,特别是在调用外部代码的时候。
在这个特定的例子中,如果编译器可以优化掉可能明显产生副作用的方法调用,我会感到非常惊讶。 或者是std :: fill inline,所以编译器可以看到实现? (我不是一个C ++程序员)。
话虽如此,这种事一般可以成为一个关切。 但是你需要考虑利用它是多么容易。 要读取另一个进程的内存,攻击者需要某种级别的pipe理员访问权限(如果没有,为什么使用该操作系统)。 如果机器受到损害,那么你已经失手了。
为什么不只是停用代码的优化?
#pragma optimize( "", off ) // Code, not to optimize goes here #pragma optimize( "", on )
这个#pragma优化的样例是特定于MSVC的,但其他编译器也支持它。
声明密码易失性,以防止编译器做出关于删除显式读取或写入的任何假设。
volatile std::string password = getPassword(); // Get the password from the user