什么可以解释对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个字节长(在这种情况下, 2
, 5
,和10
)。 在每次循环迭代中,元素都被OpenSSL“删除”(在这种情况下,既不会delete
也不会被free
:它们只是被设置为一个特定的值。 以下是一次迭代后内存的外观:
这里的最后一个元素被设置为ff ff ff 7f
,我假设它是OpenSSL确保内存在稍后未分配时不会泄漏密钥信息的方式。
在循环之后(以及在OPENSSL_free()
的调用之前),内存如下所示:
所有元素都被设置为ff ff ff 7f
, asn1_cb
为NULL
所以没有调用。 接下来的事情是OPENSSL_free(*pval)
的调用。
这个对free()
看起来是一个有效&分配内存的调用失败,并导致执行中止并显示一条消息: “HEAP CORRUPTION DETECTED” 。
对此我感到好奇,于是我迷上了malloc
, realloc
和free
(就像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;
pval
是ASN1_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?