náhled|Negativní dopady paměťových chyb v počítačových systémech Bezpečnost paměti je stav, kdy je paměť chráněna před různými softwarovými chybami a bezpečnostními zranitelnostmi při práci s přístupem k paměti, jako jsou přetečení bufferu (vyrovnávací paměti) a visící ukazatele (dangling pointers). Například jazyk Java je považován za paměťově bezpečný, protože jeho detekce chyb běhu kontroluje hranice pole a dereference ukazatelů. Naproti tomu jazyky C a C++ umožňují libovolnou aritmetiku ukazatelů s ukazateli implementovanými jako přímé adresy paměti bez kontroly mezí, a jsou tedy potenciálně nebezpečné pro paměť.

Historie Chyby paměti byly poprvé zvažovány v kontextu správy zdrojů a systémů sdílení času, ve snaze vyhnout se problémům, jako jsou fork bomby. Vývoj byl převážně teoretický až do vzniku červa Morris, který využíval přetečení vyrovnávací paměti v programu finger. Oblast počítačové bezpečnosti se poté rychle rozvíjela a eskalovala s množstvím nových útoků, jako je útok return-to-libc, a obranných technik, jako je neproveditelný zásobník (execute disable) a randomizace rozložení adresového prostoru. Randomizace zabraňuje většině útoků typu buffer overflow a vyžaduje, aby útočník použil heap spraying nebo jiné metody závislé na aplikaci k získání adres, i když její přijetí bylo pomalé. Nasazení této technologie je však obvykle omezeno na randomizaci knihoven a umístění zásobníku.

Dopad V roce 2019 bezpečnostní inženýr společnosti Microsoft oznámil, že 70 % všech bezpečnostních zranitelností bylo způsobeno problémy s bezpečností paměti. V roce 2020 tým společnosti Google podobně oznámil, že 70 % všech „závažných bezpečnostních chyb“ v Chromiu bylo způsobeno problémy s bezpečností paměti. Mnoho dalších významných zranitelností a exploitů v kritickém softwaru nakonec pramenilo z nedostatečné bezpečnosti paměti, včetně Heartbleed a dlouhodobé chyby eskalace oprávnění v sudo. Rozšířenost a závažnost zranitelností a exploitů vyplývajících z problémů s bezpečností paměti vedly několik bezpečnostních výzkumníků k popisu identifikace problémů s bezpečností paměti jako „střelbu na ryby v sudu“ (tedy jednoduché).

Přístupy Některé moderní programovací jazyky vysoké úrovně jsou ve výchozím nastavení paměťově bezpečné, i když ne zcela, protože kontrolují pouze svůj vlastní kód a ne systém, se kterým komunikují. Automatická správa paměti ve formě garbage collection je nejběžnější technikou pro prevenci některých problémů s bezpečností paměti, protože zabraňuje běžným chybám bezpečnosti paměti, jako je use-after-free, u všech dat alokovaných v rámci běhu jazyka. V kombinaci s automatickou kontrolou mezí u všech přístupů k polím a bez podpory aritmetiky surových ukazatelů poskytují jazyky s garbage collection silné záruky bezpečnosti paměti (i když tyto záruky mohou být slabší u operací na nízké úrovni, které jsou explicitně označeny jako nebezpečné, jako je použití cizího funkčního rozhraní). Výkonová zátěž garbage collection však činí tyto jazyky nevhodnými pro určité aplikace, u nichž je výkon kritický.

U jazyků, které používají ruční správu paměti, není bezpečnost paměti obvykle zaručena běhovým prostředím. Místo toho musí být vlastnosti bezpečnosti paměti zaručeny buď kompilátorem prostřednictvím statické analýzy programu a automatizovaného dokazování vět, nebo pečlivě spravovány programátorem v době běhu. Například programovací jazyk Rust implementuje kontrolu výpůjček, aby zajistil bezpečnost paměti, zatímco jazyky C a C++ neposkytují žádné záruky bezpečnosti paměti. Značné množství softwaru napsaného v jazycích C a C++ motivovalo vývoj externích nástrojů pro statickou analýzu, jako je Coverity, který nabízí statickou analýzu paměti pro jazyk C.

S přístupem ke zdrojovému kódu existují knihovny, které shromažďují a sledují legitimní hodnoty pro ukazatele („metadata“) a kontrolují platnost každého přístupu k ukazateli podle metadat, jako je například Boehmův garbage collector. Obecně lze bezpečnost paměti bezpečně zajistit pomocí trasování garbage collection a vložení kontrol běhu při každém přístupu k paměti; tento přístup má určitou režii, ale menší než Valgrind. Všechny jazyky s garbage collection používají tento přístup. Pro C a C++ existuje mnoho nástrojů, které provádějí transformaci kódu v době kompilace, aby provedly kontroly bezpečnosti paměti v době běhu, jako je CheckPointer a AddressSanitizer, které způsobují průměrné zpomalení o faktor 2.

Fuzz testování je vhodné pro hledání chyb v paměti a často se používá v kombinaci s dynamickými kontrolními nástroji, jako je AddressSanitizer.

Klasifikace chyb zabezpečení paměti Může dojít k mnoha různým typům chyb paměti:

Prostorové Přetečení bufferu (vyrovnávací paměti) - zápisy mimo rozsah mohou poškodit obsah sousedních objektů nebo interních dat (například účetních informací pro haldu) nebo návratové adresy. Přečtení přesahující velikost bufferu (vyrovnávací paměti) – čtení mimo rozsah může odhalit citlivá data nebo pomoci útočníkům obejít randomizaci rozložení adresového prostoru. Časové Use after free – dereference visícího ukazatele ukládajícího adresu objektu, který byl smazán. Double free – opakované volání funkce free může předčasně uvolnit nový objekt na stejné adrese. Pokud nebyla přesná adresa znovu použita, může dojít k dalšímu poškození, zejména v alokátorech, které používají volné seznamy. Uninitialized variables (neinitializované proměnné) – použije se proměnná, které nebyla přiřazena hodnota. Může obsahovat citlivé informace nebo bity, které nejsou pro daný typ platné. Wild pointers vznikají, když se ukazatel použije před inicializací do nějakého známého stavu. Vykazují stejné nepředvídatelné chování jako dangling pointers, i když je méně pravděpodobné, že zůstanou nezjištěny. Invalid free – předání neplatné adresy funkci free může poškodit haldu. Mismatched free – při použití více alokátorů se pokus o uvolnění paměti pomocí funkce deallocation jiného alokátoru. Prostorově-časové Při čtení dat z paměti, která jsou větší než velikost slova procesoru, může docházet k poškození struktury nebo k neatomickému čtení.

Přispívající chyby V závislosti na jazyku a prostředí mohou k nebezpečí paměti přispívat i jiné typy chyb:

Přetečení zásobníku - nastává, když programu dojde místo v zásobníku, obvykle kvůli příliš hluboké rekurzi. Ochrana paměti obvykle program zastaví a zabrání poškození paměti, ale funkce s velkými zásobníky mohou tuto stránku obejít a kód jádra nemusí mít výhodu ochrany paměťových stránek (viz též Chráněný režim). Out of memory – program se pokouší alokovat více paměti, než je k dispozici. V některých jazycích musí být tato podmínka po každé alokaci zkontrolována ručně. Únik paměti – pokud se paměť nevrátí alokátoru, může to vést k vyčerpání haldy (viz výše). Pokud se nepodaří spustit destruktor objektu RAII, může to vést k neočekávaným výsledkům. Dereference nulového ukazatele – Dereference nulového ukazatele často způsobí ve většině prostředí výjimku nebo ukončení programu, ale může způsobit poškození jader operačních systémů nebo systémů bez ochrany paměti nebo když použití nulového ukazatele zahrnuje velký nebo záporný offset. V jazyce C++, protože dereference nulového ukazatele je nedefinované chování, mohou optimalizace kompilátoru způsobit odstranění dalších kontrol, což vede k zranitelnosti jinde v kódu.

Některé seznamy mohou také zahrnovat souběh (souběžné čtení/zápis do sdílené paměti) jako součást bezpečnosti paměti (např. pro řízení přístupu). Programovací jazyk Rust ve výchozím nastavení zabraňuje mnoha druhům podmínek závodu založených na paměti, protože zajišťuje, že existuje nejvýše jeden zapisovatel nebo jeden nebo více čtenářů. Mnoho jiných programovacích jazyků, jako například Java, automaticky nezabraňuje závodním podmínkám založeným na paměti, přesto jsou obecně považovány za „bezpečné“ jazyky z hlediska paměti. Proto se potírání závodních podmínek obecně nepovažuje za nezbytné pro to, aby byl jazyk považován za bezpečný z hlediska paměti.

Reference

Kategorie:Počítačová bezpečnost