ProHoster > блог > адміністраванне > Бойцеся ўразлівасцяў, воркэраунды якія прыносяць. Частка 1: FragmentSmack/SegmentSmack
Бойцеся ўразлівасцяў, воркэраунды якія прыносяць. Частка 1: FragmentSmack/SegmentSmack
Ўсім прывітанне! Мяне клічуць Зміцер Самсонаў, я працую кіроўным сістэмным адміністратарам у «Аднакласніках». У нас больш за 7 тыс. фізічных сервераў, 11 тыс. кантэйнераў у нашым воблаку і 200 прыкладанняў, якія ў рознай канфігурацыі фармуюць 700 розных кластараў. Пераважная большасць сервераў працуюць пад кіраваннем CentOS 7.
14 жніўня 2018 г. была апублікавана інфармацыя аб уразлівасці FragmentSmack
(CVE-2018-5391) і SegmentSmack (CVE-2018-5390). Гэта ўразлівасці з сеткавым вектарам нападу і досыць высокай адзнакай (7.5), якая пагражае адмовай у абслугоўванні (DoS) з-за вычарпанні рэсурсаў (CPU). Фікс у ядры для FragmentSmack на той момант прапанаваны не быў, больш за тое, ён выйшаў значна пазней публікацыі інфармацыі аб уразлівасці. Для ўхілення SegmentSmack прапаноўвалася абнавіць ядро. Сам пакет з абнаўленнем быў выпушчаны ў той жа дзень, заставалася толькі ўстанавіць яго.
Не, мы зусім не супраць абнаўленні ядра! Аднак ёсць нюансы…
Як мы абнаўляем ядро на продзе
Увогуле, нічога складанага:
Спампаваць пакеты;
Усталяваць іх на некаторую колькасць сервераў (уключаючы серверы, якія хосцяць наша воблака);
пераканацца, што нічога не зламалася;
Пераканацца, што ўсе стандартныя налады ядра ўжыліся без памылак;
Пачакаць некалькі дзён;
Праверыць паказчыкі сервераў;
Пераключыць дэплой новых сервераў на новае ядро;
Абнавіць усе серверы па дата-цэнтрах (адзін дата-цэнтр за раз, каб мінімізаваць эфект для карыстальнікаў у выпадку праблем);
Перазагрузіць усе серверы.
Паўтарыць для ўсіх галінак наяўных у нас ядраў. На дадзены момант гэта:
Стоковае CentOS 7 3.10 – для большасці звычайных сервераў;
Ванільнае 4.19 - для нашага аблокі one-cloud, таму што нам патрэбен BFQ, BBR і г.д.;
Elrepo kernel-ml 5.2 - для высоканагружаных раздатчыкаў, таму што 4.19 раней паводзіў сябе нестабільна, а фічы патрэбныя тыя ж.
Як вы маглі здагадацца, больш за ўсё часу займае перазагрузка тысяч сервераў. Паколькі не ўсе ўразлівасці крытычныя для ўсіх сервераў, то мы перазагружаем толькі тыя, якія наўпрост даступныя з інтэрнэту. У воблаку, каб не абмяжоўваць гнуткасць, мы не прывязваем даступныя звонку кантэйнеры да асобных сервераў з новым ядром, а перазагружаем усе хасты без выключэння. На шчасце, там працэдура прасцей, чым са звычайнымі серверамі. Напрыклад, stateless-кантэйнеры могуць проста пераехаць на іншы сервер падчас рэбута.
Тым не менш працы ўсё роўна шмат, і яна можа займаць некалькі тыдняў, а пры ўзнікненні якіх-небудзь праблем з новай версіяй - да некалькіх месяцаў. Зламыснікі гэта выдатна разумеюць, таму патрэбны план "Б".
FragmentSmack/SegmentSmack. Workaround
На шчасце, для некаторых уразлівасцяў такі план "Бы" існуе, і называецца ён Workaround. Часцей за ўсё гэта змена налад ядра/прыкладанняў, якія дазваляюць мінімізаваць магчымы эфект ці цалкам выключыць эксплуатацыю ўразлівасцяў.
У выпадку з FragmentSmack/SegmentSmack прапаноўваўся такі Workaround:
«Можна змяніць дэфолтныя значэнні 4MB і 3MB у net.ipv4.ipfrag_high_thresh і net.ipv4.ipfrag_low_thresh (і іх аналагі для ipv6 net.ipv6.ipfrag_high_thresh і net.ipv6.ipfrag_low_thresh) і на . Тэсты паказваюць ад невялікага да значнага падзення выкарыстання CPU падчас нападу ў залежнасці ад абсталявання, налад і ўмоў. Аднак можа быць некаторы ўплыў на прадукцыйнасць з-за ipfrag_high_thresh=256 bytes, бо толькі два 192K-фрагмента могуць адначасова змясціцца ў чарзе на перазборку. Напрыклад, ёсць рызыка, што прыкладанні, якія працуюць з вялікімі 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 ёсць, але не значны. Нішто не прадвесціць - можна накатваць Workaround!
FragmentSmack/SegmentSmack. Першая кроў
Першая праблема, з якой мы сутыкнуліся, складалася ў тым, што хмарныя кантэйнеры часам ужывалі новыя налады толькі часткова (толькі ipfrag_low_thresh), а часам не ўжывалі наогул – проста падалі на старце. Стабільна прайграць праблему не атрымоўвалася (уручную ўсе налады ўжываліся без якіх-небудзь складанасцяў). Зразумець, чаму падае кантэйнер на старце, таксама не вось так проста: ніякіх памылак не выяўлена. Адно было вядома дакладна: адкат настроек вырашае праблему з падзеннем кантэйнераў.
Чаму недастаткова прымяніць Sysctl на хасце? Кантэйнер жыве ў сваім вылучаным сеткавым Namespace, таму прынамсі частка сеткавых Sysctl-параметраў у кантэйнеры можа адрознівацца ад хаста.
Як менавіта прымяняюцца налады Sysctl у кантэйнеры? Бо кантэйнеры ў нас непрывілеяваныя, змяніць любую наладу Sysctl, зойдучы ў сам кантэйнер, не атрымаецца – проста не хопіць правоў. Для запуску кантэйнераў наша воблака на той момант выкарыстоўвала Docker (цяпер ужо Падман). Докер праз API перадаваліся параметры новага кантэйнера, у тым ліку патрэбныя налады Sysctl.
Падчас перабору версій высветлілася, што API Docker не аддавала ўсе памылкі (прынамсі, у версіі 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 спрабаваў выставіцца на 256K, калі ipfrag_low_thresh яшчэ быў 3M, гэта значыць верхняя мяжа была ніжэй, чым ніжняя, што і прыводзіла.
На той момант у нас ужо выкарыстоўваўся свой механізм даканфігурвання кантэйнера пасля старту (замарозка кантэйнера праз cgroup freezer і выкананне каманд у namespace кантэйнера праз ip netns), і мы дадалі ў гэтую частку таксама прапісванне Sysctl-параметраў. Праблема была вырашана.
FragmentSmack/SegmentSmack. Першая кроў 2
Не паспелі мы разабрацца з ужываннем Workaround у воблаку, як сталі паступаць першыя рэдкія скаргі ад карыстачоў. На той момант прайшло некалькі тыдняў з пачатку прымянення Workaround на першых серверах. Першаснае расследаванне паказала, што скаргі паступалі на асобныя сэрвісы і не на ўсе серверы дадзеных сэрвісаў. Праблема зноў набыла вельмі нявызначаны характар.
У першую чаргу мы, вядома, паспрабавалі адкаціць наладкі Sysctl, але гэта не дало ніякага эфекту. Розныя маніпуляцыі з наладамі сервера і прыкладанні таксама не дапамаглі. Дапамог reboot. Reboot для Linux гэтак жа процінатуральны, калі ён быў звычайнай умовай для працы з Windows у былыя дні. Тым не менш ён дапамог, і мы спісалі ўсё на глюк у ядры пры ўжыванні новых налад у Sysctl. Як жа гэта было легкадумна...
Праз тры тыдні праблема паўтарылася. Канфігурацыя гэтых сервераў была даволі простай: Nginx у рэжыме проксі/балансавальніка. Трафіку няшмат. Новая ўступная: на кліентах з кожным днём павялічваецца колькасць 504-х памылак (Тайм-аўт шлюзу). На графіцы паказана колькасць 504-х памылак у дзень па гэтым сэрвісе:
Усе памылкі пра адзін і той жа бэкенд - пра той, які знаходзіцца ў воблаку. Графік спажывання памяці пад фрагменты пакетаў на гэтым бэкендзе выглядаў наступным чынам:
Гэта адна з самых яркіх праяў праблемы на графіках аперацыйнай сістэмы. У воблаку як раз у гэты ж час была пафікшана іншая сеткавая праблема з наладамі QoS (Traffic Control). На графіцы спажывання памяці пад фрагменты пакетаў яна выглядала сапраўды гэтак жа:
Здагадка была простай: калі на графіках яны выглядаюць аднолькава, то і прычына ў іх аднолькавая. Тым больш, што якія-небудзь праблемы з гэтым тыпам памяці здараюцца надзвычай рэдка.
Сутнасць пафіксанай праблемы складалася ў тым, што мы выкарыстоўвалі ў QoS пакетны шэдулер fq з дэфолтнымі наладамі. Па змаўчанні для аднаго злучэння ён дазваляе дадаваць у чаргу 100 пакетаў і некаторыя злучэнні ў сітуацыі недахопу канала сталі забіваць чаргу да адмовы. У гэтым выпадку пакеты драпаюцца. У статыстыцы tc (tc -s qdisc) гэта бачна так:
"464545 flows_plimit" - гэта і ёсць пакеты, драпнутыя з-за перавышэнні ліміту чаргі аднаго злучэння, а "dropped 464545" - гэта сума ўсіх драпнутых пакетаў гэтага шэдулера. Пасля павелічэння даўжыні чаргі да 1 тыс. і рэстарту кантэйнераў праблема перастала праяўляцца. Можна адкінуцца ў крэсле і выпіць смузи.
FragmentSmack/SegmentSmack. Апошняя кроў
Па-першае, праз некалькі месяцаў пасля анонсу ўразлівасцяў у ядры, нарэшце, з'явіўся фікс для FragmentSmack (нагадаю, што разам з анонсам у жніўні выйшаў фікс толькі для SegmentSmack), што дало шанец адмовіцца ад Workaround, які даставіў нам даволі шмат непрыемнасцяў. Частку сервераў за гэты час мы ўжо паспелі перавесці на новае ядро, і зараз трэба было пачынаць з пачатку. Навошта мы абнаўлялі ядро, не чакаючы фікса FragmentSmack? Справа ў тым, што працэс абароны ад гэтых уразлівасцяў супаў (і зліўся) з працэсам абнаўлення самога CentOS (што займае яшчэ больш часу, чым абнаўленне толькі ядры). Да таго ж SegmentSmack – больш небяспечная ўразлівасць, а фікс для яго з'явіўся адразу, так што сэнс быў у любым выпадку. Аднак, проста абнавіць ядро на CentOS мы не маглі, таму што ўразлівасць FragmentSmack, якая з'явілася ў часы CentOS 7.5, была пафікшана толькі ў версіі 7.6, таму нам прыйшлося спыняць абнаўленне да 7.5 і пачынаць усё нанова з абнаўленнем да 7.6. І так таксама бывае.
Па-другое, да нас вярнуліся рэдкія скаргі карыстальнікаў на праблемы. Цяпер мы ўжо сапраўды ведаем, што ўсе яны злучаны з аплоадам файлаў ад кліентаў на некаторыя нашы серверы. Прычым праз гэтыя серверы ішло вельмі невялікая колькасць аплаадаў ад агульнай масы.
Як мы памятаем з аповяду вышэй, адкат Sysctl не дапамагаў. Дапамагаў Reboot, але часова.
Падазрэнні з Sysctl не былі зняты, але на гэты раз патрабавалася сабраць як мага больш інфармацыі. Таксама вельмі не хапала магчымасці прайграць праблему з аплаадам на кліенце, каб больш кропкава вывучыць, што адбываецца.
Аналіз усёй даступнай статыстыкі і логаў не наблізіў нас да разумення адбывалага. Востра не хапала магчымасці прайграць праблему, каб "памацаць" канкрэтнае злучэнне. Нарэшце, распрацоўнікам на спецверсіі прыкладання атрымалася дамагчыся стабільнага прайгравання праблем на тэставай прыладзе пры падлучэнні праз Wi-Fi. Гэта стала прарывам у расследаванні. Кліент падключаўся да Nginx, той праксіраваў на бэкенд, якім з'яўлялася наша дадатак на Java.
Дыялог пры праблемах быў такі (зафіксаваны на баку Nginx-проксі):
Кліент: запыт на атрыманне інфармацыі аб дапампоўванні файла.
Java-сервер: адказ.
Кліент: POST з файлам.
Java-сервер: памылка.
Java-сервер пры гэтым піша ў лог, што ад кліента атрымана 0 байт дадзеных, а Nginx-проксі – што запыт заняў больш за 30 секунд (30 секунд – гэты час таймаўту ў кліенцкага прыкладання). Чаму ж таймаўт і чаму 0 байт? З пункта гледжання HTTP усё працуе так, як павінна працаваць, але POST з файлам як быццам знікае з сеткі. Прычым знікае паміж кліентам і Nginx. Нетутэйша час узброіцца Tcpdump! Але для пачатку трэба зразумець канфігурацыю сеткі. Nginx-проксі стаіць за L3-балансавальнікам NFware. Выкарыстоўваецца тунэляванне для дастаўкі пакетаў ад L3-балансавальніка да сервера, якое дадае свае хідэры ў пакеты:
Пры гэтым сетка на гэты сервер прыходзіць у выглядзе Vlan-тэгаванага трафіку, якое таксама дадае свае палі ў пакеты:
А яшчэ гэты трафік можа фрагментавацца (той самы невялікі адсотак уваходнага фрагментаванага трафіку, пра які мы казалі пры адзнацы рызык ад Workaround), што таксама змяняе ўтрыманне хидеров:
Яшчэ раз: пакеты інкапсуляваць Vlan-тэгам, інкапсуляваць тунэлем, фрагментаваць. Каб дакладней зразумець, як гэта адбываецца, прасочым маршрут пакета ад кліента да Nginx-проксі.
Пакет пападае на L3-балансавальнік. Для карэктнай маршрутызацыі ўсярэдзіне дата-цэнтра пакет інкапсулюецца ў тунэль і адпраўляецца на сеткавую карту.
Бо пакет + хідэры тунэля не залазяць у MTU, пакет рэжацца на фрагменты і адпраўляецца ў сетку.
Світч пасля L3-балансавальніка пры атрыманні пакета дадае да яго Vlan-тэг і адпраўляе далей.
Світч перад Nginx-проксі бачыць (па наладах порта), што сервер чакае Vlan-інкапсуляванага пакета, таму адпраўляе яго, як ёсць, не прыбіраючы Vlan-тэг.
Linux атрымлівае фрагменты асобных пакетаў і склейвае іх у адзін вялікі пакет.
Далей пакет трапляе на Vlan-інтэрфейс, дзе з яго здымаецца першы пласт – Vlan-інкапсуляванне.
Затым Linux адпраўляе яго на Tunnel-інтэрфейс, дзе з яго здымаецца яшчэ адзін пласт - Tunnel-інкапсуляванне.
Складанасць у тым, каб перадаць гэта ўсё ў выглядзе параметраў у tcpdump.
Пачнём з канца: ці ёсць чыстыя (без лішніх загалоўкаў) IP-пакеты ад кліентаў, са знятым vlan-і tunnel-інкапсуляванне?
tcpdump host <ip клиента>
Не, такіх пакетаў на сэрвэры не было. Такім чынам, праблема павінна быць раней. Ці ёсць пакеты са знятым толькі Vlan-інкапсуляванне?
tcpdump ip[32:4]=0xx390x2xx
0xx390x2xx - гэта IP-адрас кліента ў hex-фармаце.
32:4 - адрас і даўжыня поля, у якім запісаны SCR IP у Tunnel-пакеце.
Адрас поля прыйшлося падбіраць пераборам, бо ў інтэрнэце пішуць пра 40, 44, 50, 54, але там IP-адрасы не было. Таксама можна паглядзець адзін з пакетаў у hex (параметр -xx ці -XX у tcpdump) і палічыць, па якім адрасе вядомы вам IP.
Ці ёсць фрагменты пакетаў без знятага Vlan-і Tunnel-інкапсулявання?
tcpdump ((ip[6:2] > 0) and (not ip[6] = 64))
Гэтая магія пакажа нам усе фрагменты, у тым ліку апошні. Мусіць, тое ж можна зафільтраваць па IP, але я не спрабаваў, паколькі такіх пакетаў не вельмі шмат, і ў агульным струмені лёгка знайшліся патрэбныя мне. Вось яны:
Гэта два фрагменты аднаго пакета (аднолькавы ID 53652) з фатаграфіяй (бачна слова Exif у першым пакеце). Па прычыне таго, што на гэтым узроўні пакеты ёсць, а ў злепленым выглядзе ў дампах - не, то праблема відавочна са зборкай. Нарэшце гэтаму ёсць дакументальнае пацвярджэнне!
Дэкодэр пакетаў не выявіў ніякіх праблем, якія перашкаджаюць зборцы. Спрабаваў тут: hpd.gasmi.net. Спачатку, пры спробе туды нешта запіхаць, дэкодэр не падабаецца фармат пакета. Аказалася, што там былі нейкія лішнія два актэта паміж Srcmac і Ethertype (якія не адносяцца да інфармацыі аб фрагментах). Пасля іх выдалення дэкодэр зарабіў. Аднак ніякіх праблем ён не паказаў.
Як ні круці, акрамя тых самых Sysctl нічога больш не знайшлося. Заставалася знайсці спосаб выяўлення праблемных сервераў, каб зразумець маштаб і прыняць рашэнне аб далейшых дзеяннях. Досыць хутка знайшоўся патрэбны лічыльнік:
"Час нумару тэм, які быў пазначаны IP-адрасоўкай алгарытмаў (для якой reason: timed out, errors, etc.)".
Сярод групы сервераў, на якіх вывучалася праблема, на двух гэты лічыльнік павялічваўся хутчэй, на двух - павольней, а яшчэ на двух наогул не павялічваўся. Параўнанне дынамікі гэтага лічыльніка з дынамікай HTTP-памылак на Java-серверы выявіла карэляцыю. Гэта значыць лічыльнік можна было ставіць на маніторынг.
Наяўнасць надзейнага індыкатара праблем вельмі важна, каб можна было дакладна вызначыць, ці дапамагае адкат Sysctl, бо з папярэдняга апавядання мы ведаем, што па дадатку гэта адразу зразумець нельга. Дадзены індыкатар дазволіў бы выявіць усе праблемныя месцы ў прадакшэне да таго, як гэта выявяць карыстачы.
Пасля адкату Sysctl памылкі па маніторынгу спыніліся, такім чынам прычына праблем была даказана, як і тое, што адкат дапамагае.
Мы адкацілі налады фрагментацыі на іншых серверах, дзе загарэўся новы маніторынг, а дзесьці пад фрагменты вылучылі нават больш памяці, чым было да гэтага па змаўчанні (гэта была udp-статыстыка, частковая страта якой не была прыкметная на агульным фоне).
Самыя галоўныя пытанні
Чаму ў нас на L3-балансавальніку фрагментуюцца пакеты? Большасць пакетаў, якія прылятаюць ад карыстачоў на балансавальнікі, - гэта SYN і ACK. Памеры гэтых пакетаў невялікія. Але бо дзель такіх пакетаў вельмі вялікая, то на іх фоне мы не заўважылі наяўнасць вялікіх пакетаў, якія сталі фрагментавацца.
Прычынай стаў скрыпт канфігурацыі, які паламаўся. advmss на серверах з Vlan-інтэрфейсамі (сервераў з тэгаваным трафікам на той момант у прадакшэне было вельмі мала). Advmss дазваляе данесці да кліента інфармацыю аб тым, што пакеты ў наш бок павінны быць меншага памеру, каб пасля прыклейвання да іх загалоўкаў тунэля іх не прыйшлося фрагментаваць.
Чаму адкат Sysctl не дапамагаў, а рэбуты дапамагаў? Адкат Sysctl мяняў аб'ём памяці, даступнай для склейвання пакетаў. Пры гэтым, мяркуючы па ўсім, сам факт перапаўнення памяці пад фрагменты прыводзіў да тармажэння злучэнняў, што прыводзіла да таго, што фрагменты надоўга затрымліваліся ў чарзе. Гэта значыць, працэс зацыкліваўся.
Рэбут абнуляў памяць і ўсё прыходзіла ў парадак.
Ці можна было абысціся без Workaround? Так, але вялікая рызыка пакінуць карыстальнікаў без абслугоўвання ў выпадку нападу. Вядома, ужыванне Workaround у выніку прывяло да ўзнікнення розных праблем, уключаючы тармажэнне аднаго з сэрвісаў у карыстачоў, але тым не менш мы лічым, што дзеянні былі апраўданыя.
Вялікі дзякуй Андрэю Цімафееву (atimofeyev) за дапамогу ў правядзенні расследавання, а таксама Аляксею Кранёву (devicex) - за тытанічную працу па абнаўленні Centos і ядраў на серверах. Працэс, які ў дадзеным выпадку некалькі разоў прыйшлося пачынаць з пачатку, з-за чаго ён зацягнуўся на шмат месяцаў.