从 lwIP-1.4.0 开始,tcp 回调函数中调用
tcp_abort
函数终于安全了。
在此之前,如果从 tcp 回调函数中调用
tcp_abort
,则会访问未分配的内存。
应用程序关闭连接,正常情况下是调用
tcp_close
函数,经过 4 次握手安全的断开连接。但 lwIP 还支持另外一种关闭连接的 API 函数:
tcp_abort
。这个函数用于中止连接,即发生了异常情况,强制关闭连接。
但是在 lwIP-1.4.0 之前,应用层使用
tcp_abort
可能会有问题。
2009 年 10 月 30 日,
Simon Goldschmidt
报告了这个 BUG。他在
httpd
中发现了这个 BUG,
httpd
是 lwIP 内置的一个网页服务器程序,使用
raw API
编写。在
httpd
的
recv
回调函数中,当检测到状态错误,会调用
tcp_abort
并返回
ERR_ABRT
错误码。该错误码表示 **应用程序调用了
tcp_abort
函数**。
tcp_abort
函数首先会将 pcb 控制块从有效链表中删除,然后释放控制块中未应答段、无序段、未发送段的内存(如果有的话),再发送一个
RST
帧告诉远端连接我要强制断开了,最后释放 pcb 控制块内存。
所以调用完
tcp_abort
后,由于 pcb 控制块内存被释放,不能再使用了,但偏偏 lwIP 设计有漏洞,比如在调用完
recv
回调函数后,还使用了已经释放掉的 pcb 控制块,参见以下代码(代码有删减) :
if(recv_data !=NULL){/* Notify application that data has been received. */TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);// <--- 这里调用 recv 回调函数,有可能使用 tcp_abort}
tcp_input_pcb =NULL;/* Try to send something out. */tcp_output(pcb);// <--- 这里没有判断 ERR_ABRT,pcb 指向的内容可能已释放
随后,在 2010 年 1 月 28 日,
Simon Goldschmidt
修复了这个问题,修复后的代码在调用完 tcp 回调函数后,判断一下错误码是否是
ERR_ABRT
,如果是,则表示用户在回调函数中调用了
tcp_abort
函数,释放了 pcb 控制块内存,lwIP 内核则不会再使用这个 tcp 控制块(代码有删减):
if(recv_data !=NULL){/* Notify application that data has been received. */TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);if(err == ERR_ABRT){goto aborted;// <--- 这里跳走了}}
tcp_input_pcb =NULL;/* Try to send something out. */tcp_output(pcb);
但事情还没结束。
和其它 tcp 回调函数相比,
recv
回调函数具有特殊性。内核调用
recv
回调函数时,会向其传递一个
pbuf
指针,指向网卡接收到的数据。
recv
回调函数无论是正确处理这些数据(返回
ERR_OK
),还是因为某些原因中止连接(返回
ERR_ABRT
),**都需要在这个回调函数中释放
pbuf
内存**,否则就会存在内存泄漏问题。
但是,在 lwIP-2.1.0 之前,注册
recv
回调函数的说明文档存在错误。它错误的表述为返回
ERR_OK
时一定要释放
pbuf
,其它情况一定不要释放
pbuf
— 没有提及返回
ERR_ABRT
的情况。文档如下所示:
Sets the callback function that will be called when new data arrives. The callback function will be passed a NULL pbuf to indicate that the remote host has closed the connection.
If there are no errors and the callback function is to return ERR_OK, then it must free the pbuf. Otherwise, it must not free the pbuf so that lwIP core code can store it.
注意黑色加粗字体,翻译过来是:
如果回调函数没有错误并且返回ERR_OK,那么它必须释放 pbuf。否则,它不能释放 pbuf,以便 lwIP 内核存储这包数据(等到机会合适会再次提交给应用层处理)。
API 文档说明是开发人员最重要的参考依据,它的描述必须准确无误。所以这是一个 BUG,是可能引起用户内存泄漏的严重 BUG。
2017 年 9 月 25 日
Ambroz Bizjak
提交了这个 BUG,2017 年 10 月 16,开发人员
Dirk Ziegelmeier
修正了这个错误,修正后的文档描述为:
Sets the callback function that will be called when new data arrives. The callback function will be passed a NULL pbuf to indicate that the remote host has closed the connection.
If the callback function returns ERR_OK or ERR_ABRT it must have freed the pbuf, otherwise it must not have freed it.
黑色加粗字体翻译过来是:
如果回调函数返回
ERR_OK
或者
ERR_ABRT
,那么它必须释放 pbuf,否则,它必须不能释放 pbuf。
这也是
recv
回调函数编程的一个注意事项。
然而,更好的方法是应用层永远不要使用
tcp_abort
函数,使用
tcp_close
就足够了。
tcp_abort
函数存在的意义是提供给 lwIP 内核使用的,但是最初的设计者不知道出于什么考虑,将它开放成一个外部 API。在漫长的使用过程中,肯定有人在应用层使用了这个函数,所以为了保持兼容性,只能以补丁的方式堵上这个漏洞。
这也是为什么接口函数必须花时间仔细考虑的原因,一旦有人使用,就再也不容易更改了。模块代码、协议、解决方案,甚至是硬件,都符合这个道理。因此,在最开始的时候花大量时间是值得的,可以看看我的这篇博文。
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
版权归原作者 研究是为了理解 所有, 如有侵权,请联系我们删除。