我已经调试了几天的崩溃,这发生在OpenSSL的深处(与维护者here讨论)。我花了一些时间进行调查,所以我试着让这个问题变得有趣且内容丰富。
首先,为了给出一些上下文,我的最小样本再现崩溃如下:
#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
和在线维基上找到。它在Linux上运行正常(valgrind报告根本没有错误)。 它只在Windows上崩溃(Visual Studio 2013 - x64)。我确保正确的运行时链接到(/MD
在我的情况下,对于所有依赖项)。
害怕没有邪恶,我在x64-debug中重新编译了OpenSSL(这次将/MDd
中的所有内容链接起来),并逐步完成代码以查找有问题的指令集。我的搜索引导我使用此代码(在OpenSSL&#39; s 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
- 循环的作用是它通过序列的所有元素(从最后一个元素开始)和&#34;删除&#34;他们(稍后会详述)。
在崩溃发生之前,删除了3个元素的序列(*pval
,即0x00000053379575E0
)。看看记忆,可以看到以下事情发生:
序列长度为12个字节,每个元素长度为4个字节(在本例中为2
,5
和10
)。在每次循环迭代中,元素被删除&#34;通过OpenSSL(在此上下文中,不会调用delete
或free
:它们只是设置为特定值)。以下是一次迭代后内存的显示方式:
此处的最后一个元素设置为ff ff ff 7f
,我假设这是OpenSSL确保在以后未分配内存时没有密钥信息泄露的方法。
在循环之后(以及在调用OPENSSL_free()
之前),内存如下:
所有元素都设置为ff ff ff 7f
,asn1_cb
为NULL
,因此不会进行任何调用。接下来的事情是调用OPENSSL_free(*pval)
。
此free()
号调用似乎是有效的&amp;分配的内存失败并导致执行中止并显示一条消息:&#34; HEAP CORRUPTION DETECTED&#34; 。
对此感到好奇,我迷上了malloc
,realloc
和free
(正如OpenSSL允许的那样),以确保这不是双重免费或永不分配的免费记忆。事实证明,0x00000053379575E0
处的内存实际上是一个12字节的块,它确实被分配了(之前从未释放过)。
我很想知道这里发生了什么:从我的研究来看,似乎free()
在通常由malloc()
返回的指针上失败。除此之外,这个存储器位置在没有任何问题之前被写入几条指令,这证实了存储器被正确分配的假设。
我知道如果不是不可能的话,在没有所有信息的情况下进行远程调试很困难,但我不知道我的下一步应该是什么。
所以我的问题是:这究竟是什么&#34; HEAP CORRUPTION&#34;由Visual Studio的调试器检测?来自free()
的呼叫的所有可能原因是什么?
答案 0 :(得分:6)
一般来说,可能性包括:
malloc()
和朋友在这里添加了额外的簿记信息,例如大小,可能还有一个完整性检查,你将通过覆盖而失败。malloc()
编辑的内容。free()
- d。答案 1 :(得分:2)
我终于可以找到问题并解决它。
原来有些指令是在分配的堆缓冲区之外写入字节(因此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个字节,导致列表的最后一个元素的内存损坏。
有关如何在上游解决此问题的完整讨论,请参阅this thread。