
13,5. Захист ресурсів
Оскільки однією з основних сфер застосування технології Java є Інтернет, питання безпеки для цієї технології набувають особливого значення. Безпека в мережевому середовищі являє собою цілий комплекс складних питань, що розглядаються в окремому курсі. Тут же ми приділимо основну увагу захисту локальних ресурсів - аналізу можливості Java-програми отримати несанкціонований доступ до ресурсів на тому комп'ютері, на якому вона виконується.
Перш за все, в самих мовних засобах Java відсутні деякі можливості мови C / C + +, які найбільш часто призводять до неправильного використання ресурсів - випадковому або навмисному. Головна риса мови Java в цьому відношенні - відсутність покажчиків. Хоча доступ до об'єктів в Java здійснюється по посиланнях, і фізичний зміст посилання і покажчика C / C + + однаковий - адреса пам'яті, посилання не є покажчик. Різниця полягає в тому, що, по-перше, посилання не може бути перетворена в число чи якесь інше уявлення фізичної адреси, по-друге, над посиланнями неприпустимі арифметичні операції. Саме адресна арифметика в C / C + + є засобом, використання якого може призвести до доступу процесу за межі тієї області пам'яті, до якої він має право звертатися.
Іншою "лазівкою" для виконання несанкціонованих дій в мові C / C + + є слабкий захист типів. C / C + + дозволяють використовувати типи даних в операціях, цьому типу не властивих - шляхом неявного перетворення типів або шляхом прирівнювання різнотипних покажчиків (в тому числі, і для інтегрованих типів). У Java здійснюється строгий контроль типів і в більшості випадків потрібно явне перетворення типів.
Автоматичне звільнення пам'яті в Java також є властивістю, що підвищує захищеність. Можна говорити також і про те, що більш послідовне втілення в Java парадигми об'єктно-орієнтованого програмування також є виграшним обставиною з точки зору захисту.
Слід домовитися, що вказані відмінності між мовами C / C + + і Java зумовлені насамперед тим, що мови орієнтовані на різні сфери застосування. Ті "недоліки" мови C / C + +, на які ми вказуємо, перетворюються в унікальні достоїнства при застосуванні С / С + + в якості мови системного програмування, а саме таке первісне призначення цієї мови. При розробці ж додатків (а Java - мова саме для розробки додатків) ці можливості стають непотрібними і навіть небезпечними.
Однак самі властивості мови Java ще не є гарантією захищеності. Вони забезпечуються компілятором Java, але не оберігають від модифікації виконуваний модуль. Оскільки специфікації байт-коду Java і файлу класу відкриті, програми, що здійснюють несанкціонований доступ, можуть писатися безпосередньо в байт-коди або на інших мовах з компіляцією в байт-код Java. Щоб перекрити цей канал несанкціонованого доступу, в платформі Java виконується верифікація байт-коду.
Процес верифікації складається з чотирьох кроків.
Крок 1 виконується при завантаженні класу. При цьому Java VM перевіряє базовий формат файлу класу - «магічне число» і номер версії, відповідність розміру файлу сумарним розміром його складових, формальну відповідність окремих структур специфікаціям.
Шаг2 виконується при зв'язуванні, він включає в себе верифікацію без аналізу байт-кодів. На цьому кроці перевіряється:
• відсутність порушень у використанні класів і методів, оголошених з модифікатором остаточно;
• наявність у кожного класу (крім класу Object) суперкласу;
• відповідність специфікаціям вмісту пулу констант;
• правильність імен класів і інтерфейсів і дескрипторів всіх полів і методів, що посилаються на пул констант.
Перевірки правильності елементів файлу класу, виконувані на цьому кроці, - тільки формальні, не семантичні. Більш докладні перевірки виконуються на наступних кроках.
Крок 3 також виконується на етапі зв'язування. На цьому кроці верифікатор перевіряє масив байт-кодів кожного методу. При цьому аналізується потік даних, обробляти при виконанні методу. Верифікатор виходить з того, що в будь-якій точці програми, незалежно від того, яким чином управління потрапило на цю точку, повинні дотримуватися певні обмеження цілісності даних, які зводяться в основному до наступних:
• розмір стека операндів незмінний і стек містить операнди одного типу;
• не виконується доступ до локальних змінних невідомого типу;
• доступ до локальних змінних здійснюється тільки в межах масиву локальних змінних;
• всі звернення до пулу констант виробляються до елементів відповідного типу;
• полях класу призначаються значення відповідного типу;
• всі команди байт-коду використовуються з операндами (у стеку або в масиві локальних змінних) типу, відповідного типу команди;
• методи викликаються з правильними аргументами;
• команди переходу передають управління тільки всередині байт-коду методу і передача управління завжди відбувається тільки на перший байт команди байт-коду.
Крок 4 виконується при першому виклику коду будь-якого методу. Це "віртуальний крок", він виконується не у вигляді окремого кроку перевірки всього байт-коду, а при виконанні кожної окремої команди.
Для команди, яка посилається на тип, при цьому:
• завантажується визначення типу (якщо воно ще не завантажено);
• перевіряється, чи може поточний виконуваний метод посилатися на цей тип;
• виконується ініціалізація класу (якщо він ще не ініціалізований).
Для команди, яка викликає метод або здійснює доступ до поля класу, при цьому:
• перевіряється, чи існує поле або метод в даному класі;
• перевіряється правильність дескриптора викликаного методу або поля;
• перевіряється, чи має поточний виконуваний метод права доступу до цього методу або полю.
У конкретних реалізаціях Java VM допускається після виконання кроку 4 замінювати перевірену команду байт-коду альтернативною "швидкою" формою. Наприклад, в Sun Java VM команда байт-коду нового може бути замінена командою new_quick. "Швидка" команда виконується так само, як і вихідна, але при її виконанні виключається повторна верифікація команди. У файлі класу "швидкі" команди не допускаються, вони виявляються на попередніх кроках верифікації і викликають відмову. "Швидкі" форми не є специфікаціями Java, вони реалізуються в конкретної віртуальної машини Java.
Аплети є найбільш критичними з точки зору безпеки Java-програмами, оскільки аплет завантажується з Інтернету, можливо, з неперевіреного джерела. Природно, неприпустимим є надання програмі, що прийшла "невідомо звідки" доступу до ресурсів локального комп'ютера. Тому для аплетів введені дуже жорсткі обмеження на виконання. Аплету забороняється:
• отримувати відомості про користувача або його домашньої директорії;
• визначати свої системні змінні;
• працювати з файлами і директоріями на локальному комп'ютері (читати, змінювати, створювати і т.д. і навіть перевіряти існування і параметри файлу);
• здійснювати доступ по мережі до віддаленого комп'ютера, отримувати список мережевих сеансів зв'язку, що встановлює локальний комп'ютер з іншими комп'ютерами;
• відкривати без повідомлення нові вікна, запускати локальні програми і завантажувати локальні бібліотеки, створювати нові нитки, отримувати доступ до груп ниток іншого аплета;
• одержувати доступ до будь нестандартному пакету, визначати класи, що входять в локальний пакет.
Модель безпеки Java ще далека від досконалості, і в її реалізаціях іноді виявляються "лазівки" для несанкціонованого проникнення. Слід зазначити, що в мережевих публікаціях досить часто можна зустріти критику безпеки в Java і попередження про принципову можливість "злому" захисту Java тим чи іншим способам. Разом з тим, мережеві публікації не дають підстав говорити про те, що реальні інформаційні системи, в яких застосовується технологія Java, частіше піддаються злому, ніж системи, цю технологію не застосовують.