Из Википедии, бесплатной энциклопедии
  (Перенаправлено из Use-after-free )
Перейти к навигации Перейти к поиску
Висячий указатель

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

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

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

Причина оборванных указателей [ править ]

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

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

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

Если операционная система способна обнаруживать ссылки времени выполнения на нулевые указатели , решение вышеуказанного состоит в том, чтобы присвоить dp значение 0 (null) непосредственно перед выходом из внутреннего блока. Другое решение - каким-то образом гарантировать, что 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 должен иметь область за пределами функции - он может быть объявлен как статический .

Освобождение вручную без зависшей ссылки [ править ]

Антони Кречмар  [ pl ] (1945–1996) создал полную систему управления объектами, свободную от феномена висячих ссылок, см. [2]

Схема аксиом операции kill
Пусть x 1 , ..., x n - переменные, n> 0, 1≤i≤n. Каждая формула следующей схемы - это теорема о виртуальной машине, построенной Кречмаром.
читается как : если объект o является значением n переменных, то после выполнения инструкции kill (x i ) общее значение этих переменных равно нулю (это означает, что с этого момента объект o недоступен и, следовательно, часть память, занимаемая им, может быть переработана с помощью той же операции kill без какого-либо вреда).

Как следствие:

  • нет необходимости повторять операцию kill (x 1 ), kill (x 2 ), ... [3]
  • нет феномена висячих ссылок ,
  • любая попытка доступа к удаленному объекту будет обнаружена и обозначена как исключение « ссылка ни на что ».

Примечание: стоимость убийства постоянна .

Аналогичный подход был предложен Фишером и Лебланом [4] под названием « Замки и ключи» .

Причина диких указателей [ править ]

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

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

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

Бреши в безопасности, связанные с висячими указателями [ править ]

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

Как избежать ошибок висячих указателей [ править ]

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

#include  <assert.h>#include  <stdlib.h>/ * Альтернативная версия для 'free ()' * / void  safefree ( void  ** pp ) {  / * в режиме отладки, прервать, если pp равен NULL * /  assert ( pp );  if  ( pp  ! =  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 );  / * этот второй вызов не потерпит неудачу * / char  c  =  * p2 ;  / * p2 по-прежнему является висячим указателем, так что это неопределенное поведение. * /  return  i  +  c ; }

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

 safefree ( & p );  / * я не уверен, что чанк был освобожден * /  p  =  malloc ( 1000 );  / * выделить сейчас * /

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

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

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

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

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

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

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

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

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

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

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

Другое использование [ править ]

Термин « висячий указатель» может также использоваться в других контекстах, помимо программирования, особенно техническими специалистами. Например, номер телефона человека, который с тех пор сменил телефон, является реальным примером висящего указателя. [11] Другим примером является запись в онлайн-энциклопедии, которая ссылается на другую запись, название которой было изменено, при этом любые ранее существовавшие ссылки на эту запись заменяются на висячие указатели.

См. Также [ править ]

  • Распространенные уязвимости и подверженности
  • Отладчик памяти
  • Дикая ветка

Ссылки [ править ]

  1. ^ https://gcc.gnu.org/onlinedocs/gcc-4.0.2/gcc/Warning-Options.html
  2. ^ Джанна Cioni, Antoni Kreczmar, Программируемая открепление без оборванных ссылок , обработки информации Letters, v. 18, 1984 , pp.179-185
  3. ^ В C ++ помещаем инструкции delete (x 1, ); ... delete (x n ); это единственный способ избежать ошибки висящего указателя.
  4. ^ a b C.N. Фишер, Р. Дж. Леблан, Реализация диагностики во время выполнения в Паскале , IEEE Trans. Софтв. Eng., 6 (4): 313-319, 1980.
  5. ^ Дальчи, Эрик; анонимный автор; CWE Content Team (11 мая 2012 г.). «CWE-416: использовать после бесплатного» . Перечень общих слабых мест . Корпорация Митра . Проверено 28 апреля 2014 года .
  6. ^ "CVE-2014-1776" . Распространенные уязвимости и воздействия (CVE) . 2014-01-29. Архивировано из оригинала на 2017-04-30 . Проверено 16 мая 2017 .
  7. ^ Чен, Сяобо; Казелден, Дэн; Скотт, Майк (26 апреля 2014 г.). «Новый эксплойт нулевого дня, нацеленный на Internet Explorer версий с 9 по 11, обнаруженный в целевых атаках» . Блог FireEye . FireEye . Проверено 28 апреля 2014 года .
  8. ^ Шаблоны заполнения памяти Visual C ++ 6.0
  9. ^ Отладка указателя брызговика
  10. ^ Дурджати, Д. и Адве, В. Эффективное обнаружение всех видов использования висячих указателей на рабочих серверах
  11. ^ "Файл жаргона" . версия 4.4.7 . Проверено 7 января 2014 .