Теорія операційної системи

:: Меню ::

Головна
Представлення даних в обчислювальних системах
Машинні мови
Завантаження програм
Управління оперативною пам'яттю
Сегментна і сторінкова віртуальна пам'ять
Комп'ютер і зовнішні події
Паралелізм з точки зору програміста
Реалізація багатозадачності на однопроцесорних комп'ютерах  
Зовнішні пристрої
Драйвери зовнішніх пристроїв
Файлові системи
Безпека
Огляд архітектури сучасних ОС

:: Друзі ::

Карта сайту
 

:: Статистика ::

 

 

 

 

 

индивидуалки Тульская

Помилки програмування

Хоча ми і затверджували на початку попереднього розділу, що методи управління повноваженнями в сучасних ОС спільного призначення теоретично ідеальні, це відноситься лише до ідеальної ОС, тобто до специфікацій відповідних модулів. В той же час, в реальному коді зустрічаються відхилення від специфікацій, так звані помилки. Повний огляд і вичерпна класифікація всіх типів помилок, що практично зустрічаються, мабуть, нездійснимі. У цьому розділі ми опишемо лише найбільш поширені і найбільш небезпечні помилки.
Найбільшу небезпеку з точки зору безпеки представляють помилки в модулях, пов'язаних з перевіркою ACL, авторизацією і підвищенням рівня привілеїв процесора (наприклад, в диспетчерові системних викликів), а в системах сімейства Unix — в setuid-программах.
Одна з найбільш небезпечних — і в той же час задоволений поширена в сучасних програмах — помилка приведена в прикладі 2.4. Розглянемо цей код ще раз (приклад 12.2).

Приклад 12.2. Приклад програми, схильної до зриву стека

/* Фрагмент примітивної реалізації сервера SMTP (Rfc822) */
int parse_line(FILE * socket)
{
/* Згідно Rfc822, команда має довжину не більше 4 байт
а весь рядок — не більше 255 байт */
char cmd[5], args[255];
fscanf(socket, "%s %s\n", and, args);
/* Залишок програми нас не цікавить */

Видно, що наша програма прочитує з мережевого з'єднання рядок, який повинен складатися з двох полів, розділених пропуском, і закінчуватися символом перекладу рядка. Відповідно до специфікацій протоколу SMTP, перше поле (команда) не може перевищувати чотирьох символів (на жаль, не визначено в протоколі довших команд), а рядок цілком не може бути довше 255 байт.
Якщо наш партнер на іншому кінці з'єднання повністю відповідає вимогам frfc 0822], наш код працюватиме без проблем. Проблеми — причому серйозні — виникнуть, якщо нам передадуть рядок, який цим вимогам не відповідає.
Перевищення допустимої довжини кодом команди не представляє великої небезпеки: зайві байти будуть записані в початок масиву args і втрачені при записі в нього його власного поля. Справжня небезпека — це перевищення довжини всього рядка. Для нашого партнера не представляє жодних складнощів згенерувати послідовність із понад 255 символів, що не містить перекладів рядка (мал. 12.21).

Мал. 12.21. Зрив буфера

Тоді буфер arg переповниться, але за ним в пам'яті слідує зовсім не інший буфер, а — ні багато, ні мало, заголовок стекового кадру, в якому міститься адреса повернення нашої підпрограми. Поважно підкреслити, втім, що навіть якщо б там і знаходилися інші змінні, це не було б для нашого диверсанта перешкодою — йому досить просто передати довший блок даних.
Переповнювання масиву arg приведе до порушення заголовка стекового кадру. Якщо диверсант досить кваліфікований, він може передати
нам замість команди шматок коди і підроблений стековий кадр, який як адреса повернення містить адресу переданої коди (звичайно, для цього треба точно знати, де в нашої програми знаходиться стек, але це часто одне і те ж місце). І тоді, при спробі повернути управління, наша програма передасть управління на підставлений нею код (мал. 12.22). Кількість гидот, які цей код може містити, перевершує всяку уяву.

Мал. 12.22. Зрив стека з передачею троянської коди

Навіть якщо шкідник не може передати і виконати код (наприклад, тому, що не знає адреси стека або тому, що стек захищений від виконання), псування стекового кадру вистачає, щоб аварійно завершити виконання мережевого сервісу, а це теж неприємно. Помилки такого роду називаються переповнюваннями буфера (buffer overrun) або зривами буфера. Якщо буфер знаходиться в стеку, говорять ще про зриві стека. Зриви буфера можливі не лише в мережевих сервісах, але і в додатках, що просто прочитують файли і, з іншого боку, не лише у високорівневих мережевих сервісах, але і в драйверах мережевих протоколів нижнього рівня -- останній тип помилок особливо небезпечний, бо атаці піддається модуль ядра ОС. Особливу небезпеку представляє зрив буфера в модулях, що здійснюють парольну авторизацію: в цьому випадку зловмисник може навіть але руйнувати стековий кадр, йому досить лише модифікувати змінну, яка сигналізує, що пароль успішно перевірений.
Найбільш велика небезпека зриву буфера в ситуаціях, коли специфікація мережевого протоколу або формату файлу свідчить, що довжина того або іншого поля або пакету не може перевищувати певної кількості байтів, проте порушення цього правила фізично можливо. Можливість такого порушення може виникати як через те, що використовується не лічильник байтів в пакеті, а маркер кінця пакету, так і через те, що розрядність лічильника байтів дозволяє представляти значення, що перевищують встановлену протоколом межу.

Примітка
Зазвичай ці терміни застосовуються для опису прийомів, які використовуються диверсантами для спричинення збитку системам", але поважно розуміти, що атаки такого типа засновані на помилці в коді системи, що атакується. Якби помилки не було, диверсант не був би в змозі що-небудь зробити (або, в усякому разі, був би не в змозі сделать саме це).

При програмуванні на мові З основні джерела помилок такого роду — це використання стандартних процедур gets і fscanf. Процедура gets лікуванню не підлягає — їй неможливо вказати розмір буфера, виділеного для прийому даних, тому вона в принципі не здатна проконтролювати його заповнення. Замість неї сучасні версії бібліотек З надають функцію fgets якою розмір буфера передається як параметр. Настійно рекомендується використовувати саме її.
Процедура fscanf у нашому випадку лікується. Замість

fscanf(socket, "%s %s\n", cmd, args);

нам слід написати

fscanf(socket, "%4s %255s\n", cmd, args);

Проте автоматизувати перевірку того, що в кожному випадку всі специфікатори форматів вказані правильно, неможливо, і тому використовувати процедуру fscanf і інші процедури того ж сімейства не рекомендується.

Поширення червя Моріса через зрив буфера
Широко відомий зрив буфера в програмі fingerd, що входила в систему BSD Unix. На процесорах VAX і Mc68000 відсутній захист сторінок пам'яті від виконання, тому будь-яка сторінка пам'яті, доступна для читання, може бути виконана.
"Черв'як Моріса" [Компьютерпресс 1991] користувався цією дірою, передаючи шматок коди і підроблений стековий кадр, що передавав управління цьому коду. Код у свою чергу запускав на цільовій системі копію командного інтерпретатора, що виконувалася з привілеями суперкористувача. Потім черв'як використовував отриманий командний інтерпретатор для втягування в систему свого тіла.
Помилка була виявлена в 1987 р. і незабаром після виявлення була виправлена. Практично всі сучасні системи використовують безпечну в цьому відношенні версію fingerd. З тих пір скільки-небудь серйозних (тобто — що вийшли за межі однієї групи взаємно довіряючих машин) атак черв'яків на Unix-системы не відбувалося.
Черв'як Code Red, пандемія якого сталася в серпні 2001 р., використовував зриви буфера в IIS (флагманський мережевий продукт Microsoft, сервер Ftp/http і ряду інших протоколів для Windows Nt/2000/xp). Як з'ясувалося, в даному випадку йшлося не про одну, а про цілу групу помилок, бо за випуском patch, що захищав від Code Red, послідувало дещо інших атак черв'яків і полівалентних (тобто що використовують декілька каналів розмноження) вірусів, частина з яких також привела до пандемій. 19 вересня 2001 р. аналітична компанія Gartner Group випустила доповідь [www3.gartner.com 101034], у якому настійно рекомендувалося якнайскоріше відмовитися від використання IIS і висловлювалася украй песимістична оцінка здатності Microsoft виправити положення в осяжному майбутньому.

Зриви буфера є одним з найбільш поширених вразливих місць: так, в базі даних [www.cert.orgl вони складають 27% від загальної кількості документованих проблем.
При використанні інших мов програмування, наприклад C++ з бібліотекою класів для роботи з рядками, або Java, реалізувати програму, схильну до зриву буфера, дещо складніше, проте можливості людські невичерпні, і багатьом програмістам це удається. Втім, для тих, що програмують на Java є одна втіха: Java Virtual Machine використовує складнішу схему управління пам'яттю, чим компільовані алголоподобниє мови, і зруйнувати стековий кадр за допомогою переповнювання буфера в програмах, написаних на Java, неможливо, тому даний прийом не може застосовуватися для передачі і виконання шкідливої коди. Проте неправильно оброблене виключення при помилці індексації в буфері може привести до аварійної зупинки додатка Java з анітрохи не меншим успіхом, чим помилка доступу до пам'яті в C/c++.
У тісній концептуальній спорідненості із зривами буфера знаходяться помилки, що спрацьовують при використанні у вхідному потоці даних неприпустимих величин зсуву (мал. 12.23). Такі помилки зустрічаються при аналізі вхідного потоку, який містить взаємозв'язані структури даних, зв'язки між якими реалізовані у вигляді змішень в потоці (я посилаюся на структуру даних, яка послідує через 50 байт після цієї крапки). Практично важливий приклад такого протоколу — система квитування (посилки підтверджень) з ковзаючим вікном, використовувана в транспортному протоколі TCP [RFC 0793]. Адресація за допомогою зсувів широко застосовується також при роботі з послідовними файлами, тому драйвери файлових систем і мережеві файлові сервери також можуть містити такі помилки.

Мал. 12.23. Використання неприпустимих зсувів

Завдання зсувів, що перевершують розмір буфера аналізуючої програми, або неприпустимих по яких-небудь іншим правилам — наприклад, негативних, якщо по протоколу допустимі лише позитивні -может приводити до формування покажчиків за межі аналізованого буфера. Модифікація даних по цих покажчиках може приводити до всіляких наслідків — наприклад, в такий спосіб можна спробувати переконати файловий сервер, що файл, відкритий для читання, насправді відкритий для запису. Навіть якщо в межах досяжності сформованого таким чином покажчика і немає критично важливих даних, практично завжди його можна використовувати для руйнування цілісності змінних достатку атакованого модуля і здійснення DOS.
У багатопотокових сервісах поширені також помилки змагання. Для їх спрацьовування необхідна певна послідовність і тимчасове узгодження запитів до сервісу.
У деяких типах сервісів зустрічаються властиві ним помилки. Так, в багатьох серверах HTTP була виявлена помилка підйому по каталогах (directory traversal bug), коли зловмисник, запрошуючи URI, що містять послідовності '..', міг піднятися по файловій системі вище за кореневий каталог HTTP-сервера і, таким чином, вважати або навіть модифікувати файли, що не входять в ієрархію HTML-документов (мал. 12.24). Аналогічні помилки зустрічаються і в мережевих файлових серверах.

Мал. 12.24. Помилка підйому по каталогах

Спільним правилом, що дозволяє якщо не викоренити помилки такого роду, то в усякому разі, зменшити вірогідність їх здійснення, є недовіра до вхідних потоків даних. Якщо специфікації протоколу або формату свідчать що те або інша умова зобов'язана виконуватися, ми не можемо просто вважати що воно виконується, а зобов'язані як мінімум вставити явну перевірку того, що воно виконується. Програма тестування ^про-граммного комплексу повинна включати не лише перевірку правильної обробки допустимих вхідних даних в кожному з модулів, осмислену реакцію на неприпустимі вхідні дані.

 

:: Реклама ::

 

:: Посилання ::


 

 

 


Copyright © Kivik, 2017