Безопасность памяти — это состояние защищенности от различных программных ошибок и уязвимостей безопасности при работе с доступом к памяти , таких как переполнение буфера и зависшие указатели . [1] Например, Java считается безопасной для памяти, поскольку ее функция обнаружения ошибок во время выполнения проверяет границы массива и разыменовывает указатели. [1] Напротив, C и C++ допускают произвольную арифметику указателей с указателями, реализованными как прямые адреса памяти без возможности проверки границ , [2] и, таким образом, являются потенциально небезопасными для памяти . [3]
Ошибки памяти впервые были рассмотрены в контексте управления ресурсами (вычислениями) и систем разделения времени , в попытке избежать таких проблем, как fork bombs . [4] Разработки были в основном теоретическими до червя Морриса , который использовал переполнение буфера в fingerd . [5] После этого область компьютерной безопасности быстро развивалась, обостряясь множеством новых атак, таких как атака return-to-libc , и защитных методов, таких как неисполняемый стек [6] и рандомизация макета адресного пространства . Рандомизация предотвращает большинство атак переполнения буфера и требует от злоумышленника использовать heap spraying или другие зависящие от приложения методы для получения адресов, хотя ее принятие было медленным. [5] Однако развертывание технологии, как правило, ограничивается рандомизацией библиотек и расположением стека.
В 2019 году инженер по безопасности Microsoft сообщил, что 70% всех уязвимостей безопасности были вызваны проблемами безопасности памяти. [7] В 2020 году команда Google также сообщила, что 70% всех «серьезных ошибок безопасности» в Chromium были вызваны проблемами безопасности памяти. Многие другие громкие уязвимости и эксплойты в критически важном программном обеспечении в конечном итоге возникли из-за отсутствия безопасности памяти, включая Heartbleed [8] и давнюю ошибку повышения привилегий в sudo . [9] Распространенность и серьезность уязвимостей и эксплойтов, возникающих из-за проблем безопасности памяти, побудили нескольких исследователей безопасности описать выявление проблем безопасности памяти как «стрельбу по рыбе в бочке». [10]
Некоторые современные языки программирования высокого уровня по умолчанию безопасны для памяти [ требуется ссылка ] , хотя и не полностью, поскольку они проверяют только свой собственный код, а не систему, с которой взаимодействуют. Автоматическое управление памятью в форме сборки мусора является наиболее распространенной техникой для предотвращения некоторых проблем безопасности памяти, поскольку оно предотвращает распространенные ошибки безопасности памяти, такие как использование после освобождения для всех данных, выделенных в среде выполнения языка. [11] В сочетании с автоматической проверкой границ при всех обращениях к массиву и отсутствием поддержки арифметики необработанных указателей языки со сборкой мусора обеспечивают надежные гарантии безопасности памяти (хотя гарантии могут быть слабее для низкоуровневых операций, явно помеченных как небезопасные, таких как использование интерфейса внешней функции ). Однако издержки производительности сборки мусора делают эти языки непригодными для определенных приложений, критичных к производительности. [1]
Для языков, использующих ручное управление памятью , безопасность памяти обычно не гарантируется средой выполнения. Вместо этого свойства безопасности памяти должны либо гарантироваться компилятором с помощью статического анализа программы и автоматизированного доказательства теорем , либо тщательно управляться программистом во время выполнения. [11] Например, язык программирования Rust реализует проверку заимствований для обеспечения безопасности памяти, [12] в то время как C и C++ не предоставляют никаких гарантий безопасности памяти. Значительное количество программного обеспечения, написанного на C и C++, побудило разработать внешние инструменты статического анализа, такие как Coverity , который предлагает статический анализ памяти для C. [13]
DieHard, [14] его переработанный DieHarder, [15] и Allinea Distributed Debugging Tool — это специальные распределители кучи, которые выделяют объекты на своей собственной случайной странице виртуальной памяти, позволяя останавливать и отлаживать недействительные чтения и записи в точной инструкции, которая их вызывает. Защита основана на аппаратной защите памяти, и поэтому накладные расходы обычно незначительны, хотя они могут значительно возрасти, если программа интенсивно использует выделение. [16] Рандомизация обеспечивает только вероятностную защиту от ошибок памяти, но часто может быть легко реализована в существующем программном обеспечении путем повторной компоновки двоичного файла.
Инструмент проверки памяти Valgrind использует симулятор набора инструкций и запускает скомпилированную программу в виртуальной машине проверки памяти, обеспечивая гарантированное обнаружение подмножества ошибок памяти во время выполнения. Однако он обычно замедляет программу в 40 раз [17] и, кроме того, должен быть явно проинформирован о пользовательских распределителях памяти. [18] [19]
Имея доступ к исходному коду, существуют библиотеки, которые собирают и отслеживают допустимые значения для указателей («метаданные») и проверяют каждый доступ указателя на соответствие метаданным на предмет действительности, например, сборщик мусора Boehm . [20] В общем, безопасность памяти можно безопасно обеспечить, используя отслеживание сборки мусора и вставку проверок времени выполнения при каждом доступе к памяти; этот подход имеет накладные расходы, но меньше, чем у Valgrind. Все языки со сборкой мусора используют этот подход. [1] Для C и C++ существует множество инструментов, которые выполняют преобразование кода во время компиляции для выполнения проверок безопасности памяти во время выполнения, например, CheckPointer [21] и AddressSanitizer , который в среднем устанавливает коэффициент замедления 2. [22]
BoundWarden — это новый подход к обеспечению пространственной памяти, который использует комбинацию преобразований во время компиляции и методов параллельного мониторинга во время выполнения. [23]
Тестирование методом фаззинга хорошо подходит для поиска ошибок безопасности памяти и часто используется в сочетании с динамическими средствами проверки, такими как AddressSanitizer.
Могут возникнуть различные типы ошибок памяти: [24] [25]
В зависимости от языка и среды другие типы ошибок могут способствовать небезопасности памяти:
Некоторые списки могут также включать состояния гонки (одновременные чтения/записи в общую память) как часть безопасности памяти (например, для контроля доступа). Язык программирования Rust по умолчанию предотвращает многие виды состояний гонки, основанных на памяти, поскольку он гарантирует, что есть не более одного писателя или одного или нескольких читателей. Многие другие языки программирования, такие как Java, автоматически не предотвращают состояния гонки, основанные на памяти, но все еще считаются языками «безопасности памяти». Поэтому противодействие состояниям гонки, как правило, не считается необходимым для того, чтобы язык считался безопасным для памяти.
{{cite journal}}
: Цитировать журнал требует |journal=
( помощь )