Остальные две функции, 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
символов (не считая последний '\0'
)[162]. Если возвращаемое значение меньше чем max
, значит, функция успешно завершила свою работу. Если же равно или больше, значит, предел max
превышен.
Функция 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.
Трудно предсказать, какое воздействие могут оказать подобные изменения на функционирование программы!
Все это сводится к тому, что наилучшим решением для переменных окружения setuid- или setgid-программ является исключение этих переменных. Функция clearenv()
[163] стирает все значения из окружения, оставляя его пустым. После этого программа может заполнить любые необходимые ей переменные окружения известными значениями.
22.3.4. Запуск командной оболочки
Запуск системной командной оболочки из любой программы, в которой важен вопрос безопасности, является плохой идеей. При этом защита от тех проблем, которые мы уже обсуждали, становится еще более трудной.
Каждую строку, передаваемую в оболочку, необходимо очень тщательно проверять на достоверность. К примеру, символ '\n'
или ;
, вставленный в строку, может привести к тому, что оболочка примет две команды вместо одной. Если строка содержит символы `
или последовательность $()
, оболочка запускает другую программу для построения аргумента командной строки. Может также иметь место обычное расширение оболочки, при этом переменные окружения и универсализация файловых имен становятся доступными для взломщиков. Переменная IFS
позволяет указать символы (отличные от пробела и табуляции) для разделения полей при анализе командных строк при помощи символов, тем самым, открывая новые бреши для атак. Другие специальные символы, такие как <
, >
и |
, предоставляют еще больший простор для построения командных строк, которые ведут себя не так, как подразумевает программа.
Очень трудно выполнить полную проверку всех этих возможностей. Наилучшим способом предотвращения всех возможных атак против командной оболочки служит в первую очередь уклонение от ее запуска. Функции вроде pipe()
, fork()
, exec()
и glob()
позволяют достаточно легко выполнять большинство тех задач, для которых обычно используется оболочка. При этом проблемы расширения командной строки оболочки не возникают.
22.3.5. Создание временных файлов
Довольно часто в программах применяются временные файлы. Система Linux даже предусматривает для этой цели особые каталоги (/tmp
и /var/tmp
). К сожалению, использование временных файлов в безопасном режиме — дело очень ненадежное. Лучшим решением будет создание временных файлов в каталоге, который доступен только через эффективный uid программы. Неплохим выбором, например, может стать домашний каталог данного пользователя. При таком подходе употребление временных файлов становится простым и безопасным. Однако большинство программистов не любят этот способ, так как он загромождает каталоги, причем вполне возможно, что эти файлы никогда не будут удалены, если программа неожиданно выйдет из строя.
162
В некоторых устаревших версиях библиотеки С вместо этого возвращается -1 (если строка не помещается). Старая версия библиотеки С уже не поддерживается и не используется в защищенных программах, однако на man-странице по функции snprintf()
демонстрируется код, обрабатывающий оба варианта.
163
К сожалению, функция clearenv()
не очень хорошо стандартизирована. Она входит в последние версии POSIX, однако она была выброшена из стандарта Single Unix Standard и не доступна во всех системах типа Unix. Если вам необходимо поддерживать операционную систему, не включающую эту переменную, установите environ=NULL;
.