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

ЖАНРЫ

Разработка приложений в среде Linux. Второе издание

Троан Эрик В.

Шрифт:

Когда

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. Информация о процессе

Ядро предоставляет значительное количество информации о каждом процессе и часть ее передается новым программам во время их загрузки. Вся эта информация образует среду выполнения для процесса.

10.3.1. Аргументы программы

Есть два типа значений, передаваемых новым программам при их запуске: аргументы командной строки и переменные окружения. Для их использования установлено множество соглашений, но система сама по себе не придерживается их автоматически. Однако хорошим тоном считается придерживаться этих соглашений, чтобы помочь вашим программам попасть в мир Unix.

Аргументы командной строки — это набор строк, передаваемый программе. Обычно они представляют собой текст, набранный вслед за именем команды в оболочке, с необязательными аргументами, начинающимися с символа "минус"

(-
).

Переменные окружения — это набор пар "имя-значение". Каждая пара представляет отдельную строку в форме

ИМЯ=ЗНАЧЕНИЕ
, и набор таких строк образует окружение (environment) программы. Например, домашний каталог текущего пользователя обычно указан в переменной окружения HOME, поэтому программы, скажем, пользователя Joe часто запускаются, имея в своем окружении
HOME=/home/joe
.

И аргументы, и окружение становятся доступными программе при запуске. Аргументы командной строки передаются в виде параметров главной функции программы —

main
, в то время как указатель на окружение помещается в глобальную переменную
environ
, которая определена в
<unistd.h>
[18] .

Ниже представлен полный прототип функции

main
в мире Linux, Unix и языка ANSI/ISO С.

int main(int argc, char *argv[]);

18

Большинство систем передают окружение в виде параметра

main
, но такой метод не включен в стандарт POSIX. Переменная
environ
 — это метод, утвержденный POSIX.

Возможно, вас удивит, что

main
возвращает значение (отличное от
void
). Это значение, возвращаемое функцией main, передается родительскому процессу после завершения данного. По соглашению 0 означает, что процесс завершен успешно, а ненулевое значение означает возникновение сбоя. При этом принимаются во внимание только младшие 8 бит из этого кода возврата. Отрицательные значения от -1 до -128 зарезервированы для ненормального завершения процессов по инициативе другого процесса или ядра системы. Код выхода 0 сигнализирует об успешном завершении, а значения от 1 до 127 говорят о том, что программа завершена по ошибке.

Первый параметр,

argc
, содержит количество аргументов командной строки, переданных программе, тогда как
argv
— массив указателей на строки — хранит сами аргументы. Первый элемент в массиве,
argv[0]
, содержит имя вызванной программы (хотя и не обязательно полный путь к ней). В элементе
argv[argc-1]
расположен указатель на завершающий аргумент командной строки, а
argv[argc]
содержит
NULL
.

Чтобы получить прямой доступ к окружению, используйте следующую глобальную переменную:

extern char *environ[];

Это представляет

environ
как массив указателей на каждый элемент программного окружения (помните, каждый элемент — это пара
ИМЯ=ЗНАЧЕНИЕ
), и финальный элемент массива содержит
NULL
. Это объявление находится в
<unistd.h>
, поэтому вам не обязательно объявлять его самостоятельно.

Наиболее общий способ проверки элементов окружения — это вызов

getenv
, который исключает непосредственное обращение к переменной
environ
.

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