作者: 啟明星辰ADLab
公眾號: https://mp.weixin.qq.com/s/cVZvgd5xvj4ljchlwDSDYQ
一、漏洞背景
2019年6月18日,Redhat發布安全公告,Linux內核TCP/IP協議棧存在3個安全漏洞(CVE-2019-11477/CVE-2019-11478/CVE-2019-11479),這些漏洞與最大分段大小(MSS)和TCP選擇性確認(SACK)功能相關,允許遠程攻擊者進行拒絕服務攻擊。
二、關鍵概念
(一)數據包重傳確認機制
TCP數據包傳輸過程中,來自滑動窗口的數據包丟失可能對TCP吞吐量產生影響。TCP使用累積確認(ACK)方案解決該問題,其中不接收不在滑動窗口左邊緣的接收段,這會強制發送方等待往返時間以找出每個丟失的數據包,或者不必要地重新傳輸已正確接收的段,從而降低整體吞吐量。
選擇性確認(SACK)是一種在多個丟棄的段的情況下解決此行為的策略。通過選擇性確認,數據接收方可以向發送方通知已成功達到的所有段,因此發送方只需重新傳輸實際丟失的段。具體選擇性確認過程,如下圖所示。

(二)最大分段大小(Max Segment Size)
MSS(Maximum Segment Size,最大報文段大小)的概念是指TCP層所能夠接收的最大分段大小,該值只包括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 releases4.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 |
|
|
補丁: |
|
|
CVE-2019-11478 |
影響版本: Linux 2.6.29 ~ 4.19.13(stable kernel releases4.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 |
|
|
補丁: |
|
|
CVE-2019-11479 |
影響版本: Linux 2.6.29 ~ 4.19.13(stable kernel releases4.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 |
|
|
補丁: |
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/960/
暫無評論