从 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。在漫长的使用过程中,肯定有人在应用层使用了这个函数,所以为了保持兼容性,只能以补丁的方式堵上这个漏洞。
这也是为什么接口函数必须花时间仔细考虑的原因,一旦有人使用,就再也不容易更改了。模块代码、协议、解决方案,甚至是硬件,都符合这个道理。因此,在最开始的时候花大量时间是值得的,可以看看我的这篇博文。
文章来源:https://www.toymoban.com/news/detail-775529.html
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
文章来源地址https://www.toymoban.com/news/detail-775529.html
到了这里,关于lwIP更新记08:TCP 回调函数中调用 tcp_abort 终于安全了的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!