Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Tere kõigile! Minu nimi on Dmitri Samsonov, töötan Odnoklassnikis juhtiva süsteemiadministraatorina. Meil on üle 7 tuhande füüsilise serveri, pilves 11 tuhat konteinerit ja 200 rakendust, mis erinevates konfiguratsioonides moodustavad 700 erinevat klastrit. Enamik serveritest töötab CentOS 7-ga.
14. augustil 2018 avaldati teave FragmentSmacki haavatavuse kohta
(CVE-2018-5391) ja SegmentSmack (CVE-2018-5390). Need on võrguründevektori ja üsna kõrge hindega (7.5) haavatavused, mis ähvardavad ressursi ammendumise (CPU) tõttu teenuse keelamist (DoS). FragmentSmacki tuumaparandust sel ajal ei pakutud, pealegi tuli see välja palju hiljem kui haavatavuse kohta teabe avaldamine. SegmentSmacki kõrvaldamiseks soovitati kernelit värskendada. Uuenduspakett ise ilmus samal päeval, jäi vaid see installida.
Ei, me ei ole üldse tuuma uuendamise vastu! Siiski on nüansse ...

Kuidas värskendame tuuma tootmisel

Üldiselt pole midagi keerulist:

  1. Laadige paketid alla;
  2. Installige need mitmesse serverisse (sh serveritesse, mis majutavad meie pilve);
  3. Veenduge, et midagi poleks katki;
  4. Veenduge, et kõik standardsed kerneli sätted oleksid rakendatud vigadeta;
  5. Oodake paar päeva;
  6. Kontrollige serveri jõudlust;
  7. Lülita uute serverite juurutamine uuele tuumale;
  8. Värskendage kõiki servereid andmekeskuste kaupa (üks andmekeskus korraga, et probleemide korral mõju kasutajatele oleks minimaalne);
  9. Taaskäivitage kõik serverid.

Korrake seda kõigi meil olevate tuumade harude puhul. Hetkel on see:

  • Stock CentOS 7 3.10 - enamiku tavaliste serverite jaoks;
  • Vanill 4.19 - meie omale ühepilvelised pilved, sest me vajame BFQ, BBR jne;
  • Elrepo kernel-ml 5.2 - jaoks kõrgelt koormatud turustajad, sest 4.19 käitus varem ebastabiilselt, kuid vaja on samu funktsioone.

Nagu võis arvata, võtab tuhandete serverite taaskäivitamine kõige kauem aega. Kuna kõik haavatavused ei ole kõigi serverite jaoks kriitilised, taaskäivitame ainult need, millele on Internetist otse juurdepääs. Paindlikkuse mitte piiramiseks ei seo me pilves väliselt ligipääsetavaid konteinereid uue tuumaga üksikute serveritega, vaid taaskäivitame eranditult kõik hostid. Õnneks on protseduur seal lihtsam kui tavaliste serveritega. Näiteks võivad olekuta konteinerid taaskäivitamise ajal lihtsalt teise serverisse kolida.

Tööd on aga veel palju ja selleks võib kuluda mitu nädalat ja kui uue versiooniga on probleeme, siis kuni mitu kuud. Ründajad saavad sellest väga hästi aru, seega vajavad nad plaani B.

FragmentSmack/SegmentSmack. Lahendus

Õnneks on mõne haavatavuse puhul selline plaan B olemas ja seda nimetatakse lahenduseks. Enamasti on see kerneli/rakenduse sätete muudatus, mis võib minimeerida võimalikku mõju või täielikult kõrvaldada haavatavuste ärakasutamise.

FragmentSmacki/SegmentSmacki puhul tehti ettepanek Selline lahendus:

«Saate muuta failides net.ipv4.ipfrag_high_thresh ja net.ipv3.ipfrag_low_thresh (ja nende vastetes ipv4 jaoks net.ipv4.ipfrag_high_thresh ja net.ipv6.ipfrag_low_thresh) vaikeväärtusi 6 MB ja 6 MB väärtuseks vastavalt 256 või kB ja kB madalam. Testid näitavad riistvarast, sätetest ja tingimustest olenevalt CPU kasutuse väikest kuni olulist langust rünnaku ajal. Siiski võib ipfrag_high_thresh=192 baiti tõttu jõudlust veidi mõjutada, kuna kokkupanemise järjekorda mahub korraga ainult kaks 262144K fragmenti. Näiteks on oht, et suurte UDP-pakettidega töötavad rakendused lähevad katki'.

Parameetrid ise kerneli dokumentatsioonis kirjeldatakse järgmiselt:

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.

Meil ei ole tootmisteenuste osas suuri UDP-sid. LAN-is pole killustatud liiklust; WAN-is on liiklus killustatud, kuid mitte märkimisväärne. Märke pole – saate välja töötada lahenduse!

FragmentSmack/SegmentSmack. Esimene veri

Esimene probleem, millega puutusime kokku, oli see, et pilvekonteinerid rakendasid mõnikord uusi sätteid ainult osaliselt (ainult ipfrag_low_thresh) ja mõnikord ei rakendanud neid üldse – need jooksid alguses lihtsalt kokku. Probleemi ei olnud võimalik stabiilselt reprodutseerida (kõik seaded rakendati käsitsi ilma raskusteta). Ka aru saada, miks konteiner alguses kokku jookseb, pole nii lihtne: vigu ei leitud. Üks oli kindel: seadete tagasipööramine lahendab konteineri krahhide probleemi.

Miks ei piisa Sysctli rakendamisest hostis? Konteiner elab oma spetsiaalses võrgus nimeruumis, nii et vähemalt osa võrgu Sysctl parameetritest konteineris võib hostist erineda.

Kuidas täpselt Sysctli sätteid konteineris rakendatakse? Kuna meie konteinerid on privilegeerimata, ei saa te ühtegi Sysctli seadet muuta, minnes konteinerisse endasse – teil pole lihtsalt piisavalt õigusi. Konteinerite käitamiseks kasutas meie pilv sel ajal Dockerit (nüüd podman). Uue konteineri parameetrid anti API kaudu Dockerile, sealhulgas vajalikud Sysctl seaded.
Versioone otsides selgus, et Docker API ei tagastanud kõiki vigu (vähemalt versioonis 1.10). Kui proovisime konteinerit "docker run" kaudu käivitada, nägime lõpuks vähemalt midagi:

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.

Parameetri väärtus ei kehti. Aga miks? Ja miks see ei kehti ainult mõnikord? Selgus, et Docker ei garanteeri Sysctli parameetrite rakendamise järjekorda (viimane testitud versioon on 1.13.1), nii et mõnikord prooviti ipfrag_high_thresh seada 256K-le, kui ipfrag_low_thresh oli veel 3M, st ülempiir oli madalam. kui alumine piir, mis põhjustas vea.

Sel ajal kasutasime konteineri uuesti seadistamiseks pärast käivitamist (konteineri külmutamist pärast käivitamist) juba oma mehhanismi rühma sügavkülmik ja käskude täitmine konteineri nimeruumis kaudu ip netns) ja lisasime sellesse ossa ka Sysctli parameetrite kirjutamise. Probleem sai lahendatud.

FragmentSmack/SegmentSmack. Esimene veri 2

Enne kui meil oli aega pilves Workaroundi kasutamisest aru saada, hakkasid saabuma esimesed kasutajate harvad kaebused. Sel ajal oli Workaroundi esimestes serverites kasutamise algusest möödas mitu nädalat. Esialgne uurimine näitas, et kaebusi laekus üksikute teenuste, mitte kõigi nende teenuste serverite kohta. Probleem on muutunud taas äärmiselt ebakindlaks.

Esiteks proovisime loomulikult Sysctli sätteid tagasi tõmmata, kuid see ei avaldanud mingit mõju. Ei aidanud ka erinevad manipulatsioonid serveri ja rakenduse seadetega. Reboot aitas. Linuxi taaskäivitamine on sama ebaloomulik kui vanasti Windowsi jaoks tavaline. Kuid see aitas ja Sysctli uute sätete rakendamisel tõstsime selle esile kerneli tõrkeni. Kui kergemeelne see oli...

Kolm nädalat hiljem probleem kordus. Nende serverite konfigureerimine oli üsna lihtne: Nginx puhverserveri / tasakaalustaja režiimis. Liiklust pole palju. Uus sissejuhatav märkus: klientide 504 vigade arv kasvab iga päevaga (Gateway Timeout). Graafik näitab selle teenuse 504 vea arvu päevas:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Kõik vead on umbes sama taustaprogrammiga - umbes selle taustaga, mis on pilves. Selle taustaprogrammi pakettide fragmentide mälutarbimise graafik nägi välja selline:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

See on operatsioonisüsteemi graafikute probleemi üks ilmsemaid ilminguid. Pilves parandati samal ajal veel üks võrguprobleem QoS (Traffic Control) sätetega. Pakettfragmentide mälutarbimise graafikul nägi see välja täpselt sama:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Eeldus oli lihtne: kui nad näevad graafikutel välja ühesugused, siis on neil sama põhjus. Pealegi on seda tüüpi mäluga seotud probleemid äärmiselt haruldased.

Parandatud probleemi sisuks oli see, et kasutasime QoS-i vaikesätetega fq-pakettide planeerijat. Vaikimisi võimaldab see ühe ühenduse jaoks lisada järjekorda 100 paketti ja mõned ühendused hakkasid kanalite nappuse korral järjekorda ummistama. Sel juhul paketid kukutatakse. Tc statistikas (tc -s qdisc) võib seda näha järgmiselt:

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" on paketid, mis langesid ühe ühenduse järjekorrapiirangu ületamise tõttu, ja "langes 464545" on selle planeerija kõigi katkestatud pakettide summa. Pärast järjekorra pikkuse suurendamist 1 tuhandeni ja konteinerite taaskäivitamist probleem lakkas. Võite istuda ja juua smuutit.

FragmentSmack/SegmentSmack. Viimane veri

Esiteks, mitu kuud pärast kerneli haavatavustest teatamist, ilmus lõpuks FragmentSmacki parandus (tuletan teile meelde, et koos augustis avaldatud teatega anti välja ka ainult SegmentSmacki parandus), mis andis meile võimaluse loobuda lahendusest, mis meile päris palju tüli tekitas. Selle aja jooksul olime jõudnud juba osa serveritest uude kerneli üle kanda ja nüüd tuli alustada algusest. Miks värskendasime kernelit FragmentSmacki parandust ootamata? Fakt on see, et nende haavatavuste eest kaitsmise protsess langes kokku (ja ühines) CentOS-i enda värskendamise protsessiga (mis võtab isegi rohkem aega kui ainult kerneli värskendamine). Lisaks on SegmentSmack ohtlikum haavatavus ja sellele ilmus kohe parandus, nii et see oli igatahes mõistlik. Kuid me ei saanud lihtsalt CentOS-i kernelit värskendada, kuna CentOS 7.5 ajal ilmnenud FragmentSmacki haavatavus parandati alles versioonis 7.6, nii et pidime peatama värskenduse versioonile 7.5 ja alustama värskendusega versioonile 7.6. Ja seda juhtub ka.

Teiseks on meieni jõudnud harvad kasutajate kaebused probleemide kohta. Nüüd teame juba kindlalt, et need kõik on seotud failide üleslaadimisega klientidelt mõnesse meie serverisse. Lisaks läbis nende serverite kogumassist väga väike arv üleslaadimisi.

Nagu ülaltoodud loost mäletame, ei aidanud Sysctli tagasipööramine. Taaskäivitamine aitas, kuid ajutiselt.
Kahtlused Sysctli osas ei kõrvaldatud, kuid seekord oli vaja koguda võimalikult palju teavet. Samuti oli tohutult puudu võimest üleslaadimisprobleemi kliendil taasesitada, et toimuvat täpsemalt uurida.

Kogu olemasoleva statistika ja logide analüüs ei viinud meid toimuvast arusaamisele lähemale. Konkreetse seose "tunnetamiseks" puudus terav võime probleemi taastoota. Lõpuks õnnestus arendajatel rakenduse spetsiaalset versiooni kasutades saavutada Wi-Fi kaudu ühenduse loomisel testseadme probleemide stabiilne reprodutseerimine. See oli uurimises läbimurre. Klient ühendus Nginxiga, mis puhverserveris taustaprogrammi, mis oli meie Java-rakendus.

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Probleemide dialoog oli selline (fikseeritud Nginxi puhverserveri poolel):

  1. Klient: taotleb teavet faili allalaadimise kohta.
  2. Java server: vastus.
  3. Klient: POST koos failiga.
  4. Java server: viga.

Samal ajal kirjutab Java server logisse, et kliendilt saadi 0 baiti andmeid ja Nginxi puhverserver, et päring võttis aega üle 30 sekundi (30 sekundit on kliendirakenduse ajalõpp). Miks ajalõpp ja miks 0 baiti? HTTP vaatenurgast toimib kõik nii, nagu peab, kuid failiga POST näib võrgust kaduma. Veelgi enam, see kaob kliendi ja Nginxi vahel. On aeg end Tcpdumpiga relvastada! Kuid kõigepealt peate mõistma võrgu konfiguratsiooni. Nginxi puhverserver on L3 tasakaalustaja taga NF-vara. Tunneldamist kasutatakse pakettide edastamiseks L3 tasakaalustajast serverisse, mis lisab pakettidele oma päised:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Sel juhul tuleb võrk sellesse serverisse Vlan-märgistatud liikluse kujul, mis lisab pakettidele ka oma väljad:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Ja see liiklus võib olla ka killustatud (sama väike protsent sissetulevast killustatud liiklusest, millest me rääkisime lahenduse riskide hindamisel), mis muudab ka päiste sisu:

Hoiduge haavatavustest, mis toovad töövoore. 1. osa: FragmentSmack/SegmentSmack

Veel kord: paketid on kapseldatud Vlan-sildiga, kapseldatud tunneliga, killustatud. Et paremini mõista, kuidas see juhtub, jälgime paketi marsruuti kliendist Nginxi puhverserverini.

  1. Pakett jõuab L3 tasakaalustajani. Andmekeskuses õigeks marsruutimiseks kapseldatakse pakett tunnelisse ja saadetakse võrgukaardile.
  2. Kuna pakett + tunneli päised ei mahu MTU-sse, lõigatakse pakett tükkideks ja saadetakse võrku.
  3. L3 tasakaalustaja järgne lüliti lisab paketi vastuvõtmisel sellele Vlan tagi ja saadab edasi.
  4. Nginxi puhverserveri ees olev lüliti näeb (pordi seadete põhjal), et server ootab Vlan-kapseldatud paketti, seega saadab see sellisel kujul, ilma Vlan-i silti eemaldamata.
  5. Linux võtab üksikute pakettide fragmente ja liidab need üheks suureks paketiks.
  6. Järgmisena jõuab pakett Vlan liidesesse, kus sellelt eemaldatakse esimene kiht – Vlan kapseldus.
  7. Linux saadab selle seejärel Tunneli liidesesse, kus sellelt eemaldatakse veel üks kiht – Tunnel encapsulation.

Raskus on kõige selle parameetritena edastamine tcpdumpile.
Alustame lõpust: kas klientidelt on puhtaid (ilma tarbetute päisteta) IP-pakette, millest vlan ja tunnelkapseldamine on eemaldatud?

tcpdump host <ip клиента>

Ei, selliseid pakette serveris ei olnud. Nii et probleem peab olema varem olemas. Kas on pakette, millest on eemaldatud ainult Vlan-kapseldus?

tcpdump ip[32:4]=0xx390x2xx

0xx390x2xx on kliendi IP-aadress kuueteistkümnendvormingus.
32:4 — tunnelipaketis SCR-i IP-aadressi kirjutatava välja aadress ja pikkus.

Välja aadress tuli valida toore jõuga, kuna Internetis kirjutatakse umbes 40, 44, 50, 54, kuid IP-aadressi seal polnud. Võite vaadata ka ühte paketti hex-vormingus (parameeter -xx või -XX tcpdumpis) ja arvutada teadaoleva IP-aadressi.

Kas on pakettide fragmente ilma Vlani ja Tunneli kapseldamiseta?

tcpdump ((ip[6:2] > 0) and (not ip[6] = 64))

See maagia näitab meile kõiki fragmente, sealhulgas viimast. Tõenäoliselt saab sama asja IP järgi filtreerida, kuid ma ei proovinud, sest selliseid pakette pole väga palju ja need, mida ma vajasin, leiti üldisest voost kergesti. Siin nad on:

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 [email protected].
        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 In 00:de:ff:1a:94:11 ethertype IPv4 (0x0800), length 62: (tos 0x0, ttl 63, id 53652, offset 1480, flags [none], proto IPIP (4), length 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 ..............

Need on kaks fragmenti ühest pakendist (sama ID 53652) koos fotoga (esimesel pakendil on näha sõna Exif). Tulenevalt asjaolust, et sellel tasemel pakke on, kuid prügilates mitte ühendatud kujul, on probleem selgelt komplektis. Lõpuks on selle kohta dokumentaalsed tõendid!

Pakettdekooder ei näidanud ühtegi ehitamist takistavat probleemi. Proovisin siin: hpd.gasmi.net. Alguses, kui proovite sinna midagi toppida, ei meeldi dekoodrile paketivorming. Selgus, et Srcmaci ja Ethertype'i vahel oli veel kaks oktetti (pole seotud fragmenditeabega). Pärast nende eemaldamist hakkas dekooder tööle. Probleeme see siiski ei näidanud.
Mida iganes võib öelda, midagi muud peale nende Sysctl ei leitud. Jäi vaid leida viis probleemsete serverite tuvastamiseks, et mõista mastaapi ja otsustada edasiste tegevuste üle. Vajalik loendur leiti piisavalt kiiresti:

netstat -s | grep "packet reassembles failed”

See on ka snmpd-s OID=1.3.6.1.2.1.4.31.1.1.16.1 all (ipSystemStatsReasmFails).

"IP-i uuesti kokkupaneku algoritmi tuvastatud tõrgete arv (mis tahes põhjusel: aegunud, vead jne)."

Serverite rühmast, kus probleemi uuriti, suurenes see loendur kahel kiiremini, kahel aeglasemalt ja veel kahel ei suurenenud see üldse. Selle loenduri dünaamika võrdlemine Java serveri HTTP-vigade dünaamikaga näitas korrelatsiooni. See tähendab, et arvestit saaks jälgida.

Usaldusväärse probleemide indikaatori olemasolu on väga oluline, et saaksite täpselt kindlaks teha, kas Sysctli tagasipööramine aitab, kuna eelmisest loost teame, et seda ei saa rakendusest kohe aru saada. See indikaator võimaldaks meil tuvastada kõik tootmisprobleemid enne, kui kasutajad selle avastavad.
Peale Sysctl tagasikerimist seirevead lakkasid, seega sai tõestatud nii probleemide põhjus kui ka see, et tagasikerimine aitab.

Pöörasime teistes serverites killustatuse seaded tagasi, kus tuli mängu uus jälgimine ja kuskil eraldasime fragmentidele veelgi rohkem mälu, kui oli varem vaikimisi (see oli UDP statistika, mille osalist kadumist ei olnud üldisel taustal märgata) .

Kõige olulisemad küsimused

Miks on meie L3 tasakaalustaja paketid killustatud? Enamik pakette, mis saabuvad kasutajatelt tasakaalustajatele, on SYN ja ACK. Nende pakendite suurused on väikesed. Kuid kuna selliste pakettide osakaal on väga suur, ei märganud me nende taustal suuri pakette, mis hakkasid killustama.

Põhjuseks oli rikkis seadistusskript advmss Vlan-liidestega serverites (sel ajal oli märgistatud liiklusega servereid tootmises väga vähe). Advmss võimaldab meil edastada kliendile infot, et meie suunal olevad paketid peaksid olema väiksema suurusega, et pärast tunnelipäiste külge kinnitamist ei peaks neid killustama.

Miks Sysctl tagasipööramine ei aidanud, kuid taaskäivitamine aitas? Sysctli tagasipööramine muutis pakettide liitmiseks saadaoleva mälumahtu. Samal ajal põhjustas ilmselt juba fragmentide mälu ületäitumise fakt ühenduste aeglustumist, mis tõi kaasa fragmentide pikaajalise hilinemise järjekorras. See tähendab, et protsess käis tsüklitena.
Taaskäivitamine tühjendas mälu ja kõik läks järjekorda.

Kas oli võimalik ilma lahenduseta hakkama saada? Jah, kuid rünnaku korral on suur oht jätta kasutajad ilma teenuseta. Muidugi põhjustas lahenduse kasutamine mitmesuguseid probleeme, sealhulgas ühe teenuse kasutajate jaoks aeglustumist, kuid sellegipoolest usume, et meetmed olid õigustatud.

Suur tänu Andrei Timofejevile (atimofejev) abi eest uurimise läbiviimisel, samuti Aleksei Krenev (seadex) - Centose ja serverite tuumade värskendamise titaanliku töö eest. Protsess, mida antud juhul tuli mitu korda otsast alustada, mistõttu venis see mitu kuud.

Allikas: www.habr.com

Lisa kommentaar