Сохраненный и действительный идентификаторы uid проверяются только тогда, когда процесс пытается изменить его эффективный uid. Любой процесс может изменять свой эффективный uid на сохраненный или действительный. Только процессы с эффективным uid, равным 0 (процессы, запущенные от имени root), могут изменять свой эффективный uid на произвольное значение.
Обычно эффективный, реальный и действительный uid процесса совпадают. Однако этот механизм решает дилемму демона ftp. Когда он запускается, все его идентификаторы устанавливаются в 0, что предоставляет ему полномочия root. Когда подключается пользователь, демон устанавливает свой эффективный uid равным uid пользователя, оставляя сохраненный и действительный uid равными 0. Когда демону ftp требуется выполнить действие, разрешенное только root, он устанавливает свой эффективный uid в 0, выполняет действие, а затем переустанавливает эффективный uid в значение uid подключенного пользователя.
Хотя демон ftp вообще не нуждается в сохраненном uid, другие классы программ, применяющие этот механизм — двоичные модули setuid и setgid — используют его.
Программа passwd — это простой пример того, зачем нужна функциональность setuid и setgid. Программа passwd позволяет пользователям изменять свои пароли. Пользовательские пароли обычно хранятся в файле /etc/passwd. Выполнять запись в этот файл может только пользователь root, что предотвращает изменение информации о пользователях другими пользователями. Но пользователи должны иметь возможность изменять свои собственные пароли, поэтому необходим какой-то способ предоставить программе passwd права на изменение /etc/passwd.
Чтобы обеспечить эту гибкость, пользователь программы может устанавливать специальные биты в группе бит прав доступа этой программы (см. главу 11). Это сообщает ядру, что всякий раз, когда программа запускается, она должна выполняться с тем же эффективным uid (или gid), как у пользователя, который владеет файлом программы, независимо от того, какой пользователь запустил программу. Такие программы называются setuid- или setgid-программами.
Принадлежность программы passwd пользователю root и установка бита setuid в наборе битов доступа программы позволяют всем пользователям изменять свои пароли. Когда пользователь запускает passwd, эта программа выполняется с эффективным идентификатором пользователя 0, что позволяет ей модифицировать /etc/passwd и изменять пользовательский пароль. Конечно, passwd должна быть реализована очень тщательно, дабы исключить побочные эффекты. Программы setuid — это популярная цель для злоумышленников, проникающих в систему, поэтому плохо написанная программа подобного рода дает простую возможность получить неавторизованный доступ.
Существуют много случаев, когда программы setuid требуют специальных прав доступа на короткий период времени и должны переключаться на uid действительного пользователя в остальное время (как это делает демон ftp). Программы setuid имеют установленный эффективный uid равным uid ее владельца, но они также "знают" uid того пользователя, который их запустил (сохраненный uid), что упрощает обратное переключение. В дополнение они могут устанавливать свой действительный uid в значение setuid (не затрагивая сохраненный uid), при необходимости повторно получая эти специальные полномочия. В этой ситуации эффективный, сохраненный и действительный идентификаторы пользователя работают вместе, насколько возможно, упрощая систему безопасности.
К несчастью, применение этого механизма может сбивать с толку, поскольку в POSIX и BSD применяются слегка отличающиеся подходы, a Linux поддерживает оба. Решение BSD более полнофункционально, чем метод POSIX. Оно использует функцию setreuid().
int setreuid(uid_t ruid, uid_t euid);
Действительный uid процесса устанавливается в ruid, а эффективный — в euid. Если любой из параметров равен -1, идентификатор вызовом не затрагивается.
Если эффективный uid процесса равен 0, такой вызов всегда выполняется успешно. В противном случае идентификаторы могут быть установлены равными либо сохраненному uid, либо реальному uid процесса. Следует отметить, что этот вызов никогда не изменяет сохраненный uid или реальный uid текущего процесса. Чтобы сделать это, используйте функцию POSIX setuid(), которая может модифицировать сохраненный uid.
int setuid(uid_t euid);
Как и в случае setreuid() эффективный uid процесса устанавливается в euid, если euid равен действительному uid процесса либо эффективный uid процесса на момент вызова равен 0.
Когда setuid() используется процессом, чей эффективный uid установлен в 0, все uid процесса изменяются на euid. К сожалению, это делает невозможным использование setreuid() в setuid-программах, которым нужно временное использование другого uid, поскольку после вызова setreuid() процесс не может восстановить свои полномочия root.
Хотя способность переключать uid упрощает написание кода, с помощью которого нельзя нарушить безопасность системы, все же это не панацея. Существует очень много популярных методов обманного проникновения в выполняющийся код [18]. До тех пор пока либо сохраненный, либо действительный uid процесса равен 0, такие атаки легко могут устанавливать эффективный uid процесса в 0. Это не дает возможности переключению uid эффективно предотвращать серьезную уязвимость системных программ. Однако если процесс может передать любой доступ к полномочиям root, устанавливая эффективный, сохраненный и действительный идентификаторы в ненулевые значения, это ограничивает эффективность любых атак против него.
10.2.3. Идентификатор uid файловой системы
В очень специальных случаях программе может понадобиться сохранять свои права root для всего, кроме доступа к файловой системе, при котором она использует пользовательский uid. Изначально использовавшийся в Linux NFS-сервер пространства пользователя может служить иллюстрацией проблемы, которая возникает, когда процесс предполагает применение пользовательского uid. Хотя NFS-сервер в прошлом применял setreuid() для переключения uid при доступе к файловой системе, такое поведение позволяло пользователю, чей uid совпадает с uid NFS-сервера, уничтожать NFS-сервер. В конечном итоге, пользователь получал владение процессом NFS-сервера. Чтобы предотвратить проблемы подобного рода, Linux использует uid файловой системы (fsuid) для контроля доступа к файловой системе.
Всякий раз когда изменяется эффективный uid процесса, его fsuid устанавливается равным новому эффективному идентификатору пользователя, что делает fsuid прозрачным для большинства приложений. Те приложения, которые нуждаются в дополнительных возможностях, предоставляемых отличающимся значением fsuid, должны применять вызов setfsuid() для явной установки fsuid.
int setfsuid(uid_t uid);
Значение fsuid может быть установлено равным текущим эффективному, сохраненному или действительному идентификаторам пользователя. В дополнение следует сказать, что setfsuid() выполняется успешно, если fsuid остается неизменным или эффективный uid процесса равен 0.
10.2.4. Резюме по идентификаторам пользователей и групп
Подведем итоги обо всех системных вызовах, которые модифицируют права доступа выполняющегося процесса. Большинство перечисленных здесь функций, имеющих отношение к идентификаторам пользователей, уже детально рассматривались в настоящей главе, но те, что относятся к группам — еще нет. Поскольку эти функции отражают соответствующие функции, модифицирующие идентификаторы пользователя, их поведение должно быть понятно.
Все эти функции возвращают -1 в случае ошибки и 0 — в случае успеха, если только не указано иначе. Большинство их прототипов находятся в <unistd.h>. Те, что расположены где-то еще, отмечены ниже.
int setreuid(uid_t ruid, uid_t euid); Устанавливает действительный uid текущего процесса в ruid и эффективный uid процесса в euid. Если оба параметра равны -1, то uid остаются неизменными. int setregid(gid_t rgid, gid_t egid); Устанавливает действительный gid текущего процесса в rgid и эффективный gid процесса в egid. Если оба параметра равны -1, то gid остаются неизменными. int setuid(uid t uid); Если применяется обычным пользователем, то устанавливает эффективный uid текущего процесса в значение параметра uid. Если используется процессом с эффективным uid, равным 0, то устанавливает действительный, эффективный и сохраненный uid в значение параметра uid. int setgid(gid_t gid); Если применяется обычным пользователем, то устанавливает эффективный gid текущего процесса в значение параметра gid. Если используется процессом с эффективным gid, равным 0, то устанавливает действительный, эффективный и сохраненный gid в значение параметра gid. int seteuid(uid_t uid); Эквивалент setreuid(-1, uid). int setegid(gid_t gid); Эквивалент setregid(-1, gid). int setfsuid(uid_t fsuid); Устанавливает fsuid текущего процесса в значение параметра fsuid. Прототип находится в <sys/fsuid.h>. Возвращает предшествующий fsuid. int setfsgid(gid_t fsgid); Устанавливает fsgid текущего процесса в значение параметра fsgid. Прототип находится в <sys/fsuid.h>. Возвращает предшествующий fsgid. int setgroups(size_t num, const gid_t * list); Устанавливает дополнительные группы текущего процесса из списка, переданного в массиве list, который должен содержать num элементов. Макрос SC_NGROUPS_MAX указывает, сколько групп может быть в списке (от 32 до 65536, в зависимости от работающей у вас версии Linux). uid_t getuid(); Возвращает действительный uid процесса. uid_t geteuid(); Возвращает эффективный uid процесса. gid_t getgid(); Возвращает действительный gid процесса. gid_t getegid(); Возвращает эффективный gid процесса. size_t getgroups (size_t size, gid_t list[]); Возвращает текущий набор дополнительных групп процесса в массиве list. Параметр size сообщает, сколько элементов типа gid_t может содержать list. Если размер list недостаточен, чтобы вместить все группы, возвращается -1, а errno устанавливается в EINVAL. В противном случае возвращается фактическое количество групп в list. Если size равен 0, возвращается количество групп, но list не затрагивается. Прототип функции getgroups() находится в <grp.h>.
10.3. Информация о процессе