Чтение онлайн

ЖАНРЫ

Программирование для Linux. Профессиональный подход

Самьюэл Алекс

Шрифт:

Хуже всего обстоит дело с программами, которые работают в режиме демона и ожидают поступление запросов на подключение. Демоны обычно принадлежат пользователю

root
. Если в программе есть описываемая "дыра", любой, кто сможет подключиться к этой программе, способен захватить контроль над компьютером, послав по сети "смертельную" последовательность данных. Программы, не работающие с сетью, гораздо безопаснее, так как их могут атаковать только пользователи, уже зарегистрировавшиеся в системе.

Старым версиям программ

finger
,
talk
и
sendmail
присущ один общий недостаток: все они работают со строковым буфером фиксированной длины. Предельный размер строки
предполагается по умолчанию, но ничто не мешает сетевым клиентам вводить строки, вызывающие переполнение буфера. В программах содержится примерно такой код, как показан ниже.

#include <stdio.h>

int main {

 /* Никто, будучи в здравом уме, не выбирает имя пользователя

длиной более 32 символов. Кроме того, я думаю, что в UNIX

допускаются только 8-символьные имена. Поэтому выделенного

буфера должно быть достаточно. */

 char username[32];

 /* Предлагаем пользователю ввести свое имя. */

 printf("Enter your username: ");

 /* Читаем введенную строку. */

 gets(username);

 /* Выполняем другие действия... */

return 0;

}

Комбинация 32-символьного буфера и функции

gets
делает возможным переполнение буфера. Функция
gets
читает вводимые данные до тех пор, пока не встретится символ новой строки, после чего помещает весь результат в массив
username
. В комментариях к программе предполагается, что пользователи выбирают себе короткие имена, не превышающие в длину 32 символа. Но при написании защищенных программ необходимо помнить о существовании хакеров. В данном случае хакер может выбрать сколь угодно длинное имя. Локальные переменные, в частности
username
, сохраняются в стеке, поэтому выход за пределы массива оборачивается тем, что в стек помещаются произвольные данные.

К счастью, предотвратить переполнение буфера относительно несложно. При чтении строк следует всегда пользоваться функцией наподобие

getline
, которая либо динамически выделяет буфер достаточной длины, либо прекращает принимать входные данные, когда буфер оказывается заполнен. Вот вариант выхода из положения:

char* username = getline(NULL, 0, stdin);

Функция

getline
автоматически вызывает функцию
malloc
, которая выделяет буфер для введенной строки и возвращает указатель на него. Естественно, следует не забыть вызвать функцию
free
, чтобы по окончании работы с буфером вернуть память системе.

Ситуация еще проще, если используется язык C++, где есть готовые строковые примитивы. В C++ ввод строки осуществляется так:

string username;

getline(cin, username);

Буфер строки

username
удаляется автоматически, поэтому даже не придется вызывать функцию
free
.

Проблема переполнения буфера возникает при работе с любыми статическими массивами, а не только со строками. При написании безопасных программ следует тщательно проверять, не осуществляется ли запись в массив за его пределами.

10.6.2. Конкуренция доступа к каталогу /tmp

Другая распространенная проблема безопасности связана с созданием файлов с предсказуемыми именами, в основном в каталоге

/tmp
. Предположим, что программа
prog
, выполняющаяся от имени пользователя
root
,
всегда создает временный файл
/tmp/prog
и помещает в него важную информацию. Тогда злоумышленник может заранее создать символическую ссылку
/tmp/prog
на любой другой файл в системе. Когда программа попытается создать временный файл, функция
open
завершится успешно, но в действительности вернет дескриптор символической ссылки. Любые данные, записываемые во временный файл, окажутся перенаправленными в файл злоумышленника.

В такой ситуации говорят о конкуренции. Она неявно существует между автором программы и хакером. Кто первым успеет создать временный файл, тот и победит.

Посредством этой атаки часто уничтожаются системные файлы. Создав нужную символическую ссылку, хакер может заставить программу, выполняющуюся с правами суперпользователя, затереть важный системный файл, например

/etc/passwd
.

Один из способов избежать такой атаки — создавать временные файлы со случайными именами. Например, можно прочитать из устройства

/dev/random
случайные данные и включить их в имя файла. Это усложнит задачу хакеру, но не остановит его полностью. Он может попытаться создать большое число символических ссылок с потенциально верными именами. Даже если их будет 10000, одна верная догадка приведет к непоправимому.

Другой подход заключается в вызове функции

open
с флагом
O_EXCL
. Он заставляет функцию завершиться неудачей, если обнаруживается факт существования файла. К сожалению, это не срабатывает, если программа работает через NFS. Нельзя заранее предсказать, в какой файловой системе будет находиться программа, поэтому рассчитывать только на флаг
O_EXCL
нельзя.

В разделе 2.1.7, "Временные файлы", рассказывалось о применении функции

mkstemp
для создания временных файлов. К сожалению, в Linux эта функция открывает файл с флагом
O_EXCL
после того, как было выбрано трудно угадываемое имя. Другими словами, применять функцию небезопасно, если каталог
/tmp
смонтирован через NFS. [35]

35

Монтирование данного каталога по сети — ошибка системного администратора.

Прием, который всегда работает, заключается в вызове функции

lstat
(рассматривается в приложении Б, "Низкоуровневый ввод-вывод") для созданного файла. Она отличается от функции stat тем, что возвращает информацию о самой символической ссылке, а не о файле, на который она ссылается. Если функция сообщает, что новый файл является обычным файлом, а не символической ссылкой, и принадлежит владельцу программы, то все в порядке.

В листинге 10.5 представлена функция, которая пытается безопасно открыть файл в каталоге

/tmp
. Возможно, у этой функции есть свои слабости. Мы не рекомендуем читателям включать показанный код в свои программы без дополнительной экспертизы, просто мы хотим убедить читателей в том, что создание безопасных приложений — непростая задача,

Листинг 10.5. (temp-file.c) Безопасное создание временного файла

#include <fcntl.h>

#include <stdlib.h>

#include <sys/stat.h>

#include <unistd.h>

/* Функция возвращает дескриптор созданного временного файла.

Файл будет доступен для чтения и записи только тому

пользователю, чей идентификатор равен эффективному

Поделиться с друзьями: