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

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

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

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

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

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

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

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

Розрізняють декілька видів помилок пам'яті (вразливостей), які можуть виникати в деяких мовах програмування:[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]).

Виявлення помилок ред.

Можливі помилки роботи з пам'яттю можуть бути встановлені як під час компіляції програми, так і під час її виконання[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].

Див. також ред.

Примітки ред.

  1. Erik Poll. Lecture Notes on Language-Based Security. — Radboud University Nijmegen, . / «Language features that break memory safety include …»
  2. Laszlo Szekeres, Mathias Payer, Dawn Song. SoK: Eternal War in Memory. — 2013 IEEE Symposium on Security and Privacy, 2013. / «Memory corruption bugs in software written in low-level languages like C or C++ are one of the oldest problems in computer security.»
  3. Victor van der Veen, Nitish dutt-Sharma, Lorenzo Cavallaro, Herbert Bos. Memory Errors: The Past, the Present, and the Future. — RAID’12; Amsterdam, The Netherlands, . / «… and still rank among the top 3 most dangerous software errors.»
  4. Dawn Song. Memory safety — Attacks and Defenses. — Berkeley CS161 Computer Security, 2015. — Весна. / «In fact, after configuration errors, implementation errors are probably the largest single class of security errors exploited in practice.»
  5. Laszlo Szekeres, Mathias Payer, Dawn Song. SoK: Eternal War in Memory. — 2013 IEEE Symposium on Security and Privacy, 2013. / «This problem has existed for more than 30 years …»
  6. Dawn Song. Memory safety — Attacks and Defenses. — Berkeley CS161 Computer Security, 2015. — Весна. / «… preventing attackers from reading or writing to memory locations other than those intended by the programmer.»
  7. Laszlo Szekeres, Mathias Payer, Dawn Song. SoK: Eternal War in Memory. — 2013 IEEE Symposium on Security and Privacy, 2013. / «Applications written in low-level languages like C or C++ are prone to these kinds of bugs. The lack of memory safety … enables attackers to exploit memory bugs by maliciously altering the program's behavior or even taking full control over the control-flow.»
  8. Laszlo Szekeres, Mathias Payer, Dawn Song. SoK: Eternal War in Memory. — 2013 IEEE Symposium on Security and Privacy, 2013. / «… in finding the balance betweeneffectiveness(security)andefficiency.»
  9. Victor van der Veen, Nitish dutt-Sharma, Lorenzo Cavallaro, Herbert Bos. Memory Errors: The Past, the Present, and the Future. — RAID’12; Amsterdam, The Netherlands, . / «Memory errors were first publicly discussed in 1972 by the Computer Security Technology Planning Study Panel.»
  10. Victor van der Veen, Nitish dutt-Sharma, Lorenzo Cavallaro, Herbert Bos. Memory Errors: The Past, the Present, and the Future. — RAID’12; Amsterdam, The Netherlands, . / «The Internet Worm exploited a number of vulnerabilities, including memory error-related ones.»
  11. Laszlo Szekeres, Mathias Payer, Dawn Song. SoK: Eternal War in Memory. — 2013 IEEE Symposium on Security and Privacy, 2013.
  12. Dawn Song. Memory safety — Attacks and Defenses. — Berkeley CS161 Computer Security, 2015. — Весна.
  13. Katrina Tsipenyuk, Brian Chess, Gary McGraw. Seven Pernicious Kingdoms: A Taxonomy of Software Security Errors. — NIST Workshop on Software Security Assurance Tools, Techniques, and Metrics, Long Beach, CA, 2005. — Листопад.
  14. Richard Jones and Paul Kelly. Bounds Checking for C. — Imperial College, 1995. — Липень. / «One response to this analysis is to discard C, since this lack of efficient checkability is responsible for many software failures.»
  15. Edsger W. Dijkstra. Why numbering should start at zero (EWD 831). — Plataanstraat 5, 5671 AL NUENEN, The Netherlands, . / «… the use of the other three conventions has been a constant source of clumsiness and mistakes …»
  16. Джон Эриксон. Хакинг. Искусство эксплойта. — СПб. : Символ-Плюс, 2010. — С. 139. — ISBN 978-5-93286-158-5.
  17. Джон Эриксон. Хакинг. Искусство эксплойта. — СПб. : Символ-Плюс, 2010. — С. 142. — ISBN 978-5-93286-158-5.
  18. David A. Wheeler. Secure Programming HOWTO. — Published v3.72, 2015. / «Buffer overflows are an extremely common and dangerous security flaw …»
  19. Common Weakness Enumeration (08 грудня 2015). CWE-126: Buffer Over-read. Архів оригіналу за 27 вересня 2016. Процитовано 24 листопада 2016. / «This typically occurs when the pointer or its index is incremented to a position beyond the bounds of the buffer …»
  20. Steve Christey (13 вересня 2011). 2011 CWE/SANS Top 25 Most Dangerous Software Errors. MITRE. Архів оригіналу за 12 квітня 2018. Процитовано 24 листопада 2016.
  21. Guy Keren (2001—2002). Unix And C/C++ Runtime Memory Management For Programmers. Архів оригіналу за 27 вересня 2016. Процитовано 24 листопада 2016. / «The runtime environment defines not only how memory is allocated and freed …»
  22. Robert C. Seacord. Secure Coding in C and C++. — Addison-Wesley, 2013. — С. 162. — ISBN 978-0-321-82213-0.
  23. Jonathan Afek, Adi Sharabani. Dangling Pointer. Smashing the Pointer for Fun and Profit. — Watchfire Corporation, 2007.
  24. Компьютерная газета. Ссылка в никуда, или сломанный указатель. Архів оригіналу за 22 червня 2018. Процитовано 24 листопада 2016. / «… уязвимости, к которым может привести неправильное использование указателей и ссылок.»
  25. Common Weakness Enumeration (08 грудня 2015). CWE-416: Use After Free. Архів оригіналу за 18 липня 2019. Процитовано 24 листопада 2016. / «Referencing memory after it has been freed can cause a program to crash, use unexpected values, or execute code.»
  26. Juan Caballero, Gustavo Grieco, Mark Marron, Antonio Nappa. Undangle: Early Detection of Dangling Pointers in Use-After-Free and Double-Free Vulnerabilities. — IMDEA Software Institute; Madrid, Spain. / «Use-after-free vulnerabilities are rapidly growing in popularity, especially for exploiting web browsers.»
  27. comp.lang.c. Question 5.1. Архів оригіналу за 27 вересня 2016. Процитовано 24 листопада 2016. / «The language definition states that for each pointer type, there is a special value …»
  28. Oracle. Java Platform, Standard Edition 7 API Specification. Архів оригіналу за 23 квітня 2018. Процитовано 24 листопада 2016. / «Thrown when an application attempts to use null in a case where an object is required.»
  29. Common Weakness Enumeration (08 грудня 2015). CWE-415: Double Free. Архів оригіналу за 27 вересня 2016. Процитовано 24 листопада 2016. / «When a program calls free() twice with the same argument …»
  30. Yan Huang. Heap Overflows and Double-Free Attacks (PDF). Архів оригіналу (PDF) за 17 квітня 2018. Процитовано 24 листопада 2016. / «If free(p) has already been called before, undefined behavior occurs.»
  31. Andrei Alexandrescu. Modern C++ Design: Generic Programming and Design Patterns Applied. — Addison Wesley, 2001. / «… it is usually implemented as a thin wrapper around the C heap allocator …»
  32. Guy Keren (2001—2002). Unix And C/C++ Runtime Memory Management For Programmers. Архів оригіналу за 27 вересня 2016. Процитовано 25 листопада 2016. / «For example, the GNU C++ compiler's new operator actually invokes the C runtime malloc() function.»
  33. Memory Management. Архів оригіналу за 10 вересня 2018. Процитовано 25 листопада 2016. / «The C++ operators new and delete guarantee proper construction and destruction … The C-style functions … don't ensure that.»
  34. OWASP. Memory leak. Архів оригіналу за 23 листопада 2016. Процитовано 25 листопада 2016.
  35. Проблемы, связанные с указателями. Архів оригіналу за 26 лютого 2013. Процитовано 25 листопада 2016. / «Ничто так не беспокоит, как „дикие“ указатели!»
  36. Halvar Flake (2006). Attacks on uninitialized local variables (PDF). Архів оригіналу (PDF) за 3 червня 2016. Процитовано 25 листопада 2016. / «We're looking at the following situation then …»
  37. Common Weakness Enumeration (08 грудня 2015). CWE-457: Use of Uninitialized Variable. Архів оригіналу за 2 жовтня 2016. Процитовано 25 листопада 2016. / «An attacker can sometimes control or read these contents.»
  38. Using and Porting GNU Fortran. James Craig, Burley. 1 червня 1991. Архів оригіналу за 5 жовтня 2012. Процитовано 25 листопада 2016.
  39. Danny Kalev (5 вересня 2000). Understanding Stack Overflow. Архів оригіналу за 5 жовтня 2012. Процитовано 25 листопада 2016. / «The two most common causes for a stack overflow …»
  40. John Boyland. Position Paper: Handling „Out Of Memory“ Errors. — University of Wisconsin-Milwaukee, USA. Архівовано з джерела 22 березня 2016. Процитовано 2018-04-10. / «An „out of memory“ error can be catastrophic for a program, especially one written in a language such as Java that uses memory allocation frequently.»
  41. Mulyadi Santosa (11/30/2006). When Linux Runs Out of Memory. Архів оригіналу за 14 квітня 2018. Процитовано 15 листопада 2016. / «… you can no longer allocate more memory and the kernel kills a task (usually the current running one).»
  42. Anders Moller and Michael I. Schwartzbach. Static Program Analysis. — Department of Computer Science, Aarhus University, 2015. — Травень.
  43. Cppcheck — A tool for static C/C++ code analysis. Архів оригіналу за 18 січня 2016. Процитовано 25 листопада 2016. / «Detect various kinds of bugs in your code …»
  44. Semantic Designs. Memory Safety analysis with CheckPointer. Архів оригіналу за 18 квітня 2018. Процитовано 25 листопада 2016. / «Programs with pointers can commit a variety of errors in accessing memory …»
  45. PVS-Studio (25.03.2015). Статический анализ кода. Архів оригіналу за 25 січня 2018. Процитовано 25 листопада 2016.
  46. Emery D. Berger, Benjamin G. Zorn. DieHard: Probabilistic Memory Safety for Unsafe Languages. — PLDI’06; Ottawa, Ontario, Canada, .
  47. Konstantin Serebryany, Dmitry Vyukov (10 липня 2012). Finding races and memory errors with compiler instrumentation (PDF). GNU Tools Cauldron. Архів оригіналу (PDF) за 12 березня 2016. Процитовано 25 листопада 2016.
  48. Erik Poll. Language-based Security: 'Safe' programming languages (PDF). Radboud Universiteit Nijmegen. Архів оригіналу (PDF) за 5 листопада 2016. Процитовано 25 листопада 2016. / «Manual memory management can be avoided by …»
  49. Dinakar Dhurjati and Vikram Adve. Backwards-Compatible Array Bounds Checking for C with Very Low Overhead. — Department of Computer Science University of Illinois at Urbana-Champaign. / «… an unsolved problem despite a long history of work on detecting array bounds violations or buffer overruns, because the best existing solutions to date are either far too expensive for use in deployed production code …»
  50. Bruce Eckel. Thinking in Java. Fourth Edition. / «Both arrays and containers guarantee that you can't abuse them. Whether you're using an array or a container, you'll get a RuntimeException if you exceed the bounds, indicating a programmer error.»
  51. David Kieras. Using C++11’s Smart Pointers. — EECS Department, University of Michigan, 2016. — Червень. / «Smart pointers are class objects that behave like built-in pointers but also manage objects that you create …»
  52. Microsoft Developer Network. Интеллектуальные указатели (современный C++). Архів оригіналу за 5 грудня 2017. Процитовано 25 листопада 2016. / «Они чрезвычайно важны для идиомы программирования RAII или Resource Acquisition Is Initialialization …»
  53. Common Weakness Enumeration (08 грудня 2015). CWE-252: Unchecked Return Value. Архів оригіналу за 18 липня 2019. Процитовано 25 листопада 2016. / «The software does not check the return value from a method or function, which can prevent it from detecting unexpected states and conditions.»
  54. malloc. learn.microsoft.com (en-us) . 7 лютого 2023. Процитовано 8 березня 2024.
  55. operator new, operator new[]. Архів оригіналу за 29 березня 2018. Процитовано 25 листопада 2016. / «throws std::bad_alloc or another exception derived from std::bad_alloc (since C++11) on failure to allocate memory»
  56. Paul and Harvey Deitel. C : how to program.
  57. Intel Developer Zone (16 липня 2013). Introduction to Intel® Memory Protection Extensions. Архів оригіналу за 5 травня 2019. Процитовано 25 листопада 2016.
  58. Sarah Diesburg. Memory Protection: Kernel and User Address Spaces (PDF). Архів оригіналу (PDF) за 9 серпня 2017. Процитовано 25 листопада 2016.
  59. Michael D. Schroeder and Jerome H. Saltzer. A Hardware Architecture for Implementing Protection Rings. — Third ACM Symposium on Operating Systems Principles, Palo Alto, California, .

Література ред.

Посилання ред.

Загальні публікації

Тематичні публікації