Linux capabilities что это

Обновлено: 07.07.2024

То, о чём сейчас пойдёт речь, по-английски называется capabilities. Дословно "возможности", "способности". Семантически, это то, что называется привилегиями. Как ни странно, хоть тема и не нова, устоявшегося русского перевода этого термина я не видел. Точнее даже так, я никакого русского перевода не видел. И этот момент несколько смущает. Но ничего не поделаешь, придётся выкручиваться своими силами. Мне кажется, что наиболее адекватный перевод в данном случае - "разрешения", хотя по сути речь как раз о возможностях. Но раз обсуждаемый предмет - это то, что процессу разрешено делать, а не какие-то сугубо процесс-специфические возможности, им самим и создаваемые, я счёл более корректной приведённую версию перевода: разрешения процессов или привилегии. В дальнейшем я буду применять эти два понятия, как синонимы. Ещё один казуистический момент, так как с точки зрения ядра поток и процесс описываются единым дескриптором, дескриптором задачи (thread - это аппаратный контекст, являющийся частью дескриптора задачи), я не делаю разницы между потоком и процессом, так как здесь она не имеет особого значения. Важно лишь уточнить, что разрешения являются такой же частью дескриптора задачи, как и аппаратный контекст выполнения. И раз уж речь зашла о ядре (впрочем она и дальше пойдёт во многом именно о ядре), то сразу определимся, что речь идёт о версии 3.12. Система разделения власти суперпользователя на множество привилегий появилась в доисторической с точки зрения сегодняшнего дня ветке ядра 2.2. За эти годы многое изменилось самым кардинальным образом. Поэтому даже между ветками 2.6 и 3 будут расхождения в коде. Но общие принципы остаются в целом теми же.

Постановка задачи

В общем и в целом, всё просто. Кто пользуется таким замечательным инструментом, как wireshark, наверняка сталкивался уже с разрешениями процессов. Суть идеи такова: есть масса различных операций, результаты которых имеют критическое значение для всей системы, или же могут сказаться на отдельных пользователях этой самой системы, если оных много. Дабы свести к минимуму риск от потенциально деструктивных операций, система не даёт их выполнять обычным пользователям. А для административных задач есть всемогущий root и подразумевается, что человек, знающий пароль root'а на данной системе знает, что делает. Всё просто и вроде бы даже эффективно. Противопоставление "root vs обычный пользователь" простое, понятное, как чёрное и белое, и прозрачное. В идеале, пользователи имеют лишь минимально необходимый набор прав, они не могут навредить друг другу, так как доступ к данным защищён правами владения и доступа, не могут нанести вред системе и всё ещё могут добровольно делиться друг с другом доступом к данным. Идиллия. Но не всё так просто. Нынче Unix-подобные ОС уже совсем не обязательно работают лишь на системах, обслуживающих уйму пользователей. И тем не менее, вхождение в массы не изменило одного обстоятельства. Сидеть под root'ом всё так же моветон и категорически не рекомендуется без настоятельной нужды. А что делать, если пользователь на своей системе хочет изменить время? И пусть даже он единственный живой пользователь системы и никому навредить не сможет, кроме самого себя, операция изменения системного времени всё ещё требует привилегий. Что же делать? Тем более, если наш пользователь, скажем, путешествует туда-сюда по всему земному шару и меняет время чуть ли не каждый день. Прикажете ему сидеть под root'ом? Есть sudo, а вместе с этим и проблема. Когда наш пользователь сделает sudo date, процесс сможет не только изменять системное время, но делать всё то, что может сделать root. А что если злобные хацкеры подменили образ date на диске, пока наш пользователь плевал вниз с Эйфелевой башни, например? Вот тут-то и появилось осознание, что чего-то не хватает. Этот пробел призваны восполнить разрешения процессов в Linux. Если кратко, то существует целый класс операций, требующих привилегий. Однако специализированной программе, устанавливающей системное время, требуется право лишь на одну такую операцию. Чтобы избежать вероятности злоупотребления привилегиями, которые получает процесс с EUID=0, мы наделим нашу программу разрешением ровно на одну операцию. Всё остальное будет запрещено и наши злобные горе-хакеры останутся ни с чем. И хотя не стоит воспринимать приведённый выше пример всерьёз, проблема гранулярности традиционной модели безопасности Unix назрела давно. Более удачный и жизненный пример всё тот же wireshark - программа с графическим интерфейсом пользователя, которая позволяет следить за сетевым трафиком. Для того, чтобы успешно справляться со своей задачей, wireshark должен иметь право на создание "сырых" сокетов (raw sockets). При этом, GUI такие полномочия совершенно ни к чему и напротив, чем меньше кода выполняется с EUID=0, тем лучше. Ещё один пример из этой же области - ping. Раньше это решалось либо установкой suid-бита либо с помощью sudo. Решения, прямо скажем, далёкие от идеала и гибкости. Проблемы, связанные с подходами вроде sudo или suid-бита, были осознаны многими и в каждом потомке Unix они решались по-своему. В Solaris, к примеру, есть RBAC - разделение привилегий на основе ролей. Хотя это не то же самое, что разрешения процессов, но с RBAC тесно связана идея привилегий. В Linux же у нас есть process capabilities. И появились они, кстати, уже довольно давно, но по разным причинам широкого применения не нашли. Кажется, ситуация начинает медленно меняться, так что разберёмся с этим механизмом получше.

Кровавые детали

Контроль привилегий осуществляет ядро. В ядре Linux привилегии являются ничем иным, как битовыми картами. Чтобы убедиться в этом, проследите за корнями соответствующих структур в ядре Linux (интересующие нас поля выделены жирным курсивом, дескриптор задачи - task_struct , здесь сильно сокращён по понятным причинам):

Начнём с include/linux/sched.h:

И наконец include/linux/capability.h:

Итак, отдельно взятая привилегия - это ни много, ни мало бит в битовой карте, представляющей множество разрешённых, эффективных или наследуемых привилегий. Приведём наконец некоторые примеры конкретных привилегий:

CAP_SETPCAP Манипуляции привилегиями и, соответственно, разрешение использовать вызов capset() в ядрах, где поддержка файловых разрешений не включена.
Если же файловые разрешения поддерживаются, эта привилегия позволяет добавлять разрешения в ограничивающее множество потока, а также удалять их оттуда ( prctl(2) ); манипуляции с флагами безопасности (http://lwn.net/Articles/368600/)
CAP_NET_ADMIN Различные операции, относящиеся к сетевому администрированию, как то: конфигурирование сетевых интерфейсов, администрирование файрволла, изменение таблицы маршрутизации, включение "неразборчивого" режима на интерфейсе и тому подобное
CAP_CHOWN Манипуляции с идентификаторами пользователя/группы объекта файловой системы ( chown(2) )
CAP_DAC_OVERRIDE Обход проверок безопасности при операциях ввода-вывода
CAP_KILL Обход проверок безопасности при посылке сигналов ( kill(2) )
CAP_SETFCAP (начиная с Linux 2.6.24) Манипуляции с файловыми разрешениями
CAP_SYSLOG (начиная с Linux 2.6.37) Привилегированные операции с системным логом; отображение адресов ядра в /proc, если атрибут /proc/sys/kernel/kptr_restrict установлен в 1
CAP_SYS_TIME Изменение системного времени
CAP_SYS_BOOT Использование вызовов reboot() и kexec_load() - перезагрузка системы и "горячая" загрузка ядра, соответственно ( reboot(2) kexec_load(2) )
CAP_SYS_ADMIN Широкий спектр административных операций, как то: управление дисковыми квотами, активация и деактивация устройств своппинга и много чего ещё
CAP_SETGID
CAP_SETUID
Манипуляции с идентификаторами пользователя/группы процесса - вызовы setuid(2) , setreuid(2) , setresuid(2) , setfsuid(2 )

Данный список далеко не полный, т.к. я не ставил целью переписывание соответствующей man-страницы, и лишь демонстрирует некоторые более или менее типичные привилегии. Все доступные для данного ядра привилегии можно посмотреть в заголовочном файле include/uapi/linux/capability.h или в man-странице capabilities(2) .

О флагах безопасности (securebits). Это флаги, управляющие наследованием и использованием привилегий. Этими флагами манипулируют сами потоки. Для этого, впрочем, им нужно иметь привилегию CAP_SETPCAP , как уже было упомянуто.

Управление флагами возможно с помощью prctl(2) и "операторов" PR_SET_SECUREBITS / PR_GET_SECUREBITS .

Так в общих чертах выглядит объект нашего интереса. Но встаёт вопрос, а кто и как назначает привилегии конкретным процессам? Когда поддержки файловых разрешений ещё не было, единственным способом использования привилегий был некий вспомогательный процесс, который мог бы раздавать их другим процессам. Этакий сервер безопасности. Либо сама программа, запущенная изначально с правами суперпользователя, могла добровольно отказаться от ненужных ей привилегий. Оба способа явно довольно вычурны. Вероятно это в немалой степени послужило фактором, сдерживающим внедрение использования привилегий.

Файловые разрешения

Вот мы и подошли, пожалуй, к самому интересному. Действительно, без файловых разрешений реализация привилегий в Linux так и осталась бы ущербной. Говоря выше о множествах привилегий, мы узнали, что множества доступных (permitted), наследуемых (inheritable) и эффективных (effective) привилегий свойственны как файлам, так и процессам. Что это значит? Ровно то, что значит. Привилегии могут быть ассоциированы не только с выполняющимся процессом, но и с файлом, в котором хранится исполняемый образ. Какой-то особой поддержки привилегий со стороны файловой системы не требуется. Всё делом в том, что разрешения хранятся в расширенных атрибутах файлов, а это значит, что любая файловая система, которая поддерживает расширенные атрибуты (а это, в общем-то, все современные файловые системы, которые широко используются в Linux - ext, reiserfs, xfs, например), автоматически поддерживает и файловые разрешения. Linux разделяет расширенные атрибуты файлов по пространствам имён. В частности, например, определены такие пространства, как "user" - для пользовательских атрибутов, "security" - для атрибутов безопасности. Нас интересует именно последнее. Разрешения хранятся в атрибуте capability. Имя атрибута определено в заголовке include/uapi/linux/xattr.h таким образом:

Проведём небольшой эксперимент:

Что и требовалось продемонстрировать. dumpcap является частью пакета wireshark. Именно этот исполняемый файл "хватает" пакеты с сетевого интерфейса. Для того, чтобы мы могли использовать wireshark из-под обычного пользовательского аккаунта, этому файлу присвоены соответствующие разрешения: операции сетевого администрирования, право на создание символьных ("сырых", raw) сокетов во множествах доступных и эффективных привилегий. Каким образом всё это облечено в код ядра? Вся чёрная магия скрыта в файле security/commoncap.c

Ключевой здесь является функция get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps) , принимающая в качестве аргументов указатель на элемент каталога, связанный с файлом исполняемого образа и указатель на структуру, которая принимает прочитанные из атрибута разрешения. struct cpu_vfs_cap_data - то же самое, что struct vfs_cap_data из include/uapi/linux/capability.h , но при этом числа представлены в специфичном для процессора порядке следования байтов (endianness), в то время, как для struct vfs_cap_data представление little endian. Атрибуты непосредственно считываются с диска при помощи метода getxattr() объекта, представляющего индексный узел: Если таковой метод реализован для индексного узла в данной файловой системе, конечно же. Эта функция непосредственно читает данные с диска и проверяет их валидность. Прежде всего, ревизия реализации разрешений должна соответствовать размеру дисковых данных для этой реализации. В свою очередь get_vfs_caps_from_disk() используется функцией get_file_caps() , которая вычисляет множество доступных процессу разрешений основываясь на данных, прочитанных с диска из расширенных атрибутов файла и том, что мы наследуем от родителя через execve() . Собственно последняя часть задачи ложится на функцию bprm_caps_from_vfs_caps() :

В связи с упоминанием об этой функции, вспомним ещё раз о множествах. Исходя из файловых разрешений и привилегий процесса, вызывающего exec*() , результирующие разрешения для нового процесса вычисляются по следующим формулам: Здесь F - файловые разрешения, P - привилегии родительского процесса, P' - результирующие привилегии. Ещё раз посмотрите на код функции bprm_caps_from_vfs_caps() и без труда поймёте, что к чему. Через один из своих аргументов, effective , bprm_caps_from_vfs_caps() также возвращает флаг, уведомляющий о наличии среди файловых разрешений таких, которые должны быть активированы - эффективных файловых разрешений. Вопреки ожиданию наличие этих разрешений определяется не их фактическим присутствием, а специальным флагом, VFS_CAP_FLAGS_EFFECTIVE . Старые разрешения передаются через блок параметров исполняемого файла - переменную типа struct linux_binprm . Поднимаясь снизу вверх, мы обнаружим, что get_file_caps() - это ещё отнюдь не верхушка айсберга. Эта функция в свою очередь вызывается из cap_bprm_set_creds() . Последняя не объявлена, как статичная, что указывает на простой факт - она используется где-то за пределами данной единицы компиляции. Проявив такие чудеса дедукции и порывшись вокруг, обнаруживаем, что на cap_bprm_set_creds() "ссылается" static int selinux_bprm_set_creds(struct linux_binprm *bprm) из security/selinux/hooks.c , напрямую последняя не вызывается. Что же касается cap_bprm_set_creds() , то именно она и является основным хабом, где происходит вся обработка, связанная с разрешениями. Говоря об этой функции, как о хабе, я имею в виду, что её вызывает execve() с проинициализированным блоком параметров исполняемого образа, отсюда же тракт управления идёт к считыванию данных с диска, и частичной обработке полученных данных и здесь же потом происходит окончательная обработка множеств разрешений. Результаты своей работы эта функция предоставляет в распоряжение execve() опять же через блок параметров файла. Переход к этому коду происходит через указатель на операции безопасности (security ops).

Чуть ранее, прежде чем управление будет передано bprm_caps_from_vfs_caps() , в локальной переменной new , куда bprm_caps_from_vfs_caps() запишет вычисленные разрешения, кто-то должен был уже что-то записать. Это понятно уже из самого кода bprm_caps_from_vfs_caps() , ибо в самом деле, довольно странный способ вычисления разрешений, применяя их к чему-то неопределённому. Разрешения передаются между разными участками кода через параметры исполняемого образа, struct linux_binprm . Эти параметры заполняются опять же, в do_execve_common() .

Так как в процесс загрузки и запуска образа на исполнение участвует очень много кода, лишь в самых общих чертах окинем взглядом всю цепь событий. На этот раз пойдём в обратном направлении - сверху вниз. Всё начинается с execve():

Инициализация множеств разрешений нового процесса: Данные просто копируются из дескриптора текущей задачи, т.е. вызвавшей exec(), в блок параметров образа, который будет запущен. Теперь весь остальной код может сделать свою работу.

Далее, считывание разрешений с диска и вычисление новых разрешений, там же в fs/exec.c Далее, в security/security.c Структура, содержащая указатели на операции безопасности, инициализируется в security/selinux/hooks.c. Здесь без труда находим наш selinux_bprm_set_creds() Резюмируя, получим такой вот флоучарт:

Надеюсь, это головокружительное путешествие помогло понять, каким образом реализованы привилегии Linux и что делает их привлекательными, надеюсь, вскользь коснувшись кода, вы поймёте, какую роль в реализации поддержки привилегий играет VFS. К сожалению, написано уже и так больше, чем я предполагал изначально, а во все детали заглянуть всё равно не представляется возможным в заметке сколь-нибудь вменяемого объёма. Поэтому напоследок только взглянем краем глаза, как на практике реализуется контроль привилегий и на этом остановимся. Рассмотрим самый простой пример, с которого мы и начали, с настройкой системных часов. Наша программа вызывает stime(). Так как представить себе использование этого системного вызова совсем не сложно, текст самой программы не приведён:

kernel/time.c include/linux/security.h security/commoncap.c kernel/capability.c security/selinux/hooks.c security/commoncap.c

В общем-то, не то, чтобы всё это было очень сложно. Идея довольно проста. Запутанность привносит модульная модель безопасности. Но за стенами кода скрыт достаточно простой механизм. В нашем примере security_settime() не имеет никакого отношения к реальной настройке часов. Всё, что делает эта функция, проверяет наличие соответствующей привилегии. Через хуки SELinux управляющий тракт в конечном итоге приводит нас в код, находящийся в файле security/commoncap.c, где макрос cap_raised() производит тривиальную проверку (логическое И между имеющимся у процесса разрешением и требуемой привелегией - соответствующая привилегия из эффективного множества должна быть установлена в единицу). В качестве упражнения оставляю вам удовольствие найти, что за security_settime() и как мы выходим на описанный выше тракт :)

Почти живой пример

И в завершение совсем немного практики. Признаюсь честно, мне было уже лень писать код, поэтому я стащил небольшой кусочек отсюда. Эта программка должна изменить свой UID и дать нам шелл суперпользователя. Что ж, попытаем счастья: Не повезло :( Но мы-то уже знаем, дело вовсе не в везении, а в том, что наш файл не имеет соответствующих разрешений. Попробуем теперь вот так: Успех! Да, кстати, на всякий случай, не забывайте выходить из шеллов, т.к. даже при безуспешном вызове setresuid() эта программа очевидным образом спавнит новый шелл каждый раз, только без привилегий :) В первой строке она показывает файловые разрешения, во второй - разрешения выполняющегося процесса. Так что при желании можете поэкспериментировать с этим примером больше. Информацию о разрешениях выполняющегося процесса можно посмотреть через /proc: Причём, не имея прав суперпользователя, вы ничего интересного не увидите, кроме маски. Вот так - уже интереснее: Значения можно декодировать с помощью capsh: Также обратите внимание на утиль pscap, который показывает разрешения выполняющихся процессов. Всё сказанное наталкивает на мысль, что теоретически root, как таковой, уже не сильно нужен и вместо него можно ввести пользователя с полным набором привилегий. Кроме того, такая модель позволяет создавать промежуточных, недо-root'ов, с усечёнными полномочиями. Правда это требует обязательного включения поддержки некоторых параметров ядра при сборке, которые могут быть и отключены в пользовательских ядрах, так что модель root'а всё же пока актуальна, так как остаётся универсальным запасным вариантом для всех вариантов сборки ядра Linux.

PS: в заметке довольно много кода. Изначально я хотел спрятать всё это безобразие под ковриккат, но он работал не совсем так, как мне бы хотелось и я решил, что пусть уж лучше будет так, чем сломанный функционал. Надеюсь, к следующему разу я найду какой-нибудь рабочий вариант.

Перевод статьи подготовлен специально для студентов курса «Администратор Linux».

Привилегии (capabilities) используются всё больше и больше во многом благодаря SystemD, Docker и оркестраторам, таким как Kubernetes. Но, как мне кажется, документация немного сложна для понимания и некоторые части реализации привилегий для меня оказались несколько запутанными, поэтому я и решил поделиться своими текущими знаниями в этой короткой статье.


Самая важная ссылка по привилегиям — это man-страница capabilities(7). Но она не очень хорошо подходит для первоначального знакомства.

Разрешения процессов (process capabilities)

Права обычных пользователей очень ограничены, в то время как права пользователя “root” очень обширны. Хотя процессам, запущенным под «root», часто не требуются все полномочия root.

Для уменьшения полномочий пользователя “root” разрешения POSIX (POSIX capabilities) предоставляют способ ограничить группы привилегированных системных операций, которые разрешено выполнять процессу и его потомками. По сути, они делят все «root»-права на набор отдельных привилегий. Идея capabilities была описана в 1997 году в черновике POSIX 1003.1e.

В Linux каждый процесс (задача) имеет пять 64-битных чисел (наборов), содержащих биты разрешений (до Linux 2.6.25 они были 32-битными), которые можно посмотреть в .


Эти числа (здесь показаны в шестнадцатеричной системе счисления) представляют собой битовые карты, в которых представлены наборы разрешений. Вот их полные имена:

  • Inheritable (наследуемые) — разрешения, которые могут наследовать потомки
  • Permitted (доступные) — разрешения, которые могут использоваться задачей
  • Effective (текущие, эффективные) — текущие действующие разрешения
  • Bounding (ограничивающий набор) — до Linux 2.6.25 ограничивающий набор был общесистемным атрибутом, общим для всех потоков, предназначенным для описания набора, за пределы которого разрешения расширяться не могут. В настоящее время это набор для каждой задачи и является лишь частью логики execve, подробности далее.
  • Ambient (наружные, начиная с Linux 4.3) — добавлены, чтобы легче предоставлять разрешения не-root пользователю, без использования setuid или файловых разрешений (подробности позже).

Полный человекочитаемый список полномочий, определенных на данный момент, можно найти в актуальной man-странице capabilities(7) (приведенный здесь список только для справки).

Кроме того, есть библиотека libcap для упрощения управления и проверки полномочий. В дополнение к API библиотеки в пакет входит утилита capsh, которая, помимо прочего, позволяет показать свои полномочия.


Здесь есть несколько запутанных моментов:

  • Current — отображает эффективные, наследуемые и доступные привилегии процесса capsh в формате cap_to_text(3). В этом формате права перечислены как группы разрешений “capability[,capability…]+(e|i|p)” , где “e” означает эффективные, “i” наследуемые и “p” доступные. Список не разделен символом “,” , как вы могли догадаться (cap_setgid+eip, cap_setuid+eip) . Запятая разделяет разрешения в одной группе действий. Фактический список групп действий затем разделяется пробелами. Другим примером с двумя группами действий будет “= cap_sys_chroot+ep cap_net_bind_service+eip” . А также следующие две группы действий “= cap_net_bind_service+e cap_net_bind_service+ip” будут кодировать то же значение, что и одна “cap_net_bind_service+eip” .
  • Bounding set/Ambient set. Чтобы еще больше запутать, эти две строки содержат только список разрешений, заданных в этих наборах, разделенных пробелами. Здесь не используется формат cap_to_text, потому что он не содержит наборы доступных, эффективных и наследуемых разрешений, а только один (bounding/ambient) набор.
  • Securebits: отображает флаги securebits задачи в виде десятичных / шестнадцатеричных / в формате Verilog (да, все здесь ожидают, и это совершенно ясно по 'b , что каждый системный администратор программирует собственные FPGA и ASIC ). Далее следует состояние securebits. Фактические флаги определены как SECBIT_* в securebits.h, а также описаны в capabilities(7).
  • Этой утилите не хватает отображения информации “NoNewPrivs”, которую можно посмотреть в . Она упоминается только в prctl(2), хотя и влияет напрямую на права при использовании вместе с файловыми разрешениями (более подробно далее). NoNewPrivs описывается следующим образом: “При значении no_new_privs , равном 1, execve(2) обещает не предоставлять привилегий на то, что не могло бы быть сделано без вызова execve(2) (например, обработка битов set-user-ID , set-group-ID и отключение обработки файловых разрешений). После установки атрибут no_new_privs не может быть сброшен. Значение этого атрибута наследуется потомками, созданными через fork(2) и clone(2), и сохраняется через execve(2).”. Kubernetes устанавливает этот флаг в 1, когда allowPrivilegeEscalation имеет значение false в pod securityContext.

При запуске нового процесса через execve(2), полномочия для дочернего процесса преобразуются с использованием формулы, указанной в capabilities(7):

Эти правила описывают действия, выполняемые для каждого бита во всех наборах разрешений (ambient/permitted/effective/inheritable/bounding). Используется стандартный синтаксис языка Си (& — для логического И, | — для логического ИЛИ). P’ — это дочерний процесс. P — текущий процесс, вызывающий execve(2). F — это, так называемые, “файловые разрешения” у файла, запущенного через execve.

Кроме того, процесс может программно изменить свои наследуемые, доступные и эффективные наборы с помощью libcap в любое время в соответствии со следующими правилами:

  • Если вызывающая сторона не имеет CAP_SETPCAP , новый наследуемый набор должен быть подмножеством P(наследуемый) & P(доступный)
  • (с Linux 2.6.25) Новый наследуемый набор должен быть подмножеством P(наследуемый) & P(ограничивающий)
  • Новый доступный набор должен быть подмножеством P(доступный)
  • Новый эффективный набор должен быть подмножеством P(эффективный)

Разрешения файлов (file capabilities)

Иногда пользователю с ограниченным набором прав необходимо запустить файл, который требует больше полномочий. Ранее это достигалось установкой бита setuid ( chmod + s ./executable ) в бинарном файле. Такой файл, если он принадлежит root, будет иметь полные права root при выполнении любым пользователем.

Но этот механизм предоставляет слишком много привилегий файлу, поэтому POSIX-разрешения реализовали концепцию, называемую “файловые разрешения”. Они хранятся в виде расширенного атрибута файла, называемого “security.capability”, поэтому вам нужна файловая система с поддержкой расширенных атрибутов (ext*, XFS, Raiserfs, Brtfs, overlay2, …). Для изменения этого атрибута необходимо разрешение CAP_SETFCAP (в доступном наборе разрешений процесса).

Особенные случаи и замечания

Конечно, в реальности все не так просто, и есть несколько особых случаев, описанных в man-странице capabilities(7). Наверное, самыми важными из них являются:

  • Бит setuid и файловые разрешения игнорируются, если установлен NoNewPrivs или файловая система смонтирована с nosuid или процесс, вызывающий execve трассируется ptrace. Файловые разрешения также игнорируются, когда ядро ​​загружается с опцией no_file_caps .
  • “Глупый” файл (capability-dumb) — это бинарный файл, преобразованный из setuid-файла в файл с файловыми разрешениями, но без изменения его исходного кода. Такие файлы часто получаются путем установки на них разрешений +ep, например “setcap cap_net_bind_service+ep ./binary” . Важной частью является “е” — эффективный. После execve эти разрешения добавятся как к доступным, так и к действующим, поэтому исполняемый файл будет готов использовать привилегированную операцию. Напротив, ”умный” файл (capability-smart), который использует libcap или аналогичную функциональность, может использовать cap_set_proc(3) (или capset) для установки “эффективных” или “наследуемых” битов в любой момент, если это разрешение уже находится в ”доступном” наборе. Поэтому “ setcap cap_net_bind_service+p ./binary” будет достаточно для “умного” файла, поскольку он сам сможет установить необходимые разрешения в эффективном наборе перед вызовом привилегированной операции. Смотрите пример кода.
  • Файлы с setuid-root продолжают работать, предоставляя все привилегии root при запуске не root пользователем. Но если у них установлены файловые разрешения, то будут предоставлены только они. Также можно создать setuid-файл с пустым набором разрешений, что сделает его выполнение под пользователем с UID 0 без каких-либо полномочий. Есть особые случаи для пользователя root при запуске файла с setuid-root и установки различных флагов securebits (см. man).
  • Ограничивающий набор (bounding set) маскирует доступные разрешения, но не наследуемые. Помните P '(доступные) = F (доступные) & P (ограничивающие). Если у потока есть разрешение в своем наследуемом наборе, которое не находится в его ограничивающем наборе, тогда он все еще может получить это разрешение в своем доступном наборе, запустив файл, который имеет разрешение в своем наследуемом наборе — P '(доступный) = P (наследуемый) & F (наследуемый).
  • Выполнение программы, которая изменяет UID или GID через биты set-user-ID, set-group-ID, или выполнение программы, для которой установлены какие-либо файловые разрешения, очистит окружающий набор (ambient set). Разрешения добавляются в окружающий набор, используя PR_CAP_AMBIENT prctl. Эти разрешения уже должны присутствовать как в доступных, так и в наследуемых наборах процесса.
  • Если процесс с UID, отличным от 0, выполняет execve(2), то все права, присутствующие в его доступных и действующих наборах, будут удалены.
  • Если не установлен SECBIT_KEEP_CAPS (или более широкий SECBIT_NO_SETUID_FIXUP ), изменение UID с 0 на ненулевой удаляет все разрешения из наследуемого, доступного и эффективного наборов.

Если официальный контейнер nginx, ingress-nginx или ваш собственный останавливается или перезапускается с ошибкой:

bind() to 0.0.0.0:80 failed (13: Permission denied)

… это означает, что была попытка слушать порт 80 под непривилегированным (не 0) пользователем, и в наборе текущих разрешений не было CAP_NET_BIND_SERVICE . Для получения этих прав необходимо использовать xattr и установить (с помощью setcap ) для файла nginx разрешение, как минимум, cap_net_bind_service+ie . Это файловое разрешение будет объединено с унаследованным набором (заданным вместе с ограничивающим набором из pod SecurityContext/capability/add/NET_BIND_SERVICE), и также размещено в наборе доступных разрешений. В результате получится cap_net_bind_service+pie .

Это все работает до тех пор, пока securityContext/allowPrivilegeEscalation установлен в true и storage-драйвер docker/rkt (см. документацию docker) поддерживает xattrs.

Если бы nginx был умен по отношению к полномочиям, то cap_net_bind_service+i было бы достаточно. Затем он мог бы использовать libcap для расширения прав от доступного набора до эффективного. Получив в результате cap_net_bind_service+pie .

Помимо использования xattr, единственным способом получения cap_net_bind_service в не-root контейнере — это позволить Docker установить внешние разрешения (ambient capabilities). Но по состоянию на апрель 2019, это еще не реализовано.

Примеры кода

Здесь пример кода с использованием libcap для добавления CAP_NET_BIND_SERVICE в эффективный набор разрешений. Он требует наличия разрешения CAP_BIND_SERVICE+p для бинарного файла.

Для того, чтобы выполнять проверки на соответствие прав, обычные реализации Unix имели две категории процессов: привилегированные процессы (privileged, у них действующий идентификатор пользователя равен 0, соответствуя суперпользователю root) и непривилегированные процессы (unprivileged, у них действующий идентификатор пользователя не равен 0). Привилегированные процессы осуществляют все проверки прав в ядре, в то время как непривилегированные процессы являются объектом для полноценной проверки всех прав на основе параметров процесса (обычно: действующий идентификатор пользователя UID, действующий идентификатор группы GID и список дополнительных групп).

Начиная с ядра 2.2, Linux обеспечивает (хотя и не полностью) в системе много возможностей , разделяющие привилегии, традиционно ассоциированные с суперпользователем, в отдельный блок, который может быть независимо включен или выключен.

Список возможностей

Возможности процессов

Каждый процесс имеет три набора возможностей, содержащих ни одного или несколько из перечисленных возможностей: Effective (действующие): возможности используются ядром для выполнения проверок прав процессов. Permitted (разрешенные): возможности, выделенные процессу (например, ограниченный расширенный набор для действующего и наследуемого наборов). Если процесс сбрасывает возможность из его разрешенного набора, то он никогда не обретет ее повторно (пока не исполнится программа set-UID-root). Inherited (унаследованные): возможности, зарезервированные через execve (2).

В текущей реализации процессу выдаются все действующие и разрешенные возможности (объекты для операции по ограничению набора возможностей описаны ниже) при исполнении программы set-UID-root, или если процесс с действительным UID равным 0 исполняет новую программу.

Порожденный через fork (2) процесс копирует себе все наборы возможностей своего родителя.

Используя capset (2), процесс может управлять своими наборами возможностей, или, если у него есть возможность CAP_SETPCAP , возможностями других процессов.

Граница набора возможностей

Когда программа исполняется, ее действующие и разрешенные возможности логически умножаются (AND) с текущим значением так называемого границы набора возможностей , определенной в файле /proc/sys/kernel/cap-bound . Этот параметр может использоваться для размещения общесистемного лимита на возможности, выделяемые всем последовательно исполняемым программам (к сожалению, этот параметр битовой маски выражен в виде десятичного числа со знаком в /proc/sys/kernel/cap-bound .)

Только процесс init может устанавливать биты в границе набора возможностей; все другие, кроме суперпользователя, могут только очищать биты в этом наборе.

В обычной системе граница набора возможностей всегда маскируется от возможности CAP_SETPCAP . Для снятия этого ограничения измените определение CAP_INIT_EFF_SET в include/linux/capability.h и пересоберите ядро.

Текущая и будущие реализации

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

Если рассмотреть Linux 2.4.20, то в нем выполняются только первые два требования.

В конечном итоге можно ассоциировать эти три набора возможностей с исполняемым файлом, который в соединении с наборами возможностей процесса будет определять возможности процесса после исполнения exec : Allowed (разрешенные): набор логически умножается (AND) с наследованным набором процесса для определения наследуемых возможностей, разрешенных процессу после исполнения exec. Forced (принужденные): возможности автоматически разрешаются процессу, независимо от наследуемых возможностей процесса. Effective (действующие): эти возможности в новом разрешенном наборе также будут установлены в новый действующий набор. (F(effective) будет состоять либо из всех нулей, либо из единиц.)

В то же время, так как текущие реализации не поддерживают наборы возможностей файлов, то во время исполнения exec: 1. Все три набора возможностей файла считаются очищенными. 2. Если исполняется программа set-UID-root, или если действительный идентификатор пользователя процесса равен 0 (root), то файлу разрешены и принудительно устанавливаются все единицы в наборе (то есть полный набор возможностей). 3. Если исполняется программа set-UID-root, то действующий набор файла определен во все единицы.

Во время исполнения exec, ядро рассчитывает новые возможности процесса, используя следующий алгоритм: где: P обозначает набор возможностей процесса до исполнения P' обозначает значение набора возможностей после исполнения F обозначает набор возможностей файла cap_bset является значением границы набора возможностей.

Еще одним важным атрибутом процесса, определяющим его возможности по использованию системных вызовов, являются привилегии процесса cababilities.

Например, обладание привилегией CAP_SYS_PTRACE разрешает процессам трассировщиков strace и ltrace, использующих системный вызов ptrace, трассировать процессы любых пользователей (а не только «свои», EUID которых совладает с EUID трассировщика).

Аналогично, привилегия CAP_SYS_NIСЕ разрешает изменять приоритет, устанавливать привязку к процессорам и назначать алгоритмы планирования процессов и нитей любых пользователей, а привилегия CAP_КILL разрешает посылать сигналы процессам любых пользователей.

Явная привилегия «владельца» CAP_FOWNER позволяет процессам изменять режим и списки доступа, мандатную метку, расширенные атрибуты (см. статью) и флаги любых файлов так, словно процесс выполняется от лица владельца файла.

Привилегия CAP_LINUX_IMMUTABLE разрешает управлять флагами файлов i, immutable и a, append, а привилегия CAP_SETFCAP — устанавливать «файловые» привилегии (см. далее) запускаемых программ.

Необходимо отметить, что именно обладание полным набором привилегий делает пользователя root (UID=0) в Linux — суперпользователем. И наоборот, обычный, непривилегированный пользователь (в смысле UID≠o) не обладает никакими явными привилегиями. Неявно он обладает привилегией владельца для всех своих объектов.

Назначение привилегий процесса происходит при запуске программы при помощи системного вызова exec, исполняемый файл которого помечен «файловыми» привилегиями (здесь допущено намеренное упрощение механизма наследования и назначения привилегий при fork и exec без потери смысла).

В примере из листинга ниже иллюстрируется получение списка привилегий процесса при помощи утилиты getpcaps.

Как и ожидалось, процесс postgres (pid=1427), работающий рт лица обычного (непривилегированного, в смысле UID≠o) псевдопользователя postgres, не имеет никаких.привилегий, а процесс NetworkHanager (pid=1263), работающий от лица суперпользователя root (uid=o), имеет полный набор привилегий.

Однако процесс dhclient (PID=14026) выполняется от лица «суперпользователя», лишенного большинства своих привилегий, т. к. после порождения своего дочернего процесса NetworkManager уменьшил его привилегии до минимально необходимого набора, достаточного для выполнения своих функций, что способствует обеспечению защищенности операционной системы.

Привилегии (capabilities) процесса

$ ps fo user,pid,cmd -C NetworkManager,dhclient,postgres

postgres 1984 \_ postgres: writer process

postgres 1985 \_ postgres: wal writer process

postgres 1986 \_ postgres: autovacuum launcher process

postgres 1987 \_ postgres: stats collector process

$ getpcaps 1427

$ getpcaps 1263

$ getpcaps 14026

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

Например, «обычная» утилита ping для выполнения своей работы должна создать «необработанный» raw сетевой сокет, что является с точки зрения ядра привилегированной операцией.

В большинстве систем программа /bin/ping будет SUID-ной во владении суперпользователем root, чьи права и будут передаваться при ее запуске.

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

Для создания «необработанных» raw и пакетных packet сокетов достаточно только привилегии CAP_NET_RAW, а весь суперпользовательский набор привилегий более чем избыточен.

Делегирование привилегий программе ping

$ ls -l /bin/ping

-rwsr-xr-x 1 root root 3474 нояб. 8 2017 /bin/ping

$ ping ubuntu

PING ubuntu (127.0.1.1) 56(84) bytes of data.

64 bytes from ubuntu (127.0.1.1): icmp_req=1 ttl=64 time=0.074 ms

^C

rtt wln/avg/max/mdev = 0.074/0.074/0.074/0.000 ms

$ sudo chmod u-s /bin/ping

$ ping ubuntu

ping: icmp open socket: Operation not permitted

$ sudo setcap cap_net_raw+ep /bin/ping

$ ls -l /bin/ping

-rwxr-xr-x 1 root root 34740 нояб. 8 2017 /bin/ping

$ getcap /bin/ping

$ ping ubuntu

PING ubuntu (127.0.1.1) 56(84) bytes of data.

64 bytes from ubuntu (127.0.1.1): icmp_req=1 ttl=64 time=0.142 ms

^D

— ubuntu ping statistics —

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 0.142/0.142/0.142/0.000 ms

При отключении передачи полномочий программа /bin/ping лишается возможности выполнять свои функции, а при назначении ей при помощи команды setcap «файловой» привилегии CAP_NET_RAW функциональность возвращается в полном объеме, т. к. приводит к установке «процессной» привилегии CAP_NET_RAW при запуске этой программы.

Для просмотра привилегий, делегируемых при запуске программ, используется парная команда getcap.

Аналогично, при использовании анализаторов сетевого трафика tshark и/или wireshark, вызывающих для захвата сетевых пакетов утилиту dumpcap, необходимо открывать как «необработанные» raw, так и пакетные packet сетевые сокеты, что требует той же привилегии CAP_NETJRAW.

Классический способ применения анализаторов пакетов состоит в использовании явной передачи всех полномочий суперпользователя (при помощи su или sudo) при их запуске, что опять не соответствует минимально необходимым и достаточным требованиям к разрешенным возможностям программ.

Делегирование привилегий программе tshark

$ tshark

tshark: There are no interfaces on which a capture can be done

$ strace -fe execve tshark

Process 8951 attached

Process 8951 detached

tshark: There are no interfaces on which a capture can be done

$ sudo setcap cap_net_raw+ep /usr/bin/dumpcap

[sudo] password for fitz:

$ getcap /usr/bin/dumpcap

$ tshark -i wlanO

Capturing on wlan0

0.307205 fe80: :895d:9d7d:f0b3:a372 -> ff02::l:ff96:2df6 ICMPv6 86 Neighbor Solicitation

0.982542 10.2.0.126 -> 10.2.0.255 NBNS 92 Name query NB WPAD<00>

0.982811 04:18:d6:4e:53:88 -> Broadcast ARP 60 Who has 10.2.0.1? Tell 10.2.0.43.

Для эффективного использования анализаторов трафика непривилегированными пользователями достаточно делегировать их процессам захвата пакетов привилегию CAP_NET_RAW при помощи «файловых» привилегии CAP_NET_RAW для программы захвата /usr/bin/dumpcap, что и проиллюстрировано в листинге выше.

Читайте также: