29

允许 C++ 编译器优化写入内存

 {
     //all this code can be eliminated
     char buffer[size];
     std::fill_n( buffer, size, 0);
 }

在处理敏感数据时,典型的方法是使用volatile*指针来确保编译器发出内存写入。下面是SecureZeroMemory()Visual C++ 运行时库 (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++ 标准?

4

5 回答 5

8

没有便携式解决方案。如果它愿意,编译器可以在您在内存中的多个位置使用数据时制作数据的副本,并且任何零函数只能将它当时使用的那个函数归零。任何解决方案都将是不可移植的。

于 2012-11-08T09:18:31.390 回答
4

对于像之类SecureZeroMemory的库函数,库编写者通常会努力确保编译器不会内联此类函数。这意味着在片段中

char buffer[size];
SecureZeroMemory( buffer, size );

编译器不知道SecureZeroMemory用做什么buffer,因此优化器无法证明取出片段不会影响程序的可观察行为。换句话说,库编写者已经做了所有可能的事情来确保这样的代码不会被优化掉。

于 2012-11-08T08:41:07.380 回答
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 值的访问以及可以证明没有对左值的访问volatile发生的函数调用。

RtlSecureZeroMemory函数可以安全使用,因为编译器可以看到定义(包括volatile循环内部的访问,或者根据平台,汇编程序语句,它对编译器是不透明的,因此被认为是不可优化的),或者它必须假设该函数将执行volatile访问。

如果您希望避免对 <winnt.h> 头文件的依赖,那么类似的函数将适用于任何符合要求的编译器。

于 2012-11-08T10:00:58.973 回答
1

在内存中存在敏感信息和清除它的时间之间总是存在竞争条件。在那个时间窗口内,您的应用程序可能会崩溃并转储核心,或者恶意用户可能会获得进程地址空间的内存转储,其中包含纯文本的敏感信息。

可能您不应该将敏感信息以纯文本形式存储在内存中。通过这种方式,您可以获得更好的安全性并完全绕过此问题。

于 2012-11-08T09:53:49.717 回答
1

C 和 C++ 标准都没有对实现如何在物理内存中存储东西提出任何要求。然而,实现可以自由地指定这些东西,并且适用于需要某些物理内存行为的应用程序的质量实现将指定它们将始终以合适的方式运行。

许多实现处理至少两种不同的方言。在处理他们的“优化禁用”方言时,他们经常详细记录有多少动作将与物理内存交互。不幸的是,启用优化通常会切换到语义较弱的方言,这几乎无法保证任何操作将如何与物理内存交互。虽然在某些可能很重要的易于识别的情况下,虽然它应该可以处理许多简单直接的优化,同时仍然以与“禁用优化”方言一致的方式处理事物,但编译器编写者并不感兴趣提供专注于安全且容易实现的模式的模式。

确保以某种方式处理物理内存的唯一可靠方法是使用一种承诺以这种方式处理物理内存的方言。如果这样做,通常很容易获得所需的治疗。如果不这样做,没有任何东西可以保证“创造性”实现不会做一些意想不到的事情。

于 2018-07-30T16:12:23.113 回答