24

LWN: 利用TCP SACK机制让远端Linux崩溃

 4 years ago
source link: https://www.tuicool.com/articles/Mriume7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The TCP SACK panic

By Jake Edge

June 19, 2019

Selective acknowledgment (SACK) 是TCP里面用到的一个机制,帮助减少丢包重传导致的拥塞。网络的这一端点就可以利用这个机制,来告诉对端自己收到了哪部分数据,然后对端就知道只需要传送缺失的这部分数据即可。不过,最近在Linux的SACK实现里面发现了一个bu,可能会被远程攻击者利用,通过发送特制的SACK信息而让接收方的Linux进入panic状态。

TCP发送的数据都是被分割为很多个片段(根据网络对端或流经节点里所支持的maximum segment size, MSS,最大分片长度)。这些数据包成功到达对端之后,对方会恢复acknowledge包来确认说收到这些数据了。

以前,这些acknowledgement包(ACKs)只包含有限信息,如果碰到数据丢失(例如由于拥塞控制而丢弃)的话,哪怕收到后续的数据,也只会ack到第一个丢失的数据包为止。发送方就只能重发从这个包开始的所有数据(事实上跳过丢失的包之后的数据在接收方已经收到了)。这样发送方需要重新传送很多对方其实已经收到的数据包,因为发送方不知道后续这些包对方已经收到了,因此只能盲目重传。

举个简化的例子,发送方A可能发送了序号20~50的数据包,而23和37这两个包在中途就被丢弃了。接收方B只能对20~22这几个包进行ACK,所以A需要重新发送23~50这些包。

很容易想到的一点,如果链路本身拥塞很严重导致丢包,那么又重复发送了这么多数据包肯定会加剧拥塞。

Selective acknowledgment就是创建出来消除这些重复的网络数据的。它诞生于1996年,RFC 2018号。这里的核心想法就是接收端B能够ACK 20~22, 24~36,38~50,这样A只需要重发第23和37这两个包。这里应该很容易理解,就像某个人读了一个30个字的一段话给你听,但是你没有听清第三个字,那你肯定不会让对方从第三个字开始把后面所有的字都再念一遍。

为了实现这个机制,网络子系统的代码里面需要做一些记录工作,本文所提到的bug就是出现在这里。

kernel有个名为struct sk_buff(通常叫做SKB)的数据结构,这就是用来存放各种类型的网络数据的,会用于transmit queue(发送队列),receive queue(接收队列),SACK queue(SACK队列)等等等等。可以读一下网络模块的代码维护者David Miller的一篇很好的综述文章(http://vger.kernel.org/~davem/skb_data.html  ),帮助理解SKB在kernel里是怎么使用的。TCP会把data stream分成32KB(powerPC上是64KB)的fragment,然后记录下来。而kernel在做fragment分割的代码,和SACK处理代码,这里合作时出现了问题。

struct tcp_skb_cb是一个control buffer(用于控制的缓存)结构,记录一个TCP packet的各种信息,包括它被分割成多少个segment/fragment。这个主要是用于general segmentation offload (GSO)功能,用来把packet的segmentation分段动作尽量放在网络协议栈的更低层级来做,甚至可能的话放到网络硬件(例如网卡)里去做。这里的segment的数据是存在tcp_gso_segs成员变量里的,这是一个2个byte大的ussigned int数。如果segment的数量不超过64K的话,肯定是足够了。

不过,如果网络传输双方都打开了SACK机制(网络连接建立时协商的结果)的话,可能就不够了。攻击者可以使用一个很小的MSS值(例如48 byte,这样真正的用户数据就只能用8 byte空间了),然后仔细选择ack哪些segment,从而让tcp_gso_segs值溢出。在kernel里,为何能更加高效的处理多段未被ack的数据segment,可能会选择对多个SKB进行拼接合并,这个动作就可能被恶意攻击者利用来让tcp_gso_segs溢出。

如果发生溢出的话,会触发tcp_shifted_skb()里的BUG_ON()调用,导致kernel panic。这是Jonathan Looney(来自Netflix)发现的SACK相关的bug里面最严重的一个了。同时还报告了其他两个Linux bug,同样都会导致SACK变慢,或者占用大量资源,从而利用来做拒绝服务攻击(DoS)。此外Looney还在FreeBSD 12,在用RACK TCP stack的时候,发现了一个类似的SACK拖慢系统的问题。RACK是一年前Netflix刚刚贡献给FreeBSD的。

这个SACK panic问题被命名为CVE-2019-11477,很明显,这是Linux的一个相当严重的问题。CVE-2019-11478是另外一个DoS攻击漏洞,就是通过特制的SACK序列来导致TCP传输队列数据进行分片fragmentation,从而占用大量资源。CVE-2019-11479里面则指出如果Linux的MSS设置为48,就会导致在发送很少的用户数据的时候就能占用大量的CPU, memory,以及bandwidth。针对性的解决方案,是提供了一个sysctl knob开关给系统管理员,允许设置kernel能接受的最小的MSS值,缺省值仍然配成是48,主要是出于兼容性考虑,不过管理员可以很轻松的改变这个配置了。

这个问题已经完全搞清楚了,Netflix的报告里也提供了修复问题的kernel patch,它们在6月17日被分别合入了Linux 5.1.11, 4.19.52, 4.14.127, 4.9.182, 4.4.182这几个stable update(稳定版kernel发布)里。多数的发行版里都已经更新了kernel,用户也请尽量更新。

目前无法更新的用户,还有一些其他方法来避免收到攻击。可以利用iptables来限制MSS的选值,等等其他方式。不过这些方法都需要关闭MTU probing(通过设置net.ipv4.tcp_mtu_probing为0),否则无法彻底避免CVE-2019-11477和CVE-2019-11478。当然这两个CVE攻击也可以通过关闭SACK来避免(设置/proc/sys/net/ipv4/tcp_sack为0)。为了避免CVE-2019-11479攻击,管理员只需要利用Netflix列出的几种方法来把MSS值太小的给过滤掉。Red Hat的vulnerability report也跟Netflix的报告一样,提供了很多细节信息。这样的远程kernel crash攻击确实是一个让人很不爽的漏洞,可能会有大范围的受害者。不过,只有在网络连接的端点会受影响,这样稍微限制了一下影响范围。至少服务器和桌面PC都能通过更新来解决,而网络链路的各个中间节点,就不是那么好解决了。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

极度欢迎将文章分享到朋友圈 

长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~

rU7JbuJ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK