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

ere*_*eOn 14 c c++ memory openssl memory-management

我一直在调试,现在几天崩溃,出现在了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.
}
Run Code Online (Sandbox Code Playgroud)

基本上,此代码生成椭圆曲线键并尝试将其输出stdout.类似的代码可以openssl.exe ecparam在维基在线上找到.它在Linux上运行正常(valgrind报告根本没有错误).它只在Windows上崩溃(Visual Studio 2013 - x64).我确保正确的运行时链接到(/MD在我的情况下,对于所有依赖项).

我不害怕任何邪恶,我在x64-debug中重新编译了OpenSSL(这次将所有内容都链接起来/MDd),并逐步完成代码以查找有问题的指令集.我的搜索引导我使用此代码(在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...
}
Run Code Online (Sandbox Code Playgroud)

对于那些不太熟悉OpenSSL及其ASN.1例程的人来说,这个for-loop 基本上做的是它通过一个序列的所有元素(从最后一个元素开始)并"删除"它们(稍后更多).

在崩溃发生之前,删除了3个元素的序列(at *pval,即0x00000053379575E0).看一下内存,可以看到以下情况发生:

内存转储#1

该序列是12个字节长,每个元素是4个字节长(在这种情况下,2,5,和10).在每个循环迭代中,元素"已删除"的OpenSSL通过(在这种情况下,既没有deletefree分别称为:他们只是设置为特定值).以下是一次迭代后内存的显示方式:

内存转储#2

这里设置的最后一个元素是ff ff ff 7f我假设OpenSSL确保在以后未分配内存时没有密钥信息泄漏的方法.

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

内存转储#3

所有元素被设定为ff ff ff 7f,asn1_cbNULL这样没有调用.接下来的事情是打电话给OPENSSL_free(*pval).

这种free()对似乎是有效和分配的内存的调用失败并导致执行中止并显示消息:"HEAP CORRUPTION DETECTED".

对此感到好奇,我迷上了malloc,realloc并且free(正如OpenSSL允许的那样)确保这不是一个双重免费或免费的永不分配的内存.事实证明,内存0x00000053379575E0实际上是一个12字节的块,它确实被分配(并且以前从未被释放).

我无法弄清楚这里发生了什么:从我的研究来看,它似乎free()失败了通常返回的指针malloc().除此之外,此存储器位置之前被写入几条指令而没有任何问题,这证实了存储器被正确分配的假设.

我知道在没有所有信息的情况下远程调试很难,如果不是不可能的,但我不知道我的下一步应该是什么.

所以我的问题是:Visual Studio的调试器是如何检测到这种"HEAP CORRUPTION"的?来自呼叫的所有可能原因是什么free()

use*_*421 7

一般来说,可能性包括:

  1. 免费重复.
  2. 预先重复免费.
  3. (最有可能)您的代码在开始之前或结束之后写入超出分配的内存块的限制.malloc()和朋友在这里放置额外的簿记信息,例如大小,可能是一个完整性检查,你将通过覆盖失败.
  4. 释放未经过的东西malloc().
  5. 继续写入已经free()-d 的块.