1

我有一个程序在 SUSE Linux Enterprise Server 下运行了一段时间。最近,它被转移到了一个 OpenSUSE 13.2 系统上,遇到了一个问题。该程序与第 3 方接口,数据被接收到我们的程序中,其中数据块由一些标头信息和 AES 加密数据组成。使用 OpenSSL libcrypto 库,我们成功地在 SLES 下连接到这个系统。但是,在 OpenSUSE 下,我们始终会看到解密数据末尾包含垃圾的错误。我已经确定了问题发生的位置并解决了问题,但是在查看代码时,我不明白为什么会出现问题。

我创建了一个模拟问题的测试程序。该测试程序在 SUSE Linux Enterprise Server 11 和 Red Hat 7.2 Enterprise Linux 上运行良好,但在使用不同版本级别的 OpenSSL 库的 OpenSUSE 13.2 上失败。在 SLES 和 Red Hat 下,解密后的数据被干净地返回。在 OpenSUSE 下,大部分数据都被干净地解密了,除了一些出现在数据块末尾的垃圾。返回的数据块是正确的,然后包含一些垃圾,然后以正确的方式结束。下面是我的示例程序的代码,但导致问题的行是 memcpy(),我将加密数据移到数据块的前面进行处理。我的示例程序中的行如下:

   // Generates Garbage
   memcpy(encbuf, encbuf+100, enclen);                 

如果我在将加密数据移动到 encbuf 的开头之前将其移动到临时缓冲区,则不会生成垃圾。

   // This does not generate garbage
   memcpy(tmpbuf, encbuf+100, enclen);                 
   memcpy(encbuf, tmpbuf, enclen);                 

我的示例程序采用已定义的明文缓冲区,使用密钥和 IV 对其进行加密,然后将其解密并显示结果。精简代码如下:

#include <stdio.h>      
#include <stdlib.h>     
#include <string.h>     
#include <unistd.h>     
#include <time.h>       
#include <fcntl.h>      
#include <sys/types.h>  

#include <openssl/evp.h>

#define EVP_DECRYPT   0 
#define EVP_ENCRYPT   1

char clrbuf[100000];
char encbuf[100000];
char tmpbuf[100000];
int clrlen;         
int enclen;         

char enckey[1024];      
unsigned char enciv[16];

main()
{
   int rc;

   // Set clear text to 50 lines of text
   sprintf(clrbuf,                                                   
         "0001this is a test this is a test this is a test this is a test\n" \
         "0002this is a test this is a test this is a test this is a test\n" \
         "0003this is a test this is a test this is a test this is a test\n" \
         // etc etc etc……………….
         "0048this is a test this is a test this is a test this is a test\n" \
         "0049this is a test this is a test this is a test this is a test\n" \
         "0050this is a test this is a test this is a test this is a test\n"  

   sprintf(enckey, "this is the key this is the key ");
   sprintf(enciv, "1234567890123456");

   // Encrypt the data and simulate a 100 byte header by returning encrypted data 100 bytes into the data block
   //
   memcpy(encbuf, "Some header stuff that will need to be removed", 46);
   rc = evp_aes256_cbc(clrbuf, strlen(clrbuf), encbuf+100, &enclen, enckey, enciv, EVP_ENCRYPT);                  

   // Now remove the header by shifting the encrypted data to the start of the data block and decrypt
   // This is where doing the memcpy() as coded in OpenSUSE results in garbage at the end of clrbuf
   // but everything is returned correctly in SLES and Red Hat
   //
   // This work fines on all OSes:
   //         memcpy(tmpbuf, encbuf+100, enclen);                 
   //         memcpy(encbuf, tmpbuf, enclen);                 

   memcpy(encbuf, encbuf+100, enclen);                 
   rc = evp_aes256_cbc(encbuf, enclen, clrbuf, &clrlen, enckey, enciv, EVP_DECRYPT);

   printf("Decrypt: rc=%d  EncLen=%d  ClrLen=%d\n", rc, enclen, clrlen);
   printf("Data:\n\n<\n%s\n>\n\n", clrbuf);                             
}

/****************************************************************************/  

evp_aes256_cbc(char *InBuf, int InLen, char *OutBuf, int OutLen, char *Key, char *IV, int EncryptFlag)              
{                                                                               
   EVP_CIPHER_CTX ctx;                                                          
   int padlen;                                                                  

   EVP_CIPHER_CTX_init(&ctx);                                                   

   if (! EVP_CipherInit_ex(&ctx, EVP_aes_256_cbc(), NULL, Key, IV, EncryptFlag))
      return(0);                                                                

   if (! EVP_CipherUpdate(&ctx, OutBuf, OutLen, InBuf, InLen))
      return(0);                                              

   if (! EVP_CipherFinal_ex(&ctx, OutBuf+(*OutLen), &padlen)) 
      return(0);                                              

   *OutLen = *OutLen + padlen;                                

   EVP_CIPHER_CTX_cleanup(&ctx);                              

   return(1);                                                 
}                                                             

在 SLES 和 Red Hat 上,最终输出如下所示:

0046这是一个测试这是一个测试这是一个测试这是一个测试
0047这是一个测试这是一个测试这是一个测试这是一个测试0048这是一个测试这是一个测试这是一个测试这是一个测试这是一个 0049
这是一个测试
一个测试 这是一个测试 这是一个测试 这是一个测试
0050这是一个测试 这是一个测试 这是一个测试 这是一个测试 这是一个测试

在 OpenSUSE 上,最终输出可能如下所示:

0046这是一个测试这是一个测试这是一个测试这是一个测试0047这是一个测试这是一个测试这是一个测试这是一个测试0048
这是一个测试这是一个测试
╧┬S_úReÅ▌<br>|τZk╠ ½çP≥ii≡w╙p▓8ª'MêÅt▒g{Y¥ΩEô¬ ⌡n}⌐*╘¿µ2└╠LS4=Qüü├;~╕Ç<╗^¿ßD0┤T.OQΣq#≈<br> 0050这是一个测试这是一个测试这是一个测试这是一个测试

有什么想法吗?

谢谢

4

2 回答 2

1
// Generates Garbage
memcpy(encbuf, encbuf+100, enclen);

重叠缓冲区memcpy是 C/C++ 中未定义的行为。改为使用memmove。听起来你有一个可以向前或向后 memcpy 的 glibc 版本。在您的情况下,您正在通过HAS_FAST_COPY_BACKWARD.

这是该问题的经典错误报告:mp3 flash 网站上的奇怪声音。但是 Adob​​e 的任何东西都不会让您感到惊讶。他们以地球上最不安全的软件而闻名是有原因的。

此外,如果您在 Valgrind 下运行二进制文件,该工具有时会标记存在问题的代码。我记得看到 Valgrind 为Crypto++ 库标记它:

size_t ArraySink::Put2(const byte *begin, size_t length, int messageEnd, bool blocking)
{
    // Avoid passing NULL pointer to memcpy. Using memmove due to
    //  Valgrind finding on overlapping buffers.
    size_t copied = 0;
    if (m_buf && begin)
    {
        copied = STDMIN(length, SaturatingSubtract(m_size, m_total));
        memmove(m_buf+m_total, begin, copied);
    }
    m_total += copied;
    return length - copied;
}
于 2016-02-09T21:44:55.250 回答
0

通过另一个来源提供的答案:

由于内存重叠,应该使用 memmove() 而不是 memcpy()。

于 2016-02-09T20:50:58.130 回答