Copy Fail (CVE-2026-31431): 732 Python baitai — ir bet kuris vartotojas tampa root
Copy Fail (CVE-2026-31431): 732 Python baitai — ir bet kuris vartotojas tampa root
Theori tyrėjas Taeyang Lee analizavo, kas nutinka, kai Linux kriptografinė posistemė per sisteminį iškvietimą splice() gauna duomenis tiesiai iš puslapio podėlio. Intuicija rodė: jei neprivilegijuotas vartotojas gali pateikti branduolio atmintyje saugomą failo puslapį šifravimo operacijai, kažkas turi nueiti negerai. Jis paleido dirbtinio intelekto įrankį Xint Code tikrinti visą kriptografinio posistemo kodą, pasiekiamą iš vartotojo erdvės, — ir maždaug po valandos ekrane pasirodė CVE-2026-31431.
Copy Fail yra loginė klaida Linux branduolio kriptografiniame šablone authencesn (CWE-787, rašymas už buferio ribų). Ji leidžia neprivilegijuotam vietiniam vartotojui įrašyti lygiai 4 baitus į bet kurio jam skaitomo failo puslapio podėlį. Jei taikiniu pasirinksite setuid dvejetainį failą — pavyzdžiui, /usr/bin/su — tai tiesus kelias į root. CVSS 7.8, High. Koncepcijos įrodymo eksploitas paskelbtas 2026 m. balandžio 29 d. ir telpa į 732 Python baitus. Pataisymas jau pagrindinėje branduolio versijoje; distribucijos išleido atnaujinimus įprastu branduolio paketo mechanizmu.
Tai, kas išskiria Copy Fail iš kitų šių metų LPE pažeidžiamybių, yra determinizmas. Dirty Cow reikalavo laimėti lenktynių sąlygą ir neretai sugriaudavo sistemą nesėkmės atveju. Dirty Pipe veikė tik konkrečiose branduolio versijose. Copy Fail suveikia be lenktynių, be pakartojimų, be rizikingų laiko langų. Tas pats 732 baitų scenarijus be pakeitimų suteikia root Ubuntu, Amazon Linux, RHEL ir SUSE — jokie atskiri poslinkiai kiekvienai distribucijai, jokie perkompiliavimai. 100% patikimumas.
KAS YRA AF_ALG IR PUSLAPIO PODĖLIS
AF_ALG — tai lizdo tipas, atveriantis branduolio kriptografinę posistemę neprivilegijuotai vartotojo erdvei. Sukuriate lizdą, prisiregistruojate prie bet kurio AEAD šablono (Authenticated Encryption with Associated Data) ir perduodate duomenis šifravimui ar dešifravimui. Jokių teisių nereikia — tai standartinė funkcija, prieinama bet kuriam vartotojui.
Puslapio podėlis — tai branduolio atminties sluoksnis, kuriame saugomas failų turinys. Kai skaitote failą, branduolys įkelia jo puslapius į podėlį. Visos vėlesnės operacijos read(), mmap() ir execve() dirba su šiuo podėliu, o ne tiesiogiai su disku. Puslapio podėlis bendras visai sistemai — įskaitant visus konteinerius, veikiančius serveryje.
Sisteminis iškvietimas splice() perduoda duomenis tarp failų deskriptorių ir vamzdžių be kopijavimo — perduodama ne turinys, o nuoroda į atminties puslapį. Kai vartotojas daro splice() failo į AF_ALG lizdą, kriptografinė posistemė gauna tiesiogines nuorodas į to failo puslapio podėlio puslapius. Ne kopijas — lygiai tuos pačius fizinius puslapius, iš kurių branduolys skaitys failą kito execve() metu.
KAIP VEIKIA KLAIDA
AEAD dešifravimo per AF_ALG metu įvesties srautas atrodo taip: AAD (susietos autentifikavimo duomenys) || šifratekstas || autentifikavimo žyma. Kodas faile algif_aead.c nustato operaciją vietoje (in-place): tas pats išsklaidytasis sąrašas (scatterlist) tarnauja ir kaip įvestis, ir kaip išvestis. AAD ir šifratekstas kopijuojami iš TX buferio į RX buferį per memcpy_sglist. Tačiau žyma — paskutiniai keli įvesties baitai — nekopijuojama. Vietoj to puslapio podėlio puslapiai su žyma prijungiami prie išvesties išsklaidytojo sąrašo per sg_chain(). Galiausiai išvesties išsklaidytasis sąrašas atrodo taip: vartotojo RX buferis || tikslinio failo puslapio podėlio puslapiai.
Čia į žaidimą įžengia authencesn. Tai AEAD apvalkalas IPsec protokolui su išplėstinių sekos numerių palaikymu. IPsec 64 bitų sekos numeris suskaidytas į vyresnįjį pusę (seqno_hi, AAD baitai 0–3) ir jaunesnįjį (seqno_lo, baitai 4–7). HMAC skaičiavimui authencesn turi perstatyti šiuos baitus — ir tam naudoja paskirties išsklaidytąjį sąrašą kaip laikiną darbo erdvę. Konkrečiai: funkcija crypto_authenc_esn_decrypt() vykdo iškvietimą scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1) — 4 baitų įrašymą į poziciją assoclen + cryptlen, tai yra iš karto už autentifikavimo žymos.
Įprastomis sąlygomis tai būtų vartotojo buferio sritis. Tačiau kai žymos puslapiai prijungti per sg_chain() prie išvesties išsklaidytojo sąrašo — scatterwalk pereina iš RX buferio tiesiai į tikslinio failo puslapio podėlį ir įrašo 4 baitus, kuriuos visiškai kontroliuoja užpuolikas. HMAC tikrinimas stringa, recvmsg() grąžina klaidą — tačiau 4 baitai jau įrašyti. Branduolys nepažymi puslapio kaip pakeisto, todėl failas diske lieka nepakeistas. Pakeitimas tik atmintyje — bet būtent iš atminties branduolys skaito failą execve() metu.
KAIP TAI IŠNAUDOJAMA
Užpuolikas visiškai kontroliuoja tris įrašymo parametrus: kurį failą (bet kurį, skaitomą dabartiniam vartotojui), kokią poziciją failo viduje (per splice poslinkio, ilgio ir assoclen pasirinkimą), ir kokią reikšmę įrašyti (sendmsg AAD baitai 4–7). Tai ne atsitiktinis perrašymas — tai tikslus, kartojamas, programuojamas savavališkos reikšmės įrašymas į savavališką bet kurio failo puslapio podėlio poziciją.
Eksploitas nukreiptas į /usr/bin/su — setuid-root dvejetainį failą, esantį visose testuotose distribucijose. Pirmiausia atidaromas AF_ALG lizdas ir prisiregistruojama prie šablono authencesn(hmac(sha256),cbc(aes)). Nustatomas raktas. Priimamas užklausos lizdas. Jokių teisių nereikia — AF_ALG pagal nutylėjimą prieinamas bet kuriam vartotojui.
Tada kiekvienam 4 baitų naudingiausios apkrovos fragmentui konstruojama sendmsg() + splice() pora. AAD perneša reikiamus 4 baitus (seqno_lo). Splice deskriptorius tiekia /usr/bin/su puslapio podėlio puslapius reikiamu poslinkiu. Parametrai — assoclen, splice ilgis ir poslinkis — parenkami taip, kad laikinasis įrašymas pataikytų tiksliai į reikiamą dvejetainio failo .text sekcijos poziciją.
Iškvietimas recv() paleidžia dešifravimą. authencesn viduje vyksta laikinasis įrašymas į dst[assoclen + cryptlen] — scatterwalk pereina iš RX buferio į puslapio podėlį ir įrašo 4 baitus. HMAC tikrinimas iš karto stringa, recvmsg() grąžina klaidą — tačiau įrašymas įvyko. Kai visi apvalkalo kodo fragmentai sudėlioti, užpuolikas kviečia execve("/usr/bin/su"). Branduolys įkelia dvejetainį failą iš puslapio podėlio — kur jau gyvena apvalkalo kodas. Kadangi su yra setuid-root, kodas vykdomas kaip UID 0.
KAIP KLAIDA IŠGYVENO DEVYNERIUS METUS
Šios pažeidžiamybės istorija — vadovėlinis pavyzdys, kaip trys nepriklausomai saugūs pakeitimai sudaro mirtinąjį derinį. 2011 metais į branduolį buvo pridėtas authencesn IPsec ESP palaikymui su 64 bitų sekos numeriais (RFC 4303). Laikinasis įrašymas į paskirties išsklaidytąjį sąrašą buvo saugus: susieti duomenys gyveno atskirame išsklaidytajame sąraše, o vienintelis iškvietėjas buvo vidinis branduolio xfrm sluoksnis.
2015 metais AF_ALG gavo AEAD palaikymą, o authencesn buvo perkeltas į naują AEAD sąsają. Tačiau algif_aead.c tuomet veikė ne vietoje (out-of-place): req->src ir req->dst buvo atskiri išsklaidytieji sąrašai. Puslapio podėlio puslapiai patekdavo į src (tik skaitymui), laikinasis įrašymas ėjo į dst (vartotojo buferį). Vis dar saugu.
2017 metais į algif_aead.c buvo pridėta optimizacija — vietinės operacijos (commit 72548b093ee3). AAD ir šifratekstas buvo kopijuojami į RX buferį, tačiau žymos puslapiai buvo prijungiami nuoroda per sg_chain(), o tada req->src = req->dst. Puslapio podėlio puslapiai dabar atsidūrė rašomame paskirties išsklaidytajame sąraše. authencesn laikinasis įrašymas kirto buferio ribą ir pateko į juos. Niekas nesusiejo 2017 metų optimizacijos su 2015 metų authencesn laikinuoju įrašymu ir to paties laikotarpio splice kelio. Klaida egzistavo trijų pakeitimų sankirtos taške — ir išliko nematoma beveik dešimtmetį.
KODĖL RANDA DABAR
Copy Fail nebuvo rastas klasikiniu aplauzos testavimu ar rankiniu auditu. Taeyang Lee suformulavo hipotezę — kad splice() gali tiekti puslapio podėlio nuorodas į kriptografinį TX išsklaidytąjį sąrašą — ir perdavė ją Xint Code įrankiui su užduotimi patikrinti visą kriptografinę posistemę pagal šį šabloną. Po valandos rezultatuose Copy Fail buvo aukščiausio sunkumo radinys.
Tai platesnės 2026 metų tendencijos dalis: DI pagalba pagrįsti pažeidžiamybių tyrimai pradeda sistemingai aprėpti atakų paviršius, kurie metų metus nebuvo tikrinami. Kelių posistemių sankirta — kriptografija, VFS, lizdo API — yra būtent tokia vieta, kur žmogus auditorius galėjo lengvai praleisti ryšį tarp pakeitimų, išsibarsčiusių per skirtingus failus ir metus. Modelis mato visą iškvietimų grafą iš karto ir gali patikrinti konkrečią hipotezę per laiką, nepasiekiamą rankinei analizei.
LAIKO JUOSTA
2026 m. kovo 23 d. Taeyang Lee pranešė apie pažeidžiamybę Linux branduolio saugumo komandai. Kitą dieną atėjo patvirtinimas, po dviejų dienų — pataisymas buvo pasiūlytas ir peržiūrėtas. 2026 m. balandžio 1 d. pataisymas pateko į pagrindinį branduolį (commit a664bf3d603d). Balandžio 22 d. buvo priskirtas CVE-2026-31431. Balandžio 29 d. — viešas atskleidimas su pilnu techniniu aprašymu ir koncepcijos įrodymo eksploitu copy.fail svetainėje.
Koordinuotas atskleidimas užtruko 37 dienas nuo pranešimo iki paskelbimo — greitai pagal Linux branduolio standartus. Saugumo komanda per kelias valandas patvirtino kritiškumą ir paspartino pataisymo įtraukimą. Distribucijų branduolio atnaujinimai pasirodė per kelias dienas po viešo atskleidimo per įprastus branduolio paketo atnaujinimo kanalus.
KODĖL TAI SVARBU
Copy Fail uždaro svarbią mąstymo spąstą: „failas diske nepasikeitė, vadinasi sistema švari”. Vientisumo tikrinimo įrankiai, lyginantys kontrolines sumas su disku (AIDE disko tikrinimo režimu, Tripwire), Copy Fail neaptiks — nes failas diske tikrai nepasikeitė. Pasikeitė tik puslapio podėlis atmintyje, o tai yra tas, iš ko branduolys realiai vykdo kodą. FIM turi būti papildytas nenormalaus proceso elgsenos stebėjimu, o ne tik failų kontrolinėmis sumomis.
Antrasis svarbus aspektas — konteineriai. Puslapio podėlis bendras serveriui ir visiems jame veikiantiems konteineriams. Copy Fail — tai ne tik LPE: tai konteinerio pabėgimo primitivas. Vienas pažeistas pod su neprivilegijuotu vartotoju gali gauti root visame Kubernetes mazge, modifikuodamas setuid dvejetainį failą bendrame serverio puslapio podėlyje. Xint paskelbė antrą techninį aprašymą — „From Pod to Host” — būtent šiam vektoriui.
Galiausiai devynerius metus nepastebėta klaida aktyviai plėtojamame Linux branduolyje primena: kelių posistemių sankirta yra akliausias audito taškas. authencesn, AF_ALG ir splice() — trys komponentai, kiekvienas tikrintas atskirai. Niekas jų netikino kartu.
ATNAUJINIMAS
Pataisymas atšaukia 2017 metų optimizaciją: req->src dabar rodo į TX SGL (kuriame gali būti puslapio podėlio puslapiai iš splice), o req->dst — į RX SGL (vartotojo buferį). Puslapio podėlio puslapiai nebepatenka į rašomą paskirties išsklaidytąjį sąrašą. Pataisymą sudaro trys upstream komitai; pagrindinis — a664bf3d603d, kuris pašalina vietinę operaciją iš algif_aead.c.
Debian ir Ubuntu sistemose pradėkite patikrinę dabartinę branduolio versiją — įsiminkite išvestį, kad galėtumėte patvirtinti, jog po atnaujinimo ji pasikeis:
uname -r
Tada atnaujinkite ir perkraukite:
sudo apt update && sudo apt upgrade -y
Po perkrovimo vėl vykdykite uname -r — versija turi pasikeisti. Įdiegtas paketas be perkrovimo neapsaugo: sistema toliau veikia su pažeidžiamu branduoliu atmintyje.
Modprobe.d aplinkkelis, paplitęs internete po viešo atskleidimo, veikia tik Debian ir Ubuntu sistemose. RHEL, AlmaLinux, CloudLinux ir kitose RHEL šeimos distribucijose modulis algif_aead sukompiliuotas tiesiai į branduolį ir nėra atskiras įkeliamas modulis. Komandos vykdomos be klaidų, tačiau nieko nedaro — ir sukuria klaidingą saugumo jausmą. Greitas patikrinimas:
modinfo algif_aead | grep filename
Išvestis (builtin) reiškia, kad modulis įmontuotas, o modprobe.d aplinkkelis neveiks. Jei rodomas kelias iki .ko failo — modulis įkeliamas ir aplinkkelis suveiks.
RHEL šeimos sistemoms, kur negalima nedelsiant atnaujinti, CloudLinux rekomendacijoje aprašytas veikiantis laikinas aplinkkelis per initcall_blacklist. Jis reikalauja perkrovimo, tačiau uždaro atakos paviršių nekeičiant branduolio:
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot
Po perkrovimo patikrinkite, kad parametras aktyvus:
sudo grubby --info=ALL | grep initcall_blacklist
Kai pataisytas branduolys įdiegtas — pašalinkite aplinkkelį ir perkraukite dar kartą:
sudo grubby --update-kernel=ALL --remove-args="initcall_blacklist=algif_aead_init"
sudo reboot
Svarbu: dm-crypt/LUKS, kTLS, IPsec, SSH ir standartiniai OpenSSL/GnuTLS kūriniai nepriklauso nuo AF_ALG ir nuo šio aplinkkelio nenukentės. Paveiktos bus tik programos, tiesiogiai naudojančios AF_ALG AEAD operacijoms — tai reta įprastame web serveryje.
IŠVADOS
Copy Fail yra reta pažeidžiamybė, kurioje techninis mechanizmas kartu ir elegantiškas, ir destruktyvus. Trys kodo eilutės vienoje funkcijoje, pridėtos kaip optimizacija prieš devynerius metus, pavertė standartinę šifravimo API įrankiu rašymui į bet kurio sistemos failo puslapio podėlį. Rezultatas — deterministinis root per 732 Python baitus visose pagrindinėse Linux distribucijose.
Sistemos administratoriui veiksmas paprastas: atnaujinti branduolį ir perkrauti. Modprobe.d aplinkkelio neliesti, jei esate RHEL šeimoje. Dirbantiems su Kubernetes: Xint antrasis techninis aprašymas apie konteinerio pabėgimą per šį primitivą jau paskelbtas — perskaitykite.
Paveiktos branduolio versijos:
Visi Linux branduoliai, išleisti nuo 2017 iki 2026 m. kovo — tai yra versijos nuo ~4.14 iki 6.18 imtinai.
Nepaveikti:
Branduoliai senesni nei 2017 m. (iki ~4.14) — juose nebuvo vietinės optimizacijos, sukūrusios šią klaidą. CloudLinux 7 (klasikinis) taip pat nepaveiktas.
Pataisyta pradedant nuo:
| Distribucija | Pirmoji saugi branduolio versija |
|---|---|
| Debian / Ubuntu | bet kuris branduolys atnaujintas po 2026 m. balandžio 1 d. — per apt upgrade |
| RHEL 8 / AlmaLinux 8 / CL8 | 4.18.0-553.121.1.el8 |
| RHEL 9 / AlmaLinux 9 / CL9 | 5.14.0-611.49.2.el9_7 |
| RHEL 10 / AlmaLinux 10 / CL10 | 6.12.0-124.52.2.el10_1 |
| Amazon Linux 2023 | per dnf upgrade po 2026 m. balandžio |
Patikrinkite savo versiją:
uname -r
