. Во втором ее аргументе указываются различные флаги. Значение 0 означает стандартные установки. Возвращаемое значение функции говорит о том. как прошла аутентификация. В конце программы вызывается функция
pam_end
, которая удаляет выделенные ранее структуры данных.
Предположим, что пользователь должен ввести пароль "password". Если это будет сделано, получим следующее:
% ./pam
Password: password
Authentication OK.
Будучи запущенной в терминальном окне,
программа не покажет введенный пароль, чтобы кто-нибудь посторонний не смог его подглядеть.
Вот что произойдет, если в систему попробует вломиться хакер:
% ./pam
Password: badguess
Authentication failed!
Полное описание работы модулей аутентификации приведено в каталоге
/usr/doc/pam
.
10.6. Дополнительные проблемы безопасности
В этой главе мы рассматриваем лишь несколько наиболее общих проблем, связанных с безопасностью. Но существует великое множество других "дыр", и далеко не все из них еще раскрыты. Поэтому в ответственных случаях без помощи экспертов не обойтись.
10.6.1. Переполнение буфера
Почти псе основные Internet-демоны, включая демоны таких программ, как
sendmail
,
finger
,
talk
и др., подвержены атакам типа переполнение буфера. О них следует обязательно помнить при написании программ, которые должны выполняться с правами пользователя root, а также программ, осуществляющих межзадачное взаимодействие или читающих файлы, которые не принадлежат пользователю, запустившему программу.
Суть атаки заключается в том, чтобы заставить программу выполнить код, который она не собиралась выполнять. Типичный механизм достижения этой цели — перезапись части стека программы. В стеке, помимо всего прочего, сохраняется адрес памяти, по которому программа передает управление после завершения текущей функции. Следовательно, если поместить код взлома в памяти, а затем изменить адрес возврата так. чтобы он указывал на этот код, то по завершении текущей функции программа начнет выполнять код хакера с правами текущего процесса. Если процесс принадлежит пользователю
root
, последствия будут катастрофическими. Если атаке подвергся процесс другого пользователя, катастрофа наступит "только" для него (а также для любого пользователя, который работает с файлами пострадавшего).
Хуже всего обстоит дело с программами, которые работают в режиме демона и ожидают поступление запросов на подключение. Демоны обычно принадлежат пользователю
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
завершится успешно, но в действительности вернет дескриптор символической ссылки. Любые данные, записываемые во временный файл, окажутся перенаправленными в файл злоумышленника.