Tinklaraštis

Januscape: kaip vienas guest procesas nuverčia visą fizinį serverį per KVM

Januscape_CVE-2026-53359
CVE / Linux / Saugumas

Januscape: kaip vienas guest procesas nuverčia visą fizinį serverį per KVM

Išsinuomojote paprastą VPS iš debesijos paslaugų teikėjo. Viduje turite root teises — juk tai jūsų VM, administruojate ją kaip norite. Netgi įjungėte nested virtualizaciją, kad testams galėtumėte paleisti Docker konteinerius su savo VM viduje. Viską izoliuoja hipervizorius, tad kas čia gali suklysti? O suklysti gali tiek, kad kelios eilutės kodo jūsų pačių VM viduje nuverčia ne tik ją, bet ir visą fizinį serverį — kartu su visais kitais to serverio nuomininkais. Ne dėl Docker klaidos, ne dėl QEMU pažeidžiamumo. Dėl klaidos pačiame Linux branduolyje, KVM kode, kuris distribucijose keliauja nuo 2010 metų.

Tai CVE-2026-53359, pavadinta Januscape — use-after-free KVM/x86 shadow MMU (Memory Management Unit) posistemyje. Klaida vienodai veikia ir Intel, ir AMD platformose, o tai jau savaime retenybė: tokio lygio pažeidžiamumai dažniausiai priklauso vienai architektūrai. Paskelbtas PoC garantuotai sukelia hosto paniką. Egzistuoja ir antras, neviešas eksploitas — tyrėjo Hyunwoo Kim (@v4bel) teigimu, jis suteikia pilną kodo vykdymą su root teisėmis hoste, tačiau jo publikavimas kol kas atidėtas. Red Hat įvertino pažeidžiamumą kaip Important, CVSSv3 7.0, CWE-825 (use-after-free). NVD savo įvertinimo dar nesuteikė, bet tai nereiškia, kad klaida nesvarbi — nuo 2026 m. balandžio 15 d. NIST iš esmės apribojo naujų CVE įrašų praturtinimą (enrichment) trimis prioritetinėmis kategorijomis: CISA KEV sąrašu, JAV federaline programine įranga ir kritine programine įranga pagal EO 14028. Visais kitais atvejais NVD tiesiog nebeskaičiuoja savo CVSS balo, jei jį jau pateikė CNA — šiuo atveju Red Hat. Todėl laukti oficialaus NVD įvertinimo čia neverta: jo tiesiog nebus.

KAS YRA SHADOW MMU KVM

Kad VM veiktų greitai, šiuolaikiniai Intel ir AMD procesoriai aparatiškai transliuoja guest atminties adresus į fizinius hosto adresus — tai Intel EPT ir AMD NPT, antras transliacijos lygis, įdiegtas tiesiai geležyje. Būtent tuo ir naudojasi įprastas KVM hostas šiuolaikinėje įrangoje: guest puslapių lentelių niekas neliečia, visą darbą atlieka aparatinis EPT/NPT per vadinamąjį TDP MMU.

Vis dėlto yra vienas niuansas — nested virtualizacija. Jei jūsų VM (L1) viduje paleidžiate dar vieną VM (L2) — o būtent taip veikia Docker su įdėtiniais konteineriais, CI/CD testavimo laboratorijos ar bet koks VM-viduje-VM scenarijus, — aparatinis EPT/NPT geba aptarnauti tik vieną transliacijos lygį. Antrą lygį, kurį L1 sukūrė L2, fizinis hostas (L0) priverstas emuliuoti programiškai. Būtent šiam tikslui KVM turi shadow MMU — legacy kodą, kuris rankiniu būdu atkartoja guest puslapių lenteles atskiroje struct kvm_mmu_page struktūroje. Tai senas ir sudėtingas kodo ruožas, kuris šiuolaikiniuose hostuose praktiškai nenaudojamas — išskyrus atvejus su nested virtualizacija, kur be jo neapsieisite.

KAIP VEIKIA KLAIDA

Shadow MMU viduje veikia funkcija kvm_mmu_get_child_sp(), kuri sprendžia, ar galima pakartotinai panaudoti jau esamą shadow puslapį naujam lentelės mazgui, ar reikia kurti naują. Iki pataisos sprendimas buvo priimamas pagal vieną kriterijų: ar jau susieto shadow puslapio gfn (guest frame number, guest puslapio numeris) sutampa su tuo, kurio reikia dabar. Jei sutampa, funkcija grąžina -EEXIST ir pakartotinai panaudoja tą patį puslapį.

Bėda ta, kad shadow puslapis turi ne tik gfn, bet ir kitą atributą — role, puslapio tipą. Yra direct puslapiai (direct=1), kuriuos KVM sukuria tiesiogiai skaidydamas didelį 2MB puslapį į 4KB puslapius. Ir yra indirect puslapiai (direct=0), kurie iš tikrųjų atkartoja guest puslapių lentelę. Įprastame shadow MMU fetch kelyje abi situacijos gali susidaryti to paties gfn atveju: pirmiausia gali prireikti indirect puslapio guest lentelei, paskui — direct puslapio suskaidytai didelei atminčiai, arba atvirkščiai. Senasis kodas kvm_mmu_get_child_sp() lygino tik gfn, o role ignoravo. Todėl jei per tam tikrą sptep jau buvo susietas direct puslapis, o fetch tam pačiam gfn paprašo indirect puslapio, funkcija vis tiek atsakys „sutampa” ir atiduos puslapį su neteisinga role.

Toks pakartotinis panaudojimas suardo shadow puslapio gyvavimo ciklo ir parent nuorodų apskaitą. Susidaro būsena, kai vienas shadow puslapis jau atlaisvintas, o kitas vis dar turi nuorodą į jį — tai orphaned parent pointer. Kai shadow MMU vėliau bando išvalyti šią nuorodą per drop_parent_pte()mmu_spte_clear_no_track(), jis įrašo fiksuotą reikšmę (SHADOW_NONPRESENT_VALUE, x86-64 architektūroje tai 0x8000000000000000) į atmintį, kuri tuo metu jau gali priklausyti visai kitam branduolio objektui. Tai klasikinis use-after-free write: užpuolikas nevaldo pačios įrašomos reikšmės, tačiau valdo, į kurį slotą (offset) atlaisvintame puslapyje pateks šis įrašas.

KAIP TAI IŠNAUDOJAMA

Paskelbtas PoC — tai kernel modulis, įkeliamas guest VM (L1) viduje. Modulis rankiniu būdu, apeidamas QEMU, tiesiogiai kuria įdėtinę L2 sistemą per neapdorotą VMX (Intel) arba SVM (AMD) — visas triukas vyksta pačiame hosto in-kernel KVM, be jokio kreipimosi į userspace VMM. Modulis suformuoja specifinę atminties topologiją: tas pats fizinis puslapis vienu metu naudojamas ir kaip didelio 2MB puslapio leaf, ir kaip lentelės puslapis (page table page) — tai ir yra pagrindinė sąlyga, kad klaida suveiktų.

Toliau paleidžiamos lenktynės (race condition). Vienas srautas be perstojo perjunginėja PDE įrašą tarp būsenų „didelis 2MB puslapis” ir „puslapių lentelė” tam pačiam gfn. Lygiagrečiai kiti srautai nuolat vykdo L2 kodą, generuodami page fault, kurie eina per tą patį PDE. Tarp momento, kai L0 užfiksuoja naują PDE reikšmę, ir momento, kai sena shadow nuoroda faktiškai pašalinama per kvm_page_track_write, atsiranda nedidelis langas. Jei fault pataiko būtent į šį langą, kvm_mmu_get_child_sp() iškviečiamas su nauja role tuo metu, kai sptep dar tebeturi child su sena role — ir klaida suveikia. Laimėti lenktynes gali užtrukti nuo kelių sekundžių iki kelių minučių, tačiau rezultatas neišvengiamas: anksčiau ar vėliau langas atsivers.

KAS VYKSTA TOLIAU — PRIKLAUSO NUO KONFIGŪRACIJOS

Nuo šio taško scenarijus šakojasi. Viešasis PoC eina paprastesniu ir patikimesniu keliu — ne rašo į svetimą atmintį, o sukelia save išduodantį duomenų sugadinimą. Pakartotinai panaudotame direct puslapyje nėra shadowed_translation lauko, todėl vietoje tikrojo leaf gfn jis apskaičiuojamas kaip sp->gfn + index. Kai per tokį puslapį įrengiamas leaf su kitokiu, tikru gfn, šis neatitikimas iš pradžių pasireiškia kaip WARN_ONCE („gfn mismatch under direct page”) — kol kas tik nekenksmingas įspėjimas žurnale. Tikrasis smūgis ateina vėliau: kai tas pats leaf šalinamas, branduolys ieško atitinkamo įrašo rmap struktūroje pagal apskaičiuotą gfn, bet jo neranda, nes įrašas buvo sukurtas su kitu, tikru gfn. Tada įmontuota patikra KVM_BUG_ON_DATA_CORRUPTION, esanti pte_list_remove() funkcijoje, tai palaiko duomenų sugadinimu. Sistemose su CONFIG_BUG_ON_DATA_CORRUPTION=y — o tai numatytasis nustatymas tokiuose distribucijose kaip RHEL — atitinkantis šį makrosą BUG_ON iškart nuverčia branduolį į paniką. Šiam keliui net nereikia, kad atlaisvintą atmintį būtų perėmęs koks nors konkretus objektas — pakanka vidinės branduolio integralumo patikros, jokia papildoma eksploatacija nereikalinga.

Antrasis, sudėtingesnis kelias — tas pats neviešas eksploitas, leidžiantis visiškai perimti hostą. Jam reikia, kad atlaisvintą shadow puslapį perimtų guest branduolio objektas, kuriame fiksuota reikšmė 0x8000000000000000 pataikytų į prasmingą lauką — klasikinis cross-cache use-after-free scenarijus, tik pritaikytas KVM MMU struktūroms. Pati klaida vienoda ir Intel, ir AMD atveju, nes glūdi bendroje KVM logikoje, o ne aparatiniuose platformų skirtumuose. Tačiau galutiniai viešojo PoC eksploatacijos žingsniai skiriasi priklausomai nuo gamintojo — tai matyti ir iš atskirų kodo šakų su amd=1 pačiame modulyje.

REALUS ATAKOS SCENARIJUS

Įsivaizduokime tipišką daugiavartotojišką debesijos hostingą — GCP, AWS ar bet kokį VPS teikėją, kuris klientams suteikia root teises jų pačių VM ir neblokuoja nested virtualizacijos. Užpuolikui reikia tik išsinuomoti vieną paprastą instanciją — jokių papildomų privilegijų nereikia, tik root savo VM viduje, o tai jau turi bet kuris tokio hostingo klientas. Jis įkelia kernel modulį, iš anksto iškėlęs standartinį kvm_intel arba kvm_amd (nes PoC dirba tiesiogiai su neapdorota VMX/SVM būsena), paleidžia lenktynes, ir per keletą sekundžių ar minučių fizinis hostas su jo VM patiria paniką. Kartu krinta ir visos kitos to paties fizinio serverio VM — kaimynai, apie kurių egzistavimą užpuolikas gali net nenutuokti.

Štai kaip tai atrodo praktikoje — realus panikos žurnalas, gautas tyrėjo RHEL sistemoje su branduoliu 6.12.0-211.26.1.el10_2.x86_64:

gfn mismatch under direct page 8a00 (expected 8b00, got 256e4)
WARNING: CPU: 6 PID: 974281 at arch/x86/kvm/mmu/mmu.c:689 kvm_mmu_page_set_translation.part.0+0xb7/0x130 [kvm]
CPU: 6 PID: 974281 Comm: qemu-kvm  6.12.0-211.26.1.el10_2.x86_64
kernel BUG at arch/x86/kvm/mmu/mmu.c:1091!
RIP: 0010:pte_list_remove.isra.0+0xd9/0xe0 [kvm]
CPU: 3 PID: 974278 Comm: qemu-kvm

Pirmiausia pasirodo tas pats įspėjimas apie gfn neatitikimą viename CPU ir su vienu PID, o po akimirkos — jau lemtinga kernel BUG klaida pte_list_remove funkcijoje kitame CPU ir su kitu PID, ir hostas nueina į paniką.

Yra ir mažiau įdomus, šalutinis vektorius — lokalios privilegijų eskalacijos galimybė per /dev/kvm. Tokiuose distribucijuose kaip RHEL šis įrenginys numatytai turi 0666 teises, tad rašyti jį gali bet kuris lokalus neprivilegijuotas vartotojas. Teoriškai tai reiškia, kad šį pažeidžiamumą galima paversti patikimu LPE keliu iki root teisių pačiame hoste, be jokios VM apskritai. Pats tyrėjas šį scenarijų laiko antraeiliu, palyginti su guest-to-host escape viešose debesijose — kur kas mažiau įdomiu taikiniu. Vis dėlto teises /dev/kvm susiaurinti verta bet kuriuo atveju — tai paprasta ir greita priemonė.

TIMELINE

Pažeidžiamas kodas atsirado 2010 m. rugpjūčio 1 d. commit’e 2032a93d66fa — tuomečio branduolio 2.6.36 laikais. Nuo tada jis išsilaikė nepastebėtas apie 16 KVM plėtros metų. Ir tai nebuvo pirmas kartas, kai tame pačiame kode aptinkama panaši problema: commit 0cb2af2ea66ad („KVM: x86: Fix shadow paging use-after-free due to unexpected GFN”, CVE-2026-46113, publikuota 2026 m. gegužės 28 d.) jau buvo uždaręs panašų shadow paging neatitikimą — ta klaida kildavo, kai PDE mapping buvo pakeičiamas iš guest išorės ir vėliau pašalinamas memslot, o rmap_remove() nerasdavo po šio pakeitimo sukurto įrašo. Tas patch’as išsprendė gfn neatitikimo atvejį. Tačiau, kaip aiškiai pripažįsta oficialus 81ccda30b4e8 commit’o changelog, liko neuždaryta panaši spraga — kai gfn sutampa, o role ne, ir būtent šio atvejo kvm_mmu_get_child_sp() netikrino. Gegužės pataisa uždarė vieną gfn/role neatitikimo pusę, o Januscape uždaro antrąją tos pačios loginės spragos pusę.

2026 m. birželio 12 d. tyrėjas Hyunwoo Kim (@v4bel) į [email protected] išsiuntė išsamią informaciją apie pažeidžiamumą kartu su veikiančiu eksploitu. Jau kitą dieną, birželio 13-ąją, KVM prižiūrėtojai Paolo Bonzini ir Sean Christopherson aptarė, kaip tvarkyti pataisą, ir Paolo parašė fiksą. Birželio 17 d. pataisa buvo paskelbta lore.kernel.org testavimui. Birželio 19 d. commit 81ccda30b4e8 pateko į mainline. Liepos 1 d. informacija nusiųsta į linux-distros pašto konferenciją su penkių dienų embargo. Liepos 4 d. oficialiai priskirtas CVE-2026-53359, ir tą pačią dieną pasirodė pataisytos stable branduolio versijos: 7.1.3, 6.18.38, 6.12.95, 6.6.144, 6.1.177, 5.15.211 ir 5.10.260. Pasibaigus embargo, liepos 6 d., informacija buvo paskelbta oss-security pašto konferencijoje kartu su išsamiu techniniu writeup.

Verta atkreipti dėmesį į tarpą tarp birželio 19 d. (pataisa jau mainline) ir liepos 4 d. (tik tada gautas CVE numeris) — beveik dvi savaites fiksas jau buvo viešai matomas branduolio git istorijoje, tačiau be susieto CVE jį rasti per įprastus pažeidžiamumų sekimo įrankius buvo praktiškai neįmanoma.

KAIP TAI IŠLIKO NEPASTEBĖTA 16 METŲ

Reikalas ne tas, kad kažkas ieškojo šios klaidos ir nerado — jos tiesiog beveik niekas tikslingai neieškojo būtent šioje vietoje. Shadow MMU KVM sistemoje yra legacy kelias, kuris daugumoje šiuolaikinių hostų apskritai nedirba: aparatinis EPT/NPT padengia didžiąją dalį darbo krūvių, o shadow kodas tyliai stovi nenaudojamas. Jis suveikia tik esant labai specifinei sąlygai — nested virtualizacijai, kai L1 pats tampa hipervizoriumi savo L2. Tai retas scenarijus „įprastuose” produkcijos serveriuose, tačiau visiškai kasdienis debesijos platformose, kuriose klientai vykdo CI/CD, testavimo aplinkas ar savo pačių įdėtines VM.

Be to, pati klaida nėra trivialus ribų tikrinimas — tai subtili loginė spraga: palyginimas atliktas pagal vieną struktūros lauką (gfn), o ne pagal du (gfn ir role). Tokio pobūdžio klaidos neaptinka joks statinis analizatorius, nebent jis specifiškai suvokia KVM MMU role laukų semantiką. Reikėjo žmogaus, kuris vienu metu išmano shadow paging subtilybes ir tikslingai fuzzina arba rankiniu būdu analizuoja būtent šią retai naudojamą kodo šaką.

KODĖL TAI ATRANDAMA BŪTENT DABAR

Januscape nėra pavienis atvejis — tai dalis serijos: per pastaruosius du mėnesius tai jau trečias didelis Hyunwoo Kim atskleistas branduolio pažeidžiamumas. Gegužę jis paskelbė Dirty Frag (CVE-2026-43284 / CVE-2026-43500) — page-cache write klaidų grandinę, suteikiančią determinuotą root teisę daugumoje distribucijų ir tęsiančią tą pačią pažeidžiamumų klasę kaip Dirty Pipe ir Copy Fail. Birželį pasirodė ITScape (CVE-2026-46316) — pirmasis viešai pademonstruotas guest-to-host escape KVM/arm64 platformoje per race condition virtualiame pertraukčių valdiklyje. Januscape užbaigia x86 pusę tos pačios tyrimų programos.

Vienas šios bangos variklių — Google kvmCTF, atlygio programa būtent už guest-to-host escape KVM sistemoje, siūlanti iki 250 000 dolerių prizą už visišką pabėgimą. Būtent per kvmCTF Januscape iš pradžių ir buvo pateikta kaip zero-day paraiška. Ši programa sąmoningai skatina tokio pobūdžio tyrimus — gilų retai naudojamų, tačiau kritiškų pasekmių kodo kelių, tokių kaip shadow MMU, auditą, o ne paviršutinišką populiarių API fuzzingą.

KODĖL TAI SVARBU

Pasekmių mastas priklauso nuo to, kas jūs esate. Jei esate debesijos teikėjas ar hostingo bendrovė, suteikianti klientams root teises jų VM viduje ir numatytai neblokuojanti nested virtualizacijos — jums gresia, kad vienas klientas nuvers fizinį hostą kartu su visais kaimynais. Tai ir reputacijos rizika, ir SLA rizika vienu metu: klientai, kurie nieko blogo nepadarė, praranda savo paslaugas dėl svetimo VPS toje pačioje mašinoje. Jei esate tokio hostingo klientas ir pasitikite hipervizoriaus izoliacija kaip saugumo riba — ši riba pralaidi kaip tik tose konfigūracijose, kuriose įjungta nested virtualizacija, nepriklausomai nuo to, kaip gerai apsaugota jūsų pačių VM viduje.

Verta pažymėti atskirai: klaida glūdi grynajame KVM branduolio kode, o ne QEMU ar bet kuriame userspace VMM. Tai reiškia, kad ji vienodai aktuali tiek atvirojo steko debesims (KVM + QEMU + libvirt), tiek nuosavybinėms hiperskaleriaus platformoms, savo virtualizaciją statančioms virš in-kernel KVM — juk ir GCP, ir AWS pagrinde naudoja būtent KVM. Klaidos universalumas tarp Intel ir AMD papildomai išplečia atakos paviršių — paprastai reikia uždaryti tik vieną architektūrą, čia iš karto abi.

ATNAUJINIMAS

Pataisa iš esmės yra viena eilutė kvm_mmu_get_child_sp() funkcijoje: prie shadow puslapio pakartotinio panaudojimo sąlygos pridėta papildoma patikra spte_to_child_sp(*sptep)->role.word == role.word — tai reiškia, kad dabar puslapis pakartotinai panaudojamas tik tuo atveju, jei sutampa ir gfn, ir role. Commit 81ccda30b4e8 pateko į mainline 2026 m. birželio 19 d. ir buvo įtrauktas į stable šakas 7.1.3, 6.18.38, 6.12.95, 6.6.144, 6.1.177, 5.15.211 ir 5.10.260, išleistas 2026 m. liepos 4 d. Red Hat šią problemą registravo kaip CVE-2026-53359 (Bugzilla 2497033), Important, CVSSv3 7.0, su pataisomis RHEL 6-10 versijoms; AlmaLinux paruošė testines sąrankas 8, 9 ir 10 šakoms.

Branduolio atnaujinimas atliekamas standartiniu jūsų distribucijos būdu:

# Debian/Ubuntu
sudo apt update && sudo apt upgrade && sudo reboot

# RHEL/AlmaLinux/Rocky
sudo dnf update kernel && sudo reboot

Nepasitikėkite vien tik uname -r rodoma versija — distribucijų backportai dažnai keičia versijos numerį kitaip, nei tai daroma upstream. Patikimiau patikrinti jūsų branduolio paketo changelog, ar jame minima CVE-2026-53359 arba commit 81ccda30b4e8:

# Debian / Ubuntu
apt-get changelog linux-image-$(uname -r)

# RHEL / AlmaLinux
rpm -q --changelog kernel-$(uname -r)

Jei skubus branduolio atnaujinimas šiuo metu neįmanomas, yra laikina priemonė, visiškai pašalinanti šį atakos vektorių nepatikimiems guest — išjungti nested virtualizaciją hoste.

# Intel
echo "options kvm_intel nested=0" | sudo tee /etc/modprobe.d/kvm-nested.conf
sudo rmmod kvm_intel
sudo modprobe kvm_intel

# AMD
echo "options kvm_amd nested=0" | sudo tee /etc/modprobe.d/kvm-nested.conf
sudo rmmod kvm_amd
sudo modprobe kvm_amd

Atkreipkite dėmesį: rmmod atsisakys iškelti modulį, jei hoste šiuo metu veikia VM — jos laiko modulį užimtą. Tokiu atveju prieš iškeldami modulį sustabdykite guest mašinas, arba geriau suplanuokite tai kaip techninę priežiūrą su pilnu hosto perkrovimu — praktikoje tai patikimiau, nei rankiniu būdu iškelti modulį veikiant apkrovai.

Tai vis dėlto tik laikina priemonė — ji apsaugo tik nuo tų guest, kuriems nested virtualizacija apskritai nereikalinga. Jei jūsų infrastruktūra ją naudoja (pavyzdžiui, parduodate klientams galimybę paleisti VM VM viduje), vienintelis tikrai patikimas sprendimas — branduolio pataisa. Papildomai tuose distribucijuose, kur /dev/kvm numatytai turi 0666 teises, verta jas susiaurinti, kad sumažintumėte lokalios eskalacijos riziką:

ls -l /dev/kvm
sudo chmod 0660 /dev/kvm

IŠVADOS

Jei esate debesijos teikėjas arba valdote daugiavartotojišką KVM hostingą su įjungta nested virtualizacija — branduolio atnaujinimas iki 7.1.3 / 6.18.38 / 6.12.95 / 6.6.144 / 6.1.177 / 5.15.211 / 5.10.260 (arba lygiavertio distribucijos backporto su commit 81ccda30b4e8) nėra tas dalykas, kurį galima atidėlioti: delsimo kaina — visų VM paveiktuose fiziniuose hostuose kritimas vienu metu. Jei pataisos taikyti dabar neįmanoma, išjunkite nested virtualizaciją nepatikimiems guest jau šiandien — tai užims vos penkias minutes.

Jei tiesiog nuomojatės VPS iš didelio teikėjo, jokių tiesioginių veiksmų iš jūsų pusės nereikia — atsakomybė už hipervizoriaus pataisymą tenka teikėjui. Vis dėlto verta paklausti jo apie pataisymo statusą, ypač jei patys naudojate nested virtualizaciją CI/CD ar testavimo aplinkoms. Ir bet kuriuo atveju: /dev/kvm su 0666 teisėmis — tai atskira, su branduolio atnaujinimu tiesiogiai nesusijusi problema, kurią verta ištaisyti bet kuriame serveryje, kuriame vykdote KVM lokaliai.

Leave your thought here

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