Безопасность доступа к памяти
Безопасность доступа к памяти (англ. memory safety) — концепция в разработке программного обеспечения, направленная на предотвращение программных ошибок, приводящих к уязвимостям, связанным с обращением к оперативной памяти компьютера, таким как переполнение буфера и висячие указатели.
Языки программирования с низким уровнем абстракции, такие как Си и Си++, поддерживающие непосредственный доступ к памяти компьютера (произвольную арифметику указателей, выделение и освобождение памяти) и приведение типов, но не имеющие автоматической проверки границ массивов, не считаются безопасными с точки зрения доступа к памяти[1][2]. Тем не менее, Си и C++ предоставляют инструменты (такие как умные указатели), которые помогают повысить безопасность доступа к памяти. Также с этой целью применяются техники управления памятью[3]. Однако, полностью избежать ошибок доступа к памяти, особенно в сложных системах, часто не удаётся[4].
Уязвимости, связанные с доступом к памяти
Один из наиболее распространённых классов уязвимостей программного обеспечения — проблемы безопасности памяти[5][6]. Эта проблема известна с 1980-х годов[2]. Под безопасностью памяти понимается предотвращение попыток использовать или изменять данные, если это не было явно определено программистом при создании программы[6].
Множество критически важных для производительности программ написаны на языках низкого уровня (Си и Си++), которые склонны к подобным уязвимостям. Отсутствие защищённости этих языков позволяет злоумышленникам получить полный контроль над программой, менять поток управления и получать несанкционированный доступ к конфиденциальной информации[2]. В настоящее время предложено множество решений защиты памяти, которые должны быть эффективны одновременно по критериям безопасности и производительности[2].
Впервые публичные обсуждения ошибок памяти состоялись в 1972 году[7]. В дальнейшем они оставались проблемой для многих программных продуктов, выступая причиной для применения эксплойтов. Например, червь Морриса использовал несколько уязвимостей, некоторые из которых были связаны с ошибками работы с памятью[7].
Типы ошибок памяти
Различают несколько типов ошибок памяти (уязвимостей), которые могут возникать в некоторых языках программирования[2][8]:
- Нарушение границ массивов — выход обращения за пределы размера массива.
- Переполнение буфера — запись за пределами выделенного буфера[9].
- Чтение за границами буфера — чтение данных за границей выделенного буфера[10].
- Ошибки при работе с динамической памятью — неправильное управление памятью и указателями во время выполнения[11].
- Висячий указатель — указатель, не ссылающийся на допустимый объект. Особый подтип — использование после освобождения[12].
- Обращение по нулевому указателю — вызывает исключение и аварийную остановку программы[13].
- Двойное освобождение памяти — повторный вызов освобождения для одной области памяти[14].
- Смешивание менеджеров памяти — использование различных средств управления памятью для одной области, например mix malloc/free и new/delete[15].
- Потеря указателя — утеря адреса выделенного блока памяти, ведущая к утечке памяти[16].
- Неинициализированные переменные — переменные, объявленные, но не инициализированные значением[17].
- Ошибки нехватки памяти
Обнаружение ошибок
Ошибки работы с памятью могут быть выявлены как на этапе компиляции, так и во время исполнения программы.
Для выявления ошибок до компиляции используются статические анализаторы кода, которые позволяют обнаружить:
- Выход за границы массивов.
- Использование висячих, нулевых или неинициализированных указателей.
- Неправильное использование библиотечных функций.
- Утечки памяти из-за ошибки с указателями.
Во время отладки могут использоваться специальные менеджеры памяти, создающие «мёртвые» области вокруг объектов для обнаружения ошибок[20]. Альтернативой являются специализированные виртуальные машины (например, Valgrind), а также системы инструментирования кода, такие как различные санитайзеры.
Способы обеспечения безопасности
Большинство языков высокого уровня устраняют эти проблемы удалением арифметики указателей, ограничением приведения типов и обязательным введением сборки мусора[21]. В отличие от низкоуровневых языков, высокоуровневые часто выполняют дополнительные проверки, например, контроля границ при доступе к массивам и объектам[22].
В современном Си++ безопасность обеспечивают умные указатели, реализующие идиому RAII, обеспечивающую автоматическое освобождение ресурсов при уничтожении объекта[23].
При использовании библиотечных функций важно проверять возвращаемые значения[24]. Например, в Си при ошибке выделения памяти возвращается нулевой указатель, а в Си++ генерируется исключение. Корректная обработка этих ситуаций предотвращает аварийное завершение программы.
Аппаратные расширения, такие как Intel MPX, ускоряют проверки границ указателей[25].
На уровне операционной системы безопасность памяти обеспечивается менеджером виртуальной памяти, разделяющим адресные пространства, и средствами синхронизации при многопоточности[26]. Аппаратный уровень обычно реализует дополнительные механизмы, например кольца защиты[27].
Наиболее надёжным, но и наименее дешёвым способом обеспечения безопасности доступа к памяти является формальная верификация, например, с помощью логики разделения[28].
См. также
Примечания
Литература
- Erik Poll (21 января 2016). “Lecture Notes on Language-Based Security” (PDF). Radboud University Nijmegen [англ.].
- David A. Wheeler. Secure Programming HOWTO : [англ.]. — v3.72 Edition. — 2015.
Ссылки
- Laszlo Szekeres, Mathias Payer, Tao Wei, Dawn Song (19–22 мая 2013). “SoK: Eternal War in Memory”. IEEE Computer Society Washington, DC, USA [англ.]. DOI:10.1109/SP.2013.13. ISBN 978-0-7695-4977-4.
- Dawn Song (2015). “Memory safety — Attacks and Defenses” (PDF). Berkeley CS 161 Computer Security [англ.].
- Katrina Tsipenyuk, Brian Chess, Gary McGraw (12 декабря 2005). “Seven Pernicious Kingdoms: A Taxonomy of Software Security Errors” (PDF) [англ.]. DOI:10.1109/MSP.2005.159. ISSN 1540-7993.
- Emery D. Berger, Benjamin G. Zorn (11–14 июня 2006). “DieHard: Probabilistic Memory Safety for Unsafe Languages” (PDF). PLDI '06; Ottawa, Ontario, Canada [англ.]. DOI:10.1145/1133981.1134000. ISBN 1-59593-320-4.
- Victor van der Veen, Nitish dutt-Sharma, Lorenzo Cavallaro, Herbert Bos (12–14 сентября 2012). “Memory Errors: The Past, the Present, and the Future” (PDF). RAID'12; Amsterdam, The Netherlands [англ.]. DOI:10.1007/978-3-642-33338-5_5. ISBN 978-3-642-33337-8.
- Jonathan Afek, Adi Sharabani (2007). “Dangling Pointer. Smashing the Pointer for Fun and Profit” (PDF). Watchfire Corporation [англ.].
- Juan Caballero, Gustavo Grieco, Mark Marron, Antonio Nappa (15–20 июля 2012). “Undangle: Early Detection of Dangling Pointers in Use-After-Free and Double-Free Vulnerabilities” (PDF). ISSTA 2012; Minneapolis, MN, USA [англ.]. DOI:10.1145/2338965.2336769. ISBN 978-1-4503-1454-1.
- Yan Huang. Heap Overflows and Double-Free Attacks (англ.) (2016). Дата обращения: 24 ноября 2016.
- Halvar Flake. Attacks on uninitialized local variables (англ.). Black Hat Federal (2006). Дата обращения: 24 ноября 2016.
- John Boyland (2005). “Position Paper: Handling "Out Of Memory" Errors” (PDF). ECOOP 2005 Workshop on Exceptional Handling in Object-Oriented Systems; University of Wisconsin-Milwaukee, USA [англ.]. Архивировано из оригинала (PDF) 2016-03-22.
- David Kieras (июнь 2016). “Using C++11's Smart Pointers” (PDF). EECS Department, University of Michigan [англ.]. Проверьте дату в
|date=(справка на английском) - Dinakar Dhurjati, Vikram Adve (20–28 мая 2006). “Backwards-Compatible Array Bounds Checking for C with Very Low Overhead” (PDF). ICSE '06; Shanghai, China [англ.]. DOI:10.1145/1134285.1134309. ISBN 1-59593-375-1.