c-triks-9
сишные трюки\\ (9й выпуск)
крис касперски ака мыщъх, ака souriz, akanezumi, akaelraton, no-email
unionsvs нецензурный кастинг
Типизация, призванная оградить программиста от совершения ошибок, хорошо работает лишь на бумаге, а в реальной жизни порождает множество проблем (особенно при низкоуровневом разборе байтов), решаемых с помощью явного преобразования типов или, другим словами «кастинга» (от английского «casting»), например, так:
int *p; char x;
…
x = *1); инициализируем область памяти структуры XY … что-то делаем со структурами
…
if (!memcmp(&XX, &XY, sizeof(XX))
/* структуры идентичны */
else
/* структуры _не_ идентичны */
Листинг 5 «обнуление» области памяти, занятой структурой, дает зеленый свет операции побайтового сравнения
strncpyvsstrcpy
В борьбе с переполняющимися буферами программисты перелопачивают тонны исходного кода на погонный метр, заменяя все потенциально опасные функции их безопасными аналогами с суффиксом «n», позволяющим задать предельный размер обрабатываемой строки или блока памяти.
Часто подобная замена делается чисто механически без учета специфики n-функций и не только не устраняет ошибки, но даже увеличивает их число. Вероятно, самым популярным ляпом является замена strcpy на strncpy.
Рассмотрим код вида:
f00(char *s)
{
char buf[BUF_SIZE];
…
strcpy(buf,s);
}
Листинг 6 потенциально опасный код, подверженный переполнению
Если длина строки s превысит размер буфера buf, произойдет переполнение, результатом которого зачастую становится полная капитуляция компьютера перед злоумышленником, чего допускать ни в коем случае нельзя и в благородном порыве гражданского долга многие переписывают потенциально опасный код так:
f00(char *s)
{
char buf[BUF_SIZE];
…
strncpy(buf,s, BUF_SIZE);
}
Листинг 7 исправленный, но по-прежнему потенциально опасный вариант того же самого кода
или так…
f00(char *s)
{
char buf[BUF_SIZE];
…
strncpy(buf,s, BUF_SIZE-1);
}
Листинг 8 …еще один потенциально опасный вариант
Хе-хе. Если размер строки s превысит значение BUF_SIZE (или BUF_SIZE-1), функция strncpy прервет копирование, «забыв» поставить завершающий ноль. Причем, об этом будет очень трудно узнать, поскольку сообщение об ошибке в этом случае не возвращается, а попытка определить фактическую длину скопированной строки через strlen(buf) ни к чему хорошему не приводит, поскольку в отсутствии завершающего нуля в лучшем случае мы получаем неверный размер, в худшем — исключение.
Находятся программисты, добавляют завершающий ноль вручную, делая это приблизительно так:
f00(char *s)
{
char buf[BUF_SIZE];
…
buf[BUF_SIZE-1] = 0;
strncpy(buf,s, BUF_SIZE-1);
}
Листинг 9 не подверженный переполнению, но по-прежнему неправильно работающий код
Такой код вполне безопасен в плане переполнения, однако, порочен, греховен и ненадежен, поскольку, маскирует факт «обрезания» строки, что приводит к непредсказуемой работе программы. Вот только один пример. Допустим, в переменной s передается путь к каталогу для удаления его содержимого. Допустим так же, что в силу каких-то обстоятельств, длина пути превысит BUF_SIZE и он окажется усечен. Если усечение произойдет на границе «\», то удаленным окажется совсем другой каталог, причем каталог более высокого уровня!!!
Самый простой и единственно правильный вариант выглядит как показано в листинге 10, а функция strncpy, кстати говоря, изначально задумывалась для копирования не ASCIIZ строк, т.е. строк _не_ содержащих символа завершающего нуля и это совсем не аналог strcpy! Эти две функции _не_ взаимозаменяемы!
f00(char *s)
{
char buf[BUF_SIZE];
…
if (strlen(s)>=BUF_SIZE) return ERROR; else strcpy(buf,s);
}
Листинг 10 безопасный и правильно работающий вариант