Tinklaraštis

Bad Epoll (CVE-2026-46242): šešios lenktynių instrukcijos — ir bet kuris vartotojas tampa root

CVE-2026-46242_bad-epoll
CVE / Linux / Saugumas

Bad Epoll (CVE-2026-46242): šešios lenktynių instrukcijos — ir bet kuris vartotojas tampa root

Paprastas vartotojas be jokių privilegijų atsidaro terminalą jūsų serveryje, paleidžia eksploitą — ir po kelių sekundžių jau turi root teises. Ne per spragą web aplikacijoje, ne per pavogtą slaptažodį, o per epoll — Linux branduolio mechanizmą, ant kurio laikosi tiesiog viskas: nginx, systemd, bet koks asinchroninis servisas, naršyklė, Android. Jo išjungti negalima. Tai nėra hipotetinis scenarijus: 2026 m. gegužės 30 d. jis gavo numerį CVE-2026-46242, o liepos 1 d. tyrėjas Jaeyoung Chung paskelbė veikiantį eksploitą, kurio patikimumas testiniame 6.12.67 branduolyje siekia apie 99%.

Pagal kernel.org ir NVD skalę pažeidžiamumas įvertintas CVSS 3.1 — 7.8 (High), vektorius AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. Paprastais žodžiais tai reiškia: ataka vietinė, sudėtingumas žemas, reikia minimalių privilegijų, vartotojo sąveikos nereikia, o rezultatas — pilna konfidencialumo, vientisumo ir prieinamumo kontrolė. Kitaip tariant: paprastas vietinis shell — ir po kelių bandymų root. CWE-416 — use-after-free, klasika. Red Hat vertina klaidą kiek kukliau, 7.0 balu, teisingai pažymėdama, kad lenktynių langas mažytis — bet 99% eksploito sėkmės rodiklis kalba pats už save.

PoC jau viešai prieinamas, guli GitHub’e pas patį atradėją. CISA KEV sąraše publikacijos metu klaida nefigūruoja, patvirtintos eksploatacijos realiuose atakose nėra — tačiau pataisa be jokio skubaus saugumo pranešimo ramiai laukė backport’o du mėnesius, ir tai kaip tik ta situacija, kur ramybė apgaulinga.

KAS YRA EPOLL

epoll — tai būdas, kuriuo Linux procesas efektyviai stebi iš karto daug failų deskriptorių: lizdų, kanalų (pipe), kitų epoll egzempliorių. Vietoj to, kad apklaustų kiekvieną deskriptorių paeiliui, procesas užregistruoja juos visus viename epoll objekte ir vienu sistemos iškvietimu gauna sąrašą tų, kurie paruošti skaitymui ar rašymui. Būtent šiuo mechanizmu remiasi nginx, Node.js, Python asyncio, Android event loop našumas — praktiškai bet kuris servisas, aptarnaujantis daug ryšių vienu metu.

Branduolio viduje kiekvienas epoll egzempliorius — tai struktūra struct eventpoll, o kiekvienas stebimas deskriptorius jos viduje atstovaujamas struktūra struct epitem. Ypatingas atvejis — kai epoll stebi kitą epoll: susidaro sujungtas objektų sąrašas, kur vieno uždarymas turi tvarkingai atsieti jį nuo kito. Kaip tik toje tvarkoje ir buvo rasta spraga.

KAIP VEIKIA KLAIDA

Problema funkcijoje ep_remove(), tiksliau — ep_remove_file(). Funkcija turi saugiai nutraukti šį ryšį: pirma išnulinti file->f_ep po file->f_lock apsauga, po to atlikti likusį darbą. Tačiau kodas toliau naudojo kintamąjį @file kritinės sekcijos viduje jau po to, kai f_ep buvo išnulintas: buvo iškviečiama is_file_epoll(), po to hlist_del_rcu() per nuorodą į sąrašo head elementą, ir tik tada — spin_unlock().

Į šį tarpą gali įsiterpti tuo pačiu metu vykstantis __fput() iškvietimas — galutinis failo objekto atlaisvinimas, kai jo nuorodų skaitiklis nukrenta iki nulio. Jei __fput() eina greituoju keliu per eventpoll_release() būtent tuo momentu, jis mato jau išnulintą f_ep, nusprendžia, kad epoll valymas nereikalingas, praleidžia eventpoll_release_file() ir iškart pereina prie f_op->release, o po to prie file_free().

Atveju, kai epoll stebi kitą epoll, f_op->release — tai ep_eventpoll_release(), kuri iškviečia ep_clear_and_put(), o ta — ep_free(). Ši iškvietimų grandinė per kfree() atlaisvina pačią stebimą struktūrą eventpoll. Jos įmontuotas hlist_head pavadinimu ->refs yra kaip tik ten, kur rodo epi->fllink.pprev — tai yra nuoroda į ankstesnį sujungto sąrašo elementą. Kai po to vykdoma hlist_del_rcu(), operacija *pprev = next įrašo duomenis į jau atlaisvintą atmintį iš kmalloc-192 slab talpyklos.

Yra ir antra problemos dalis. struct file pažymėta vėliavėle SLAB_TYPESAFE_BY_RCU — tai reiškia, kad atminties lizdas, kuriame ji laikoma, gali būti iškart pakartotinai panaudotas funkcijos alloc_empty_file() visiškai kitam failui, iš naujo inicijuojant f_lock ir f_ep, kol ep_remove() formaliai vis dar mano, kad laiko užraktą ant seno objekto. Galiausiai gaunamas atakuotojo valdomas kmem_cache_free() iškvietimas ne tai slab talpyklai, kuri turėjo būti atlaisvinta.

KAIP TAI IŠNAUDOJAMA

Eksploitas naudoja keturis epoll objektus, sugrupuotus į dvi poras. Viena pora paleidžia pačias lenktynes, antra tampa atminties pažeidimo auka. Aštuonių baitų use-after-free įrašas pirmiausia paverčiamas kontrole virš struct file per cross-cache ataką — techniką, kurios metu atakuotojas priverčia branduolio alokatorių pakartotinai panaudoti atlaisvintą atmintį būtent tam objektui, kurio jam reikia.

Gavęs failo objekto turinio kontrolę, eksploitas pasiekia laisvą branduolio atminties skaitymą per /proc/self/fdinfo — kabantis struct file, atspindėtas per pipe, leidžia ištraukti branduolio adresus tiesiai iš šio pseudofailo išvesties. Toliau — valdymo srauto perėmimas ir ROP grandinės (return-oriented programming: atakuotojas sujungia jau esančius branduolio kodo fragmentus į jam reikalingą seką) vykdymas, kuris ir suteikia root shell.

Pats lenktynių langas — apie šešios mašininės instrukcijos. Įprastas bandymas beveik niekada į jį nepataiko. Eksploitas šią problemą sprendžia laikmačio pertraukimu: jis specialiai organizuoja apkrovą taip, kad laikmačio pertraukimas suveiktų būtent pažeidžiamo kodo vykdymo lango viduryje, ir kartoja bandymus cikle, kuris nesukelia branduolio panikos praleidus. Būtent šis mechanizmas ir suteikia deklaruojamą 99% patikimumą tiksle lts-6.12.67 ir 98% tiksle cos-121-18867.294.100 Google kernelCTF programoje.

KODĖL ŠI KLAIDA YPATINGA

Iš maždaug 130 pažeidžiamumų, kada nors išnaudotų kernelCTF programoje, tik apie dešimt tinka Android root gavimui. Bad Epoll — vienas iš jų. Dauguma pastarųjų mėnesių garsių Linux LPE, tokių kaip Copy Fail, reikalauja modulių, kurių Android tiesiog niekada nekrauna. Čia apribojimai kitokie: klaida gyvena pačiame epoll branduolyje, o ne pasirenkamame modulyje, ir ją galima suaktyvinti net iš Chrome renderer smėlio dėžės vidaus, kuri blokuoja beveik visus kitus branduolio pažeidžiamumus. Tai atveria teorinę grandinę: naršyklės renderer eksploitas plius Bad Epoll — ir gauname kodo vykdymą branduolio lygmenyje, kaip tik tą scenarijų, kurį Google Project Zero aprašė straipsnyje apie perėjimą iš Chrome renderer į branduolį per MSG_OOB.

Copy Fail ir jos variantai turi paprastą laikiną sprendimą — iškrauti pažeidžiamą modulį. Bad Epoll tokio neturi: epoll negalima išjungti, tai pagrindinė branduolio dalis, kuria remiasi operacinė sistema, tinklo servisai ir naršyklės. Vienintelis veikiantis variantas — įdiegti pataisą.

LAIKO JUOSTA

2023 m. balandžio 8 d. commit’as 58c9b016e128 į epoll kodą įneša ne vieną, o iš karto dvi atskiras lenktynių sąlygas (race condition) — maždaug 2500 eilučių kodo atkarpoje. Abi vėliau taps kritiniais pažeidžiamumais su privilegijų eskalavimo galimybe.

2026 m. vasario 17 d. Chung komanda praneša apie rastą klaidą [email protected]. Tą pačią dieną prižiūrėtojai pasiūlo pataisos prototipą — bet jis pasirodo esąs nepilnas taisymas, ir diskusija sustringa.

2026 m. balandžio 2 d. į mainline patenka pirmojo iš dviejų klaidų taisymas — to paties, kurį rado Anthropic modelis Mythos ir kuris gavo numerį CVE-2026-43074. Mythos radinys pats savaime iškalbingas: branduolio lenktynių sąlyga — vienas sunkiausiai aptinkamų klaidų klasių, ir tai, kad pažangus DI modelis rado vieną tokių šioje kodo atkarpoje, kalba apie realų automatizuotos saugumo analizės progresą.

2026 m. balandžio 22 d. Chung komanda pakartotinai praneša apie likusią, antrąją problemą — tą, kuri vėliau taps Bad Epoll. 2026 m. balandžio 24 d. jos taisymas pagaliau patenka į mainline commit’u a6dc643c6931. O 2026 m. liepos 1 d. išeina pilna techninė analizė su veikiančiu PoC — tai yra tarp tylios pataisos ir viešo atskleidimo praėjo daugiau nei du mėnesiai.

KODĖL DI PRALEIDO ŠIĄ KLAIDĄ

Tas pats 2023 metų commit’as pasodino abi lenktynių sąlygas epoll kode. Mythos, tyrinėdama šią kodo atkarpą, rado pirmąją — ir nerado antrosios, nors logiška manyti, kad modelis nagrinėjo tą patį kodą pakankamai giliai, jei aptiko gretimą klaidą. Tikslios praleidimo priežasties žinoti negalima, bet tyrėjai turi dvi darbines versijas.

Pirma, lenktynių langas itin siauras — apie šešios instrukcijos, ir tikslią gijų persipynimo seką sunku įsivaizduoti net tiesiogiai nagrinėjant pažeidžiamą kodą. Antra, kai pirmoji klaida (CVE-2026-43074) buvo ištaisyta, antrosios klaidos use-after-free nustojo reguliariai suaktyvinti KASAN — pagrindinį branduolio atminties klaidų detektorių. Be šio signalo modeliui, tikėtina, nesusikaupė pakankamai pasitikėjimo, kad praneštų apie radinį kaip apie realų pažeidžiamumą.

Iškalbinga ir tai, kad klaida pasirodė sudėtinga ne tik aptikti, bet ir ištaisyti: pirmoji prižiūrėtojų pataisa problemos pilnai neišsprendė, o teisingas taisymas pasirodė tik po dviejų mėnesių nuo pirmojo pranešimo — neįprastai ilgas laikas branduoliui, kuris paprastai reaguoja į saugumo problemas su prioritetu.

KODĖL TAI SVARBU

Bad Epoll smogia būtent tai infrastruktūrai, kur vietinė izoliacija ir yra visas produktas: shared hosting, daugianuomininkiai hostai, CI/CD runneriai, konteinerių mazgai su nepatikimu kodu. Bet kuris vartotojas su įprasta shell prieiga tokiame serveryje — potencialus root. Ir kadangi epoll naudojamas taip visuotinai, šio atakos paviršiaus tiesiog neįmanoma išjungti: vietinis privilegijų eskalavimas čia tampa ne teoriška rizika, o riba, kurią kažkas iš tikrųjų gali peržengti.

Hostingo paslaugų teikėjams tai tiesioginė grėsmė klientų izoliacijai vieni nuo kitų ant bendros geležies. Komandoms, valdančioms Kubernetes klasterius ir CI runnerius — rizika, kad pažeistas subkonteineris ar nepatikimas pipeline skriptas gaus root mazge ir prieigą prie visų kitų jame veikiančių krūvių. Įprastų VPS ir dedikuotų serverių savininkams situacija šiek tiek ramesnė tik viena prasme: jei pas jus nėra pašalinių vietinių vartotojų, atakos vektorius reikalauja, kad kažkas jau būtų gavęs bent neprivilegijuotą shell — pavyzdžiui, per web aplikacijos pažeidžiamumą. Bet būtent todėl Bad Epoll yra tas pats antrasis žingsnis, paverčiantis bet kokį pradinį prieigos gavimą pilnu serverio kompromitavimu.

ATNAUJINIMAS

Oficialus taisymas — upstream commit’as a6dc643c69311677c574a0f17a3f4d66a5f3744b, patekęs į pagrindinę branduolio šaką. Distribucijoms taisymas prieinamas kaip backport stabiliose šakose: 5.15.209 ir vėliau 5.15 linijoje, 6.1.175 ir vėliau 6.1 linijoje, 6.18.33 ir vėliau 6.18 linijoje, 7.0.10 ir vėliau 7.0 linijoje, ir pilnai 7.1 (commit’as buvo jau nuo 7.1-rc1 stadijos). Pirmiausia pažiūrėkite, kokia branduolio versija realiai veikia dabar — ne ta, kuri įdiegta, o ta, kuri įkelta į atmintį:

uname -r

Vien tik versijos numerio nepakanka, nes distribucijos backportina taisymus į savo pačių versijų numeravimą. Patikimas būdas patikrinti, ar būtent šis taisymas pateko į jūsų įdiegtą branduolio paketą — perskaityti paties paketo changelog ir jame paieškoti CVE numerio. Debian ir Ubuntu tai daroma taip:

apt-get changelog linux-image-$(uname -r) | grep -i "CVE-2026-46242"

Jei komanda grąžina eilutę, kurioje minimas CVE-2026-46242 — pataisa jau įtraukta į įdiegtą branduolio paketą, ir belieka tik perkrauti sistemą, kad ji realiai pradėtų veikti. Jei eilutė tuščia — branduolyje taisymo dar nėra, ir reikia atnaujinti paketą per apt update && apt upgrade, palaukti, kol įsidiegs naujas branduolys, ir perkrauti. RHEL, AlmaLinux, Rocky Linux ir kitose RPM pagrindu veikiančiose sistemose ta pati patikra atliekama per rpm:

rpm -q --changelog kernel | grep -i "CVE-2026-46242"

Logika identiška: tuščia išvestis reiškia, kad taisymo dar nėra, ir reikia atsinaujinti per dnf update kernel (arba yum update kernel senesnėse sistemose), po to perkrauti hostą.

Atskirai verta pasitikrinti su konkrečios distribucijos saugumo sekimo sistema, o ne pasikliauti vien lokaliu changelog: Debian, pavyzdžiui, bookworm ir bullseye šakų statusas tiesiogiai pažymėtas kaip not-affected — pažeidžiamo kodo ten tiesiog nėra, nes klaida buvo įnešta tik nuo 6.4 branduolio šakos. Naujesnes Debian šakas ir kitas distribucijas su aktualiais branduoliais reikia tikrinti atskirai.

Įdiegus naują branduolio paketą, būtina perkrauti sistemą — gyvas branduolys atmintyje toliau vykdo pažeidžiamą kodą iki perkrovimo, o paprastas paketo atnaujinimas be perkrovimo jau veikiančiai sistemai nieko nepakeičia. Įsitikinti, kad iš tikrųjų įsikrovėte su nauju branduoliu, galima pakartotinai paleidus uname -r ir palyginus versijos numerį su tuo, kurio buvo tikimasi po atnaujinimo.

Jokio laikino sprendimo pačiame epoll neegzistuoja — mechanizmo negalima nei išjungti, nei apriboti konfigūracija. Jei dėl kažkokių priežasčių perkrauti dabar neįmanoma, vienintelis realus būdas sumažinti riziką tuo tarpu — sumažinti nepatikimų vietinių vartotojų ir procesų, galinčių vykdyti savavališką kodą hoste, skaičių: pašalinti nereikalingą shell prieigą, glaudžiau izoliuoti CI runnerius ir konteinerius, iškelti tikrai nepatikimas krūvis į atskirus, specialiai išskirtus ir atidžiai stebimus mazgus. Tai nepašalina pažeidžiamumo — tai laikinai susiaurina ratą tų, kas gali jį pasiekti.

Jei production hosto perkrovimas iš principo yra problema, live-patching servisai geba uždaryti tokias spragas be perkrovimo. Ubuntu tai canonical-livepatch — įdiegus klientą (sudo snap install canonical-livepatch ir aktyvavus tokeną per sudo canonical-livepatch enable <jūsų-tokenas>), pataisymo statusas tikrinamas komanda:

sudo canonical-livepatch status --verbose

Išvestyje eilutė patch state: turėtų rodyti all applicable livepatch kernel modules applied — tai reiškia, kad visi jūsų branduoliui prieinami live-patch’ai jau pritaikyti. Jei matote kernel state: ✗ kernel not supported by Canonical arba paminėjimą, kad branduolys išėjo iš Livepatch palaikymo lango, šis branduolys vis dar nepadengtas, ir reikia pilno paketo atnaujinimo su perkrovimu. RHEL suderinamoms sistemoms analogišką vaidmenį atlieka Red Hat kpatch, kitoms distribucijoms — tretieji sprendimai, tokie kaip KernelCare ar SUSE Live Patching. Bet kokiu atveju, prieš pasikliaujant live-patch kaip apsauga, verta aiškiai patvirtinti pas tiekėją, kad pataisa būtent CVE-2026-46242 jau pristatyta į jūsų feed’ą, o ne tik susijusiam CVE-2026-43074: vieno use-after-free ištaisymas šioje kodo atkarpoje negarantuoja automatinio antrojo uždarymo, o live-patching CVE aprėpties ataskaitos kartais atsilieka nuo realios feed’o būklės.

IŠVADOS

Jei administruojate shared hosting ar daugianuomininkes virtualias mašinas — patikrinkite branduolio versiją dabar pat ir pasitikrinkite su savo distribucijos saugumo sekimo sistema; tokiai aplinkai tai turėtų tapti tokiu pat pataisymo prioritetu kaip kritiniai RCE, nes izoliacijos riba tarp jūsų klientų čia tiesiogine prasme laikosi ant šio kodo.

Jei valdote Kubernetes klasterį ar CI/CD infrastruktūrą su runneriais, vykdančiais nepatikimą kodą — mazgų branduolių atnaujinimas turėtų įvykti prieš kitą deploy ciklą, o ne pagal planinių pataisų grafiką: tai kaip tik toks pažeidžiamumo tipas, kuris paverčia pažeistą podą root’u visame mazge.

Jei turite įprastą dedikuotą serverį ar VPS be pašalinių vietinių vartotojų — atsinaujinkite pirmą rutininę progą, bet be panikos: atakos vektorius reikalauja jau turimos vietinės prieigos, tad Bad Epoll čia greičiau antra gynybos linija, kurią verta uždaryti, o ne iš karto degantis incidentas.

Ir bet kuriuo atveju — nepasikliaukite vien branduolio versijos numeriu. Pasitikrinkite su būtent savo distribucijos saugumo sekimo sistema: backport politikos ir šakų statusai skiriasi, ir tai, kas atrodo kaip sena pažeidžiama versija, gali būti jau pataisyta, o iš pirmo žvilgsnio nauja — vis dar turėti spragą.

Leave your thought here

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *