Show process capability linux

Linux capabilities 101

Security of Linux systems and applications can be greatly improved by using hardening measures. One of these measures is called Linux capabilities. Capabilities are supported by the kernel for some while now. Using capabilities we can strengthen applications and containers. Unfortunately, this powerful tool is still underutilized. Time to change that! This article helps to understand and apply them.

Table of Contents

What are Linux capabilities?

Normally the root user (or any ID with UID of 0) gets a special treatment when running processes. The kernel and applications are usually programmed to skip the restriction of some activities when seeing this user ID. In other words, this user is allowed to do (almost) anything.

Linux capabilities provide a subset of the available root privileges to a process. This effectively breaks up root privileges into smaller and distinctive units. Each of these units can then be independently be granted to processes. This way the full set of privileges is reduced and decreasing the risks of exploitation.

Why capabilities?

To better understand how Linux capabilities work, let’s have a look first at the problem it tries to solve.

Let’s assume we are running a process as a normal user. This means we are non-privileged. We can only access data that owned by us, our group, or which is marked for access by all users. At some point in time, our process needs a little bit more permissions to fulfill its duties, like opening a network socket. The problem is that normal users can not open a socket, as this requires root permissions.

Option 1: Giving everyone root permissions

One of the solutions is to allow some permissions (by default) to all users. There is a serious flaw in this approach. Allowing this kind of permissions, for all users, would open up the system for a flood of system abuse. The reason is that every small opportunity is being used for good, but also for bad. Giving away too many privileges by default will result in unauthorized changes of data, backdoors and circumventing access controls, just to name a few.

Option 2: Using a fine-grained set of privileges

For example, a web server normally runs at port 80. To start listening on one of the lower ports ( Replacing setuid with capabilities

Assigning the setuid bit to binaries is a common way to give programs root permissions. Linux capabilities is a great alternative to reduce the usage of setuid.

Insight: Capabilities break up root privileges in smaller units, so root access is no longer needed. Most of the binaries that have a setuid flag, can be changed to use capabilities instead.

See the related article Hardening Linux binaries by removing the setuid bit and apply this to your system.

Available capabilities

Support by the Linux kernel

Linux capabilities are defined in a header file with the non-surprising name capability.h. The number of capabilities supported by recent Linux versions is close to 40. To see the highest capability number for your kernel, use the data from the /proc file system.

The full list of available Linux capabilities for the active kernel can be displayed using the capsh command.

The same number from the cap_last_cap file might be also displayed at the end of a capability set.

The capsh command shows the available Linux capabilities

Normal users typically don’t have any capabilities assigned. This is also what we have seen in the screenshot. That is why the current list is empty. Now that changes if you switch to your root user.

Current capabilities

To see the capabilities for a particular process, use the status file in the /proc directory. As it provides more details, let’s limit it only to the information related to Linux capabilities.

This command should return 5 lines on most systems.

  • CapInh = Inherited capabilities
  • CapPrm – Permitted capabilities
  • CapEff = Effective capabilities
  • CapBnd = Bounding set
  • CapAmb = Ambient capabilities set

An explanation about these specific types will follow. Let’s start with some example output that you may get on your system.

These hexadecimal numbers don’t make sense. Using the capsh utility we can decode them into the capabilities name.

Although that works, there is another and easier way. To see the capabilities of a running process, simply use the getpcaps tool followed by its process ID (PID). You can also provide a list of process IDs.

The getpcaps tool uses the capget() system call to query the available capabilities for a particular thread. This system call only needs to provide the PID to obtain more information.

In this output, we see also version 3 of the capabilities. This was added since Linux version 2.6.26.

It is also interesting to see the capabilities of a set of processes that have a relationship.

If you run this on a system with nginx, you will see something special. The PID of the master process has capabilities, while the child processes or workers have none. This is because only the master requires the special permissions, like listening to a network port. The child processes then can do the work, like answering HTTP requests.

Capability bounding set

The capability bounding set defines the upper level of available capabilities. During the time a process runs, no capabilities can be added to this list. Only the capabilities in the bounding set can be added to the inheritable set, which uses the capset() system call. If a capability is dropped from the boundary set, that process or its children can no longer have access to it.

Capabilities overview

Let’s have a look at some of the available capabilities and what they do.

CAP_CHOWN

If you ever changed the owner of a file, you will be familiar with the chown command. This capability provides the privilege to do this. It allows changing both the owner as the group. Good to know is that this only applies when _POSIX_CHOWN_RESTRICTED is active, which is true on most Linux systems. By using the getconf command we can validate this.

getconf -a | grep _POSIX_CHOWN_RESTRICTED

Limiting the capabilities for processes

You can test what happens when a particular capability is dropped by using the capsh utility. This is a way to see what capabilities a particular program may need to function correctly. The capsh command can run a particular process and restrict the set of available capabilities.

Run the same command with one single ping to our local system.

capsh —print — -c «/bin/ping -c 1 localhost»

Dropping capabilities with capsh

If we drop the CAP_NET_RAW capabilities for ping, then the ping utility should no longer work.

capsh —drop=cap_net_raw —print — -c «/bin/ping -c 1 localhost»

Besides the output of capsh itself, the ping command itself should also raise an error.

ping: icmp open socket: Operation not permitted

The error clearly shows that the ping command is not allowed to open an ICMP socket. Now we know for sure that this works as expected.

Читайте также:  Windows media server download

Binaries with setuid bit

Capabilities are a great way to replace binaries with the setuid bit set. This special bit gives users full root permissions under the context of that process. As you can imagine, if the program contains a flaw, the non-privileged user can “break out” and become the equivalent of the root user.

Still many Linux distributions use the setuid on several binaries, while capabilities can replace the bit.

Conclusion

Capabilities are a great way to split up root permissions and hand out some permissions to non-privileged users. Unfortunately, still many binaries have the setuid bit set, while they should be replaced with capabilities instead.

Keep learning

So you are interested in Linux security? Join the Linux Security Expert training program, a practical and lab-based training ground. For those who want to become (or stay) a Linux security expert.

Run automated security scans and increase your defenses. Lynis is an open source security tool to perform in-depth audits. It helps with system hardening, vulnerability discovery, and compliance.

Continue reading

Livepatch: Linux kernel updates without rebooting

How to secure a Linux system

The state of Linux security in 2017

Linux security myths

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

About Linux Audit

This blog is part of our mission: help individuals and companies, to scan and secure their systems. We simply love Linux security, system hardening, and questions regarding compliance.

Besides the blog, we have our security auditing tool Lynis. Open source, GPL, and free to use.

For those with enterprise needs, or want to audit multiple systems, there is an Enterprise version.

«One security solution to audit, harden, and secure your Linux/UNIX systems.»

Benefits:

  • Perform audits within a few minutes
  • Central management
  • Powerful reporting
  • Compliance checks (e.g. PCI DSS)
  • Additional plugins and more tests

Enjoy the articles!

Linux and UNIX security automation

Lynis is a free and open source security scanner. It helps with testing the defenses of your Linux, macOS, and Unix systems. Typical use-cases for this software include system hardening, vulnerability scanning, and checking compliance with security standards (PCI-DSS, ISO27001, etc).

Recent Posts

Contact

This blog is part of our mission to share valuable tips about Linux security. We are reachable via @linuxaudit

Company details

CISOfy
De Klok 28,
5251 DN, Vlijmen, The Netherlands
+31-20-2260055

Источник

rfLinux

Sunday, June 8, 2014

Разрешения процессов в Linux

Терминологический казус

То, о чём сейчас пойдёт речь, по-английски называется 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. И появились они, кстати, уже довольно давно, но по разным причинам широкого применения не нашли. Кажется, ситуация начинает медленно меняться, так что разберёмся с этим механизмом получше.

Читайте также:  Windows x32 4gb ram

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

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

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

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

Т.е., в текущей версии реализации одно множество разрешений представлено двумя 32-битными словами или 64 битами (строка » #define _LINUX_CAPABILITY_U32S_3 2 » в файле include/uapi/linux/capability.h ). Всего таких множеств 3 (см. выше определение структуры struct cred ): разрешённые (permitted), эффективные (effective), наследуемые (inheritable). В общем-то названия этих множеств и краткие комментарии в коде говорят сами за себя. Множество разрешённых привилегий — это те разрешения, которые наш процесс может активировать в принципе, но которые не обязательно действуют в данный момент времени. Если процесс исключает привилегию из множества доступных (privilege drop), то он больше не может ею воспользоваться, в том числе, он не сможет вернуть себе право на данную привилегию (reacquire privilege) Множество эффективных привилегий — это те элементы множества разрешённых привилегий, которые были активированы (запрошены) процессом и действуют в данный момент. Именно это поле используется ядром для проверки права на данную операцию. И, наконец, множество наследуемых привилегий — это те разрешения, с которыми может работать дочерний процесс, порождённый нашим процессом, т.е., множество привилегий, которые присваиваются процессу, порождённому с помощью exec*() (все они в конечном счёте сходятся на execve() ). Процесс, созданный в результате fork() , получает полные копии множеств привилегий родительского процесса. Эти три множества привилегий свойственны и выполняющимся процессам, и файлам (о файлах и привилегиях чуть ниже). Ещё одно множество — ограничивающее (bounding set). Если с 3 другими множествами всё ясно, то на этом стоит остановиться чуть подробнее. Во-первых, это множество в отличие от уже рассмотренных свойственно только процессам, но не файлам. Во-вторых, это ещё один ограничительный механизм, но в отличие от множества доступных процессу (permitted) привилегий, устанавливаемого при запуске исполняемого образа или при посредничестве другого процесса, это множество устанавливается для процессов-потомков, порождённых вызовом execve() . Происходит это следующим образом: во время выполнения exec*() к привилегиям из ограничивающего множества (bounding set) процесса, вызывающего exec*() , и множества доступных привилегий (permitted), ассоциированного с файлом исполняемого образа на диске, применяется логическое И, а результат заносится в множество разрешённых привилегий дочернего процесса. Таким образом, потомку будут доступны лишь те операции, которые есть и в ограничивающем множестве, и во множестве разрешённых привилегий, прочитанном с диска. Начиная с версии Linux 2.6.25 процесс не может добавить привилегию в множество наследуемых привилегий (inheritable), если она не входит в ограничивающее множество, даже не смотря на то, что данная привилегия доступна вызывающему потоку (т.е., она может быть в его permitted-множестве). Ограничивающее множество является маской разрешённых привилегий, определённых для файла, но не для множества уже унаследованных привилегий. Таким образом, это механизм контроля наследования разрешений, а не их использования уже существующим процессом. Следует отметить важный технический момент: операция ограничения привилегий в соответствии с маской ограничивающего множества не выполняется непосредственно над атрибутами файла, а над разрешениями свежесозданного процесса. Так что хоть ограничивающее множество действует в первую очередь на те разрешения, которые ассоциированы с файлом, вычисления происходят на множествах разрешений процесса, не файла. Создаваемый процесс получит в точности те разрешения, которые свойственны файлу исполняемого образа, но позже они будут откорректированы в соответствии с маской родительского процесса. Звучит запутанно, но надеюсь, всё станет понятнее далее, когда мы дойдём до файловых атрибутов. В зависимости от версии ядра ограничивающее множество — это либо системный атрибут, либо свойство конкретного процесса (http://lwn.net/Articles/251666/). До версии 2.6.25 маска задаётся через файл /proc/sys/kernel/cap-bound в виде целого со знаком в десятичной системе. Эту маску может установить только init. Процессы c EUID=0, имеющие разрешение CAP_SYS_MODULE , могут удалять отдельные привилегии из этой маски. Обычно на таких системах маска не включает разрешение CAP_SETPCAP . Чтобы снять это ограничение, достаточно изменить определение CAP_INIT_EFF_SET в файле include/linux/capability.h и пересобрать ядро. Начиная с версии 2.6.25 ограничивающее множество больше не является глобальным общесистемным атрибутом. Оно наследуется при вызове fork() и exec*() . Поток может удалять привилегии из своего ограничивающего множества. Детали см. в capabilities(2)

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

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 , как уже было упомянуто.

SECBIT_KEEP_CAPS Поток, имеющий UID 0, сохраняет все разрешения при смене идентификатора пользователя (не root)

По умолчанию этот флаг очищен и даже если он установлен потоком, он принудительно очищается при вызове exec*() с целью предотвращения утечки пивилегий

SECBIT_NO_SETUID_FIXUP Множества разрешеий не будут корректироваться ядром при смене UID между 0 и любым другим значением
SECBIT_NOROOT Программа с suid-битом не получает автоматически все привилегии, которые имеет root

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

Источник

Читайте также:  Плеер onkyo для windows
Оцените статью