Защита от переполнения буфера — это любой из различных методов, используемых во время разработки программного обеспечения для повышения безопасности исполняемых программ путем обнаружения переполнений буфера в переменных, выделенных в стеке , и предотвращения того, чтобы они вызывали неправильное поведение программы или становились серьезными уязвимостями безопасности . Переполнение буфера стека происходит, когда программа записывает данные по адресу памяти в стеке вызовов программы за пределами предполагаемой структуры данных, которая обычно представляет собой буфер фиксированной длины. Ошибки переполнения буфера стека возникают, когда программа записывает больше данных в буфер, расположенный в стеке, чем фактически выделено для этого буфера. Это почти всегда приводит к повреждению смежных данных в стеке, что может привести к сбоям программы, неправильной работе или проблемам безопасности.
Обычно защита от переполнения буфера изменяет организацию данных, выделенных в стеке, так что она включает значение canary , которое при уничтожении переполнением буфера стека показывает, что предшествующий ему буфер в памяти переполнен. Проверяя значение canary, можно прекратить выполнение затронутой программы, предотвращая ее неправильное поведение или позволяя злоумышленнику получить над ней контроль. Другие методы защиты от переполнения буфера включают проверку границ , которая проверяет доступы к каждому выделенному блоку памяти, чтобы они не могли выйти за пределы фактически выделенного пространства, и тегирование , которое гарантирует, что память, выделенная для хранения данных, не может содержать исполняемый код.
Переполнение буфера, выделенного в стеке, с большей вероятностью повлияет на выполнение программы, чем переполнение буфера в куче , поскольку стек содержит адреса возврата для всех активных вызовов функций. Однако существуют также аналогичные специфичные для реализации защиты от переполнений кучи.
Существует несколько реализаций защиты от переполнения буфера, в том числе для GNU Compiler Collection , LLVM , Microsoft Visual Studio и других компиляторов.
Переполнение буфера стека происходит, когда программа записывает данные по адресу памяти в стеке вызовов программы за пределами предполагаемой структуры данных, которая обычно является буфером фиксированной длины. Ошибки переполнения буфера стека возникают, когда программа записывает больше данных в буфер, расположенный в стеке, чем фактически выделено для этого буфера. Это почти всегда приводит к повреждению смежных данных в стеке, а в случаях, когда переполнение было вызвано ошибкой, часто приводит к сбою программы или ее неправильной работе. Переполнение буфера стека — это тип более общей программной ошибки, известной как переполнение буфера (или переполнение буфера). Переполнение буфера в стеке с большей вероятностью сорвет выполнение программы, чем переполнение буфера в куче, поскольку стек содержит адреса возврата для всех активных вызовов функций. [1]
Переполнение буфера стека может быть вызвано преднамеренно как часть атаки, известной как разбивание стека . Если затронутая программа работает с особыми привилегиями или принимает данные с ненадежных сетевых хостов (например, публичного веб-сервера ), то ошибка является потенциальной уязвимостью безопасности, которая позволяет злоумышленнику внедрить исполняемый код в работающую программу и взять под контроль процесс. Это один из старейших и наиболее надежных методов, с помощью которых злоумышленники могут получить несанкционированный доступ к компьютеру. [2]
Обычно защита от переполнения буфера изменяет организацию данных в стековом кадре вызова функции , чтобы включить «канарейное» значение, которое при уничтожении показывает, что предшествующий ему буфер в памяти был переполнен. Это обеспечивает преимущество предотвращения целого класса атак. По мнению некоторых исследователей [3] , влияние этих методов на производительность незначительно.
Защита от разрушения стека не может защитить от определенных форм атак. Например, она не может защитить от переполнения буфера в куче. Не существует разумного способа изменить расположение данных в структуре ; ожидается, что структуры будут одинаковыми между модулями, особенно с общими библиотеками. Любые данные в структуре после буфера невозможно защитить с помощью канареек; поэтому программисты должны быть очень осторожны с тем, как они организуют свои переменные и используют свои структуры.
Канарейки или канареечные слова или стековые куки — это известные значения, которые размещаются между буфером и управляющими данными в стеке для отслеживания переполнений буфера. При переполнении буфера первыми поврежденными данными обычно являются канарейки, и неудачная проверка данных канарейки, таким образом, оповестит о переполнении, которое затем можно устранить, например, сделав поврежденные данные недействительными. Канареечное значение не следует путать со значением- сентинелом .
Терминология является ссылкой на историческую практику использования канареек в угольных шахтах , поскольку они подвергались воздействию токсичных газов раньше, чем шахтеры, тем самым обеспечивая биологическую систему оповещения. Канарейки также известны как stack cookies , что призвано вызывать образ «сломанного печенья», когда ценность повреждена.
Используются три типа канареек: терминатор , случайный и случайный XOR . Текущие версии StackGuard поддерживают все три, в то время как ProPolice поддерживает терминатор и случайные канарейки.
Канарейки-терминаторы используют наблюдение, что большинство атак переполнения буфера основаны на определенных строковых операциях, которые заканчиваются на терминаторах строк. Реакция на это наблюдение заключается в том, что канарейки построены из нулевых терминаторов, CR , LF и FF . В результате злоумышленник должен написать нулевой символ перед записью адреса возврата, чтобы избежать изменения канарейки. Это предотвращает атаки с использованием strcpy()
и других методов, которые возвращаются при копировании нулевого символа, в то время как нежелательным результатом является то, что канарейка известна. Даже с защитой злоумышленник потенциально может перезаписать канарейку ее известным значением и управляющей информацией с несовпадающими значениями, таким образом передавая код проверки канарейки, который выполняется вскоре перед инструкцией возврата из вызова конкретного процессора.
Случайные канарейки генерируются случайным образом, обычно демоном сбора энтропии , чтобы помешать злоумышленнику узнать их значение. Обычно логически невозможно или правдоподобно прочитать канарейку для эксплуатации; канарейка — это безопасное значение, известное только тем, кому оно необходимо — в данном случае коду защиты от переполнения буфера.
Обычно случайная канарейка генерируется при инициализации программы и сохраняется в глобальной переменной. Эта переменная обычно дополняется неотображенными страницами, так что попытка прочитать ее с помощью любых трюков, которые используют ошибки для чтения из ОЗУ, вызывает ошибку сегментации, завершающую программу. Все еще может быть возможно прочитать канарейку, если злоумышленник знает, где она находится, или может заставить программу читать из стека.
Случайные XOR-канарейки — это случайные канарейки, которые XOR-скремблированы с использованием всех или части контрольных данных. Таким образом, как только канарейка или контрольные данные затираются, значение канарейки становится неверным.
Случайные канарейки XOR имеют те же уязвимости, что и случайные канарейки, за исключением того, что метод «чтения из стека» получения канарейки немного сложнее. Атакующий должен получить канарейку, алгоритм и контрольные данные, чтобы повторно сгенерировать исходную канарейку, необходимую для подмены защиты.
Кроме того, случайные XOR-канарейки могут защитить от определенного типа атак, включающих переполнение буфера в структуре в указатель для изменения указателя так, чтобы он указывал на часть управляющих данных. Из-за кодировки XOR канарейка будет неправа, если управляющие данные или возвращаемое значение будут изменены. Из-за указателя управляющие данные или возвращаемое значение могут быть изменены без переполнения канарейки.
Хотя эти канарейки защищают управляющие данные от изменения затираемыми указателями, они не защищают никакие другие данные или сами указатели. Указатели функций представляют собой особую проблему, поскольку они могут переполняться и могут выполнять шеллкод при вызове.
Проверка границ — это основанная на компиляторе техника, которая добавляет информацию о границах времени выполнения для каждого выделенного блока памяти и проверяет все указатели на соответствие им во время выполнения. Для C и C++ проверка границ может выполняться во время вычисления указателя [4] или во время разыменования. [5] [6] [7]
Реализации этого подхода используют либо центральный репозиторий, который описывает каждый выделенный блок памяти, [4] [5] [6], либо толстые указатели , [7] которые содержат как указатель, так и дополнительные данные, описывающие область, на которую они указывают.
Тегирование [8] — это основанная на компиляторе или оборудовании (требующая тегированной архитектуры ) техника для тегирования типа фрагмента данных в памяти, используемая в основном для проверки типов. Помечая определенные области памяти как неисполняемые, она эффективно предотвращает выделение памяти для хранения данных от содержания исполняемого кода. Кроме того, определенные области памяти могут быть помечены как невыделенные, предотвращая переполнение буфера.
Исторически тегирование использовалось для реализации языков программирования высокого уровня; [9] при соответствующей поддержке со стороны операционной системы тегирование также может использоваться для обнаружения переполнений буфера. [10] Примером является аппаратная функция NX bit , поддерживаемая процессорами Intel , AMD и ARM .
Защита от разрушения стека была впервые реализована StackGuard в 1997 году и опубликована на симпозиуме по безопасности USENIX 1998 года . [11] StackGuard был представлен как набор исправлений для бэкэнда Intel x86 GCC 2.7. StackGuard поддерживался для дистрибутива Immunix Linux с 1998 по 2003 год и был расширен реализациями для терминатора, случайных и случайных XOR-канареек. StackGuard был предложен для включения в GCC 3.x на саммите GCC 2003 года, [12] но это так и не было сделано.
С 2001 по 2005 год IBM разработала патчи GCC для защиты от разрушения стека, известные как ProPolice . [13] Он улучшил идею StackGuard, разместив буферы после локальных указателей и аргументов функций в стековом фрейме. Это помогло избежать повреждения указателей, предотвращая доступ к произвольным ячейкам памяти.
Однако инженеры Red Hat выявили проблемы с ProPolice и в 2005 году повторно реализовали защиту от разрушения стека для включения в GCC 4.1. [14] [15] Эта работа представила -fstack-protectorфлаг, который защищает только некоторые уязвимые функции, и -fstack-protector-allфлаг, который защищает все функции, независимо от того, нужны они им или нет. [16]
В 2012 году инженеры Google внедрили -fstack-protector-strongфлаг, чтобы достичь лучшего баланса между безопасностью и производительностью. [17] Этот флаг защищает больше видов уязвимых функций, чем -fstack-protector, но не все функции, обеспечивая лучшую производительность, чем -fstack-protector-all. Он доступен в GCC с версии 4.9. [18]
Все пакеты Fedora скомпилированы с помощью -fstack-protectorначиная с Fedora Core 5 и -fstack-protector-strongс Fedora 20. [19] [20] Большинство пакетов в Ubuntu скомпилированы с помощью -fstack-protectorначиная с 6.10. [21] Каждый пакет Arch Linux скомпилирован с помощью -fstack-protectorс 2011 года. [22] Все пакеты Arch Linux, собранные с 4 мая 2014 года, используют -fstack-protector-strong. [23] Защита стека используется только для некоторых пакетов в Debian , [24] и только для базовой системы FreeBSD с 8.0. [25] Защита стека является стандартной в некоторых операционных системах, включая OpenBSD , [26] Hardened Gentoo [27] и DragonFly BSD . [ необходима ссылка ]
StackGuard и ProPolice не могут защитить от переполнений в автоматически выделяемых структурах, которые переполняются в указатели функций. ProPolice по крайней мере перестроит порядок выделения, чтобы такие структуры выделялись до указателей функций. Отдельный механизм защиты указателей был предложен в PointGuard [28] и доступен в Microsoft Windows. [29]
Компиляторный пакет от Microsoft реализует защиту от переполнения буфера с версии 2003 года с помощью ключа командной строки /GS , который включен по умолчанию с версии 2005 года. [30] Использование /GS- отключает защиту.
Защиту от разрушения стека можно включить с помощью флага компилятора -qstackprotect
. [31]
Clang поддерживает те же -fstack-protectorпараметры, что и GCC [32] , а также более мощную систему «безопасного стека» ( -fsanitize=safe-stack ) с аналогичным низким влиянием на производительность. [33] Clang также имеет три детектора переполнения буфера, а именно AddressSanitizer ( ), [6] UBSan ( ), [34]
и неофициальный SafeCode (последнее обновление для LLVM 3.0). [35]-fsanitize=address
-fsanitize=bounds
Эти системы имеют различные компромиссы с точки зрения штрафа производительности, накладных расходов памяти и классов обнаруженных ошибок. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD . [36]
Компиляторы Intel C и C++ поддерживают защиту от разрушения стека с параметрами, аналогичными тем, которые предоставляются GCC и Microsoft Visual Studio. [37]
Fail-Safe C [7] — это безопасный для памяти компилятор ANSI C с открытым исходным кодом, который выполняет проверку границ на основе толстых указателей и объектно-ориентированного доступа к памяти. [38]
StackGhost, изобретенный Майком Франценом, представляет собой простую настройку процедур сброса/заполнения окна регистра, что значительно затрудняет эксплуатацию переполнений буфера. Он использует уникальную аппаратную функцию архитектуры Sun Microsystems SPARC (а именно: отложенный сброс/заполнение окна регистра в стеке внутри кадра) для прозрачного обнаружения изменений указателей возврата (распространенный способ для эксплойта перехватить пути выполнения), автоматически защищая все приложения без необходимости внесения изменений в двоичные или исходные файлы. Влияние на производительность незначительно, менее одного процента. Возникшие в результате проблемы с gdb были устранены Марком Кеттенисом два года спустя, что позволило включить эту функцию. После этого события код StackGhost был интегрирован (и оптимизирован) в OpenBSD /SPARC.
{{cite web}}
: CS1 maint: бот: исходный статус URL неизвестен ( ссылка )появился в GCC 4.9.
gcc поставляется с расширением защиты стека
ProPolice
, которое включено по умолчанию.
Gentoo hardened GCC включает защиту стека по умолчанию, если явно не указано иное.
В clang по умолчанию включена защита стека, эквивалентная опции
-fstack-protector-strong
в других системах.