ugidctl — linux uid/gid control, часть 2

В прошлом посте я уже сделал небольшой модуль ядра, который при инициализации находит адреса обработчиков системных вызовов setuid(2), setgid(2) и setgroups(2). Пора в него добавить средства коммуникации с usersapce окружением. Выбор механизмов достаточно широк, но я решил решил остановиться на старом и добром ioctl(2). Он позволяет передавать адрес буфера с данными при вызове и возвращать статус операции в виде int’а. Назову char-device соответственно — /dev/ugidctl.

Теперь пора придумать протокол общения userspace-окружения с ядром. Разделю его на 2 части. Первые несколько вызовов будут работать когда вызывающий процесс обладает правами рута, т.е. в моем случае «взведены» CAP_SETUID и CAP_SETGID. Эти вызовы будут инициализировать список uid’ов и gid’ов, между которыми процесс сможет свободно переключаться, будучи непривилегированным. Остальная часть вызовов будет непосредственно проверять права и обращаться к sys_setuid, sys_setgid и sys_setgroups. В качестве дополнительной защиты введу случайный ключ, который процесс может получить только во время стадии инициализации ugidctl и будет обязан предоставлять в процессе переключения при каждом вызове. Также ограничу процессы, которые могут совершать переключения проверкой PID’а (идентификатора процесса), PGID’а (идентификатора группы процессов) или SID’а (идентификатора сессии), потому как открытый файловый дескриптор моего устройства процесс может намеренно или случайно передать другому.

Опишу протокол здесь:


Получение ключа для доступа к вызовам в непривелигерованном режиме

Функция возвращает ключ, ассоциированный с файловым дескриптором.

Требует CAP_SETUID и CAP_SETGID, т.е. root-привилегии.

Возвращает 0 и заполненную структуру ugidctl_key_rq в случае успеха. При ошибке возвращает -1, а в errno номер ошибки:

EPERM — у вызывающего процесса недостаточно прав.
EFAULT — в запросе указан недоступный участок памяти.

Запрос и установка типа проверки идентификатора запроса

Обе функции возвращают тип идентификатора процесса, который будет проверяться в последующих непривилегерованных вызовах, т.е. UGIDCTLIO_SETUID, UGIDCTLIO_SETGID и UGIDCTLIO_SETGROUPS. Функция UGIDCTLIO_SETPIDCHKTYPE также устанавливает новый тип, а именно:

UGIDCTL_PIDTYPE_PID — будет проверяться идентификатор процесса (PID)
UGIDCTL_PIDTYPE_PGID — будет проверяться идентификатор группы процессов (PGID)
UGIDCTL_PIDTYPE_SID — будет проверяться идентификатор сессии (SID)

Требует CAP_SETUID и CAP_SETGID, т.е. root-привилегии.

В случае ошибки возвращает -1, а errno будет сожержать номер ошибки:

EPERM — у вызывающего процесса недостаточно прав.
EINVAL — указан неверный pid_check_type

Добавление UID’ов и GID’ов в список разрешенных

Обе функции добавляют в список uid’ов и gid’ов, ассоциированный с файловым дескриптором, соответствующие значения.

Требуют CAP_SETUID и CAP_SETGID, т.е. root-привилегии.

При успешном выполнении возвращают 0, а в случае ошибки возвращают -1. errno будет содержать номер ошибки:

EPERM — у вызывающего процесса нет привилегий CAP_SETUID и CAP_SETGID.
EINVAL — поле count содежит значение, превышающее UGIDCTL_LISTMAX
ENOMEM — недостаточно памяти для обработки запроса.
EFAULT — в запросе указан недоступный участок памяти.

Установка UID’а / GID’а

Команда UGIDCTLIO_SETUID устанавливает фактический, действительный и сохраненный идентификаторы владельца текущего процесса. Если у вызывающего процесса нет CAP_SETUID, ключ, иденификатор процесса (или группы процессов / сессии) и uid проверяются. Запрошенный uid должен быть в списке uid’ов, разрешенных предыдущими вызовами команды UGIDCTLIO_ADDUIDLIST.

Абсолютно аналогично, команда UGIDCTLIO_SETGID устанавливает фактический, действительный и сохраненный идентификаторы группы текущего процесса, и все, описанное для UGIDCTLIO_SETUID, остается в силе с заменой «uid» на «gid».

При успешном выполнении возвращают 0, а в случае ошибки возвращают -1. errno будет содержать номер ошибки:

EPERM — процесс непривилегован, а ключ, pid, pgid, sid или uid/gid не прошли проверку.
EAGAIN — uid относится к процессу, который превышает свой RLIMIT_NPROC лимит ресурсов (только для UGIDCTLIO_SETUID).
ENOMEM — недостаточно памяти для обработки запроса.
EFAULT — в запросе указан недоступный участок памяти.

Установка списка дополнительных идентификаторов групп

Устанавливает идентификаторы дополнительных групп для текущего процесса. Если у вызывающего процесса нет CAP_SETGID, ключ, иденификатор процесса (или группы процессов / сессии) и список gid’ов проверяются. Запрошенные gid’ы должены быть в списке gid’ов, разрешенных предыдущими вызовами команды UGIDCTLIO_ADDGIDLIST.

При успешном выполнении возвращает 0, а в случае ошибки возвращает -1. errno будет содержать номер ошибки:

EPERM — процесс непривилегован, а ключ, pid, pgid, sid или gid-list не прошли проверку.
EINVAL — gid_count больше чем NGROUPS_MAX
ENOMEM — недостаточно памяти для обработки запроса.
EFAULT — в запросе указан недоступный участок памяти.


Для протокола пригодиться header-файл ugidctl.h, который описывает все структуры данных и номера команд ioctl(2). Примерно такой:

Отлично, теперь можно приступить к описанию всего этого в модуле ядра. В сети полно документации, как написать создание char-device’а, поэтому подробно останавливаться на этом не буду. Вот собственно отправная точка:

Тут функция ugidctl_open будет вызвана когда кто-то откроет (open(2)) /dev/ugidctl, ugidctl_release — когда закроет (close(2)) и ugidctl_ioctl, соответственно, когда вызовет ioctl(2) на открытом устройстве. Проверим:

Теперь я не только имею доступ к системным вызовам sys_setuid, sys_setgid и sys_setgroups, но и создал устройство и заглушки для будующих функций модуля. В следующем посте надо будет придумать, как хранить списки uid’ов и gid’ов внури ядра и эффективно по ним искать.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *