Топ питань
Часова шкала
Чат
Перспективи

Безпека доступу до пам'яті

З Вікіпедії, вільної енциклопедії

Remove ads

Безпека доступу до пам'яті (англ. Memory safety)  — концепція в розробці програмного забезпечення, метою якої є запобігти виникненню програмних помилок, що призводять до вразливостей пов'язаних з доступом до оперативної пам'яті комп'ютера, таким як переповнення буфера, завислі вказівники тощо.

Мови програмування з низьким рівнем абстракції, такі як C чи C++, що надають безпосередній доступ до пам'яті комп'ютера (довільна арифметика вказівників, виділення чи вивільнення пам'яті) та приведення типів, але в той же час не мають автоматичної перевірки меж масивів[en], не є безпечними з точки зору доступу до пам'яті[1][2].

Remove ads

Вразливості, пов'язані з доступом до пам'яті

Узагальнити
Перспектива

Одним із найрозповсюдженіших типів вразливостей програмного забезпечення є саме проблеми безпеки доступу до пам'яті[3][4]. Даний тип вразливостей відомий протягом понад 30 років[5]. Безпеку доступу до пам'яті слід розуміти як запобігання спробам використати або модифікувати дані в тих випадках, де це не було спроектовано при створенні програмного продукту[6].

Більшість критичних за продуктивністю програм створюють мовами програмування з низьким рівнем абстракції (C та C++), чим обумовлено виникнення вразливостей даного типу. Відсутність захищеності цих мов програмування дозволяє атакуючій стороні отримати повний контроль над програмою, змінювати потік керування, мати несанкціонований доступ до конфіденційної інформації[7]. Існують різні варіанти рішення проблеми щодо доступу до пам'яті, механізми захисту повинні бути водночас ефективними як з точки зору безпеки, так і з точки зору продуктивності її виконання[8].

Перше історичне освітлення помилки пам'яті мало місце в 1972 році[9]. Відтоді й надалі вона була проблемою багатьох програмних продуктів, засобом, що дозволяє застосовувати експлойти. Наприклад, Хробак Морріса використовував численні вразливості, значна частина котрих була зв'язана саме з помилками роботи із пам'яттю[10].

Remove ads

Різновидності помилок пам'яті

Узагальнити
Перспектива

Розрізняють декілька видів помилок пам'яті (вразливостей), які можуть виникати в деяких мовах програмування:[11][12][13]

  • Порушення меж масивів[en] (або вихід за межі масиву; англ. bounds checking) — спроба використання значень, що знаходяться поза допустимими межами. За звичай помилка виникає, якщо намагатись зберегти значення змінної поза межами яку не підтримує тип даних. Іншим відомим випадком порушення меж масивів є спроба звертатися до неіснуючої комірки масиву не перевіривши його межі[14]. Окремо виділяють помилку на одиницю[15], логічна помилка в алгоритмі, коли задана кількість ітерацій циклу виявляється на одиницю більше або менше необхідного, або ж виникає плутанина з початком відліку індексації масиву (у багатьох мовах вона починається з нуля а не з одиниці).
    • Переповнення буфера (англ. buffer overflow) — запис за межами виділеного об'єму пам'яті буфера. Виникає при спробі запису в буфер блоку даних, що перевищує розмір цього буфера. В результаті переповнення інші дані що знаходяться поруч з буфером може бути пошкоджено[16], інтерпретація інформації як виконуючого коду може бути порушена[17]. Використання даної вразливості є однією з найбільш популярних способів злому комп'ютерних систем[18].
    • Читання поза межами буфера[en] (англ. buffer over-read) — аномальне читання чи його спроба поза межами виділеного в пам'яті буфера. Наслідками можуть стати порушення безпеки системи (втрата конфіденційності), нестабільна та неправильна поведінка виконання програмного коду, помилки прав доступу до пам'яті[19]. Ця вразливість входить у список найбільш поширених та небезпечних помилок в програмному забезпеченні[20].
  • Помилки при роботі з динамічною пам'яттю — неправильне використання динамічно виділяємої пам'яті та вказівниками. В даному випадку виділення пам'яті під об'єкти здійснюється під час виконання програми[21], що може спричинити помилки часу виконання[en] (англ. runtime system). До цієї вразливості схильні мови програмування з низьким рівнем абстракції, що підтримують безпосередній доступ до пам'яті комп'ютера (C, C++)[22].
    • Завислий вказівник (або символ покажчик; англ. dangling pointer)[23] — вказівник, що не має посилання на допустимий об'єкт відповідного типу. Цей вид вказівників виникає в разі, коли об'єкт був видалений (або переміщений), але значення вказівника не було змінено на нульове. У цьому разі він все ще вказує на ділянку пам'яті, де знаходився цей об'єкт і може стати причиною отримання конфіденційної інформації зловмисником. Також можливий випадок коли система вже перерозподілила адресну пам'ять під інший об'єкт, а доступ через завислий вказівник може зіпсувати розташовані там дані[24]. Особливий підтип помилки використання після вивільнення (англ. use after free) (звернення до вже вивільненої області пам'яті) — є найбільш поширеною причиною помилок програм[25], наприклад вразливостей інтернет браузерів[26].
    • Звернення за нульовим вказівником (англ. null pointer) — так як нульовий вказівник має спеціальне зарезервоване значення, що повідомляє що даний вказівник не посилається на допустимий об'єкт[27], звернення за нульовим вказівником стане причиною обробки винятків[28] і призведе до аварійної зупинки програми.
    • Вивільнення завчасно не виділеної пам'яті — спроба вивільнити область оперативної пам'яті, яка не є виділеною (тобто на даний момент вільна). Найбільш часто це проявляться у випадку подвійного вивільнення пам'яті[29], коли виникає повторна спроба вивільнити вже вивільнену пам'ять. Дана дія може спричинити помилку керування пам'яттю в менеджері пам'яті[30]. Наприклад в мові програмування C це виникає при повторному виклику функції free з одним і тим же вказівником, де другий виклик намагається вивільнити не виділену пам'ять.
    • Використання різних менеджерів пам'яті — помилка полягає в розриві зв'язку аллокатор-деаллокатор пам'яті з використанням різних засобів для роботи з одним сегментом. Наприклад, в C++ використати free для ділянки пам'яті, виділеною за допомогою new або ж, аналогічно, використати delete після виклику malloc. Стандарт C++ не описує який-небудь зв'язок між new/delete та функціями роботи з динамічною пам'яттю з мови C, хоча new/delete в загальному випадку і реалізовані через обгортки malloc/free[31][32], та змішане використання може спричинити невизначену поведінку програми[33].
    • Втрата вказівника — втрата адреси виділеного фрагмента пам'яті під час перезапису його новим значення, що посилається на іншу ділянку пам'яті[34]. При цьому адресована попереднім вказівником пам'ять стає недосяжною. Такий тип помилки приводить до явища витоку пам'яті (англ. memory leak), так як виділена пам'ять більше не може бути вивільнена. В мові програмування C це може трапитися при повторному присвоюванні результату функції malloc одному і тому ж вказівнику, без проміжного вивільнення пам'яті.
  • Неініціалізовані змінні[en] (англ. uninitialized variable) — змінні, що були об'явлені[en] без присвоєння значення. При спробі їх використання значення вони все ж матимуть, але, загалом, важко передбачуване (зчитується попередня, неперезаписана у ділянку пам'яті інформація). Вразливість для пам'яті може виникати за наявності неініціалізованих завислих («диких») вказівників[35]. Такі вказівники в своїй поведінці схожі з завислими вказівниками, спроба звернення до них у більшості випадків буде супроводжуватися помилками сегментації чи пошкодженням даних. Однак, можливе отримання конфіденційної інформації, тої що могла лишитися в даній області пам'яті після попереднього використання[36][37].
  • Помилки нестачі пам'яті — проблеми, що виникають при нестачі кількості доступної пам'яті для даної програми.
    • Переповнення стека (англ. stack overflow) — перевищення програмою кількості інформації, яка може знаходитися у стеку викликів (вказівник вершини стеку виходить за межі допустимої області). При цьому програма аварійно завершується[38]. Причиною помилки може бути глибока (або нескінченна) рекурсія, або виділення великої кількості пам'яті для локальних змінних у стеку[39].
    • Переповнення купи[en] (англ. out of memory) — спроба програми виділити більшу кількість пам'яті, ніж їй доступно. Виникає внаслідок частого і, частіше всього, невірного користування динамічною пам'яттю[40]. У разі виникнення помилки, операційна система завершить найбільш умісний з її точки зору процес (той що викликав помилку, але інколи — довільний[41]).
Remove ads

Виявлення помилок

Можливі помилки роботи з пам'яттю можуть бути встановлені як під час компіляції програми, так і під час її виконання[en] (налагодження програми).

Окрім попереджень з боку компілятора, для виявлення помилок до моменту збірки програми[en] використовуються статичні аналізатори коду. Вони дозволяють покрити значну частину небезпечних ситуацій досліджуючи вихідний код більш детально, ніж поверхневий аналіз компілятора. Статичні аналізатори можуть виявити:[42][43][44][45]

  • вихід за межі масивів;
  • використання завислих (а також нульових або неініціалізованих) вказівників;
  • неправильне використання бібліотечних функцій;
  • витік пам'яті, як наслідок неправильної роботи з вказівниками.

Під час налагодження програми можуть використовуватися спеціальні менеджери пам'яті. У даному випадку навколо аллоційованих в купі об'єктів створюються «мертві» області пам'яті, потрапляючи в які стає можливим виявити помилки[46]. Альтернативою є спеціалізовані віртуальні машини, що перевіряють доступ до пам'яті (Valgrind). Виявити помилки допомагають системи інструментування[en] коду, в тому числі забезпечені компілятором (Sanitizer[47]).

Способи забезпечення безпеки

Узагальнити
Перспектива

Більшість мов програмування високого рівня забезпечують рішення таких проблем шляхом видалення з мови арифметики вказівників, обмеженням можливості приведення типів, а також введенням збирання сміття (англ. garbage collection) як єдиної схеми управління пам'яттю[48]. На відміну від низькорівневих мов, де важливою є швидкість виконання, високорівневі, загалом, здійснюють додаткові перевірки[49], наприклад меж при звертанні до масивів та об'єктів[50].

Щоб уникнути витоку пам'яті і ресурсів та забезпечити безпеку щодо винятків у сучасному C++ використовуються розумні вказівники. Зазвичай вони являють собою клас, що імітує інтерфейс звичайного вказівника, чим розширює його функціональність[51], наприклад перевірку меж масивів та об'єктів, автоматичне управління виділенням та вивільненням пам'яті для виконуваного об'єкта. Вони допомагають реалізувати ідіому «Отримання ресурсу є ініціалізація», що означає: отримання об'єкта неподільно зв'язано з його ініціалізацією, а вивільнення — із його знищенням[52].

При використанні бібліотечних функцій слід приділяти увагу значенням що з них повертаються[en], щоб виявити можливі порушення в їх роботі[53]. Функції для роботи з динамічною пам'яттю в мові C сигналізують про помилку (нестача вільної пам'яті запрошеного розміру), повертаючи замість вказівника на блок пам'яті нульовий вказівник[54]; в C++ використовується обробка винятків[55]. Правильна обробка даних ситуацій дозволяє уникнути неправильного (аварійного) завершення програми[56].

Підвищенню безпеки сприяє перевірка меж при використанні вказівників. Подібні перевірки додаються під час компіляції та можуть сповільнювати роботу програм; для їх пришвидшення були розроблені спеціальні апаратні додатки (наприклад Intel MPX[57]).

На нижніх рівнях абстракцій існують спеціальні системи, що забезпечують безпеку пам'яті. На рівні операційної системи цим займається менеджер віртуальної пам'яті[en], він розподіляє доступні області пам'яті для окремих процесів (підтримка багатозадачності), та засоби синхронізації для підтримання багатопоточності[58]. Апаратний рівень також, як правило, включає певні механізми, такі як кільця захисту[59].

Remove ads

Див. також

Примітки

Література

Посилання

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads