Ядро Linux в комментариях

       

Возможности


На заре развития Unix пользователь являлся либо привилегированным (root), либо обычным. Привилегированный пользователь мог делать почти все, даже если в действительности это было не очень хорошей идеей, например, удалением всех файлов на системном загрузочном диске. Обычный же пользователь не мог особенно повредить системе, но и не мог выполнять какие-либо существенные задачи администрирования.

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

Некоторые из этих проблем можно было обойти посредством правильного использования групп или с помощью таких программ, как sudo, но это получалось не всегда. Для выполнения некоторых важных операций все же приходилось предоставлять процессам общий привилегированный доступ, даже когда в действительности нужно было разрешить им выполнить всего одну-две привилегированные операции. Linux решает эту проблему посредством воплощения идеи, заимствованной из уже не существующего проектного стандарта POSIX — возможностей.

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

В настоящее время разработка возможностей продолжается. Для полного использования потенциальных преимуществ возможностей некоторые новые функции все еще ждут своей реализации; например, ядро все еще не обеспечивает поддержку присоединения к самому файлу требуемых программе возможностей. В результате, иногда система Linux продолжает проверять, выполняется ли процесс в качестве привилегированного, вместо того, чтобы проверять наличие конкретных возможностей, требуемых процессу. Но даже уже сделанное является достаточно полезным.


Наследуемый набор — не совсем то, что можно было бы предположить. Это не набор возможностей, которые родительский процесс передает дочерним процессам во время выполнения подпрограммы fork — в действительности в момент создания (т.е. непосредственно после выполнения fork) все три набора возможностей дочернего процесса совпадают с наборами родительского процесса. Наследуемый процесс выступает на сцену во время выполнения exec. Непосредственно перед вызовом exec наследуемый набор процесса помогает определить разрешенный и наследуемый наборы, которые процесс сохранит после выполнения exec — для более подробного ознакомления с этим процессом обратитесь к функции compute_creds (строка ). Обратите внимание, что сохранение возможности после выполнения exec лишь частично зависит от наследуемого набора процесса; это зависит также от разрядов возможностей, установленных в самом файле (или, по крайней мере, так планируется — это свойство реализовано еще не полностью).

Попутно отметим, что разрешенный набор всегда должен быть включать действующий и наследуемый наборы (или же совпадать с ними). (Строго говоря, это справедливо только по отношению к действующему набору. Один процесс может расширить наследуемый набор другого процесса так, что он больше не будет поднабором его разрешенного набора, но, насколько можно судить, это было бы бессмысленным, поэтому пока мы не будем рассматривать такую возможность.) Однако в отличие от того, что можно было бы предположить, действующий набор не обязательно должен включать наследуемый набор (или совпадать с ним). Т.е., после выполнения exec процесс может иметь возможность, которую он не имел до этого (хотя возможность должна входить в его разрешенный набор — т.е. она должна являться возможностью, которую оригинал мог бы получить для себя). Полагаю, что частично это связано с тем, что процессу не нужно временно получать возможность, которую ему негде использовать, разве что он может выполнить программу, которая в ней нуждается.



Возможности показаны на рис. 7.4. На этом рисунке показаны три набора возможностей для гипотетического процесса, причем разряды нумеруются справа налево. Процессу разрешено получить возможность CAP_KILL, которая позволяет ему прервать любой другой процесс, независимо от его владельца; но процесс пока не имеет этой возможности и не будет получать ее автоматически во время выполнения exec. В настоящий момент он имеет возможность вставлять и удалять модули ядра (используя CAP_SYS_MODULE), но также не будет получать ее во время выполнения exec. Процесс мог бы получить возможность CAP_SYS_NICE и будет получать ее во время выполнения exec (при условии, что соответствующие разряды возможностей файла установлены). И наконец, непосредственно сейчас процесс может изменить системное время (CAP_SYS_TIME) и будет сохранять эту возможность после выполнения exec (опять таки, при условии, что соответствующие разряды возможностей файла установлены). Ни этот процесс, ни один из тех, которые он может запустить с помощью функции exec, не может получить иные возможности, если только они не обеспечиваются каким-либо другим процессом, имеющим возможность CAP_SETPCAP.



Рис. 7.4. Наборы возможностей

Код, поддерживающий все эти свойства, в основном размещается в файле kernel/capability.c, начинающемся со строки . Двумя основными функциями являются sys_capget (строка ), которая считывает возможности, и sys_capset (строка ), которая их устанавливает; эти функции освещаются далее в этом разделе. Как уже отмечалось, наследование возможностей после выполнения exec обеспечивается функцией compute_creds файла fs/exec.c (строка ).

Конечно, как правило, привилегированный процесс располагает всеми возможностями. Функция возможностей ядра обеспечивает привилегированный процесс структурированным способом избирательно предоставлять данному процессу только необходимые возможности, независимо от того, выполняется ли процесс в качестве привилегированного.

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



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

Здесь ядро распознает начало возможностей. Поскольку операторы #define снабжены развернутыми комментариями, мы не станем подробно останавливаться на каждом из них.

Каждой возможности были просто присвоены последовательные целые числа, но поскольку они используются для адресации разрядов внутри целого значения без знака, с помощью макроса CAP_TO_MASK они преобразуются в степени числа 2.

В основе установки и проверки возможностей лежит всего лишь набор простых манипуляций разрядами; некоторые макросы и встроенные функции, представленные в строках от этой до конца файла include/linux/capability.h, служат для прояснения манипуляций с разрядами.


Содержание раздела