如何编写可靠的memcmp()独立于内容的实现?

如何编写可靠的memcmp()独立于内容的实现?,c,security,compiler-optimization,memcmp,C,Security,Compiler Optimization,Memcmp,memcmp()的一个简单实现如下所示: 现在,使用链接时代码生成(VisualC++ + LTCG)或链接时间优化(GCC LTO),编译器能够同时看到 CuffiMeMCPMP()/实现和调用站点(即使它们在不同的翻译单元中)。它可以看到调用站点没有使用实际值,只是将其与null进行比较。因此,它可以自由转换CRYPTO_memcmp(),这样一旦发现第一对不匹配的字节,它就会立即返回,并且memcmp()的“安全”版本不再安全 如何实现memcmp(),使符合标准的编译器不会将其转换为有助

memcmp()
的一个简单实现如下所示:

现在,使用链接时代码生成(VisualC++ + LTCG)或链接时间优化(GCC LTO),编译器能够同时看到<代码> CuffiMeMCPMP()/<代码>实现和调用站点(即使它们在不同的翻译单元中)。它可以看到调用站点没有使用实际值,只是将其与null进行比较。因此,它可以自由转换

CRYPTO_memcmp()
,这样一旦发现第一对不匹配的字节,它就会立即返回,并且
memcmp()
的“安全”版本不再安全


如何实现
memcmp()
,使符合标准的编译器不会将其转换为有助于定时攻击的版本?

有两种可能的解决方案

第一个是绝对可移植的,符合标准的-declare
x
volatile,它基本上告诉编译器它必须保留更新
x
的顺序,因此它必须完全读取两个数据数组。这并不能阻止编译器在更大的部分进行读取(比如一次读取几个字节,然后按正确的顺序使用它们),但这没什么大不了的——编译器必须发出与数据数组中字节数成比例的读取数。这种方法的问题是它会使代码变慢——我运行的一些基准测试显示,在特定处理器和特定工具集上,速度会降低50%。YMMV

第二种可能的解决方案是将指针强制转换为
volatile unsigned char*
,并通过它们进行访问

const volatile unsigned char * cs = (const volatile unsigned char*) cs_in;
const volatile unsigned char * ct = (const volatile unsigned char*) ct_in;
// the rest of the code is the same

速度同样快,但不完全符合标准(请参阅)。许多编译器将此类强制转换视为不应更改这些读取的提示,但标准并未对此做出任何保证,因此编译器有可能故意破坏此代码。因此,此解决方案不可移植。

告诉编译器不要进行优化。使用标志或
#pragma
。很明显,它依赖于编译器。标准明确要求进行优化,并鼓励进行优化。基本上,任何不影响可观察结果的优化都是可以的。您必须依赖编译器而不是优化。您可以在标志级别或特定于编译器的宏/属性级别执行此操作。故事结束。@sharptooth 1)您是在寻找一个
int memcmp()
类函数,该函数在小于、等于或大于时返回0,还是一个不同的函数,如
bool memeq()
在匹配/不匹配时返回真/假?2) 数据必须是
const
还是可以在函数退出时进行操作并返回到相同的状态?@chux:让我们假设它应该是
const
,结果应该是
0
,与通常的
memcmp()
一样,变量
unsigned char x
是否可以设置为
volatile unsigned char x
,以防止编译器转换
CRYPTO_memcmp()
?如果函数要检查
x
是否大于值始终为零的导出变量,而不是检查x是否为非零,编译器生成有效的提前退出代码的唯一方法是在启动循环之前测试导出变量的值,然后如果导出变量恰好为零,则运行一个循环(具有提前退出功能),如果导出变量恰好为零,则运行另一个循环。如果代码中的某个地方根据某个
volatile
设置导出变量的值,该值也始终为零,编译器将……没有实际的基础来期望这样的“优化”有帮助,并且在程序执行期间的某个时间,成本将是一个
volatile
加载和普通存储,对于内存比较函数的每个完整调用,加上一个非限定加载和比较。顺便说一句,我会调用类似于
mem_notequal
的函数,以明确返回值不遵循
memcmp
模式。
int CRYPTO_memcmp(const void *in_a, const void *in_b, size_t len)
{
    size_t i;
    const unsigned char *a = in_a;
    const unsigned char *b = in_b;
    unsigned char x = 0;

    for (i = 0; i < len; i++)
         x |= a[i] ^ b[i];

    return x;
}
 static int des_ede3_unwrap(EVP_CIPHER_CTX *ctx,
     unsigned char *out, const unsigned char *in, size_t inl)
 {
      unsigned char icv[8], iv[8], sha1tmp[SHA_DIGEST_LENGTH];
      //whatever, unrelated then...
      if (!CRYPTO_memcmp(sha1tmp, icv, 8))
         rv = inl - 16;
      //whatever, unrelated
 }
const volatile unsigned char * cs = (const volatile unsigned char*) cs_in;
const volatile unsigned char * ct = (const volatile unsigned char*) ct_in;
// the rest of the code is the same