The TCP SACK panic
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的一篇很好的綜述文章(~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都能通過更新來解決,而網路鏈路的各個中間節點,就不是那麼好解決了。