stringtranslate.com

Висячий указатель

Висячий указатель

Висячие указатели и дикие указатели в компьютерном программировании — это указатели , которые не указывают на действительный объект соответствующего типа. Это частные случаи нарушений безопасности памяти . В более общем смысле, висячие ссылки и дикие ссылки — это ссылки , которые не преобразуются в допустимое место назначения.

Висячие указатели возникают во время уничтожения объекта , когда объект, имеющий входящую ссылку, удаляется или освобождается без изменения значения указателя, так что указатель по-прежнему указывает на место в памяти освобожденной памяти. Система может перераспределить ранее освобожденную память, и если затем программа разыменует висячий указатель (сейчас), это может привести к непредсказуемому поведению , поскольку теперь память может содержать совершенно другие данные. Если программа выполняет запись в память, на которую ссылается висячий указатель, это может привести к скрытому повреждению несвязанных данных, что приведет к тонким ошибкам , которые чрезвычайно сложно обнаружить. Если память была перераспределена другому процессу, то попытка разыменовать висячий указатель может привести к ошибкам сегментации (UNIX, Linux) или общим ошибкам защиты (Windows). Если у программы есть достаточные привилегии, позволяющие ей перезаписывать бухгалтерские данные, используемые распределителем памяти ядра, повреждение может привести к нестабильной работе системы. В объектно-ориентированных языках со сборкой мусора висячие ссылки предотвращаются за счет уничтожения только недоступных объектов, то есть у них нет входящих указателей; это обеспечивается либо трассировкой, либо подсчетом ссылок . Однако финализатор может создавать новые ссылки на объект, требуя воскрешения объекта , чтобы предотвратить висячую ссылку.

Дикие указатели, также называемые неинициализированными указателями, возникают, когда указатель используется до инициализации некоторого известного состояния, что возможно в некоторых языках программирования. Они демонстрируют такое же неустойчивое поведение, как и висячие указатели, хотя с меньшей вероятностью останутся незамеченными, поскольку многие компиляторы выдают предупреждение во время компиляции, если к объявленным переменным осуществляется доступ до их инициализации. [1]

Причина висячих указателей

Во многих языках (например, языке программирования C ) явное удаление объекта из памяти или уничтожение кадра стека при возврате не приводит к изменению связанных указателей. Указатель по-прежнему указывает на то же место в памяти, хотя теперь это место можно использовать для других целей.

Простой пример показан ниже:

{ char * dp = NULL ; /* ... */ { char c ; дп = & с ; } /* c выходит за пределы области видимости */ /* dp теперь является висячим указателем */ }               

Если операционная система способна обнаруживать ссылки на нулевые указатели во время выполнения , решением вышеописанной проблемы является присвоение 0 (ноль) переменной dp непосредственно перед выходом из внутреннего блока. Другим решением было бы каким-то образом гарантировать, что dp не будет использоваться снова без дальнейшей инициализации.

Другим частым источником висячих указателей является беспорядочная комбинация вызовов библиотеки malloc()и free(): указатель становится висячим, когда блок памяти, на который он указывает, освобождается. Как и в предыдущем примере, один из способов избежать этого — обязательно сбросить указатель на ноль после освобождения его ссылки, как показано ниже.

#include <stdlib.h> void func () { char * dp = malloc ( A_CONST ); /* ... */ бесплатно ( dp ); /* dp теперь становится висячим указателем */ dp = NULL ; /* dp больше не висит */ /* ... */ }             

Слишком распространенной ошибкой является возврат адресов локальной переменной, выделенной в стеке: как только вызываемая функция возвращает значение, пространство для этих переменных освобождается, и технически они имеют «мусорные значения».

int * func ( void ) { int num = 1234 ; /* ... */ return & num ; }        

Попытки чтения указателя могут по-прежнему возвращать правильное значение (1234) в течение некоторого времени после вызова func, но любые функции, вызванные после этого, могут перезаписать выделенную для него память стека numдругими значениями, и указатель больше не будет работать правильно. Если указатель на numдолжен быть возвращен, numон должен иметь область действия за пределами функции — его можно объявить как static.

Ручное освобождение без висячей ссылки

Антони Кречмар  [ pl ] (1945–1996) создал полную систему управления объектами, свободную от феномена висячих ссылок. [2] Похожий подход был предложен Фишером и Лебланом [3] под названием «Замки и ключи» .

Причина диких указателей

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

Чаще всего это происходит из-за пропуска инициализации, а не из-за ее пропуска. Большинство компиляторов способны об этом предупредить.

int f ( int i ) { char * dp ; /* dp — это дикий указатель */ static char * scp ; /* scp не является диким указателем:  * статические переменные инициализируются значением 0  * при запуске и сохраняют свои значения  * после последнего вызова.  * Использование этой функции может считаться  * плохим стилем, если не прокомментировано */ }         

Дыры в безопасности, связанные со свисающими указателями

Подобно ошибкам переполнения буфера , ошибки висячих/диких указателей часто становятся дырами в безопасности. Например, если указатель используется для вызова виртуальной функции , может быть вызван другой адрес (возможно, указывающий на код эксплойта) из-за перезаписи указателя vtable . Альтернативно, если указатель используется для записи в память, может быть повреждена другая структура данных. Даже если память читается только после того, как указатель становится висячим, это может привести к утечке информации (если интересные данные помещаются в следующую структуру, выделенную там) или к повышению привилегий (если теперь недействительная память используется в проверках безопасности). Когда висячий указатель используется после того, как он был освобожден без выделения для него нового фрагмента памяти, это становится известно как уязвимость «использования после освобождения». [4] Например, CVE - 2014-1776 представляет собой уязвимость «использование после освобождения» в Microsoft Internet Explorer 6–11 [5], которая используется атаками «нулевого дня» со стороны усовершенствованной постоянной угрозы . [6]

Как избежать ошибок висячего указателя

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

#include <assert.h> #include <stdlib.h>  /* Альтернативная версия для 'free()' */ static voidsafefree ( void ** pp ) { /* в режиме отладки, прерывание , если pp равно NULL */ Assert ( pp ); /* free(NULL) работает правильно, поэтому никаких проверок, кроме утверждения в режиме отладки, не требуется */ free ( * pp ); /* освобождаем фрагмент, обратите внимание, что допустимо значение free(NULL) */ * pp = NULL ; /* сброс исходного указателя */ }            int f ( int i ) { char * p = NULL , * p2 ; р = malloc ( 1000 ); /* получаем фрагмент */ p2 = p ; /* копируем указатель */ /* используем фрагмент здесь */ safefree (( void ** ) & p ); /* безопасное освобождение; не влияет на переменную p2 */ safefree (( void ** ) & p ); /* этот второй вызов не завершится неудачно, поскольку p сбрасывается в NULL */ char c = * p2 ; /* p2 по-прежнему является висячим указателем, поэтому это неопределенное поведение. */ вернуть я + с ; }                               

Альтернативную версию можно использовать даже для того, чтобы гарантировать достоверность пустого указателя перед вызовом malloc():

 безопаснобесплатно ( & p ); /* я не уверен, что чанк выпущен */ p = malloc ( 1000 ); /* выделяем сейчас */     

Это использование может быть замаскировано с помощью #defineдиректив для создания полезных макросов (обычно это #define XFREE(ptr) safefree((void **)&(ptr))), создания чего-то вроде метаязыка или может быть отдельно встроено в библиотеку инструментов. В любом случае программисты, использующие эту технику, должны использовать безопасные версии в каждом случае, где они free()будут использоваться; невыполнение этого требования снова приводит к проблеме. Кроме того, это решение ограничено рамками одной программы или проекта и должно быть надлежащим образом задокументировано.

Среди более структурированных решений популярным способом избежать висячих указателей в C++ является использование интеллектуальных указателей . Интеллектуальный указатель обычно использует подсчет ссылок для восстановления объектов. Некоторые другие методы включают метод надгробий и метод замков и ключей . [3]

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

В таких языках, как Java, висячие указатели не могут возникнуть, поскольку не существует механизма явного освобождения памяти. Скорее, сборщик мусора может освободить память, но только тогда, когда объект больше не доступен ни по каким ссылкам.

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

Обнаружение висячего указателя

Чтобы выявить ошибки висячего указателя, одним из распространенных методов программирования является установка указателей на нулевой указатель или на недопустимый адрес после освобождения хранилища, на которое они указывают. При разыменовании нулевого указателя (в большинстве языков) программа немедленно завершает работу — вероятность повреждения данных или непредсказуемого поведения отсутствует. Это облегчает обнаружение и устранение основной ошибки программирования. Этот метод не помогает, когда имеется несколько копий указателя.

Некоторые отладчики автоматически перезаписывают и уничтожают освобожденные данные, обычно по определенному шаблону, например 0xDEADBEEF(например, отладчик Microsoft Visual C/C++ использует 0xCC, 0xCDили 0xDDв зависимости от того, что было освобождено [7] ). Обычно это предотвращает повторное использование данных, делая их бесполезными, а также очень заметными (шаблон служит для того, чтобы показать программисту, что память уже освобождена).

Такие инструменты, как Polyspace , TotalView , Valgrind , Mudflap, [8] AddressSanitizer или инструменты на основе LLVM [9] также могут использоваться для обнаружения использования висячих указателей.

Другие инструменты (SoftBound, Insure++ и CheckPointer) инструментируют исходный код для сбора и отслеживания законных значений указателей («метаданных») и проверки достоверности каждого доступа к указателю по метаданным.

Другая стратегия, если вы подозреваете небольшой набор классов, заключается в том, чтобы временно сделать все их функции-члены виртуальными : после того, как экземпляр класса был разрушен/освобожден, его указатель на таблицу виртуальных методов устанавливается в NULL, и любой вызов функции-члена будет аварийно завершить программу, и она отобразит виновный код в отладчике.

Смотрите также

Рекомендации

  1. ^ «Параметры предупреждения — использование коллекции компиляторов GNU (GCC)» .
  2. ^ Джанна Чиони, Антони Кречмар, Запрограммированное освобождение без висящей ссылки , Письма об обработке информации , т. 18, 1984 , стр. 179–185
  3. ^ ab CN Fisher, RJ Leblanc, Реализация диагностики во время выполнения в Паскале , Транзакции IEEE по разработке программного обеспечения , 6 (4): 313–319, 1980.
  4. ^ Дальчи, Эрик; анонимный автор; Группа контента CWE (11 мая 2012 г.). «CWE-416: Использовать после освобождения». Перечень распространенных слабостей . Корпорация Митра . Проверено 28 апреля 2014 г. {{cite web}}: |author2=имеет общее имя ( справка )
  5. ^ «CVE-2014-1776». Распространенные уязвимости и риски (CVE) . 29 января 2014 г. Архивировано из оригинала 30 апреля 2017 г. Проверено 16 мая 2017 г.
  6. ^ Чен, Сяобо; Кэселден, Дэн; Скотт, Майк (26 апреля 2014 г.). «Новый эксплойт нулевого дня, нацеленный на Internet Explorer версий с 9 по 11, обнаруженный в целевых атаках». Блог FireEye . Огненный Глаз . Проверено 28 апреля 2014 г.
  7. ^ Шаблоны заполнения памяти Visual C++ 6.0
  8. ^ Отладка указателя брызговика
  9. ^ Дхурджати Д. и Адве В. Эффективное обнаружение всех случаев использования висячих указателей на производственных серверах