Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Доверяй, но проверяй

Косякин Антон

Спецвыпуск Xakep, номер #045, стр. 045-024-1


(deil@gameland.ru)

Учимся грамотной работе с памятью

Ущерб от ошибок и недосмотров программистов при работе с памятью огромен. Взять хотя бы всем известный MS Windows (R): в последнее время большая часть патчей к нему связана как раз с устранением последствий кривой работы с памятью и переполнения буфера. Давай попробуем во всем разобраться, чтобы в будущем этого избежать.

Неправильный доступ и переполнения

Рассмотрим для примера функцию, добавляющую одну строку в конец другой, strcat(s1, s2). Допустим, в начале программы мы сделали так: char *s1 = malloc(2); memset(s1, 0, 2), выделив строке s1 два байта и обнулив их. Теперь, если, например, strcat(s1, "j00 r 0wn3d"), функция честно скопирует строку "j00 r 0wn3d" по адресу, на который указывает s1. Ведь strcat не знает о том, что наша программа выделила всего два байта под s1! Поэтому она просто перезапишет чужие данные, заполнив их куском нашей строки. В данном случае это не очень критично.

Однако вот такой код уже может прилично навредить:

Листинг

Нарочно делаем себе гадости

typedef struct _test {

char s1[2];

char s2[10];

} test_t;

test_t t;

strcpy(t.s2, "hello world");

strcpy(t.s1, "j00 r 0wn3d");

Отличие данного варианта от описанного выше в том, что в здесь s2 будет указывать на область, следующую сразу же за s1. И поэтому strcat(s1, "j00 r 0wn3d") с радостью перезапишет строку, хранящуюся в s2, даже не заметив этого :-).

В таком случае опасность не особо велика и заключается только в том, что пользовательские данные могут быть утеряны и программа просто начнет себя странно вести. Но могут произойти и более занятные вещи.

Рассмотрим функцию strlen, которая подсчитывает количество символов в строке. Предположим, что она перебирает символы до тех пор, пока не встретит ноль – он будет означать, что строка закончилась. Также будем считать, что у нас есть указатель на строку s1, для которой мы выделили сто байт. Давай забудем проинициализировать выделенную память нулями. Затем сделаем strcpy(s1, "hello world") и посчитаем длину нашей строки: strlen(s1) честно пробежится по словосочетанию “hello world” и пойдет считать дальше, пока не встретит ноль. В лучшем случае программа выдаст неправильное значение и все дальнейшие расчеты будут, мягко говоря, неточны :-). А в худшем?

Наша бравая функция смело пробежит выделенные строке 100 б и пошлепает дальше, где наверняка в один прекрасный момент попытается прочитать байт, на чтение которого она прав не имеет. Любая система тотчас неодобрительно отреагирует на подобную наглость, а UNIX-система сразу же пошлет процессу сигнал SIGSEGV и грохнет его. В результате чего пользователь увидит заветное "Segmentation fault".

Теперь обсудим такой пример:

float *pi;

*pi = 3.141592654;

printf("pi is %f", *pi);

Догадываешься, что может тут случиться? В начале программы создастся указатель на вещественное число, но мы его нарочно не проинициализировали. Выполнение такого кода в большинстве случаев приведет к "Segmentation fault" или появлению окошечка с ошибкой.

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

Содержание  Вперед на стр. 045-024-2