我已经调试了几天的崩溃,该崩溃发生在 OpenSSL 的深处(与维护者讨论here https://www.mail-archive.com/openssl-dev@openssl.org/msg38571.html)。我花了一些时间进行调查,所以我会尽力使这个问题变得有趣且内容丰富。
首先,为了提供一些背景信息,我重现崩溃的最小样本如下:
#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 中tasn_fre.c
file):
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
, and 10
)。在每次循环迭代中,元素都会被 OpenSSL“删除”(在这种情况下,既不会删除元素,也不会删除元素)delete
or free
被调用:它们只是被设置为特定值)。以下是一次迭代后内存的样子:
这里的最后一个元素被设置为ff ff ff 7f
我认为这是 OpenSSL 确保稍后未分配内存时不会泄漏关键信息的方法。
就在循环之后(以及调用之前OPENSSL_free()
),内存如下:
所有元素均设置为ff ff ff 7f
, asn1_cb
is NULL
所以没有打电话。接下来的事情是调用OPENSSL_free(*pval)
.
这个电话给free()
似乎有效且分配的内存失败并导致执行中止并显示一条消息:“检测到堆损坏”.
对此感到好奇,我迷上了malloc
, realloc
and free
(如 OpenSSL 允许)以确保这不是双重释放或从未分配的内存上的释放。事实证明内存在0x00000053379575E0
实际上是一个确实已分配的 12 字节块(之前从未释放过)。
我不知道这里发生了什么:根据我的研究,似乎free()
通常返回的指针失败malloc()
。除此之外,该内存位置之前已被写入几条指令,没有任何问题,这证实了内存已正确分配的假设。
我知道在没有所有信息的情况下远程调试即使不是不可能也是很困难的,但我不知道下一步应该做什么。
所以我的问题是:Visual Studio 的调试器到底是如何检测到这个“HEAP CORRUPTION”的?当源自呼叫时,所有可能的原因是什么?free()
?