Programování

Zabezpečení a ověřovatel třídy

Článek z tohoto měsíce pokračuje v diskusi o bezpečnostním modelu Java, která byla zahájena v srpnovém filmu „Pod kapotou“. V tomto článku jsem poskytl obecný přehled o bezpečnostních mechanismech zabudovaných do virtuálního stroje Java (JVM). Také jsem se podrobně podíval na jeden aspekt těchto bezpečnostních mechanismů: integrované bezpečnostní prvky JVM. V září „Under the Hood“ jsem zkoumal architekturu zavaděče tříd, což je další aspekt integrovaných bezpečnostních mechanismů JVM. Tento měsíc se zaměřím na třetí bod bezpečnostní strategie JVM: ověřovatel třídy.

Ověřovatel souboru třídy

Každý virtuální stroj Java má ověřovač souborů třídy, který zajišťuje, že načtené soubory tříd mají správnou vnitřní strukturu. Pokud ověřovatel souboru třídy objeví problém se souborem třídy, vyvolá výjimku. Vzhledem k tomu, že soubor třídy je pouze posloupností binárních dat, virtuální stroj nemůže vědět, zda byl soubor konkrétní třídy vygenerován dobře míněným kompilátorem Java nebo stinnými crackery usilujícími o narušení integrity virtuálního stroje. V důsledku toho mají všechny implementace JVM ověřovač souborů tříd, který lze vyvolat na nedůvěryhodných třídách, aby se zajistilo bezpečné použití tříd.

Jedním z cílů zabezpečení, kterého pomáhá ověřovatel třídních souborů dosáhnout, je robustnost programu. Pokud buginový kompilátor nebo důvtipný cracker vygeneroval soubor třídy, který obsahoval metodu, jejíž bajtové kódy obsahovaly instrukci přeskočit za konec metody, mohla by tato metoda, pokud by byla vyvolána, způsobit zhroucení virtuálního stroje. Z důvodu robustnosti je tedy důležité, aby virtuální stroj ověřil integritu bajtových kódů, které importuje.

Přestože návrháři virtuálních strojů Java mohou rozhodnout, kdy jejich virtuální stroje tyto kontroly provedou, mnoho implementací provede většinu kontrol hned po načtení třídy. Takový virtuální stroj analyzuje bytecodes (a ověří jejich integritu) jednou, než se vůbec spustí. V rámci ověření bytových kódů zajišťuje virtuální stroj Java všechny instrukce skoku - například jít do (skok vždy), ifeq (skok, pokud je horní část zásobníku nula) atd. - způsobí skok na jinou platnou instrukci v proudu bytového kódu metody. V důsledku toho nemusí virtuální stroj kontrolovat platný cíl pokaždé, když narazí na instrukci skoku při provádění bajtových kódů. Ve většině případů je kontrola všech bytových kódů jednou před jejich provedením efektivnějším způsobem, jak zaručit robustnost, než kontrola každé instrukce bytecode při každém jejím provedení.

Ověřovatel souboru třídy, který provádí kontrolu co nejdříve, s největší pravděpodobností pracuje ve dvou odlišných fázích. Během první fáze, která proběhne hned po načtení třídy, ověřovatel souboru třídy zkontroluje vnitřní strukturu souboru třídy, včetně ověření integrity bajtových kódů, které obsahuje. Během druhé fáze, která probíhá při provádění bajtových kódů, ověřovatel souboru třídy potvrzuje existenci symbolicky odkazovaných tříd, polí a metod.

Fáze jedna: Interní kontroly

Během první fáze ověřovatel souboru třídy zkontroluje vše, co je možné zkontrolovat v souboru třídy, a to tak, že se podívá pouze na samotný soubor třídy (bez zkoumání jakýchkoli jiných tříd nebo rozhraní). Fáze jedna ověřovatele souboru třídy zajišťuje, že importovaný soubor třídy je správně vytvořen, vnitřně konzistentní, dodržuje omezení programovacího jazyka Java a obsahuje bajtové kódy, které budou bezpečné pro virtuální stroj Java. Pokud ověřovatel souboru třídy zjistí, že některý z nich není pravdivý, vyvolá chybu a program třídy program nikdy nepoužije.

Kontrola formátu a vnitřní konzistence

Kromě ověření integrity bytových kódů provádí ověřovatel během první fáze mnoho kontrol správného formátu souboru třídy a vnitřní konzistence. Například každý soubor třídy musí začínat stejnými čtyřmi bajty, magické číslo: 0xCAFEBABE. Účelem magických čísel je usnadnit analyzátorům souborů rozpoznat určitý typ souboru. První věc, kterou pravděpodobně ověřuje ověřovatel souboru třídy, je tedy to, že importovaný soubor skutečně začíná 0xCAFEBABE.

Ověřovatel souboru třídy také zkontroluje, zda soubor třídy není zkrácen ani vylepšen o další koncové bajty. Ačkoli různé soubory třídy mohou mít různé délky, každá jednotlivá součást obsažená v souboru třídy označuje její délku i typ. Ověřovatel může pomocí typů a délek komponent určit správnou celkovou délku pro každý jednotlivý soubor třídy. Tímto způsobem může ověřit, že importovaný soubor má délku konzistentní s jeho interním obsahem.

Ověřovatel také prohlédne jednotlivé komponenty, aby se ujistil, že jsou dobře vytvořenými instancemi jejich typu komponenty. Například deskriptor metody (návratový typ metody a počet a typy jejích parametrů) je uložen v souboru třídy jako řetězec, který musí dodržovat určitou bezkontextovou gramatiku. Jednou z kontrol, které ověřovatel provádí u jednotlivých komponent, je ujistit se, že každý deskriptor metody je dobře vytvořený řetězec příslušné gramatiky.

Kromě toho ověřovatel souboru třídy ověří, zda třída sama dodržuje určitá omezení kladená specifikací programovacího jazyka Java. Například ověřovatel vynucuje pravidlo, že všechny třídy, kromě třídy Objekt, musí mít nadtřídu. Ověřovač třídních souborů tedy za běhu kontroluje některá pravidla jazyka Java, která měla být vynucena v době kompilace. Protože ověřovatel nemá žádný způsob, jak zjistit, zda byl soubor třídy vygenerován benevolentním kompilátorem bez chyb, kontroluje každý soubor třídy, aby se ujistil, že jsou dodržována pravidla.