Сигурност и проверка на класа

Статията от този месец продължава дискусията за модела за сигурност на Java, започнала през август "Под капака". В тази статия дадох общ преглед на механизмите за сигурност, вградени във виртуалната машина Java (JVM). Също така разгледах отблизо един аспект на тези механизми за сигурност: вградените функции за безопасност на JVM. През септември „Under the Hood“ разгледах архитектурата на load load, друг аспект от вградените механизми за сигурност на JVM. Този месец ще се съсредоточа върху третия зъб на стратегията за сигурност на JVM: проверката на класа.

Проверката на файла на класа

Всяка Java виртуална машина има проверителен файл на класа, който гарантира, че заредените класови файлове имат правилна вътрешна структура. Ако проверката на файла на класа открие проблем с файл на класа, той извежда изключение. Тъй като файлът с клас е само поредица от двоични данни, виртуалната машина не може да знае дали даден файл с клас е генериран от добронамерен Java компилатор или от сенчести крекери, насочени към компрометиране на целостта на виртуалната машина. В резултат на това всички имплементации на JVM имат проверка на файл на класа, която може да бъде извикана за ненадеждни класове, за да се гарантира, че класовете са безопасни за използване.

Една от целите за сигурност, които верификаторът на файловете на класа помага да постигне, е надеждността на програмата. Ако бъги компилатор или разбирам крекер генерира файл на клас, който съдържа метод, чиито байтови кодове включват инструкция за прескачане след края на метода, този метод може, ако бъде извикан, да доведе до срив на виртуалната машина. Поради това, за по-голяма надеждност, е важно виртуалната машина да провери целостта на байт кодовете, които импортира.

Въпреки че на дизайнерите на виртуални машини Java е разрешено да решават кога техните виртуални машини ще извършват тези проверки, много реализации ще направят най-много проверки веднага след зареждането на клас. Такава виртуална машина анализира байт кодове (и проверява тяхната цялост) веднъж, преди да бъдат изпълнени. Като част от своята проверка на байт кодове, виртуалната машина Java се уверява, че всички инструкции за прескачане - например goto(прескачане винаги),ifeq(прескачане, ако горната част на стека е нула) и т.н. - предизвика преход към друга валидна инструкция в потока от байт кодове на метода. В резултат на това виртуалната машина не трябва да проверява за валидна цел всеки път, когато срещне инструкция за скок, докато изпълнява байт кодове. В повечето случаи проверката на всички байт кодове веднъж, преди да бъдат изпълнени, е по-ефективен начин за гарантиране на стабилност от проверката на всяка инструкция на байт кода при всяко изпълнение.

Проверката на файл с клас, която извършва проверката си възможно най-рано, най-вероятно работи в две отделни фази. По време на първа фаза, която се провежда веднага след зареждане на клас, проверяващият файл на класа проверява вътрешната структура на файла на класа, включително проверка на целостта на байт кодовете, които съдържа. По време на втора фаза, която се провежда при изпълнение на байт кодове, верификаторът на класа на класа потвърждава съществуването на символно препратени класове, полета и методи.

Първа фаза: Вътрешни проверки

По време на първа фаза проверяващият файл на класа проверява всичко, което е възможно да провери във файл на класа, като разглежда само самия файл на класа (без да изследва други класове или интерфейси). Първата фаза на проверяващия файл на класа гарантира, че импортираният файл на класа е правилно оформен, вътрешно последователен, спазва ограниченията на езика за програмиране Java и съдържа байт кодове, които ще бъдат безопасни за изпълнение на Java виртуалната машина. Ако проверката на файла на класа установи, че някое от тях не е вярно, той извежда грешка и файлът на класа никога не се използва от програмата.

Проверка на формата и вътрешната последователност

Освен проверка на целостта на байт кодовете, проверяващият извършва много проверки за правилен формат на файла на класа и вътрешна съгласуваност по време на първа фаза. Например, всеки клас файл трябва да започва с едни и същи четири байта, магическото число: 0xCAFEBABE. Целта на магическите числа е да улесни файловите анализатори да разпознават определен тип файлове. По този начин първото нещо, което вероятно проверява проверяващият файл на класа, е, че импортираният файл наистина започва 0xCAFEBABE.

Проверката на файла на класа също проверява дали файлът на класа не е нито пресечен, нито подобрен с допълнителни последващи байтове. Въпреки че различните файлове на класа могат да бъдат с различна дължина, всеки отделен компонент, съдържащ се във файл на клас, посочва неговата дължина, както и типа му. Проверяващият може да използва типовете и дължините на компонентите, за да определи точната обща дължина за всеки отделен файл на класа. По този начин той може да провери дали импортираният файл има дължина, съответстваща на вътрешното му съдържание.

Проверяващият също така разглежда отделни компоненти, за да се увери, че са добре оформени екземпляри от техния тип компоненти. Например дескриптор на метод (типът на връщане на метода и броят и типовете негови параметри) се съхранява във файла на класа като низ, който трябва да се придържа към определена безконтекстна граматика. Една от проверките, които проверяващият извършва върху отделни компоненти, е да се увери, че всеки дескриптор на метод е добре оформен низ от съответната граматика.

Освен това проверяващият файл на класа проверява дали самият клас се придържа към определени ограничения, поставени върху него от спецификацията на програмния език Java. Например, проверяващият налага правилото, че всички класове, с изключение на класа Object, трябва да имат суперклас. По този начин проверяващият файл на класа проверява по време на изпълнение някои от правилата на езика Java, които би трябвало да бъдат приложени по време на компилация. Тъй като проверяващият няма начин да разбере дали файлът на класа е генериран от добронамерен компилатор без грешки, той проверява всеки файл на класа, за да се увери, че се спазват правилата.