允许C ++编译器optimize away writes into memory:
{
//all this code can be eliminated
char buffer[size];
std::fill_n( buffer, size, 0);
}
处理敏感数据时,典型的方法是使用volatile*
指针来确保编译器发出内存写入。以下是Visual C ++运行时库中SecureZeroMemory()
函数的实现方式(WinNT.h):
FORCEINLINE PVOID RtlSecureZeroMemory(
__in_bcount(cnt) PVOID ptr, __in SIZE_T cnt )
{
volatile char *vptr = (volatile char *)ptr;
#if defined(_M_AMD64)
__stosb((PBYTE )((DWORD64)vptr), 0, cnt);
#else
while (cnt) {
*vptr = 0;
vptr++;
cnt--;
}
#endif
return ptr;
}
该函数将传递的指针强制转换为volatile*
指针,然后通过后者写入。但是,如果我在局部变量上使用它:
char buffer[size];
SecureZeroMemory( buffer, size );
变量本身不是volatile
。因此根据C ++标准定义的可观察行为写入buffer
并不算作可观察行为,看起来它可以被优化掉。
现在有很多关于页面文件,缓存等的评论都是有效的,但是我们在这个问题中忽略它们。这个问题唯一的问题是存储器写入的代码是否被优化掉了。
是否有可能确保在C ++中没有优化写入内存的代码? SecureZeroMemory()
中的解决方案是否符合C ++标准?
答案 0 :(得分:8)
没有便携式解决方案。如果需要,编译器可以在您在内存中的多个位置使用数据时制作数据的副本,并且任何零功能都可以将当时使用的数据归零。任何解决方案都是不可移植的。
答案 1 :(得分:4)
对于像SecureZeroMemory
这样的库函数,库编写者通常会努力确保编译器不会内联这些函数。
这意味着在代码段
char buffer[size];
SecureZeroMemory( buffer, size );
编译器不知道SecureZeroMemory
对buffer
的作用,因此优化器无法证明取出片段不会影响程序的可观察行为。
换句话说,库编写者已经完成了所有可能的工作,以确保这些代码不会被优化掉。
答案 2 :(得分:2)
volatile
关键字可以应用于指针(或C ++中的引用)而无需强制转换,这意味着不会优化通过此指针的访问。变量的声明无关紧要。
行为类似于const
:
char buffer[16];
char const *p = buffer;
buffer[0] = 'a'; // okay
p[0] = 'b'; // error
指向缓冲区的const
指针不会以任何方式改变变量的行为,只改变修改指针的行为。如果变量声明为const
,则禁止生成非const
指针:
char const buffer[16];
char *p = buffer; // error
类似地,
char buffer[16];
char volatile *p = buffer;
buffer[0] = 'a'; // may be optimized out
p[0] = 'b'; // will be emitted
和
char volatile buffer[16];
char *p = buffer; // error
编译器可以通过非volatile
lvalues以及函数调用来自由删除访问,它可以证明不会发生对volatile
左值的访问。
RtlSecureZeroMemory
函数可以安全使用,因为编译器可以看到定义(包括循环内的volatile
访问,或者根据平台,看到汇编语句,它对于编译器因此被认为是不可优化的,或者它必须假设该函数将执行volatile
访问。
如果您希望避免依赖< winnt.h>头文件,然后类似的函数将适用于任何符合标准的编译器。
答案 3 :(得分:1)
内存中的敏感信息和擦除时间之间始终存在竞争条件。在那个时间窗口中,您的应用程序可能崩溃并转储核心或恶意用户可能会使用纯文本的敏感信息获取进程地址空间的内存转储。
可能是您不应该以明文形式将敏感信息存储在内存中。这样,您可以获得更好的安全性并完全绕过此问题。
答案 4 :(得分:1)
C或C ++标准都没有对实现如何在物理内存中存储内容施加任何要求。实现是可以自由指定的,但是,适合需要某些物理内存行为的应用程序的高质量实现将指定它们将以适当的方式一致地表现。
许多实现处理至少两个不同的方言。在处理“禁用优化”的方言时,他们通常会详细记录与物理内存交互的动作数。不幸的是,启用优化通常会在语义上较弱的方言中切换,这几乎不保证任何动作将如何与物理内存交互。虽然在可能很重要的某些易于识别的情况下,有可能在处理许多简单明了的优化的同时仍以与“禁用优化”的方言一致的方式处理事物,但是编译器编写者对此并不感兴趣。提供专注于安全的低挂水果的模式。
确保以某种方式处理物理内存的唯一可靠方法是使用保证以该方式处理物理内存的方言。如果这样做的话,通常很容易获得所需的治疗。如果不这样做,没有任何东西可以保证“创造性”的实现不会做意外的事情。