В такой ситуации говорят о конкуренции. Она неявно существует между автором программы и хакером. Кто первым успеет создать временный файл, тот и победит.
Посредством этой атаки часто уничтожаются системные файлы. Создав нужную символическую ссылку, хакер может заставить программу, выполняющуюся с правами суперпользователя, затереть важный системный файл, например
/etc/passwd
.
Один из способов избежать такой атаки — создавать временные файлы со случайными именами. Например, можно прочитать из устройства
/dev/random
случайные данные и включить их в имя файла. Это усложнит задачу хакеру, но не остановит его полностью. Он может попытаться создать большое число символических ссылок
с потенциально верными именами. Даже если их будет 10000, одна верная догадка приведет к непоправимому.
Другой подход заключается в вызове функции
open
с флагом
O_EXCL
. Он заставляет функцию завершиться неудачей, если обнаруживается факт существования файла. К сожалению, это не срабатывает, если программа работает через NFS. Нельзя заранее предсказать, в какой файловой системе будет находиться программа, поэтому рассчитывать только на флаг
O_EXCL
нельзя.
В разделе 2.1.7, "Временные файлы", рассказывалось о применении функции
mkstemp
для создания временных файлов. К сожалению, в Linux эта функция открывает файл с флагом
O_EXCL
после того, как было выбрано трудно угадываемое имя. Другими словами, применять функцию небезопасно, если каталог
Монтирование данного каталога по сети — ошибка системного администратора.
Прием, который всегда работает, заключается в вызове функции
lstat
(рассматривается в приложении Б, "Низкоуровневый ввод-вывод") для созданного файла. Она отличается от функции stat тем, что возвращает информацию о самой символической ссылке, а не о файле, на который она ссылается. Если функция сообщает, что новый файл является обычным файлом, а не символической ссылкой, и принадлежит владельцу программы, то все в порядке.
В листинге 10.5 представлена функция, которая пытается безопасно открыть файл в каталоге
/tmp
. Возможно, у этой функции есть свои слабости. Мы не рекомендуем читателям включать показанный код в свои программы без дополнительной экспертизы, просто мы хотим убедить читателей в том, что создание безопасных приложений — непростая задача,
Листинг 10.5. (temp-file.c) Безопасное создание временного файла
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
/* Функция возвращает дескриптор созданного временного файла.
Файл будет доступен для чтения и записи только тому
идентификатору текущего процесса. Если файл не удалось создать,
возвращается -1. */
int secure_temp_file {
/* Этот дескриптор ссылается на устройство /dev/random, из
которого будут получены случайные данные. */
static int random_fd = -1;
/* Случайное целое число. */
unsigned int random;
/* Буфер для преобразования числа в строку. */
char filename[128];
/* дескриптор создаваемого временного файла. */
int fd;
/*
информация о созданном файле. */
struct stat stat_buf;
/* Если устройство /dev/random еще не было открыто,
открываем его. */
if (random_fd == -1) {
/* Открытие устройства /dev/random. Предполагается, что
это устройство является источником случайных данных,
а не файлом, созданным хакером. */
random_fd = open("/dev/random", O_RDONLY);
/* Если устройство /dev/random не удалось открыть,
завершаем работу. */
if (random_fd == -1)
return -1;
}
/* чтение целого числа из устройства /dev/random. */
if (read(random_fd, &random, sizeof(random)) != sizeof(random))
return -1;
/* Формирование имени файла из случайного числа. */
sprintf(filename, "/tmp/%u", random);
/* Попытка открытия файла. */
fd = open(filename,
/* Используем флаг O_EXCL. */
O_RDWR | O_CREAT | O_EXCL,
/* Разрешаем доступ только владельцу файла. "/
S_IRUSR | S_IWUSR);
if (fd == -1)
return -1;
/* Вызываем функцию lstat, чтобы проверить, не является ли
файл символической ссылкой */
if (lstat(filename, &stat_buf) == -1)
return -1;
/* Если файл не является обычным файлом, кто-то пытается
обмануть нас. */
if (!S_ISREG(stat_buf.st_mode))
return -1;
/* Если файл нам не принадлежит, то, возможно, кто-то успел
подменить его. */
if (stat_buf.st_uid != geteuid ||
stat_buf.st_gid != getegid)
return -1;
/* Если у файла установлены дополнительные биты доступа,
что-то не так. */
if ((stat_buf.st_mode & ~(S_IRUSR | S_IWUSR)) != 0)
return -1;
return fd;
}
Рассмотренная функция вызывает функцию
open
для создания файла, а затем функцию
lstat
для проверки того, что файл не является символической ссылкой. Внимательный читатель обнаружит здесь то, что называется состоянием гонки. Между вызовами функций
open
и
lstat
злоумышленник может успеть удалить файл и заменить его символической ссылкой. Это не вызовет разрушающих последствий, но приведет к тому, что функция завершится ошибкой и не сможет выполнить свою задачу. Такой тип атаки называется отказом от обслуживания.