2009-06-19

BIO_copy_next_retry

这个礼拜还是比较充实的,至少很久没有这么充实了。终于弄出一个补丁,使得我们的服务器可以支持IPv6。基本的PoC已经在上周完成。因为没法等OpenSSL 1.0.0的正式release,于是只能自己改代码。手工创建 socket然后把accept()之后的connfd包装在一个BIO里面,所谓“曲线救国”了。本周只是把那些已经验证过的逻辑加到库中。本以为没啥问题的小事,却折腾了整整五天。

第一个问题出在Buffered IO上。代码里面原来用的是BIO_gets()来读数据,而这个函数只在Buffered IO中才有效。一开始我自己写了个BIO_readline(),想想不太好,于是在自己的BIO上又chain了一个BIO_f_buffer(),这样不改变原来的逻辑。
sbio = BIO_new_ssl(server->ctx, 0);
bbio = BIO_new(BIO_f_buffer());
sbio = BIO_push(bbio, sbio);


有了这一层bbio后,HTTP连接开始正常工作,而HTTPS的时候却一直SIGSEGV,无论32/64位机器,无论OpenSSL 0.9.7/0.9.8,屡试不爽。GDB、valgrind一起祭上,确信了不是自己代码中内存管理的问题,而gdb的backtrace显示,出问题的函数乃是:BIO_copy_next_retry()。它的代码很简单:
void BIO_copy_next_retry(BIO *b)
{
BIO_set_flags(b,BIO_get_retry_flags(b->next_bio));
b->retry_reason=b->next_bio->retry_reason;
}


问题是,它没有判断b->next_bio是否为NULL!稍微搜索一下,发现03年底就有人问过类似问题,只是无人解答。折腾了很久,这个SIGSEGV如同幽灵一般如影随形。晚上躺在床上梳理建立连接的流程,百思不得其解为何它的next_bio是NULL,而PoC中写的代码几乎如出一辙,却没有这个问题,无论数据量的大小如何。几于绝望的时候却是灵光乍现的时候。当再次瞄了一眼BIO_copy_next_retry()的时候,我突然想到:虽然俺不能改这段代码,我可以在我的BIO chain中再append一个BIO嘛!这样它的bio_next就一定不是NULL啦!于是这个礼拜最出彩的一行诞生了:
sbio = BIO_push(sbio, BIO_new(BIO_f_null()));


在BIO_push(bbio, sbio)之前,我在sbio后添加了一个啥事都不做的BIO filter,于是这个BIO chain看起来是这样:
bbio -> sbio -> null


由于null这个BIO啥事都不做,对结果没什么影响。而正如我一直希望的那样,这次SIGSEGV终于消失了。

标签:

0 Comments:

发表评论

<< Home