/* Формирование строки 'grep -x WORD /usr/dict/words'.
Строка выделяется динамически во избежание
переполнения буфера. */
length =
strlen("grep -х ") + strlen(word) +
strlen(" /usr/dict/words") + 1;
buffer = (char*)malloc(length);
sprintf(buffer, "grep -x %s /usr/dict/words", word);
/* Запуск команды. */
exit_code = system(buffer);
/* Очистка буфера. */
free(buffer);
/* Если команда grep вернула значение 0, значит, слово найдено
в словаре. */
return exit_code == 0;
}
Обратите внимание на подсчет числа символов в строке и динамическое выделение буфера, что позволяет обезопасить программу от переполнения буфера. К сожалению, небезопасна сама функция system()
(описана в разделе 3.2.1, "Функция system()"). Функция вызывает стандартный интерпретатор команд и принимает от него код завершения. Но что произойдет, если злоумышленник вместо слова введет показанную ниже строку?
foo /dev/null; rm -rf /
В этом случае сервер выполнит такую команду:
grep -х foo /dev/null; rm -rf / /usr/dict/words
Теперь проблема стала очевидной. Пользователь запустил одну команду, якобы grep, а на самом деле их оказалось две, так как интерпретатор считает точку с запятой разделителем команд. Первая команда — это по-прежнему безобидный вызов утилиты grep
, зато вторая команда пытается удалить все файлы в системе. Даже если серверная программа не имеет привилегий суперпользователя, она удалит все файлы, доступные запустившему ее пользователю. Похожая проблема возникает и при использовании функции popen()
(описана в разделе 3.4.4, "Функции popen()
и pclose()
"), которая создает канал между родительским и дочерним процессами, но тоже вызывает интерпретатор для запуска команды.
Существуют два способа устранения подобных проблем. Первый заключается в использовании функции семейства exec()
вместо функции system()
или popen()
. Специальные символы интерпретатора команд (например, точка с запятой) не подвергаются обработке, если они присутствуют в списке аргументов функции exec()
. Естественно, при этом пропадают преимущества таких функций, как system()
и popen()
.
Второй способ — проверка строки на предмет "благонадежности". В случае сервера словарей следует убедиться в том, что слово содержит только буквы (для этого предназначена функция isalpha()
). Такое слово не представляет угрозы.
Глава 11
Демонстрационное Linux-приложение
В этой главе кусочки мозаики сложатся в единую композицию. Мы опишем и реализуем законченную Linux-программа, в которой объединятся многие рассмотренные в данной книге методики. Программа через протокол HTTP выдает информацию о системе, в которой она работает.
11.1. Обзор
Демонстрационная программа является частью пакета мониторинга Linux-системы и предоставляет следующие возможности.
■ Программа реализует минимально необходимые функции Web-сервера. Локальные и удаленные клиенты получают доступ к системной информации, запрашивая Web-страницы у сервера по протоколу HTTP.
■ Программа не работает со статическими HTML-страницами. Все страницы динамически генерируются модулями, каждый из которых вычисляет итоговую информацию о какой-либо характеристике системы.
■ Все модули подключаются к серверу динамически, загружаясь из совместно используемых библиотек. Их можно добавлять, удалять и заменять по ходу работы сервера.
■ Для каждого запроса на подключение сервер создает дочерний процесс. Это позволяет серверу продолжать реагировать на запросы, а также защищает его от ошибок в модулях.
■ Серверу не требуются привилегии суперпользователя (он не работает с привилегированным портом). Это ограничивает его в доступе к системной информации.
Программу сопровождают четыре модуля, в которых иллюстрируются методики сбора системной информации. В модуле time
используется системный вызов gettimeofday()
. В модуле issue
применяются функции низкоуровневого ввода-вывода и системный вызов sendfile()
. В модуле diskfree
показано, как с помощью функций fork()
, exec()
и dup2()
выполнять команды в дочерних процессах. В модуле processes
продемонстрирована работа с файловой системой /proc
.
11.1.1. Существующие ограничения