通过将数据与自身进行相互比较,可以在C中优化存储器的存储器归零吗?

Gra*_*ers 3 c memory cryptography xor

有时,出于安全考虑,我们需要将内存清零以防止无意中访问敏感数据,比如在加密某些数据后安全地删除密钥.大多数人建议这样做的方法是将随机数据写入包含敏感信息的数组,因为编译器无法对其进行优化.众所周知,memset如果它是在数据超出范围之前对数据进行的最后一次操作,那么由于as-if规则,优化编译器可以优化掉这些函数的天真使用.但是,获取和写入随机数据的速度很慢,我可能已经找到了解决方案.在将其部署到生产代码中之前,我想要一个专家意见.

根据运算符的本质,将任何东西与自身相混合,总是得到零值,而且速度非常快.遍历内存块并将其与自身进行对比似乎是解决归零问题的一种非常有效的解决方案,但我担心它可以通过足够好的优化编译器进行优化.它是跨平台和可移植的,除了使用size_t数据类型外,它不需要使用标准库.我已经在下面列出了我的意思的参考实现.在其中我有一个函数调用nuke,它接受一个指针data_to_zero并迭代xor的size字节与自己.

void nuke (void *data_to_zero, size_t size)
{
    size_t i;

    for (i = 0; i < size; i++) {
        ((unsigned char*)data_to_zero)[i] ^= ((unsigned char*)data_to_zero)[i];
    }
}
Run Code Online (Sandbox Code Playgroud)

这种实现相当慢,但明显快于获取足够随机的数据并将其写入data.优化后,它比memset我可以访问的实现更快,这是令人惊讶的.

我还没有学习汇编,但是在O2和O3级别的GCC和Clang优化之后的汇编输出64位x86处理器xorl在代码中的某个地方有指令,有时两次.这向我表明,记忆的实际情况正在发生,但我希望有人知道他们正在谈论什么以确认.

这是一个可行的解决方案?

мал*_*ров 7

这样做的正确方法是调用该memset_s()函数.它使用volatile类型限定符来通知编译器不应优化对memset_s()函数的调用.

遗憾的是,这种解决方案可能效率不高,因为易失性类型的性质,保护免受各种优化,它可能会阻止编译器使用最佳汇编指令,并可能导致代码效率降低.另一个问题memset_s()是它是在C11中引入的.

如果您无法使用memset_s(),则必须考虑以下方法之一:

  1. 另一种解决方案可能是通过访问memset()之后的内存来"触摸"内存,就像这样*(volatile char*)pwd= *(volatile char*)pwd.此解决方案的问题是它可能不适用于所有实现.
  2. 编写自己的版本memset_s()(例1).这样做的问题是它仍然无法保证工作 - C标准规定访问volatile对象是不可更改的可观察行为的一部分 - 但它没有说明通过带有volatile类型的左值表达式的访问
  3. 据我所知,最好的方法是使用易失性函数指针(例2)

作为结论 - 无论你选择什么,强烈建议总是检查生成的可编译代码,实际清除内存,并且没有任何内存调用被优化掉.

例1.

static void secure_memzero(void * p, size_t len)
{ 
    volatile uint8_t * _p = p;

    while (len--) *_p++ = 0;
}
Run Code Online (Sandbox Code Playgroud)

例2.

static void * (* const volatile memset_ptr)(void *, int, size_t) = memset;

static void secure_memzero(void * p, size_t len)
{

    (memset_ptr)(p, 0, len);
}

void
dosomethingsensitive(void)
{
    uint8_t key[32];

    ...

    /* Zero sensitive information. */
    secure_memzero(key, sizeof(key));
}
Run Code Online (Sandbox Code Playgroud)