Бравые помощники компилятора

Крис Касперски ака мыщъх

Хакер, номер #076, стр. 076-104-1

Обзор примочек для GCC

Времена, когда компиляторы — компилировали, а программисты — программировали уже давно позади. Современный компилятор это могучий инструмент, совмещающий в себе функциональность кухонного комбайна со стремительностью пикирующего бомбардировщика. Когда же его возможностей оказывается недостаточно, на помощь приходит множество полезных (и не очень полезных) утилит, от изобилия которых начинает рябить в глазах. Как же выбрать из всего этого хлама действительно нужное?

У разбитого корыта

Нет нужды говорить, что языки Си и Си++ не для прикладников. Это не Паскаль, складывающий строки так же, как и остальные типы данных, и не Ада с ее поддержкой динамических массивов и встроенным контролем границ. Идеологию Си хорошо выражают слова японского мультипликатора Миядзаки Хаяо — "стоит ли использовать компьютер для того, что можно сделать руками?". Обо всех проверках Си-программист должен заботиться самостоятельно, и если хоть однажды он об этом забудет (или допустит небрежность), последствия в виде нестабильной работы, червей или утечек памяти не заставят себя ждать.

Казалось бы — не умеешь программировать на Си, выбирай другой язык, например, Яву или Фортран. Так ведь нет, не хотят! Упрекают создателей Си в кретинизме, но с него не слезают. Попытки исправить язык, добавив в него, например, автоматический сборщик мусора, предпринимались неоднократно. Дружелюбно настроенные программисты предлагают не трогать язык, оставив Си/Си++ таким, какой он есть (руки прочь! пасть порву!), но изменить компилятор, заставляя его внедрять проверочный код после каждой потенциально небезопасной операции. Еще предлагают переписать все стандартные библиотеки, научив их распознавать наиболее характерные ошибки распределения памяти... Расплатой за это становится значительное падение производительности, что просто недопустимо.

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

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

Вот о примочках к компиляторам мы и будем говорить. Их можно разделить на две категории — средства противохакерской защиты, предотвращающие переполнения буфера (а вместе с этим — и засылку shell-кода) и детекторы ошибок распределения памяти, удерживающие программу от входа вразнос. Все описываемые утилиты, во-первых, бесплатны, а, во-вторых, не зажимают исходные тесты.

Хакеры под прицелом

Переполнения буфера чаще всего возникают не где-нибудь, а сосредоточены в строго определенных местах, которыми, как правило, являются следующие функции: strcpy(), strcat(), gets(), sprintf(), семейство scanf()-функций, [v][f]printf(), [v]snprintf() и syslog(). В девяти из десяти случаев передача управления на shell-код осуществляется путем подмены адреса возврата из функции. Остальные способы приходятся на модификацию индексов, указателей и прочих типов переменных. Причем переполнение буфера, как правило, происходит последовательно, то есть затирается непрерывный регион памяти. Индексное переполнение, при котором затирается несколько ячеек далеко за концом буфера, носит эпизодический характер и большой опасности не представляет.

Содержание  Вперед на стр. 076-104-2
ttfb: 3.2031536102295 ms