Борьба с утечками ресурсов и переполняющимися буферами

строка, реализованная в виде списка (продвинутая реализация)


Размер буфера может быть как фиксированным, так и динамическим. Хорошей стратег выделяет под первый элемент списка ~64 байт, под второй ~128 байт и так далее вплоть до 1000h, что позволяет обрабатывать как длинные, так и кроткие сроки с минимальным оверхидом.

Списки на 100% защищены от ошибок переполнения (исключение составляют попытки обработать строку свыше 2 Гбайт, вызывающую исчерпание свободной памяти, но, во-первых, исчерпание это все-таки не переполнение и заслать shell-код злоумышленник не сможет, а во-вторых, это _явная_ ошибка, которую легко обработать, установив предельный лимит на максимально разумную длину строки).

Хуже другое. Реализовав свою библиотеку для работы со "списочными строками", мы будем вынуждены переписать _все_ остальные библиотеки, создавая обертки для каждой строковой функции, включая fopen, CreateProcess и т. д., поскольку все они ожидают увидеть непрерывный массив байт, а вовсе не список! Это чрезвычайно утомительная работа, но зато когда она будет закончена, о переполнения можно забыть раз и навсегда. Правда, производительность (за счет постоянного преобразования типов) падает и весьма значительно…

А вот более быстрое, но менее надежное решение. Отказываемся от стековых буферов, переходя на динамическую память. Выделяем каждому блоку на одну страницу больше, чем нужно и присваиваем последней странице атрибут PAGE_NOACCESS, чтобы каждое обращение к ней вызывало исключение, отлавливаемое нашим обработчиком, который в зависимости от ситуации либо увеличивал размер буфера, либо завершал работу программы с сообщением об ошибке. На коротких строках оверхид весьма значителен, но на длинных он минимален, однако, такая защита страхует лишь от последовательного переполнения, но бессильна предотвратить индексное (подробнее о видах переполнения можно прочитать в моей книжке "shellcoders's programming uncovered", которую можно найти в Осле), к тому же переход на динамические массивы порождает проблему утечек памяти и получается так, что одно лечим, а другое калечим.


Тем не менее, лишний раз подстраховаться никогда не помешает! Чтобы защититься от  переполнения кучи (которое в последнее время приобретает все большую популярность) после вызова любой функции, работающей с динамической памятью, необходимо защищать служебные данные кучи атрибутом PAGE_NOACCESS, а перед вызовом функции — снимать их. Для этого нам, опять-таки потребуется написать обертки вокруг всех функций менеджера памяти, что требует времени. К тому же, в реализации кучи от Microsoft Visual C++, служебные данные лежат по смещению -10h от начала выделенного блока, а защищать мы можем только всю страницу целиком, поэтому, во-первых, необходимо увеличить размер каждого блока до 512 Кбайт, чтобы начальный адрес совпадал с началом страницы, а во-вторых, использовать блок только со второй страницы. В результате, при работе с мелкими блоками мы получаем чудовищный оверхид, но зато компенсируемый надежной защитой от переполнения. Так что данный метод, при всех его недостатках, все-таки имеет право на жизнь.


Содержание раздела