Безопасность доступа к памяти — концепция в разработке программного обеспечения, целью которой является избежание программных ошибок, которые ведут к уязвимостям, связанным с доступом к оперативной памяти компьютера, таким как переполнения буфера и висячие указатели.

Языки программирования с низким уровнем абстракций, такие как Си и Си++, поддерживающие непосредственный доступ к памяти компьютера (произвольную арифметику указателей, выделение и освобождение памяти) и приведение типов, но не имеющие автоматической проверки границ массивов, не являются безопасными с точки зрения доступа к памяти / «Language features that break memory safety include …» / «Memory corruption bugs in software written in low-level languages like C or C++ are one of the oldest problems in computer security.». C и C++, тем не менее, содержат инструменты (такие как умные указатели), позволяющие повысить безопасность доступа к памяти. Этой же цели служат техники управления памятью. Тем не менее, избежать ошибок доступа к памяти, особенно в сложных системах, зачастую не удаётся / «Clearly, if your code has new operations, delete operations, and pointer arithmetic all over the place, you are going to mess up somewhere and get leaks, stray pointers, etc. This is true independently of how conscientious you are with your allocations: eventually the complexity of the code will overcome the time and effort you can afford.».

Уязвимости, связанные с доступом к памяти Одним из наиболее распространённых классов уязвимостей программного обеспечения являются проблемы безопасности памяти / «… and still rank among the top 3 most dangerous software errors.» / «In fact, after configuration errors, implementation errors are probably the largest single class of security errors exploited in practice.». Данный тип уязвимости известен с 1980-х годов / «This problem has existed for more than 30 years …». Безопасность памяти подразумевает предотвращение попыток использовать или модифицировать данные, если это не было намерено разрешено программистом при создании программного продукта / «… preventing attackers from reading or writing to memory locations other than those intended by the programmer.».

Множество критических с точки зрения производительности программ реализованы на языках программирования c низким уровнем абстракций (Си и Си++), которые склонны к появлению уязвимостей данного типа. Отсутствие защищённости этих языков программирования позволяет атакующим получить полный контроль над программой, изменять поток управления, иметь несанкционированный доступ к конфиденциальной информации / «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.». На данный момент предложены различные решения проблем, связанных с доступом к памяти. Механизмы защиты должны быть эффективны одновременно как с точки зрения безопасности, так и с точки зрения производительности / «… in finding the balance betweeneffectiveness(security)andefficiency.».

Первую огласку ошибки памяти получили в 1972 году / «Memory errors were first publicly discussed in 1972 by the Computer Security Technology Planning Study Panel.». И далее они являлись проблемой многих программных продуктов, средством, позволяющим применять эксплоиты. Например, червь Морриса использовал множество уязвимостей, часть из которых была связана с ошибками работы с памятью / «The Internet Worm exploited a number of vulnerabilities, including memory error-related ones.».

Типы ошибок памяти Различают несколько видов ошибок памяти (уязвимостей), которые могут возникать в некоторых языках программирования:

Нарушение границ массивов — выражение, индексирующее массив, выходит из диапазона значений, установленного при определении этого массива. Отдельно выделяется особый подтип — ошибка неучтённой единицы / «… the use of the other three conventions has been a constant source of clumsiness and mistakes …». Встречается при отсутствии проверок границ массивов и строк (Си, Си++) / «One response to this analysis is to discard C, since this lack of efficient checkability is responsible for many software failures.». Переполнение буфера — запись за пределами выделенного в памяти буфера. Возникает при попытке записи в буфер блока данных, превышающего размер этого буфера. В результате переполнения могут быть испорчены данные, расположенные рядом с буфером, либо программа вовсе изменит своё поведение, вплоть до интерпретации записанных данных как исполняемого кода. Использование данной уязвимости является одним из наиболее популярных способов взлома компьютерных систем / «Buffer overflows are an extremely common and dangerous security flaw …».  — чтение за пределами выделенного в памяти буфера. Последствиями могут служить нарушения безопасности системы (утрата конфиденциальности), нестабильное и неправильное поведение программы, ошибки прав доступа к памяти / «This typically occurs when the pointer or its index is incremented to a position beyond the bounds of the buffer …». Эта уязвимость входит в список наиболее распространённых и опасных ошибок в программном обеспечении. Ошибки при работе с динамической памятью — неправильное распоряжение динамически выделяемой памятью и указателями. В данном случае выделение памяти под объекты осуществляется во время выполнения программы / «The runtime environment defines not only how memory is allocated and freed …», что может повлечь за собой ошибки времени исполнения. Данной уязвимости подвержены языки программирования с низким уровнем абстракций, поддерживающие непосредственный доступ к памяти компьютера (Си, Си++). Висячий указатель — указатель, не ссылающийся на допустимый объект соответствующего типа. Данный вид указателей возникает, когда объект был удалён (или перемещён), но значение указателя не изменили на нулевое. В данном случае он всё ещё указывает на область памяти, где находился данный объект. В некоторых случаях это может стать причиной получения конфиденциальной информации злоумышленником; либо, если система уже перераспределила адресуемую память под другой объект, доступ по висячему указателю может повредить расположенные там данные / «… уязвимости, к которым может привести неправильное использование указателей и ссылок.». Особый подтип ошибки — использование после освобождения (use after free) (обращение к освобождённой области памяти) — является распространённой причиной ошибок программ / «Referencing memory after it has been freed can cause a program to crash, use unexpected values, or execute code.», например, уязвимостей веб-обозревателей / «Use-after-free vulnerabilities are rapidly growing in popularity, especially for exploiting web browsers.». Обращение по нулевому указателю. Нулевой указатель имеет специальное зарезервированное значение, показывающее, что данный указатель не ссылается на допустимый объект / «The language definition states that for each pointer type, there is a special value …». Обращение по нулевому указателю будет причиной исключительной ситуации / «Thrown when an application attempts to use null in a case where an object is required.» и аварийной остановки программы. Освобождение ранее не выделенной памяти — попытка освободить область оперативной памяти, которая не является на данный момент выделенной (то есть свободна). Наиболее часто это проявляется в двойном освобождении памяти / «When a program calls free() twice with the same argument …», когда происходит повторная попытка освободить уже освобождённую память. Данное действие может вызвать ошибку менеджера памяти / «If free(p) has already been called before, undefined behavior occurs.». В Си это происходит при повторном вызове функции с одним и тем же указателем, без промежуточного выделения памяти. Использование различных менеджеров памяти — ошибка, заключающаяся в разрыве связки аллокатор-деаллокатор памяти и использованием различных средств для работы с одним сегментом. Например, в Си++ использованием для участка памяти, выделенного с помощью или, аналогично, использованием после . Стандарт Си++ не описывает какую-либо связь между / и функциями работы с динамической памятью из Си, хотя / в общем случае реализованы как обёртки / / «… it is usually implemented as a thin wrapper around the C heap allocator …» / «For example, the GNU C++ compiler’s new operator actually invokes the C runtime malloc() function.». Смешанное использование может стать причиной неопределённого поведения / «The C++ operators new and delete guarantee proper construction and destruction … The C-style functions … don’t ensure that.». Потеря указателя — утеря адреса выделенного фрагмента памяти при перезаписи его новым значением, который ссылается на другую область памяти. При этом адресуемая предыдущим указателем память более недосягаема. Такой тип ошибки приводит к утечкам памяти, так как выделенная память не может быть освобождена. В Си это может случиться при повторном присваивании результата функции одному и тому же указателю, без промежуточного освобождения памяти.  — переменные, которые были объявлены, но не установлены в какое-либо значение, известное до времени их использования. Переменные будут иметь значение, но, в общем случае, труднопредсказуемое. Уязвимость для памяти могут возникнуть при наличии неинициализированных («диких») указателей / «Ничто так не беспокоит, как „дикие“ указатели!». Эти указатели в своём поведении схожи с висячими указателями, попытка обращения по ним в большинстве случаев будет сопровождаться ошибками доступа или повреждением данных. Однако, возможно получение конфиденциальной информации, которая могла остаться в данной области памяти после предыдущего использования / «We’re looking at the following situation then …» / «An attacker can sometimes control or read these contents.». Ошибки нехватки памяти — проблемы, возникающие при недостатке количества доступной памяти для данной программы. Переполнение стека — превышение программой количества информации, которое может находиться в стеке вызовов (указатель вершины стека выходит за границу допустимой области). При этом программа аварийно завершается. Причиной ошибки может быть глубокая (или бесконечная) рекурсия, либо выделение большого количества памяти для локальных переменных на стеке / «The two most common causes for a stack overflow …».  — попытка программы выделить большее количество памяти, чем ей доступно. Является следствием частого (Java / «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.») и зачастую неправильного обращения с динамической памятью. В случае возникновения ошибки, операционная система завершит наиболее подходящий с её точки зрения для этого процесс (часто, вызвавший ошибку, но иногда — произвольный / «… you can no longer allocate more memory and the kernel kills a task (usually the current running one).»).

Обнаружение ошибок Возможные ошибки работы с памятью могут быть установлены как во время компиляции программы, так и во время исполнения (отладки).

Помимо предупреждений со стороны компилятора, для обнаружения ошибок до момента сборки программы используются статические анализаторы кода. Они позволяют покрыть значительную часть опасных ситуаций, исследуя исходный код более подробно, чем поверхностный анализ компилятора. Статические анализаторы могут обнаружить / «Detect various kinds of bugs in your code …» / «Programs with pointers can commit a variety of errors in accessing memory …»: Выход за границы массивов Использование висячих (а также нулевых или неинициализированных) указателей Неправильное использование библиотечных функций Утечки памяти, как следствие неправильной работы с указателями