байтов и возвращает указатель на начало этой области. Вместо использования памяти из программной кучи этот метод распределяет память из вершины стека, из того же места, где хранятся локальные переменные. Первое преимущество данной функции перед локальными переменными состоит в том, что необходимое количество байтов точно вычисляется в программе, а не определяется приблизительно. Превосходство над
malloc
заключается в том, что при завершении работы функции память освобождается автоматически. Все это позволяет охарактеризовать
alloca
как легкий способ распределения памяти, которая требуется только временно. До тех пор, пока размер буфера вычисляется должным образом (не забудьте учесть
'\0'
в конце каждой строки С!), можно не бояться переполнения буфера [161] .
161
Функция
alloca
не является стандартным средством языка С, однако компилятор gcc предоставляет
alloca
в большинстве поддерживаемых операционных систем. В более старых версиях gcc (до версии 3.3)
alloca
не всегда должным образом взаимодействовала с массивами динамических размеров (другое расширение GNU), поэтому примите во внимание, что использовать нужно только одну из версий.
Есть еще также несколько других функций, которые помогают избежать переполнения буфера. Библиотечные методы
strncpy
и
strncat
легко предотвращают перегрузки буфера при копировании строк.
Обе функции ведут себя как их родственники, называемые аналогично,
strcpy
и
strcat
, но они возвращают за один раз только max байт, копируемые в строку назначения. Если достигнут предел, то результирующая строка не завершается
'\0'
, поэтому обычные строковые функции не смогут с ней работать. По этой причине необходимо явно завершить строку после вызова одной из подобных функций.
strncpy(dest, src, sizeof(dest));
dest[sizeof(dest) - 1] = '\0';
Частой ошибкой при использовании
strncat
является передача общего размера
dest
в качестве параметра
max
. Это приводит к потенциальному переполнению буфера, так как
strncat
добавляет до
max
байт в
dest
; она не прекращает копировать байты, когда общая длина
dest
достигает
max
байтов.
Несмотря на то что эти функции могут сделать выполнение программы некорректным при передаче длинных строк (из-за усечения этих строк), данный прием хорошо предотвращает перегрузки в буферах статических размеров. Во многих случаях это приемлемый компромисс (во всяком случае, при этом не произойдет ничего хуже того, что может случиться из-за переполнения буфера).
Функция
strncpy
решает проблему копирования строки в статический буфер без переполнения его. А функции
strdup
автоматически выделяют буфер, достаточный для хранения строки, до начала копирования в него исходной строки.
#include <string.h>
char * strdup(const char * src);
char * strdupa(const char * src);
char * strndup(const char * src, int max);
char * strndupa(const char * src, int max);
Первая из приведенных функций,
strdup
, копирует строку
src
в буфер, выделенный методом
malloc
, и возвращает буфер вызывающему оператору. Вторая функция,
strdupa
, выделяет буфер с помощью
alloca
. При этом обе функции выделяют буфер, в точности достаточный для хранения строки и замыкающего символа
'\0'
.
Остальные две функции,
strndup
и
strndupa
, копируют не более чем max байтов из
str
в буфер вместе с замыкающим
'\0'
(и выделяют не более чем
max
+1 байтов). При этом выделение буфера происходит при помощи метода
malloc
(для
strndup
) или
alloca
(для
strndupa
).
Функция
sprintf
также входит в число тех, которые часто вызывают переполнение буфера. Так же как
strcat
и
strcpy
, функция
sprintf
имеет разновидность, позволяющую облегчить защиту от перегрузок.
#include <stdio.h>
int snprintf(char * str, size_t max, char * format, ...);
Попытки определить размер буфера, необходимый для
sprintf
, могут
оказаться слишком сложными. Он зависит от таких элементов, как значения всех форматируемых чисел (для которых могут быть нужны или не нужны знаки чисел), используемые аргументы форматирования и длины всех строк, которые были затронуты форматированием. Для того чтобы избежать переполнения буфера, функция
snprintf
помещает в
str
не более чем max символов, включая замыкающий
'\0'
. В отличие от
strcat
и
strncat
, функция
snprintf
корректно завершает строку, при необходимости пренебрегая символом из форматируемой строки. Она возвращает количество символов, которые будет занимать конечная строка при наличии доступного пространства. Также сообщается, нужно ли усекать строку до
, значит, функция успешно завершила свою работу. Если же равно или больше, значит, предел
max
превышен.
162
В некоторых устаревших версиях библиотеки С вместо этого возвращается -1 (если строка не помещается). Старая версия библиотеки С уже не поддерживается и не используется в защищенных программах, однако на man-странице по функции
snprintf
демонстрируется код, обрабатывающий оба варианта.
Функция
vsprintf
несет те же проблемы, a
vsnprintf
предлагает способ их преодоления.
22.3.2. Разбор имен файлов
Абсолютно обычным действием для привилегированных приложений является предоставление доступа к файлам ненадежным пользователям и разрешение этим пользователям передавать имена файлов, к которым необходим доступ. Хорошим примером служит Web-сервер. URL-адрес HTTP содержит имя файла, полученное сервером как запрос на передачу удаленному (ненадежному) пользователю. На Web-сервере необходимо убедиться, что возвращаемый файл — это именно тот, который был сконфигурирован на отправку, а также внимательно проверить правильность имен файлов.
Представьте Web-сервер, обслуживающий файлы из
home/httpd/html
, выполняющий это посредством простого добавления имени файла из URL, который требуется предоставить, концу
/home/httpd/html
. Такой процесс дает правильный файл, однако это также позволяет удаленным пользователям увидеть любой файл системы, к которой Web-сервер имеет доступ, просто запросив, к примеру, файл
../../.. /etc/passwd
. Подобные каталоги
..
необходимо явно проверять и отклонять. Системный вызов
chroot
предоставляет хороший способ, позволяющий сделать обработку имен файлов в программах более простой.
Если имена файлов передаются в другие программы, то необходима еще более тщательная проверка. Например, если в имени файла используется начальный символ
–
, то весьма вероятно, что другая программа интерпретирует его как опцию командной строки.
22.3.3. Переменные окружения
В программах, работающих с возможностями setuid или setgid, нужно проявлять особую осторожность с установками окружения. Эти переменные определяются пользователем, активизировавшим программу, тем самым открывается путь для атак. Самая явная атака может пройти через переменную окружения
PATH
, изменяющую те каталоги, в которых функции
execlp
и
execvp
отыскивают программы. Если привилегированная программа запускает другие программы, то она должна убедиться, что это именно те программы, которые нужны! Пользователь, который имеет возможность подменить программный путь поиска, легко может подвергнуть программу опасности.
Существуют и другие переменные окружения, которые могут оказаться опасными. Например, переменная
LD_PRELOAD
позволяет пользователю указать некоторую библиотеку для загрузки до стандартной библиотеки С. Это может быть полезным, но одновременно и очень опасным в привилегированных приложениях (по этой же причине переменная окружения игнорируется, если реальное и эффективное универсальные имена совпадают).
Если программа локализована, то переменная
NLSPATH
также становится проблемной. Она позволяет пользователю переключать используемый программой языковой каталог, который определяет способ перевода строк. Это означает, что в переводных программах пользователь имеет возможность указать значение для любой переводимой строки. Строку можно сделать сколько угодно длинной, вынуждая программу быть крайне осторожной при выделении буфера. Еще более опасным является то, что при переводе форматирующих строк для таких функций, как
printf
, можно изменить формат. Например, строка
Hello World, today is %s
может превратиться в
Hello World, today is %c%d%s.
Трудно предсказать, какое воздействие могут оказать подобные изменения на функционирование программы!