什么可以解释对free()的调用堆腐败?

我已经调试了几天的崩溃,这发生在OpenSSL的深处(与维护人员讨论)。 我花了一些时间进行调查,所以我会尽量让这个问题有趣和丰富。

首先,为了给出一些上下文,我的最小样本再现崩溃如下:

#include <openssl/crypto.h>
#include <openssl/ec.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/engine.h>

int main()
{
    ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); 
    ENGINE_load_builtin_engines();

    EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect571k1);
    EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED);
    EC_KEY* eckey = EC_KEY_new();
    EC_KEY_set_group(eckey, group);
    EC_KEY_generate_key(eckey);
    BIO* out = BIO_new(BIO_s_file());
    BIO_set_fp(out, stdout, BIO_NOCLOSE);
    PEM_write_bio_ECPrivateKey(out, eckey, NULL, NULL, 0, NULL, NULL); // <= CRASH.
}

基本上,这段代码生成一个椭圆曲线密钥,并尝试将其输出到stdout 。 可以在openssl.exe ecparam和Wikis在线上找到类似的代码。 它在Linux上运行良好(valgrind报告没有错误)。 它只在Windows上崩溃(Visual Studio 2013 - x64)。 我确定正确的运行时间被链接到( /MD在我的情况下,所有依赖)。

由于担心没有邪恶,我在x64-debug(这次连接/MDd所有内容)中重新编译了OpenSSL,并且遍历代码以找到有问题的指令集。 我的搜索让我tasn_fre.c了这个代码(在OpenSSL的tasn_fre.c文件中):

static void asn1_item_combine_free(ASN1_VALUE **pval, const ASN1_ITEM *it, int combine)
{
    // ... some code, not really relevant.
    tt = it->templates + it->tcount - 1;

    for (i = 0; i < it->tcount; tt--, i++) {
        ASN1_VALUE **pseqval;
        seqtt = asn1_do_adb(pval, tt, 0);
        if (!seqtt) continue;

        pseqval = asn1_get_field_ptr(pval, seqtt);
        ASN1_template_free(pseqval, seqtt);
    }
    if (asn1_cb)
        asn1_cb(ASN1_OP_FREE_POST, pval, it, NULL);
    if (!combine) {
        OPENSSL_free(*pval); // <= CRASH OCCURS ON free()
        *pval = NULL;
    }
    // Some more code...
}

对于那些不太熟悉OpenSSL和ASN.1例程的人来说,基本上这个for -loop所做的就是它通过一个序列的所有元素(从最后一个元素开始)并“删除”它们(稍后会介绍) 。

在发生崩溃之前,将删除3个元素的序列(在*pval ,即0x00000053379575E0 )。 看内存,人们可以看到以下事情发生:

该序列是12个字节长,每个元素是4个字节长(在这种情况下, 25 ,和10 )。 在每次循环迭代中,元素都被OpenSSL“删除”(在这种情况下,既不会delete也不会被free :它们只是被设置为一个特定的值。 以下是一次迭代后内存的外观:

这里的最后一个元素被设置为ff ff ff 7f ,我假设它是OpenSSL确保内存在稍后未分配时不会泄漏密钥信息的方式。

在循环之后(以及在OPENSSL_free()的调用之前),内存如下所示:

所有元素都被设置为ff ff ff 7fasn1_cbNULL所以没有调用。 接下来的事情是OPENSSL_free(*pval)的调用。

这个对free()看起来是一个有效&分配内存的调用失败,并导致执行中止并显示一条消息: “HEAP CORRUPTION DETECTED”

对此我感到好奇,于是我迷上了mallocreallocfree (就像OpenSSL允许的一样),以确保这不是一个双免费或者永不分配的内存。 原来0x00000053379575E0的内存实际上是一个12字节的块,它的确被分配(并且从未释放过)。

我很想知道这里发生了什么:从我的研究中,似乎free()在通常由malloc()返回的指针上失败。 除此之外,这个内存位置正在被写入几条指令之前,没有任何问题,这证实了内存被正确分配的假设。

我知道即使不是不可能,也很难在没有所有信息的情况下进行远程调试,但我不知道我的下一步应该做什么。

所以我的问题是:Visual Studio的调试器检测到这个“HEAP CORRUPTION”到底有多严重? 从free()的调用发起时,所有可能的原因是什么?


一般来说,可能性包括:

  • 免费重复。
  • 事先重复免费。
  • (最有可能)您的代码在开始之前或结束之后写入超出分配内存块的限制。 malloc()和朋友在这里放置了额外的簿记信息,比如大小,并且可能是一个理智检查,你会因覆盖而失败。
  • 释放一些未被malloc()引用的东西。
  • 继续写入已经free()的块free() -d。

  • 我终于可以找到问题并解决它。

    原来一些指令是通过分配的堆缓冲区写入字节(因此0x00000000而不是预期的0xfdfdfdfd )。

    在调试模式下,直到内存被free()释放或用realloc()重新分配之后,这些内存保护的覆盖才会被检测到。 这是导致我面临的HEAP CORRUPTION消息的原因。

    我期望在发布模式下,这可能会产生显着的效果,如覆盖应用程序中其他位置使用的有效内存块。


    为了将来参考面临类似问题的人,以下是我的做法:

    OpenSSL提供了一个CRYPTO_set_mem_ex_functions()函数,定义如下:

    int CRYPTO_set_mem_ex_functions(void *(*m) (size_t, const char *, int),
                                    void *(*r) (void *, size_t, const char *,
                                                int), void (*f) (void *))
    

    该函数允许您在OpenSSL中挂接和替换内存分配/释放功能。 好的是添加了const char *, int参数,这些参数基本上由OpenSSL为您填充,并包含分配的文件名行号

    掌握了这些信息后,很容易找出内存块的分配位置。 然后,我可以一边查看内存检查器,一边浏览代码,等待内存块被损坏。

    在我的情况下发生的事情是:

    if (!combine) {
        *pval = OPENSSL_malloc(it->size); // <== The allocation is here.
    
        if (!*pval) goto memerr;
    
        memset(*pval, 0, it->size);
        asn1_do_lock(pval, 0, it);
        asn1_enc_init(pval, it);
    }
    for (i = 0, tt = it->templates; i < it->tcount; tt++, i++) {
        pseqval = asn1_get_field_ptr(pval, tt);
    
        if (!ASN1_template_new(pseqval, tt))
            goto memerr;
    }
    

    在3个序列元素上调用ASN1_template_new()来初始化它们。

    依次变为ASN1_template_new()调用asn1_item_ex_combine_new()这样做:

    if (!combine)
        *pval = NULL;
    

    pvalASN1_VALUE** ,该指令在Windows x64系统上设置8个字节,而不是预期的4个字节,导致列表最后一个元素的内存损坏。

    有关如何解决上游问题的完整讨论,请参阅此主题。

    链接地址: http://www.djcxy.com/p/82327.html

    上一篇: What can explain heap corruption on a call to free()?

    下一篇: Does Visual C++ debug build identify heap corruption errors?