
大家好!我叫德米特里·薩姆索諾夫,是 Odnoklassniki 的首席系統管理員。我們擁有超過 7 台實體伺服器、11 個雲端容器和 200 個應用程序,它們以各種配置組成了 700 個不同的叢集。絕大多數伺服器都在運行 CentOS 7.
14年2018月XNUMX日,FragmentSmack漏洞資訊發布
() 和 SegmentSmack ()。這些漏洞具有網路攻擊向量且得分相當高 (7.5),可能會因資源耗盡 (CPU) 而導致拒絕服務 (DoS)。當時並沒有提出對 FragmentSmack 的核心修復;而且,它的發布比漏洞的資訊發布要晚得多。為了消除SegmentSmack,建議更新核心。更新包本身已在同一天發布,剩下的就是安裝它。
不,我們根本不反對更新核心!然而,還有一些細微差別......
我們如何在生產環境中更新內核
一般來說,沒什麼複雜的:
- 下載包;
- 將它們安裝在許多伺服器上(包括託管我們雲端的伺服器);
- 確保沒有任何損壞;
- 確保應用所有標準核心設定且沒有錯誤;
- 等幾天;
- 檢查伺服器效能;
- 將新伺服器的部署切換到新核心;
- 依資料中心更新所有伺服器(一次一個資料中心,以盡量減少出現問題時對使用者的影響);
- 重新啟動所有伺服器。
對我們擁有的核心的所有分支重複此操作。目前是:
- 庫存 CentOS 7 3.10 - 適用於大多數普通伺服器;
- Vanilla 4.19 - 適合我們 ,因為我們需要BFQ、BBR等;
- Elrepo kernel-ml 5.2 - 適用於 ,因為 4.19 過去表現不穩定,但需要相同的功能。
正如您可能已經猜到的那樣,重新啟動數千台伺服器需要花費最長的時間。由於並非所有漏洞都對所有伺服器都至關重要,因此我們僅重新啟動那些可直接從 Internet 存取的伺服器。在雲端中,為了不限制靈活性,我們不會將外部可存取的容器與具有新核心的各個伺服器綁定在一起,而是毫無例外地重新啟動所有主機。幸運的是,那裡的程式比常規伺服器更簡單。例如,無狀態容器可以在重新啟動期間簡單地移動到另一台伺服器。
不過,工作量仍然很大,可能需要幾週的時間,如果新版本出現任何問題,可能需要幾個月的時間。攻擊者非常了解這一點,因此他們需要 B 計畫。
FragmentSmack/SegmentSmack。解決方法
幸運的是,對於某些漏洞,存在這樣的 B 計劃,它被稱為解決方法。大多數情況下,這是內核/應用程式設定的更改,可以最大限度地減少可能的影響或完全消除漏洞的利用。
以 FragmentSmack/SegmentSmack 為例 這個解決方法:
«您可以將net.ipv4.ipfrag_high_thresh和net.ipv3.ipfrag_low_thresh(以及ipv4 net.ipv4.ipfrag_high_thresh和net.ipv6.ipfrag_low_thresh的對應值)中的6MB和6MBMB和net.ipv256.ipfrag_low_thresh的對應值)中的192MB和262144MBMB 。測試顯示,根據硬體、設定和條件,攻擊期間 CPU 使用率會出現小幅到顯著的下降。但是,由於 ipfrag_high_thresh=64 位元組,可能會對效能產生一些影響,因為一次只能容納兩個 XNUMXK 片段到重組佇列。例如,處理大型 UDP 封包的應用程式存在崩潰的風險“。
參數本身 說明如下:
ipfrag_high_thresh - LONG INTEGER
Maximum memory used to reassemble IP fragments.
ipfrag_low_thresh - LONG INTEGER
Maximum memory used to reassemble IP fragments before the kernel
begins to remove incomplete fragment queues to free up resources.
The kernel still accepts new fragments for defragmentation.
我們在生產服務上沒有大型 UDP。 LAN 上沒有碎片流量;WAN 上有碎片流量,但不嚴重。沒有任何跡象 - 您可以推出解決方法!
FragmentSmack/SegmentSmack。第一滴血
我們遇到的第一個問題是雲端容器有時僅部分應用新設定(僅 ipfrag_low_thresh),有時根本不應用它們 - 它們只是在開始時崩潰。無法穩定地重現該問題(所有設定均手動應用,沒有任何困難)。理解容器在開始時崩潰的原因也不是那麼容易:沒有發現任何錯誤。有一點是確定的:回滾設定可以解決容器崩潰的問題。
為什麼在主機上套用 Sysctl 還不夠?容器位於其自己的專用網路命名空間中,因此至少 容器中的內容可能與主機不同。
Sysctl 設定到底是如何應用在容器中的?由於我們的容器沒有特權,您將無法透過進入容器本身來更改任何 Sysctl 設定 - 您根本沒有足夠的權限。為了運行容器,我們當時的雲端使用了 Docker(現在 )。新容器的參數透過 API 傳遞給 Docker,包括必要的 Sysctl 設定。
在搜尋版本時,發現 Docker API 並未傳回所有錯誤(至少在 1.10 版本中)。當我們嘗試透過「docker run」啟動容器時,我們終於看到了至少一些東西:
write /proc/sys/net/ipv4/ipfrag_high_thresh: invalid argument docker: Error response from daemon: Cannot start container <...>: [9] System error: could not synchronise with container process.
參數值無效。但為什麼?為什麼它只是有時無效?原來,Docker並不能保證Sysctl參數的應用順序(最新測試的版本是1.13.1),所以有時ipfrag_high_thresh嘗試在ipfrag_low_thresh還是256M的時候設定為3K,即上限更低低於下限,從而導致錯誤。
當時,我們已經使用了自己的機制來在啟動後重新配置容器(在啟動後凍結容器) 並透過以下方式在容器的命名空間中執行命令 ),這部分我們也加入了寫入Sysctl參數。問題解決了。
FragmentSmack/SegmentSmack。第一滴血2
還來不及了解 Workaround 在雲端的使用,第一批罕見的用戶抱怨就開始到來。當時,距離在第一批伺服器上開始使用解決方案已經過了幾個星期。初步調查顯示,收到的投訴針對的是個別服務,而不是這些服務的所有伺服器。問題再次變得極度不確定。
首先,我們嘗試回滾 Sysctl 設置,但沒有效果。對伺服器和應用程式設定的各種操作也無濟於事。重啟後問題解決。重啟 Linux 雖然這很不自然,但對與…一起工作來說卻是一種正常的狀況。 Windows 以前確實這樣。雖然當時確實有效,但我們把這歸咎於應用新的 Sysctl 設定時出現的「核心故障」。我們當時真是太愚蠢了…
三週後,問題再次出現。這些伺服器的設定非常簡單:Nginx 處於代理/平衡器模式。流量不大。新介紹說明:客戶端 504 錯誤的數量每天都在增加()。該圖顯示了該服務每天的 504 錯誤數量:

所有錯誤都與同一個後端有關 - 與雲端中的後端有關。此後端的包片段的記憶體消耗圖如下所示:

這是作業系統圖中問題最明顯的表現之一。同時,在雲端,另一個與 QoS(流量控制)設定相關的網路問題也得到了修復。在資料包片段的記憶體消耗圖上,它看起來完全相同:

假設很簡單:如果它們在圖表上看起來相同,那麼它們就會有相同的原因。此外,這種類型的記憶體出現任何問題的可能性都非常小。
解決問題的本質是我們使用了 QoS 中預設設定的 fq 封包調度程序。預設情況下,對於一個連接,它允許您將 100 個資料包新增至佇列中,並且某些連接在通道短缺的情況下開始堵塞佇列。在這種情況下,資料包會被丟棄。在 tc 統計資料(tc -s qdisc)可以看到如下:
qdisc fq 2c6c: parent 1:2c6c limit 10000p flow_limit 100p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 refill_delay 40.0ms
Sent 454701676345 bytes 491683359 pkt (dropped 464545, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
1024 flows (1021 inactive, 0 throttled)
0 gc, 0 highprio, 0 throttled, 464545 flows_plimit
「464545 Flows_plimit」是因為超過一個連接的佇列限製而丟棄的資料包,而「dropped 464545」是該調度器所有丟棄的資料包的總和。將佇列長度增加到 1 並重新啟動容器後,問題不再發生。您可以坐下來喝一杯冰沙。
FragmentSmack/SegmentSmack。最後的血
首先,在內核漏洞公佈幾個月後,FragmentSmack 的修復程序終於發布了(請記住,8 月份的公告只發布了 SegmentSmack 的修復程序),這讓我們有機會放棄 Workaround,而 Workaround 之前給我們帶來了不少麻煩。在此期間,我們已經將一些伺服器遷移到了新內核,現在我們不得不從頭開始。為什麼我們在 FragmentSmack 修復程式發布之前就更新了核心呢?事實上,針對這些漏洞的防護工作與 Workaround 本身的更新工作是同時進行的(並且已經合併了)。 CentOS (這甚至比僅僅更新核心還要耗時)。此外,SegmentSmack 漏洞更加危險,而且它的修復程式很快就發布了,所以無論如何,這樣做都是合理的。然而,僅僅更新內核 CentOS 我們無法做到這一點,是因為在期間出現的 FragmentSmack 漏洞。 CentOS 7.5 版本的問題只有在 7.6 版本才得到修復,所以我們必須停止向 7.5 版本的更新,然後重新開始向 7.6 版本更新。這種情況也時有發生。
其次,罕見的用戶投訴問題又回到了我們這裡。現在我們已經確定它們都與從客戶端到我們的某些伺服器的檔案上傳有關。此外,總質量中的極少數上傳是透過這些伺服器的。
正如我們從上面的故事中記得的那樣,回滾 Sysctl 並沒有幫助。重新啟動有幫助,但只是暫時的。
對Sysctl的懷疑並未消除,但這次有必要收集盡可能多的信息。也嚴重缺乏在客戶端重現上傳問題的能力,以便更準確地研究發生的情況。
對所有可用統計數據和日誌的分析並沒有讓我們更進一步了解正在發生的事情。嚴重缺乏重現問題以「感受」特定連結的能力。最後,開發人員使用該應用程式的特殊版本,成功地在透過 Wi-Fi 連接的測試設備上實現了問題的穩定再現。這是調查的突破。客戶端連接到 Nginx,Nginx 代理到後端,即我們的 Java 應用程式。

問題的對話是這樣的(在Nginx代理端修復):
- 客戶端:請求接收有關下載檔案的資訊。
- Java 伺服器:回應。
- 客戶端:使用文件進行 POST。
- Java 伺服器:錯誤。
同時,Java 伺服器向日誌寫入從客戶端接收到 0 位元組的數據,Nginx 代理寫入該請求花費了超過 30 秒(30 秒是客戶端應用程式的逾時時間)。為什麼超時,為什麼是 0 位元組?從 HTTP 的角度來看,一切都以其應有的方式運作,但帶有檔案的 POST 似乎從網路中消失了。而且,它在客戶端和Nginx之間消失了。是時候用 Tcpdump 武裝自己了!但首先您需要了解網路配置。 Nginx 代理程式位於 L3 平衡器後面 。隧道用於將資料包從 L3 平衡器傳送到伺服器,伺服器將其標頭新增至資料包:

在這種情況下,網路以 Vlan 標記流量的形式到達該伺服器,該流量也會將自己的欄位新增至資料包:

而且此流量也可以分段(我們在評估解決方法的風險時討論的傳入分段流量的相同小比例),這也會更改標頭的內容:

再次:資料包封裝有 Vlan 標籤、封裝有隧道、分片。為了更好地理解這是如何發生的,讓我們追蹤從客戶端到 Nginx 代理程式的封包路由。
- 資料包到達 L3 平衡器。為了在資料中心內正確路由,資料包被封裝在隧道中並發送到網路卡。
- 由於封包 + 隧道標頭不適合 MTU,因此封包被切割成片段並傳送到網路。
- L3 平衡器之後的交換器在收到資料包時,會為其新增 Vlan 標記並繼續發送。
- Nginx 代理程式前面的交換器發現(基於連接埠設定)伺服器正在等待 Vlan 封裝的封包,因此它會原樣傳送該封包,而不刪除 Vlan 標記。
- Linux 接收各個獨立包裝的碎片,並將它們黏合成一個大包裝。
- 接下來,封包到達 Vlan 接口,其中第一層被刪除 - Vlan 封裝。
- 然後 Linux 將其發送到隧道接口,在那裡移除另一層——隧道封裝。
困難在於將所有這些作為參數傳遞給 tcpdump。
讓我們從頭開始:是否有來自客戶端的乾淨(沒有不必要的標頭)IP 封包,並且刪除了 VLAN 和隧道封裝?
tcpdump host <ip клиента>
不,伺服器上沒有這樣的套件。所以問題一定是更早出現的。是否有隻去掉Vlan封裝的封包?
tcpdump ip[32:4]=0xx390x2xx
0xx390x2xx 是十六進位格式的客戶端 IP 位址。
32:4 — 隧道封包中寫入 SCR IP 的欄位的位址和長度。
字段位址必須透過暴力來選擇,因為在網路上他們寫的是40、44、50、54,但那裡沒有IP位址。您也可以查看十六進位封包之一(tcpdump 中的 -xx 或 -XX 參數)並計算您知道的 IP 位址。
是否有未移除Vlan和Tunnel封裝的報文分片?
tcpdump ((ip[6:2] > 0) and (not ip[6] = 64))
這個魔法會向我們展示所有的碎片,包括最後一個。也許同樣的東西可以透過IP過濾,但我沒有嘗試,因為這樣的資料包不是很多,而且我需要的資料包很容易在一般流程中找到。他們來了:
14:02:58.471063 In 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), length 1516: (tos 0x0, ttl 63, id 53652, offset 0, flags [+], proto IPIP (4), length 1500)
11.11.11.11 > 22.22.22.22: truncated-ip - 20 bytes missing! (tos 0x0, ttl 50, id 57750, offset 0, flags [DF], proto TCP (6), length 1500)
33.33.33.33.33333 > 44.44.44.44.80: Flags [.], seq 0:1448, ack 1, win 343, options [nop,nop,TS val 11660691 ecr 2998165860], length 1448
0x0000: 0000 0001 0006 00de fb1a 9441 0000 0800 ...........A....
0x0010: 4500 05dc d194 2000 3f09 d5fb 0a66 387d E.......?....f8}
0x0020: 1x67 7899 4500 06xx e198 4000 3206 6xx4 .faEE.....@.2.m.
0x0030: b291 x9xx x345 2541 83b9 0050 9740 0x04 .......A...P.@..
0x0040: 6444 4939 8010 0257 8c3c 0000 0101 080x dDI9...W.......
0x0050: 00b1 ed93 b2b4 6964 xxd8 ffe1 006a 4578 ......ad.....jEx
0x0060: 6966 0000 4x4d 002a 0500 0008 0004 0100 if..MM.*........
14:02:58.471103 在 00:de:ff:1a:94:11 乙太網路類型 IPv4 (0x0800),長度 62: (tos 0x0, ttl 63, ID 53652,偏移量 1480,標誌 [none],proto IPIP (4),長度 40)
11.11.11.11 > 22.22.22.22: ip-proto-4
0x0000:0000 0001 0006 00de fb1a 9441 0000 0800 ..........A....
0x0010: 4500 0028 d194 00b9 3f04 faf6 2x76 385x E..(....?....f8}
0x0020:1x76 6545 xxxx 1x11 2d2c 0c21 8016 8e43 .faE...D-,.!...C
0x0030:x978 e91d x9b0 d608 0000 0000 0000 7c31 .x............|Q
0x0040: 881d c4b6 0000 0000 0000 0000 0000 ........................
這是一個包裹(相同 ID 53652)的兩個片段,帶有照片(Exif 一詞在第一個包裹中可見)。由於此層級存在包,但轉儲中不是合併形式,因此問題顯然出在程序集上。終於有文獻證明了!
資料包解碼器沒有發現任何會阻止建置的問題。在這裡嘗試過: 。起初,當你嘗試在那裡填充一些東西時,解碼器不喜歡這種資料包格式。原來Srcmac和Ethertype之間多了一些額外的兩個八位元組(與片段資訊無關)。移除它們後,解碼器開始工作。然而,它沒有顯示出任何問題。
不管怎麼說,除了那些 Sysctl 之外,什麼也沒找到。剩下的就是找到一種方法來識別問題伺服器,以便了解規模並決定進一步的行動。很快就找到了所需的計數器:
netstat -s | grep "packet reassembles failed”
它也在 snmpd 下的 OID=1.3.6.1.2.1.4.31.1.1.16.1 下().
“IP 重組演算法檢測到的失敗數量(無論出於何種原因:超時、錯誤等)。”
在研究該問題的伺服器群組中,有兩台伺服器的計數器增加得較快,有兩台伺服器的計數器增加速度較慢,還有兩台伺服器的計數器根本沒有增加。將該計數器的動態與 Java 伺服器上 HTTP 錯誤的動態進行比較,揭示了一種相關性。也就是說,可以對儀表進行監控。
擁有可靠的問題指示器非常重要,這樣您就可以準確地確定回滾 Sysctl 是否有幫助,因為從前面的故事我們知道這不能立即從應用程式中理解。該指標使我們能夠在用戶發現之前識別生產中的所有問題區域。
回滾Sysctl後,監控錯誤停止了,從而證明了問題的原因,並且回滾有幫助。
我們回滾了其他伺服器上的碎片設置,新的監控開始發揮作用,並且我們為碎片分配的內存比以前的默認值還要多(這是UDP 統計數據,其部分丟失在一般背景下並不明顯) 。
最重要的問題
為什麼資料包在我們的 L3 平衡器上會出現碎片?從使用者到達平衡器的大多數資料包都是 SYN 和 ACK。這些封裝的尺寸很小。但由於此類資料包的份額非常大,因此在其背景下,我們沒有註意到開始分段的大資料包的存在。
原因是配置腳本損壞 在具有 Vlan 介面的伺服器上(當時生產中很少有帶有標記流量的伺服器)。 Advmss 讓我們可以向客戶端傳達這樣的訊息:我們方向的封包大小應該會更小,以便在將隧道標頭附加到它們之後,它們不必被分段。
為什麼 Sysctl 回滾沒有幫助,但重新啟動卻有幫助?回滾 Sysctl 更改了可用於合併包的記憶體量。同時,顯然片段記憶體溢出導致連接速度減慢,從而導致片段在隊列中延遲很長時間。也就是說,該過程循環進行。
重新啟動會清除內存,一切恢復正常。
沒有解決方法是否可以做到?是的,但一旦發生攻擊,用戶無法獲得服務的風險很高。當然,使用解決方法會導致各種問題,包括使用者的一項服務速度變慢,但我們相信這些行動是合理的。
非常感謝安德烈·季莫費耶夫()尋求協助進行調查,以及 Alexey Krenev()——為了更新工作的艱鉅任務 Centos 以及伺服器核心。在這種情況下,該過程不得不多次重啟,導致耗時數月。
來源: www.habr.com
