Linux内核TCP协议多个SACK功效拒绝服务误差剖析

宣布时间 2019-06-21
误差配景


2019年6月18日 ,Redhat宣布清静通告 ,Linux内核TCP/IP协议栈保存3个清静误差(CVE-2019-11477/CVE-2019-11478/CVE-2019-11479) ,这些误差与最大分段大�。∕SS)和TCP选择性确认(SACK)功效相关 ,允许远程攻击者举行拒绝服务攻击。


要害看法



数据包重传确认机制


TCP数据包传输历程中 ,来自滑动窗口的数据包丧失可能对TCP吞吐量爆发影响。TCP使用累积确认(ACK)计划解决该问题 ,其中不吸收不在滑动窗口左边沿的吸收段 ,这会强制发送方期待往返时间以找出每个丧失的数据包 ,或者不须要地重新传输已准确吸收的段 ,从而降低整体吞吐量。


选择性确认(SACK)是一种在多个扬弃的段的情形下解决此行为的战略。通过选择性确认 ,数据吸收方可以向发送方通知已乐成抵达的所有段 ,因此发送方只需重新传输现实丧失的段。详细选择性确认历程 ,如下图所示。


尊龙凯时 - 人生就是搏!


最大分段大�。∕aximum Segment Size)


MSS(Maximum Segment Size ,最大报文段大�。┑目捶ㄊ侵窽CP层所能够吸收的最大分段巨细 ,该值只包括TCP段的数据部分 ,不包括Option部分。另外 ,在TCP首部有一个MSS选项 ,在三次握手历程中 ,TCP发送端使用该选项告诉对方自己所能接受的最大分段巨细。


TSO(TCP Segmentation Offload)


TSO是一种使用网卡来对大数据包举行自动分段 ,降低CPU负载的手艺。其主要是延迟分段。


GSO(Generic Segmentation Offload)


GSO是协议栈是否推迟分段 ,在发送到网卡之前判断网卡是否支持TSO ,若是网卡支持TSO则让网卡分段 ,不然协议栈分完段再交给驱动。若是TSO开启 ,GSO会自动开启。


误差原理


CVE-2019-11477


凭证补丁可知 ,该误差是由一个16bit无符号数溢出导致的 ,该无符号数保存如下结构体中。


尊龙凯时 - 人生就是搏!


该tcp_skb_cb结构体存放着TCP每个数据包的控制信息 ,凭证注释可知 ,tcp_gso_segs/size只用于写行列历程中。


Linux内核TCP/IP协议栈实现中 ,每个数据缓冲区是由一个sk_buff结构体统一管理的。在一个完整的数据缓冲区中skb_end后面紧随着一个skb_shared_info结构体数据 ,skb_shared_info结构体如下所示:


尊龙凯时 - 人生就是搏!


结构体最后一个成员是frags[MAX_SKB_FRAGS]数据。MAX_SKB_FRAGS声明如下所示:

尊龙凯时 - 人生就是搏!


PAGE_SIZE为4KB情形下(即一个内存页面为4KB大�。� ,MAX_SKB_FRAGS取值为65536/4096 + 1即17 ,因此一个skb中最多容纳17个数据分片。关于x86系统 ,每个数据分片最多可以纪录32KB数据的巨细。
数据分片skb_frag_struct结构体如下所示:


尊龙凯时 - 人生就是搏!


在整个协议栈操作历程中 ,数据包既要举行IP被分片的 ,又要举行TCP分段。传输数据时 ,协议栈会凭证GSO值 ,MSS值以及滑动窗口三者之间的巨细关系判断是否举行分片。并通过tcp_set_skb_tso_segs()函数设置GSO ,详细实现如下图所示:


尊龙凯时 - 人生就是搏!


若是skb->len大于mss_now ,行1207 ,将tcp_gso_segs设置为skb->len/mss_now。行1208 ,将tcp_gso_size设置为mss_now。


若是启用了SACK ,在爆发丢包后 ,吸收端会返回SACK块 ,SACK块中纪录着丧失包的序列编号。发送端会剖析SACK块中纪录的丧失包序列编号 ,并重新传输 ,并且在一个滑动窗口中可能包括多个SACK块 ,SACK块中也可能包括多个skb行列。在TCP重传数据包历程中 ,可以将多个skb行列合并到一个skb行列中举行重传。


tcp_shift_skb_data()函数实现这个功效。实验将跨越多个skb的SACK块折叠为一个skb。要害代码如下:


尊龙凯时 - 人生就是搏!


skb_shift()和tcp_shifted_skb()两个函数主要实现该功效。重传历程中多个skb行列合并到一个skb行列中 ,若是填充17个分片到最大容量 , 17*32*1024/8=69632 ,已经大于65535 ,导致无符号整数溢出。


在skb_shift ()函数中 ,tcp_gso_segs溢出后 ,进入tcp_shifted_skb()函数后 ,如下所示:


尊龙凯时 - 人生就是搏!


行1299 ,判断tcp_gso_segs和pcount的巨细 ,若是tcp_gas_segs小于pcount ,BUG_ON断言触发导致内核瓦解。


凭证补丁可知 ,skb_shift()被tcp_skb_shift()取代 ,只是加了两个判断 ,如下所示:

尊龙凯时 - 人生就是搏!


补丁中划分判断了skb->len+shift_len不可大于65535*8字节和tcp_skb_pcount(to) + pcount不可大于65535。第一个判断 ,skb->len是体现sk_buff结构体中体现payload长度 ,shift_len体现要合并到skb中的payload。


CVE-2019-11478


该误差也是整数溢出 ,在数据包重新传输历程中 ,将传输行列分段为多个细小的skbs ,膨胀skb中写行列内存爆发溢出。在处置惩罚SACK块中包括的skb并将其合并后 ,凭证GSO判断举行是否分片 ,若是需要 ,挪用tcp_fragement()函数举行分片。凭证补丁可知:


尊龙凯时 - 人生就是搏!


补丁在tcp_fragment()函数中加入了最小空间判断。Sk是sock结构体类型 ,每一个tcp链接对应一个。以是所有要发送的skb数据巨细都要累加到sk->sk_wmem_queued中 ,sk->sk_wmem_queued体现为该套接字TCP写行列缓冲区巨细。通常在使用时间需要判断该值是否够用。如下所示:


尊龙凯时 - 人生就是搏!


凭证注释可知 ,判断最新排队skb包所需的最小可写空间。补丁中 ,判断剩余发送缓存为大于即是目今发送行列占用空间的一半 ,即尚有1/3以上的空余空间时 ,并且小于sk->sk_sndbuf发送上限才可以正常发送 ,不然就判断TCP写行列太大。


CVE-2019-11479


该误差由于太过消耗资源导致拒绝服务。若是恶意数据包将MSS选项设置成较小值 ,这将迫使协议栈破费很是高的网络或CPU资源发送数据包开销。Linux内核中将MSS_NOW硬编码为48。凭证补丁可知:

尊龙凯时 - 人生就是搏!


举行了max最大值判断 ,而不再是牢靠硬编码。这里的sysctl_tcp_min_snd_mss被设置为65535 ,如下所示:


尊龙凯时 - 人生就是搏!


阻止了攻击者使用极小MSS值。


影响版本及补丁修复


实时更新最新补丁或禁用SACK和过滤极小MSS的数据包。


CVE-2019-11477

影响版本:

  • Linux 2.6.29 ~ 4.19.13(stable kernel releases 4.4.182,  4.9.182, 4.14.127, 4.19.52, 5.1.11除外)

  • RHEL 8 (kernel, kernel-rt) ,RHEL 7 (kernel, kernel-rt) ,RHEL 6

禁用sack:

  • sudo sysctl -w net.ipv4.tcp_sack=0

补�。�

  • https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=3b4929f65b0d8249f19a50245cd88ed1a2f78cff

CVE-2019-11478

影响版本:

  • Linux 2.6.29 ~ 4.19.13(stable kernel releases 4.4.182,  4.9.182, 4.14.127, 4.19.52, 5.1.11除外)

  • RHEL 8 (kernel, kernel-rt) ,RHEL 7 (kernel, kernel-rt) ,RHEL 6 ,RHEL 5

禁用sack:

  • sudo sysctl -w net.ipv4.tcp_sack=0

补�。�

  • https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=f070ef2ac66716357066b683fb0baf55f8191a2e

CVE-2019-11479

影响版本:

  • Linux 2.6.29 ~ 4.19.13(stable kernel releases 4.4.182,  4.9.182, 4.14.127, 4.19.52, 5.1.11除外)

  • RHEL 8 (kernel, kernel-rt) ,RHEL 7 (kernel, kernel-rt) ,RHEL 6 ,RHEL 5

过滤下令:

  • sudo iptables -A INPUT -p tcp -m tcpmss --mss 1:500 -j DROP

关闭tcp_mtu_probing:

  • sysctl net.ipv4.tcp_mtu_probing

补�。�

  • https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=5f3e2bf008c2221478101ee72f5cb4654b9fc363

  • https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=967c05aee439e6e5d7d805e195b3a20ef5c433d6