Является ли переполнение стека дырой в безопасности? - PullRequest
7 голосов
/ 05 ноября 2010

Примечание: этот вопрос относится к переполнению стека (например, бесконечная рекурсия), а НЕ к переполнению буфера.

Если я пишу корректную программу, но она принимает данные из Интернета, определяющие уровень рекурсиив рекурсивной функции, которую она вызывает, этого достаточно для того, чтобы позволить кому-то скомпрометировать машину?

Я знаю, что кто-то может остановить процесс, вызвав переполнение стека, но может ли он внедрить код?Или среда выполнения c обнаруживает состояние переполнения стека и корректно прерывает работу?

Просто любопытно ...

Ответы [ 5 ]

4 голосов
/ 05 ноября 2010

Rapid Refresher

Прежде всего, вы должны понимать, что основными единицами защиты в современных ОС являются процесс и страница памяти.Процессы являются доменами защиты памяти;это уровень, на котором ОС применяет политику безопасности, и, таким образом, они строго соответствуют работающей программе.(Там, где они этого не делают, это либо потому, что программа работает в нескольких процессах, либо потому, что программа используется совместно в какой-то среде; последний случай потенциально может быть «интересным с точки зрения безопасности», но это «другая история».)Страницы виртуальной памяти - это уровень, на котором оборудование применяет правила безопасности;каждая страница в памяти процесса имеет атрибуты, которые определяют, что процесс может делать со страницей: может ли он читать страницу, может ли он писать на нее и может ли он выполнять программный код на ней (хотя третий атрибут довольноредко используется, чем, возможно, должно быть).Скомпилированный программный код отображается в памяти на страницах, которые могут быть как читаемыми, так и исполняемыми, но недоступными для записи, тогда как стек должен быть читаемым и записываемым, но не исполняемым. Большинство страниц памяти вообще не читаемы, не доступны для записи и не выполняются; ОС позволяет процессу использовать только столько страниц, сколько ему явно требуется, и именно этим управляют библиотеки выделения памяти (malloc() и др.)для вас.

Анализ

При условии, что каждый кадр стека меньше страницы памяти [1] , так что, когда программа перемещается по стеку, она записывает в каждыйНа странице ОС (т. е. привилегированная часть среды выполнения) может, по крайней мере, в принципе надежно обнаружить переполнение стека и завершить программу, если это произойдет.По сути, все, что происходит при таком обнаружении, - это наличие страницы, на которую программа не может записать в конце стека;если программа пытается выполнить запись в нее, аппаратное обеспечение управления памятью перехватывает ее, и ОС получает возможность вмешаться.

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

Последствия

Каковы последствия этого?Ну, ОС должна правильно делать с управлением памятью.(Ссылка @ Michael иллюстрирует, что может случиться, когда это происходит неправильно.) Но также опасно позволять злоумышленнику определять размеры выделения памяти, когда вы не форсируете запись для всего выделения немедленно;Массивы переменного размера alloca и C99 представляют собой особую угрозу.Более того, я был бы более подозрительным к коду C ++, так как он имеет тенденцию делать намного больше выделения памяти на основе стека;это может быть хорошо, но есть большая вероятность того, что что-то пойдет не так.

Лично я предпочитаю в любом случае сохранять размеры стеков и размеры стековых фреймов небольшими и делать все выделения с переменным размером в куче.Частично это наследие работы с некоторыми типами встроенных систем и с кодом, который использует очень большое количество потоков, но это значительно упрощает защиту от атак переполнения стека;ОС может надежно их перехватить, и тогда у злоумышленника есть отказ в обслуживании (раздражающий, но редко фатальный).Я не знаю, является ли это решением для всех программистов.


[1] Типичные размеры страниц: 4 КБ в 32-разрядных системах, 16 КБ в 64-разрядных системах.Проверьте системную документацию, чтобы узнать, что находится в вашей среде.

3 голосов
/ 05 ноября 2010

Большинство систем (например, Windows) завершают работу при переполнении стека. Я не думаю, что вы, вероятно, увидите здесь проблему безопасности. По крайней мере, не проблема повышения привилегий. Вы можете получить некоторые проблемы отказа в обслуживании.

2 голосов
/ 05 ноября 2010

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

Стоит отметить, что обычно довольно легко использовать счетчик для установки произвольного, но щедрого предела рекурсивной глубины: вы можете использовать статическую локальную переменную, если она однопоточна, или конечный параметр (обычно по умолчанию 0, если ваш язык позволяет, в противном случае внешний вызывающий абонент должен предоставить 0 в первый раз).

1 голос
/ 05 ноября 2010

Да, это так.Есть алгоритмы, позволяющие избежать рекурсивности.Например, в случае арифметических выражений обратная польская запись позволяет избежать рекурсивности.Основная идея заключается в том, чтобы изменить оригинальное выражение.Там может быть какой-то алгоритм, который также может вам помочь.

Еще одна проблема с переполнением стека: если обработка ошибок не подходит, она может вызвать что угодно.Например, чтобы объяснить это в Java StackOverflowError , это ошибка, и она обнаруживается, если кто-то ловит Throwable , что является распространенной ошибкой.Поэтому обработка ошибок является ключевым вопросом в случае переполнения стека.

0 голосов
/ 05 ноября 2010

Да, это так. Доступность является важным аспектом безопасности, который в основном игнорируется.

Не падайте в эту яму.

редактировать

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

...