Hlavní navigace

„Špinavá kráva“ přepisuje soubory pro čtení, v jádře je 9 let

24. 10. 2016
Doba čtení: 6 minut

Sdílet

Dirty COW je název vážné bezpečnostní mezery, která byla před několika dny objevena v linuxovém jádře. Ohrožuje systémy napříč distribucemi, protože je součástí kódu od roku 2007.

V linuxovém jádře byla objevena velmi vážná bezpečnostní chyba, která ohrožuje prakticky všechny linuxové distribuce a dovoluje libovolnému uživateli přepsat systémové soubory. Může tak velmi snadno získat například rootovská práva a stát se pánem systému. Pokud provozujete linuxové servery, desktopy, routery nebo další zařízení, měli byste co nejdříve aktualizovat.

Chyba je součástí subsystému správy paměti, konkrétně v mechanismu copy-on-write. Libovolná aplikace jej může ovlivnit tak, že dokáže zapsat do části paměti, ve které je namapován soubor a která by měla zůstat pouze ke čtení. Změny této paměti se pak zapíší na disk a běžný uživatel v tu chvíli přepsal například systémový konfigurační soubor či binárku s nastaveným setuid bitem. Neštěstí je na světě a uživatel přebírá kontrolu nad systémem.

Dlouhověká a nebezpečná

Chyba je pozoruhodná z několika různých hledisek: je velmi snadno zneužitelná, exploity už kolují internetem a jsou velmi spolehlivé a především chyba je v jádře od verze 2.6.22, tedy od roku 2007. Sám Linus Torvalds se chybu pokoušel opravit už před 11 lety, kdy ještě nebyla tak dobře zneužitelná. Už tehdy si uvědomoval možné dopady této chyby.

There's no real guarantee that handle_mm_fault() will always be able to
break a COW situation - if an update from another thread ends up
modifying the page table some way, handle_mm_fault() may end up
requiring us to re-try the operation.

That's normally fine, but get_user_pages() ended up re-trying it as a
read, and thus a write access could in theory end up losing the dirty
bit or be done on a page that had not been properly COW'ed.

This makes get_user_pages() always retry write accesses as write
accesses by making "follow_page()" require that a writable follow has
the dirty bit set.  That simplifies the code and solves the race: if the
COW break fails for some reason, we'll just loop around and try again.

 Patch byl ale odstraněn kvůli problémům na platformě s390. O čtyři roky později se změnily mechanismy v jádře tak, že se z malého problému stal problém obrovský, který zůstal v jádře nepovšimnut dalších devět let.

Chybu nyní objevil Phil Oester a dostala označení CVE-2016–5195. Jak už bývá dobrým zvykem, má chyba vlastní webovou stránku, maskota i název: Dirty COW.

Logo „Špinavé krávy“

Opravný patch je již vydán a je součástí uvolněných jader 4.8.3, 4.7.9 a 4.4.26. Některé distribuce už vydaly záplatované balíčky: například Red Hat, Debian a Ubuntu. Pokud někde provozujete libovolný linuxový systém, měli byste co nejdříve nainstalovat záplatu.

Podle některých zdrojů se problém týká také operačního systému Android, který je také na Linuxu postaven. Tady ovšem záleží na výrobci konkrétního přístroje, jak se k záplatě postaví. Zároveň je jisté, že postižené jádro nalezneme v řadě různých přístrojů, kamer, routerů a dalších věcí připojených k internetu. K těm se záplata nemusí dostat nikdy.

Pohled do kravské duše

Tolik k obecným informacím pro správce. Nyní se můžeme podívat podrobně na to, co chybu způsobuje a jak je možné ji konkrétně zneužít. Chyba má v názvu COW, neboť zneužívá mechanismus copy-on-write. Ten je součástí linuxového jádra, aby zefektivnil správu paměti v operačním systému.

Mimo jiné to umožňuje šetřit pamětí tak, že procesy mohou sdílet společná data do chvíle než potřebují provést vlastní změnu. Teprve v tu chvíli jádro měněný obsah zkopíruje do nové stránky a tu přidělí procesu, který do ní změny provede. Ostatní procesy stále sdílejí původní obsah a pokud do dané části paměti nikdy nepotřebují nic zapsat, nepotřebují ani vlastní kopii.

Exploit dokáže mechanismus narušit. Využívá se při tom takzvaný souběh (angl. race condition), kdy chyba vznikne tím, že se nad daty operuje v nesprávném a neočekávaném pořadí. Exploit si nejprve vyhlédne soubor, ze kterého sice může číst, ale nemá do něj právo zapisovat. Poté jej otevře pouze ke čtení a pomocí funkce mmap() si jej nechá jádrem namapovat do vlastního adresního prostoru.

Během této operace exploit opakovaně na mapování volá funkci madvise(), kterou se jádru předávají oznámení o využití paměti. V tomto případě se předává hodnota MADV_DONTNEED, tedy že nezamýšlí namapovanou paměť vůbec využívat.

V jiném vlákně stejného procesu si exploit otevře speciální soubor /proc/self/mem pro čtení a zápis. Tento pseudo-soubor dovoluje procesu pracovat s vlastní pamětí jako se souborem. Součástí paměti procesu je ale i namapovaný systémový soubor a paměť je skrze zmíněný speciální soubor zapisovatelná. Vlákno začne pomocí běžných souborových operací opakovaně část paměti v souboru mem přepisovat. Potud je všechno v pořádku, protože změny zapsané tímto způsobem do paměti procesu se objeví v kopii paměti (zajištěné CoW) a jsou k dispozici jen uvnitř procesu – neovlivní původní soubor.

Shrňme si situaci v tomto bodě: Nyní má proces exploitu namapovaný nezapisovatelný soubor do své vlastní paměti. Jedno vlákno na tento objekt stále sype volání madvise() a druhé vlákno do něj neustále zapisuje. Zápis do této paměti vede ke spuštění mechanismu copy-on-write a jádro by mělo vytvořit novou samostatnou stránku v paměti procesu, která bude kopií původního namapovaného souboru. Obsah by tak měl být změněn pouze v paměťovém prostoru procesu – ne v původní paměti určené pouze pro čtení.

Bohužel kvůli zmíněné chybě sice dojde k vytvoření kopie, ale procesu je dovoleno přepsat i obsah původní paměti. Jádro pak změny přepíše na disk a dílo je dokonáno – exploit změnil obsah souboru na disku, ke kterému neměl práva zápisu.

Podstata chyby

Co se ale vlastně stalo a proč selhaly mechanismy jádra? Problém nastává v souběhu zápisu s neustále volanou funkcí madvise(). Kvůli chybě totiž nedojde k oddělení zapisovatelné kopie paměti od namapovaného souboru na disku.

Jakmile se proces pokouší zapisovat do paměti určené pro čtení, je vyvolána výjimka. S tou se jádro za obvyklých okolností vypořádá právě tak, že spustí copy-on-write, je alokována nová stránka a do ní je zkopírován obsah té původní.

Funkce madvise() ale během toho procesu jádru sdělí, že paměť nebude potřeba a jádro příslušnou stránku zahodí. Pokud se to stane během zápisu do /proc/self/mem, dojde k nekonzistentnímu stavu – stránky s namapovaným obsahem souboru jsou odstraněny, ale zápis do této oblasti paměti je už povolen. Přestože se měl změněný obsah správně objevit v nové stránce paměti, nahradí v paměti původní namapovaný soubor z disku. Stránka je totiž již uvolněna pro zápis, ale jádro nestihlo aktualizovat tabulky stránek (exploit to zkouší tolikrát, až má štěstí a trefí se) a domnívá se, že v daném místě je stále ještě namapovaný původní soubor. Změny jsou pak automaticky poslány na disk.

UX DAy - tip 2

Poznámka: Varianta se zápisem přes /proc není jediná, objevil se také exploit využívající ptrace. Zatímco v tomto článku popsaný postup nefunguje na distribucích využívajících grsecurity nebo zamezující zápisu do /proc/self/mem  (třeba Red Hat), postup s ptrace je funkční i na nich.

zmíněná oprava je vlastně triviální – upravuje dva řádky a jeden do kódu přidává. Výsledkem je nový příznak, který signalizuje provedenou operaci copy-on-write. Upravené řádky pak zajistí kontrolu příznaku a dohlédnou na to, že se stránka původně použitá pro namapování souboru neodemkne a nezpřístupní pro zápis.

Použité zdroje

Byl pro vás článek přínosný?

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.