Безопасность памяти - это состояние защиты от различных программных ошибок и уязвимостей при обращении к памяти, таких как переполнение буфера и висячие указатели. Например, Java считается безопасной для памяти, потому что ее обнаружение ошибок во время выполнения проверяет границы массива и разыменование указателей. Напротив, C и C ++ допускают произвольную арифметику указателей с указателями, реализованными как прямые адреса памяти без обеспечения проверки границ, и, таким образом, потенциально небезопасны для памяти.
Ошибки памяти сначала были рассмотрены в контексте систем управления ресурсами и разделения времени, чтобы избежать таких проблем, как « бомбы вилки». Разработки были в основном теоретическими, пока не появился червь Морриса, который использовал переполнение буфера в fingerd. С тех пор область компьютерной безопасности быстро развивалась, обостряясь с появлением множества новых атак, таких как атака с возвратом к libc, и такие методы защиты, как неисполняемый стек и рандомизация разметки адресного пространства. Рандомизация предотвращает большинство атак на переполнение буфера и требует от злоумышленника использования распыления кучи или других зависимых от приложения методов для получения адресов, хотя его внедрение было медленным. Однако развертывание технологии обычно ограничивается рандомизацией библиотек и расположением стека.
DieHard, его переработанный DieHarder и Allinea Distributed Debugging Tool - это специальные распределители кучи, которые выделяют объекты на своей собственной странице случайной виртуальной памяти, позволяя останавливать и отлаживать недопустимые операции чтения и записи и выполнять отладку по той инструкции, которая их вызывает. Защита полагается на аппаратную защиту памяти, поэтому накладные расходы обычно невелики, хотя они могут значительно возрасти, если программа интенсивно использует выделение памяти. Рандомизация обеспечивает только вероятностную защиту от ошибок памяти, но часто может быть легко реализована в существующем программном обеспечении путем повторного связывания двоичного файла.
Инструмент memcheck от Valgrind использует симулятор набора инструкций и запускает скомпилированную программу на виртуальной машине с проверкой памяти, обеспечивая гарантированное обнаружение подмножества ошибок памяти во время выполнения. Однако он обычно замедляет программу в 40 раз и, кроме того, должен быть явно проинформирован о пользовательских распределителях памяти.
Имея доступ к исходному коду, существуют библиотеки, которые собирают и отслеживают допустимые значения указателей («метаданные») и проверяют каждый доступ указателя на соответствие метаданным на предмет достоверности, например сборщик мусора Boehm. В общем, безопасность памяти можно безопасно гарантировать, используя трассировку сборки мусора и вставку проверок времени выполнения при каждом доступе к памяти; у этого подхода есть накладные расходы, но меньше, чем у Valgrind. Все языки со сборкой мусора используют этот подход. Для C и C ++ существует множество инструментов, которые выполняют преобразование кода во время компиляции для проверки безопасности памяти во время выполнения, например CheckPointer и AddressSanitizer, которые налагают средний коэффициент замедления равный 2.
Другой подход использует статический анализ программы и автоматическое доказательство теорем, чтобы гарантировать, что программа свободна от ошибок памяти. Например, в языке программирования Rust реализована проверка заимствований для обеспечения безопасности памяти. Такие инструменты, как Coverity, предлагают статический анализ памяти для C. Интеллектуальные указатели C ++ являются ограниченной формой этого подхода.
Могут возникать самые разные типы ошибок памяти: