- •5.1. Передумови виникнення вразливостей у комп'ютерних системах
- •5.2. Класифікація вад захисту
- •5.2.1. Класифікація вад захисту за причиною їх появи
- •5.2.2. Класифікація вад захисту за їх розміщенням у системі
- •5.2.3. Класифікація вад захисту за етапами їх появи
- •5.3. Класифікація помилок, що виникають у процесі програмної реалізації системи
- •5.4. Помилки переповнення буфера
- •5.4.1. Переповнення буфера у стеку
- •5.4.2. Переповнення буфера у статичній або динамічній пам'яті
- •5.4.3. Помилка переповнення в один байт
- •5.5. Помилки оброблення текстових рядків
- •5.5.1. Використання конвеєра
- •5.5.2. Переспрямування введення-виведення
- •5.5.3. Спеціальні символи
- •5.6. Люки
- •5.6.1. Режим debug у програмі sendmail
5.4.2. Переповнення буфера у статичній або динамічній пам'яті
Буфер не завжди розміщують у стеку. Програмісти, які знають про небезпеку «зривання стека», можуть спробувати виправити ситуацію, оголосивши буфер у статичній або динамічній пам'яті. Для наведеного вище фрагмента коду функції test_function() рядок коду
char buff[5];
у першому варіанті достатньо замінити рядком
static char buff [5];
а у другому ~ рядком
buff = (char *j malloc (5);
що справді вилучить буфери зі стека.
Але проблеми на цьому не вичерпуються [60]. Оскільки буфер оголошено не у стеку, порушників позбавляють можливості підмінити адресу повернення із функції. Щоправда, в них тоді з'являється інша можливість: здійснивши переповнення буфера, модифікувати дані, які знаходяться в адресному просторі програми, що виконується. Поряд з уразливим до переповнення буфером можуть знаходитися покажчики на функції та дані структур для функцій long jmp (), модифікувавши які, також можна викликати виконання власного коду. Крім того, поряд із буфером у статичній або динамічній пам'яті можуть знаходитися змінні, після модифікування яких з'являється можливість викликати виконання функцій порушника, навіть не передаючи керування, а саме: імена файлів, паролі та ідентифікатори процесів (PID), користувачів (UID), груп (GID) тощо.
Отже, переповнення буфера небезпечне завжди, де б це не відбувалося. Відтак усувати проблему потрібно не переміщенням буфера, а скасуванням можливості переповнення, що досягається перевірками довжини рядків і розмірів масивів, які копіюються або до яких здійснюється звернення.
5.4.3. Помилка переповнення в один байт
Розглянемо специфічну помилку переповнення буфера, менш помітну, ніж розглянуті вище, але теж здатну викликати неприємності. Причина наявності помилки переповнення в один байт полягає в особливостях форматів подання рядків символів. У більшості форматів рядок займає у пам'яті на 1 байт більше, ніж потрібно для розміщення всіх його символів. У деяких мовах програмування рядок завершується символом NULL, який не враховується для визначення його довжини. Старі функції MS-DOS передбачали роботу з рядками, які завершувалися символом $. А в мові Паскаль нульовий байт було відведено для значення
довжини рядка.
Повернімося до прикладу функції testf unction () (див. рис. 5.4). У функції оголошено буфер buff довжиною у 5 байт, куди можна помістити рядок із максимальною довжиною у 4 символи. Як було показано вище, для запобігання переповненню буфера доцільно передбачити перевірку довжини рядка. І якщо програміст припуститься помилки переповнення в один байт (тобто дозволить максимальну довжину рядка у 5 байт), то помилка переповнення буфера виникатиме тоді і лише тоді, коли довжина рядка дорівнює 5.
Оскільки логіка роботи функції під час аналізу вихідного тексту виглядатиме абсолютно правильною, таку помилку помітити важко, якщо не шукати її спеціально. Звісно, ця помилка не дає змоги передати керування, позаяк змінюється (точніше, обнуляється) лише 1 байт, розташований безпосередньо за виділеним буфером. У наведеному прикладі це змінна Ь. Але цілком імовірно, що вона матиме суттєве значення для логіки функціонування програми. Наприклад, значення цієї змінної може встановлювати рівень привілеїв користувача у системі, причому її нульове значення відповідає рівню суперкористувача.
Ще одна особливість полягає в тому, що різні компілятори можуть змінювати порядок розміщення локальних змінних у стеку. Тоді замість b у цій позиції опиниться змінна а. На практиці це виглядає так: інколи після введення певного рядка програма діє некоректно, хоча після оброблення іншим компілятором на тих самих вхідних даних діє цілком коректно або ж демонструє зовсім іншу помилку.
