Шрифт:
Недостаточная «случайность» случайных чисел проявляется в ситуации, когда вы хотите создать файл, каталог или другой объект с уникальным именем в общедоступной области. Если применяется генератор псевдослучайных чисел или – того хуже – увеличение некоторого счетчика на единицу, то противник часто может предсказать, каким будет следующее имя, а это первый шаг на пути к хаосу. Отметим, что многие библиотечные функции для создания временных файлов гарантируют лишь уникальность имени, но не его непредсказуемость. Если вы создаете файлы или каталоги в общедоступной области, то для порождения имен применяйте генератор случайных чисел криптографического качества. Один такой способ описан в главе 23 книги Майкл Ховард, Дэвид Лебланк «Защищенный код», 2–ое издание (Русская редакция, 2004). Хотя он предназначен для Windows, но сам подход является переносимым.
Где искать ошибку
Гонки чаще всего возникают при следующих условиях:
□ Несколько потоков или процессов должны осуществлять запись в один и тот же ресурс. Ресурсом может быть разделяемая память, файловая система (например, когда несколько Web–приложений манипулируют файлами в общем каталоге), другие хранилища данных, как, скажем, реестр Windows, и даже база данных. Это может быть даже одна разделяемая переменная!
□ Файлы или каталоги создаются в общедоступных областях, например в каталоге для временных файлов (/tmp или /usr/tmp в UNIX–подобных системах).
□ В обработчиках сигналов.
□ В нереентерабельных функциях в многопоточном приложении или вызываемых из обработчика сигнала. Отметим, что в системах Windows сигналы практически бесполезны и этой проблеме не подвержены.
Выявление ошибки на этапе анализа кода
Внимательно присмотритесь как к собственным, так и к библиотечным функциям, которыми вы пользуетесь. Нереентерабельным является код, манипулирующий переменными, объявленными вне локальной области видимости, например глобальными или статическими. Любая функция, в которой используется статическая переменная, реентерабельной не является. Хотя применение глобальных переменных вообще осуждается, поскольку усложняет сопровождение программы, но сами по себе они еще не создают гонок. Следующее, на что надо обратить внимание, – это бесконтрольное изменение таких переменных. Например, статический член класса в С++ разделяется всеми экземплярами класса и, следовательно, оказывается глобальной переменной. Если этот член инициализируется в момент первого обращения к классу, а затем только читается, то ничего страшного не случится. Если же некоторая переменная обновляется, то необходимо ставить замки, чтобы обновление не осуществлялось одновременно разными частями программы. Особое внимание следует обращать на обработчики сигналов, поскольку они могут оказаться нереентерабельными, даже если для остальной программы эта проблема не актуальна.
Внимательно изучите код обработчиков сигналов, в частности те данные, которыми они манипулируют.
Следующий случай – это внешние процессы, которые могут прерывать вашу программу. Тщательно изучите места, где файлы или каталоги создаются в общедоступных областях, обращайте внимание на предсказуемость имен.
Найдите все случаи создания файлов (к примеру, временных) в разделяемых каталогах (/tmp или /usr/tmp в UNIX, \Windows\temp в Windows). При создании такого файла библиотечной функцией ореп надо указывать флаг 0_EXCL (или его эквивалент), а если он создается функцией CreateFile -флаг CREATE_NEW. В этом случае вызов завершится неудачно, если файл с указанным именем уже существует. Поместите обращения к этим функциям в цикл, который будет пробовать новые случайные имена, пока не создаст файл. Если имя выбирается действительно случайно (следите, чтобы имя файла содержало только допустимые в вашей системе символы), то почти наверняка потребуется только одна итерация. К сожалению, функция fopen из стандартной библиотеки С не позволяет указать флаг 0_EXCL, поэтому применяйте функцию ореп с последующим преобразованием возвращенного дескриптора в указатель FILE*. В операционных системах Microsoft системные вызовы типа CreateFile не только более гибки, но и работают быстрее. Никогда не полагайтесь на функции, подобные mktemp(3), поскольку они создают предсказуемые имена файлов; противник может создать файл точно с таким же именем. В командных интерпретаторах UNIX нет встроенных операций для создания имен временных файлов, а конструкции типа Is > / tmp/ list. $ $ способствуют возникновению гонки. Поэтому в shell–сценариях нужно пользоваться функцией mktemp(l).
Тестирование
Обнаружить гонку во время тестирования трудно, но существуют методики по искоренению этого греха. Одна из самых простых – прогонять тесты на быстрой многопроцессорной машине. Если вы наблюдаете отказы, которые не удается воспроизвести на однопроцессорной машине, значит, дело почти наверняка в гонке.
Для поиска ошибок, связанных с сигналами, напишите программу, которая будет один за другим посылать сигналы тестируемому приложению, и понаблюдайте за поведением последнего. Отметим, что одного прогона теста может оказаться недостаточно, так как ошибка возникает нерегулярно.
Чтобы найти гонки, возникающие из–за временных файлов, включите протоколирование операций с файловой системой или воспользуйтесь утилитами для протоколирования системных вызовов. Внимательно изучите все случаи создания файлов, посмотрите, не создаются ли файлы с предсказуемыми именами в общедоступных каталогах. Если возможно, включите в протокол информацию об использовании флага 0_EXCL, чтобы узнать, задается ли он при создании файлов в разделяемых каталогах. Особый интерес представляют ситуации, когда файл первоначально создается с неправильными правами, которые затем корректируются. Промежутка времени между двумя этими действиями достаточно для создания эксплойта. Аналогично подозрение должно вызывать любое понижение привилегий, необходимых для доступа к файлу. Если противник сумеет заставить программу работать со ссылкой вместо настоящего файла, то сможет получить доступ к данным, которых он видеть не должен.
Примеры из реальной жизни
Следующие примеры взяты из базы данных CVE .
CVE–2001–1349
Цитата из бюллетеня CVE:
В программе sendmail до версии 8.11.4, а также версии 8.12.0 до 8.12.0.BetalO имеется гонка в обработчике сигнала, которая позволяет локальному пользователю провести DoS–атаку, возможно, затереть содержимое кучи и получить дополнительные привилегии.
Эта ошибка описана в статье Залевски о доставке сигналов, на которую мы уже ссылались выше. Ошибка, допускающая написание эксплойта, возникает из–за двойного освобождения памяти, на которую указывает глобальная переменная. Это происходит при повторном входе в обработчик сигнала. Хотя ни в документации по Sendmail, ни в базе уязвимостей SecurityFocus не приводится код общедоступного эксплойта, но в первоначальной статье такая ссылка (сейчас не работающая) была.
CAN–2003–1073
Цитата из бюллетеня CVE:В программе at, поставляемой в составе ОС Solaris версий с 2.6 по 9, может возникнуть гонка, позволяющая локальному пользователю удалить произвольный файл путем задания флага–г с аргументом, содержащим две точки (..) в имени задания. Для этого нужно изменить содержимое каталога после того, как программа проверит разрешение на удаление файла, но до выполнения самой операции удаления.
Этот эксплойт детально описан на странице www.securityfocus.com/archive/ 1 /308577/2003–01–27/2003–02–02/0. Помимо гонки, в нем используется еще одна ошибка: не проверяется наличие последовательности символов../ в имени файла, из–за чего планировщик at может удалить файлы, находящиеся вне каталога для хранения заданий.
CVE–2004–0849
Цитата из бюллетеня CVE: