Разрешения POSIX для файлов: Разделяем полномочия root

Как безопасно распространить власть root на более широкий круг пользователей

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

Серж Е. Халлин, штатный программист, IBM

Серж Халлин (Serge Hallyn) — сотрудник Linux Technology Center корпорации IBM, специализирующийся на разработке ядра и системы безопасности Linux. Он получил степень Ph.D. в области компьютерных наук в College of William and Mary. Он является автором и соавтором ряда модулей безопасности. Сейчас он занимается реализацией поддержки функциональности виртуальных серверов, контрольных точек/рестарта приложений и разрешений POSIX для файлов.



25.01.2008

Некоторые программы требуют выполнения привилегированных операций от имени непривилегированного пользователя. Например, программа passwd записывает данные в очень важные файлы /etc/passwd и /etc/shadow. В UNIX®-системах подобный контроль достигается за счет установки бита setuid в двоичном файле. Этот бит говорит системе, что пока программа работает, она должна рассматриваться как принадлежащая пользователю, который владеет файлом - обычно root, - независимо от того, кто ее запустил. Поскольку пользователь не имеет полномочий на запись в программу passwd и эта программа очень ограничена в доступных пользователю функциях, такая конфигурация обычно безопасна. Более сложные программы используют для переключения между правами root и обычного пользователя сохраненные идентификаторы uid.

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

exec(3)

Из страниц man Linux: Семейство функций exec() заменяет образ текущего процесса образом нового процесса. Более подробную информацию см. по ссылке в разделе Ресурсы

  • Можно удалить разрешения из текущего набора, сохранив их в доступном наборе, для предотвращения случайного злоупотребления.
  • Можно удалить все ненужные разрешения из текущего набора без возможности их восстановления. Строго говоря, большинство разрешений достаточно опасны и могут быть использованы во вред, поэтому сокращение прав, доступных атакующему, может в значительной степени защитить вашу систему.
  • После применения exec(3) к обычному исполняемому файлу все права доступа будут утеряны. (Детали этого механизма достаточно сложны и скоро в них ожидаются изменения; об этом будет рассказано ниже в этой статье.)

В данной статье рассказывается о том, как использовать разрешения POSIX в программах, как узнать, какие разрешения необходимы программе, а также о том, как предоставить их программе.

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

Много лет разрешения POSIX могли быть предоставлены процессам, но не файлам. Поэтому программа должна была быть запущена под учетной записью root (или принадлежать root и иметь установленный бит setuid), и лишь после этого можно было удалить у нее часть разрешений root, сохранив остальные. Дополнительно отметим, что порядок удаления разрешений был довольно специфичен:

  1. Программа говорит системе, что хочет сохранить свои разрешения при смене текущего идентификатора пользователя (userid). Это делается с помощью функции prctl.
  2. Программа изменяет свой userid на какой-либо другой, отличный от root.
  3. Программа создает наборы требуемых разрешений и активизирует их.

Каждый процесс имеет три набора прав доступа: доступные (permitted) (P), наследуемые (inheritable) (I) и текущие (effective) (E). Когда процесс разветвляется, наборы возможностей новых процессов копируются из родительского. Когда процесс запускает новую программу, ее новые наборы разрешений рассчитываются по определенной формуле, о которой будет сказано далее.

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

В Листинге 1 приведены три формулы, которые определяют новые наборы разрешений процесса после выполнения файла согласно проекту стандарта POSIX (см. ссылку на стандарт IEEE 1003.1-2001 в разделе Ресурсы).

Листинг 1. Формулы вычисления новых наборов разрешений после запуска exec()
pI' = pI
pP' = fP | (fI & pI)
pE' = pP' & fE

Значение, заканчивающееся на ',означают новое рассчитанное значение. Значение, начинающееся с p , означает права процесса. Значение, начинающееся с f, означает права файла.

Наследуемый набор берется неизменным из родительского процесса, поэтому, когда процесс удаляет из него разрешения, их уже невозможно восстановить (см., однако, далее обсуждение SECURE_NOROOT). Новый доступный набор рассчитывается как объединение доступного набора файла с результатом пересечения наследуемых наборов файла и процесса. Текущий набор процесса — это объединение новых доступного и текущего наборов файла. Технически в Linux fE представляет собой не набор разрешений, а логическую переменную. Если она истинна, то pE' устанавливается в pP'. Если нет, то pE' инициализируется пустым.

Чтобы процесс сохранил какие-либо права после использования файла, они должны входить в доступный или наследуемый набор разрешений файла. Поскольку в течение большей части времени существования Linux наборы разрешений файлов не использовались, это порождало непреодолимое ограничение. Чтобы обойти его, был разработан «безопасный режим» ("secure mode"), состоящий из двух битов:

  • Когда бит SECURE_NOROOT не установлен, то при выполнении процессом файла новые наборы прав могут быть рассчитаны так, как если бы файл имел некоторый полностью заполненный набор разрешений. В частности:
    • Наследуемый и доступный наборы файла будут полностью заполнены, если реальный или текущий uid процесса есть 0 (root) или пользователем файла является root.
    • Текущий набор файла будет полностью заполнен, если текущий uid процесса есть root или пользователем файла является root.
  • Когда бит SECURE_NO_SETUID_FIXUP не установлен, при переключении процессом настоящего или текущего userid на 0 или с 0, наборы возможностей также переключаются:
    • Если процесс переключает текущий uid с 0 на неравный 0, то текущий набор разрешений процесса обнуляется.
    • Если процесс переключает свои настоящий, текущий и сохраненный uid, среди которых есть хотя бы один 0, на все неравные 0, то доступные и текущие разрешения обнуляются.
    • Если процесс устанавливает текущий uid с ненулевого на равный нулю, то текущие возможности устанавливаются равными разрешенным.

Этот свод правил позволяет процессу иметь такие права доступа, как если бы он принадлежал root или был запущен файл с установленным setuid root. Однако правила SECURE_NO_SETUID_FIXUP не допускают сохранения разрешений после того, как процесс переходит от пользователя root к другому пользователю. Однако с неустановленным битом SECURE_NOROOT процесс, принадлежащий root и снявший некоторые разрешения, может легко выполнить другую программу, чтобы восстановить свои разрешения. Таким образом, для того, чтобы разрешения были полезными, процесс root должен иметь возможность безвозвратно переключить свой uid на неравный нулю, сохранив при этом часть прав.

Используя prctl(3), процесс может запросить сохранение своих прав через вызов setuid(2). Это означает, что процесс может:

  • Запуститься от имени пользователя root, авторизовавшись как root или выполнив двоичный файл пользователя root с установленным битом setuid.
  • Вызвать prctl(2), чтобы установить PR_SET_KEEPCAPS, который через setuid(2) запрашивает систему о сохранении его разрешений.
  • Вызвать setuid(2) или аналогичный системный вызов для изменения значения своего userid.
  • Вызвать cap_set_proc(3) для снятия разрешений.

Теперь процесс может продолжать выполняться с подмножеством привилегий root. Если он будет взят под контроль злоумышленником, атакующий может использовать только права, представленные в его текущем наборе, или — при вызове cap_set_proc(3) — в доступном наборе разрешений. Если атакующий принудит программу выполнить другой файл, то все разрешения будут сброшены и файл будет выполнен от имени непривилегированного пользователя.

Функция exec_with_caps() в Листинге 2 демонстрирует функцию, которая может быть использована программой с установленным setuid пользователя root для продолжения выполнения в заданной функции с заданным userid пользователя и с набором разрешений, определенным строковым параметром.

Листинг 2 . Выполнение кода с ограниченными возможностями
#include <sys/prctl.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <stdio.h>

int printmycaps(void *d)
{
	cap_t cap = cap_get_proc();
	printf("Running with uid %d\n", getuid());
	printf("Running with capabilities: %s\n", cap_to_text(cap, NULL));
	cap_free(cap);
	return 0;
}

int exec_with_caps(int newuid, char *capstr, int (*f)(void *data), void *data)
{
	int ret;
	cap_t newcaps;

	ret = prctl(PR_SET_KEEPCAPS, 1);
	if (ret) {
		perror("prctl");
		return -1;
	}
	ret = setresuid(newuid, newuid, newuid);
	if (ret) {
		perror("setresuid");
		return -1;
	}
	newcaps = cap_from_text(capstr);
	ret = cap_set_proc(newcaps);
	if (ret) {
		perror("cap_set_proc");
		return -1;
	}
	cap_free(newcaps);
	f(data);
}

int main(int argc, char *argv[])
{
	if (argc < 2) {
		printf("Usage: %s <capability_list>\n",
			argv[0]);
		return 1;
	}
	return exec_with_caps(1000, argv[1], printmycaps, NULL);
}

Для тестирования кода сохраните его в файле под названием execwithcaps.c, затем скомпилируйте его и запустите от имени пользователя root:

gcc -o execwithcaps execwithcaps.c -lcap
./execwithcaps cap_sys_admin=eip

Разрешения файлов

Файловые разрешения в настоящее время реализованы в дереве ядра -mm ; к версии ядра 2.6.24 ожидается их реализация в основной ветке. Благодаря файловым разрешениям вы можете присваивать разрешения программам. Например, программа ping требует CAP_NET_RAW для своей работы. По этой причине она исторически была программой с установленным setuid пользователя root. Используя файловые разрешения, вы можете сократить количество привилегий, доступных этой программе, выполнив команды:

chmod u-s /bin/ping
setfcaps -c cap_net_admin=p -e /bin/ping

Для этого нужна новейшая версия библиотеки libcap и связанных с ней программ, которые имеются на сайте GoogleCode (см. ссылки в разделе Ресурсы). Сначала мы удаляем бит setuid двоичного файла, а затем даем ему разрешение на функцию CAP_NET_RAW, которая нам необходима. После этого любой пользователь может выполнить ping с разрешением CAP_NET_RAW, но если программа ping будет взломана, атакующий не сможет использовать другие привилегии.

Возникает вопрос об определении минимального набора прав, требуемого непривилегированному пользователю для выполнения каждой конкретной программы. Если бы речь шла об одной программе, можно было бы проанализировать приложение, его динамически подгружаемые библиотеки и используемые ресурсы ядра. Однако это нужно было бы сделать для всех программ с установленным setuid пользователя root. Конечно, такой вариант был бы лучше, чем позволить приложениям запускаться непривилегированными пользователями с правами root, но, к сожалению, это нереалистичный путь.

Если программа ведет себя добросовестно и подробно информирует об ошибках, то можно просто выполнить ее от имени непривилегированного пользователя, и программа выдаст сообщение о том, каких привилегий ей не хватает. Попробуем проделать это с ping.

chmod u-s /bin/ping
setfcaps -r /bin/ping
su - myuser
ping google.com
	ping: icmp open socket: Operation not permitted

Такой метод может быть полезен, если вы понимаете, как реализован протокол icmp, но нам это не подходит.

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

strace -oping.out ping google.com
grep EPERM ping.out
   socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = -1 EPERM (Operation not permitted)

Разрешение, которого нам не хватало – это разрешение создавать сокет типа SOCK_RAW. Просмотрев /usr/include/linux/capability.h, вы увидите следующее:

/* Allow use of RAW sockets */
/* Allow use of PACKET sockets */

#define CAP_NET_RAW          13

В данном случае понятно, что функция, необходимая для использования команды ping непривилегированным пользователем – это CAP_NET_RAW. Однако вполне возможно, что некоторые программы попытаются вызвать функции, которые им фактически не нужны, и будут отклонены с ошибкой -EPERM. Также возможны ситуации, когда необходимые разрешения нелегко угадать.

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

Пакет kprobes позволяет разработчикам писать небольшие модули ядра для выполнения кода в начале функции (jprobe), в конце функции (kretprobe) или по любому адресу в функции (kprobe). Использование kprobes позволяет получить информацию о том, какие разрешения требуются ядру для выполнения определенных программ. (Оставшаяся часть текста предполагает, что в вашем ядре реализованы как kprobes, так и файловые разрешения.)

В Листинге 3 приведен пример модуля ядра, который вставляет jprobe в начало функции ядра cap_capable().

Листинг 3. capable_probe.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/sched.h>

static const char *probed_func = "cap_capable";

int cr_capable (struct task_struct *tsk, int cap)
{
	printk(KERN_NOTICE "%s: asking for capability %d for %s\n",
		__FUNCTION__, cap, tsk->comm);
	jprobe_return();
	return 0;
}

static struct jprobe jp = {
	.entry = JPROBE_ENTRY(cr_capable)
};

static int __init kprobe_init(void)
{
	int ret;
	jp.kp.symbol_name = (char *)probed_func;

	if ((ret = register_jprobe(&jp)) < 0) {
		printk("%s: register_jprobe failed, returned %d\n",
			__FUNCTION__, ret);
		return -1;
	}
	return 0;
}

static void __exit kprobe_exit(void)
{
	unregister_jprobe(&jp);
	printk("capable kprobes unregistered\n");
}

module_init(kprobe_init);
module_exit(kprobe_exit);

MODULE_LICENSE("GPL");

При использовании этого модуля ядра любые вызовы cap_capable() заменяются вызовами функции cr_capable(). Эта функция печатает имя программы, которой требуются специальные разрешения, и результат проверки наличия достаточных прав. Затем она продолжает выполнение вызова cap_capable() через вызов jprobe_return().

Скомпилируйте модуль, используя makefile, представленный в Листинге 4:

Листинг 4. Makefile для capable_probe
obj-m := capable_probe.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
	rm -f *.mod.c *.ko *.o

Затем выполните от имени пользователя root:

	/sbin/insmod capable_probe.ko

Теперь в одном окне просмотрите системные журналы командой

	tail -f /var/log/messages

В другом окне, не от имени пользователя root, выполните ping без установленного бита setuid:

	/bin/ping google.com

Системные журналы теперь должны содержать множество записей о команде ping. Это записи с правами, которые программа пыталась использовать. Не все из них действительно нужны. С помощью файла /usr/include/linux/capability.h мы можем перевести цифровые значения в названия требуемых разрешений; мы видим, что ping запросил возможности 21, 13 и 7.

  • 21 — это CAP_SYS_ADMIN. Избегайте предоставления этой функции любой программе.
  • 7 — это CAP_SETUID. Ping не должен этого требовать.
  • 13 — это CAP_NET_RAW. Это то, что необходимо команде ping.

Предоставим необходимые разрешения команде и посмотрим, удастся ли нам теперь выполнить программу

	setfcaps -c cap_net_raw=p -e /bin/ping
	(теперь не от имени пользователя root)
	ping google.com

Как мы и ожидали, команда ping была выполнена успешно.

Сложности

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

Примером этого является программа at в Fedora. Программа позволяет пользователям запланировать задания для выполнения в определенное время. Например, простейший способ получить напоминание о встрече в 2 часа будет следующим:

echo "xterm -display :0.0 -e \
\"echo Call customer 555-5555; echo ^V^G; sleep 10m\" " | \
at 14:00

Программа at доступна для всех UNIX-систем и может быть выполнена любым пользователем. Пользователи используют общий буфер заданий в /var/spool. Поэтому безопасность здесь исключительно важна. Но программа at написана так, чтобы работать со многими системами, поэтому не использует специфичных системных механизмов безопасности, таких как файловые разрешения. Тем не менее программа пытается ограничить свои привилегии, используя setuid(2). Кроме того, пакет Fedora добавляет патчи, используемые модулями PAM.

Самый быстрый путь проверить, может ли at выполняться без прав root, не требуя установки setuid root, — это удалить бит setuid и затем предоставить программе полные права.

chmod u-s /usr/bin/at
setfcaps -c all=p -e /usr/bin/at
su - (заканчиваем сессию работы пользователя root)
/usr/bin/at

Указав -c all=p, мы просим установить полный набор разрешений для /usr/bin/at. Таким образом, любой пользователь, запускающий программу, делает это с привилегиями пользователя root. Но на Fedora 7 запуск /usr/bin/at приведет к следующему сообщению:

You do not have permission to run at.

Причина этого будет понятна, если скачать и изучить исходный код программы, но детальный разбор данной ситуации не будет полезен в контексте этого упражнения. Несмотря на то, что можно изменить исходный код программы at, добавив использование файловых разрешений, в Fedora бит setuid не может быть заменен простым присвоением файловых разрешений.

Детали файловых разрешений

До сих пор мы использовали специальный формат для установки разрешений исполняемым файлам. Команду ping мы запускали следующим образом:

	setfcaps -c cap_net_raw=p -e /bin/ping

setfcaps — это программа, которая предоставляет права заданному файлу через установку расширенного атрибута, названного security.capability. За флагом -c следует список разрешений в произвольном порядке:

	capability_list=capability_set(s)

capability_set может содержать значения i и p, а capability_list — любые корректные разрешения. Эти типы разрешений представляют собой наследуемый и доступный наборы разрешений соответственно, и для каждого набора можно указать раздельные списки разрешений. Флаг -e и флаг -d указывают, будут ли права из доступного набора в текущем наборе разрешений при запуске программы или нет, соответственно. Если разрешения отсутствуют в текущем наборе, то программа должна поддерживать разрешения, чтобы самостоятельно активировать биты в своем текущем наборе для задействования разрешений.

До сих пор мы устанавливали желаемые права в доступном, но не в наследуемом наборе. В действительности существуют более интересные и мощные приемы обращения с правами. Вспомним первый пример, повторив его здесь:

Повтор Листинга 1. Формулы вычисления новых наборов разрешений после запуска exec()
pI' = pI
pP' = fP | (fI & pI)
pE' = pP' & fE

Наследуемый набор файла определяет, какие из наследуемых разрешений процесса могут быть в его новом доступном наборе. Если в наследуемом наборе файла содержится только cap_dac_override, то только это разрешение может быть унаследовано в новом доступном наборе.

Доступный набор разрешений файла, также известный как «принудительный набор разрешений»,- это набор прав, который содержится в новом доступном наборе независимо от того, был ли он в наследуемом наборе задания или нет.

Наконец, текущий бит файла определяет, будут ли биты из нового доступного набора разрешений в новом текущем наборе; то есть может ли программа реально пользоваться правами без прямого запроса на использование cap_set_proc(3).

Напомним, что система вносит целый ряд изменений для пользователя root, если бит SECURE_NOROOT не установлен. В частности система считает, что при выполнении файла наследуемый (fI), доступный (fP) и текущий (fE) наборы полностью заполнены. Таким образом, наследуемый набор разрешений в двоичном файле полезен только для процесса без прав root с непустыми наборами возможностей. В частности, для программы, которая сохранила свои разрешения, перейдя к правам пользователя, не являющегося пользователем root, формулы Листинга 1 применяются без этих ухищрений. Вполне вероятно, что SECURE_NOROOT в будущем станет индивидуальной настройкой для каждого процесса, так что ветвящийся процесс сможет выбирать, использовать ли действительные права или модель привилегированного пользователя (root-user-is-privileged). Однако на момент написания статьи эта установка является общесистемной и пользователь root по умолчанию обладает всеми правами.

Чтобы проиллюстрировать взаимодействия этих наборов, предположим, что администратор использовал следующую команду для установки файловых разрешений для /bin/some_program:

	setfcaps -c cap_sys_admin=i,cap_dac_read_search=p -e \
	/bin/some_program

Если пользователь, не являющийся пользователем root, запускает эту программу со всеми разрешениями, его наследуемый набор pI сначала маскируется fI, сокращаясь в результате до cap_sys_admin. Затем fP объединяется с этим набором, поэтому промежуточный результат будет равен cap_sys_admin+cap_dac_read_search. Этот набор разрешений становится новым доступным набором задания.

Наконец, поскольку текущий бит установлен, новый текущий набор разрешений задания будет содержать оба бита из нового доступного набора.

В отличие от этого, если непривилегированный пользователь запустит эту же программу, его пустой наследуемый набор будет маскирован с fI, в результате чего получится снова пустой набор. Он будет объединен с fP, что даст cap_dac_read_search, и станет новым доступным набором задания. Наконец, так как текущий бит установлен, новый доступный набор будет скопирован в новый текущий набор разрешений, что снова даст результат cap_dac_read_search.

В любом случае, если текущий бит файла не был установлен, заданию потребуется вызов cap_set_proc(3), чтобы скопировать требующиеся ему биты из доступного набора в текущий.


Подведение итогов и упражнения

Подведем итог:

  • Текущий бит файла определяет возможность программы использовать разрешенные по умолчанию права.
  • Доступный набор файла — это набор разрешений, который всегда будет включен в выполняемый процесс.
  • Наследуемый набор файла – это набор разрешений, который может быть скопирован из родительского наследуемого набора в новый доступный набор.

Чтобы проиллюстрировать то, что здесь было описано, поэкспериментируем с программами из Листингов 5 и 6. В Листинге 5 print_caps просто отображает наборы прав, имеющиеся при запуске. В Листинге 6 exec_as_nonroot_priv предназначен для использования с правами пользователя root. Он запрашивает сохранение разрешений через setuid(2), становится пользователем, отличным от root, задаваемым первым аргументом командной строки, затем устанавливает набор разрешений, определяемых вторым аргументом, а затем запускает программу, определяемую третьим аргументом.

Листинг 5. print_caps.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>

int main(int argc, char *argv[])
{
	cap_t cap = cap_get_proc();

	if (!cap) {
		perror("cap_get_proc");
		exit(1);
	}
	printf("%s: running with caps %s\n", argv[0], cap_to_text(cap, NULL));
	cap_free(cap);
	return 0;
}
Листинг 6. exec_as_nonroot_priv.c
#include <sys/prctl.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

void printmycaps(void)
{
	cap_t cap = cap_get_proc();

	if (!cap) {
		perror("cap_get_proc");
		return;
	}
	printf("%s\n",  cap_to_text(cap, NULL));
	cap_free(cap);
}

int main(int argc, char *argv[])
{
	cap_t cur;
	int ret;
	int newuid;

	if (argc<4) {
		printf("Usage: %s <uid> <capset>"
			"<program_to_run>\n", argv[0]);
		exit(1);
	}
	ret = prctl(PR_SET_KEEPCAPS, 1);
	if (ret) {
		perror("prctl");
		return 1;
	}
	newuid = atoi(argv[1]);
	printf("Capabilities before setuid: ");
	printmycaps();
	ret = setresuid(newuid, newuid, newuid);
	if (ret) {
		perror("setresuid");
		return 1;
	}
	printf("Capabilities after setuid, before capset: ");
	printmycaps();
	cur = cap_from_text(argv[2]);
	ret = cap_set_proc(cur);
	if (ret) {
		perror("cap_set_proc");
		return 1;
	}
	printf("Capabilities after capset: ");
	cap_free(cur);
	printmycaps();
	ret = execl(argv[3], argv[3], NULL);
	if (ret)
		perror("exec");
}

Воспользуемся этими программами для проверки эффекта наследуемых и доступных прав файла. Мы сделаем это, включив файловые разрешения с помощью print_caps, а затем запустим print_capsс исходными наборами разрешений, установив их с помощью exec_as_nonroot_priv. Сначала добавим некоторые права в доступный набор print_caps:

gcc -o print_caps print_caps.c -lcap
setfcaps -c cap_dac_override=p -d print_caps

Теперь запустим print_caps от имени пользователя, отличного от пользователя root:

su - (username)
./print_caps

Теперь из учетной записи root выполним print_caps через exec_as_nonroot_priv:

./exec_as_nonroot_priv 1000 cap_dac_override=eip ./print_caps

В обоих случаях print_caps запускается с cap_dac_override=p. Заметим, что текущий набор разрешений пуст. Это означает, что print_caps придется использовать cap_set_proc(3) до фактического использования разрешений cap_dac_override. Чтобы исправить это, используйте флаг -e в setflags для установки текущего бита

setfcaps -c cap_dac_override=p -e print_caps

print_caps имеет пустой набор fI, поэтому pI не будет помещен в pP'. Единственный бит вpP' происходит из принудительного набора файла fP.

Более интересным является тест наследования разрешений файла и запуска print_caps от имени обычного пользователя и через программу exec_as_nonroot_priv:

setfcaps -c cap_dac_override=i -e print_caps
su - (заканчиваем сессию работы пользователя root)
	./print_caps
	exit
./exec_as_nonroot_priv 1000 cap_dac_override=eip ./print_caps

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

Запустите теперь print_caps еще раз, теперь уже от имени пользователя root без использования exec_as_nonroot_priv. Заметим, что набор разрешений заполнен. Пользователь root всегда получает полный набор разрешений после запуска программы независимо от разрешений файла. exec_as_nonroot_priv не выполняет print_caps с правами root. Вместо этого он использует привилегии пользователя root для запуска процесса без прав пользователя root, но с некоторыми наследуемыми разрешениями.


Заключение

Теперь вы знаете, как определить, какие разрешения требуются программе, как установить необходимые разрешения и как производить другие интересные манипуляции с разрешениями файлов.

Обращайтесь с разрешениями осторожно; они по-прежнему являются опасной составляющей привилегий root. С другой стороны, история с ошибкой в разрешениях sendmail (более подробную информацию см. в разделе Ресурсы) показывает, что предоставление слишком малых прав тоже может оказаться опасным. Тем не менее благоразумное применение файловых разрешений к системным бинарным файлам вместо установки для всех них бита setuid пользователя root может помочь защитить ваши системы.

Ресурсы

Научиться

  • Оригинал этой статьи (EN).
  • В серии Secure programmer на developerWorks имеется несколько статей, рассказывающих о setuid():
    • В "Keep an eye on inputs" (EN) (developerWorks, декабрь 2003 г.) рассматриваются способы получения данных программами и методы работы с ними
    • В "Minimizing privileges" (EN) (май 2004 г.) обсуждаются пути предоставления минимальных привилегий без ущерба для пользователей системы
    • В "Call components safely" (EN) (декабрь 2004 г.) рассказывается о том, как предотвратить использование атакующими вызовов компонентов
  • В статье "Securing Linux, Part 3: Hardening the system" (EN) (developerWorks, апрель 2005 г.) описываются методы повышения устойчивости Linux-систем к атакам: защита процесса загрузки и локальных файловых систем, блокировка демонов и сервисов, введение квот и ограничений, включение обязательного контроля доступа и распознавание уязвимостей в безопасности, которые могут быть внесены при обновлениях программного обеспечения.
  • Статья "Speaking UNIX, Part 8" (EN) (developerWorks, апрель 2007 г.) показывает, как контролировать процессы и использовать ряд команд для слежения за системой.
  • "Anatomy of the Linux kernel" (EN) (developerWorks, июнь 2007 г.) - это хорошая статья для начала изучения взаимодействий сочетаний битов в Linux.
  • Прочтите историю об ошибке разрешений sendmail.
  • Из страниц man Linux: Семейство функций exec(), заменяющих текущий образ процесса новым образом.
  • Из страниц man Linux: prctl(), производит операции над процессами, вызывается с первым аргументом, описывающим воздействие на процесс (значения заданы в <linux/prctl.h>), а также параметрами, зависящими от значения первого параметра.
  • Из страниц man Linux: setuid() устанавливает фактический идентификатор владельца (User ID) текущего процесса. Если фактический пользователь, вызвавший эту функцию - root, то также устанавливаются действительный и сохраненный идентификаторы. В Linux setuid реализован так, как это описано в стандарте POSIX, включая возможность _POSIX_SAVED_IDS. Это позволяет setuid-программам (отличным от root) сбрасывать все привилегии, выполнять непривилегированные операции, а затем безопасно возвращать себе исходный фактический идентификатор пользователя.
  • Из страниц man Linux: cap_set_proc() устанавливает значения всех флагов разрешений для всех разрешений c наборами значений, определяемыми cap_p. Новый набор значений процесса полностью определяется содержимым cap_p после успешного завершения работы этой функции. Если какие-то флаги в cap_pустановлены в значения, запрещенные для текущего процесса, функция не сработает и набор разрешений не изменится.
  • POSIX Threads Programming — это великолепный учебник, начинающийся с основ и доходящий до таких тем, как разработка гибридных MPI/Pthreads.
  • Стандарт POSIX, также известный как стандарт IEEE Std 1003.1-2001, определяет стандартный интерфейс и среду операционной системы, включая командный интерпретатор ("shell") и основные программные утилиты для поддержки переносимости приложений на уровне исходного кода. Предполагается, что этот стандарт будет использоваться как разработчиками приложений, так и системными разработчиками.
  • Посмотрите статью "Using ReiserFS with Linux" (EN) (developerWorks, апрель 2006 г.), чтобы узнать об "альтернативной, расширенной файловой системе для любителей приключений."
  • В статье "Differentiating UNIX and Linux" (EN) (developerWorks, март 2006 г.), имеется небольшой урок по различиям в поддержке файловых систем между Linux и UNIX—ищите главу "Filesystem support".
  • В продолжение темы файловых систем статья "System Administration Toolkit: Migrating and moving UNIX filesystems" (EN) (developerWorks, июль 2006 г.) рассказывает, как перенести существующую файловую систему в новую, включая описание создания, копирования и инициализации.
  • В разделе Linux сайта developerWorks, имеется множество ресурсов для разработчиков Linux, а также самые популярные статьи и учебные пособия.
  • Советы и учебные пособия по Linux на developerWorks.
  • Раздел технических мероприятий и Web-трансляций developerWorks поможет вам оставаться в курсе новейших технологий.

Получить продукты и технологии

  • Библиотеки libcap libraries и связанные с ними программы можно найти на GoogleCode.
  • Linux PAM — это гибкий механизм аутентификации пользователей, который позволяет разработчикам создавать программы с независимой схемой аутентификации (так, что "новое устройство" не имеет эквивалентных записей во всех программах, поддерживающих аутентификацию).
  • Используйте в своем следующем проекте для Linux ознакомительное ПО IBM, которое можно скачать напрямую с developerWorks.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, AIX и UNIX
ArticleID=284280
ArticleTitle=Разрешения POSIX для файлов: Разделяем полномочия root
publish-date=01252008