Отредактировано для описания причины исходного сбоя:
В Linux есть три набора возможностей: наследуемые, разрешенные и эффективные. Наследуемый определяет, какие возможности остаются разрешенными в exec()
. Разрешено определяет, какие возможности разрешены для процесса. Эффективный определяет, какие возможности действуют в данный момент.
При смене владельца или группы процесса с root на non-root действующий набор полномочий всегда сбрасывается.
По умолчанию разрешенный набор возможностей также очищается, но вызов prctl(PR_SET_KEEPCAPS, 1L)
перед изменением идентификатора сообщает ядру, что разрешенный набор должен оставаться нетронутым.
После того, как процесс изменил идентификатор обратно на непривилегированного пользователя, CAP_SYS_NICE
должен быть добавлен к действующему набору. (Он также должен быть установлен в разрешенном наборе, поэтому, если вы очистите свой набор возможностей, не забудьте также установить его. Если вы просто измените текущий набор возможностей, то вы знаете, что он уже установлен, потому что вы его унаследовали.)
Вот процедура, которую я рекомендую вам выполнить:
Сохраните реальный идентификатор пользователя, реальный идентификатор группы и дополнительные идентификаторы групп:
#define _GNU_SOURCE
#define _BSD_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <grp.h>
uid_t user = getuid();
gid_t group = getgid();
gid_t *gid;
int gids, n;
gids = getgroups(0, NULL);
if (gids < 0) /* error */
gid = malloc((gids + 1) * sizeof *gid);
if (!gid) /* error */
gids = getgroups(gids, gid);
if (gids < 0) /* error */
Отфильтруйте ненужные и привилегированные дополнительные группы (будьте параноиками!)
n = 0;
while (n < gids)
if (gid[n] == 0 || gid[n] == group)
gid[n] = gid[--gids];
else
n++;
Поскольку вы не можете «очистить» идентификаторы дополнительных групп (которые просто запрашивают текущий номер), убедитесь, что список никогда не пуст. Вы всегда можете добавить реальный идентификатор группы в дополнительный список, чтобы он не был пустым.
if (gids < 1) {
gid[0] = group;
gids = 1;
}
Переключите реальные и эффективные идентификаторы пользователей на root
if (setresuid(0, 0, 0)) /* error */
Установите возможность CAP_SYS_NICE
в наборе CAP_PERMITTED
. Я предпочитаю очистить весь набор и оставить только четыре возможности, необходимые для работы этого подхода (а позже удалить все, кроме CAP_SYS_NICE):
cap_value_t capability[4] = { CAP_SYS_NICE, CAP_SETUID, CAP_SETGID, CAP_SETPCAP };
cap_t capabilities;
capabilities = cap_get_proc();
if (cap_clear(capabilities)) /* error */
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 4, capability, CAP_SET)) /* error */
if (cap_set_flag(capabilities, CAP_PERMITTED, 4, capability, CAP_SET)) /* error */
if (cap_set_proc(capabilities)) /* error */
Сообщите ядру, что вы хотите сохранить возможности перехода от root к непривилегированному пользователю; по умолчанию возможности сбрасываются до нуля при переходе от root к не-root удостоверению
if (prctl(PR_SET_KEEPCAPS, 1L)) /* error */
Установите реальный, эффективный и сохраненный идентификатор группы на первоначально сохраненный идентификатор группы.
if (setresgid(group, group, group)) /* error */
Установить дополнительные идентификаторы групп
if (setgroups(gids, gid)) /* error */
Установите реальный, эффективный и сохраненный идентификатор пользователя на первоначально сохраненный идентификатор пользователя.
if (setresuid(user, user, user)) /* error */
На этом этапе вы фактически отказываетесь от привилегий root (без возможности получить их обратно), за исключением возможности CAP_SYS_NICE
. Из-за перехода от пользователя root к пользователю без полномочий root эта возможность никогда не действует; ядро всегда будет очищать действующий набор возможностей при таком переходе.
Установите возможность CAP_SYS_NICE
в наборе CAP_PERMITTED
и CAP_EFFECTIVE
if (cap_clear(capabilities)) /* error */
if (cap_set_flag(capabilities, CAP_PERMITTED, 1, capability, CAP_SET)) /* error */
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 1, capability, CAP_SET)) /* error */
if (cap_set_flag(capabilities, CAP_PERMITTED, 3, capability + 1, CAP_CLEAR)) /* error */
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 3, capability + 1, CAP_CLEAR)) /* error */
if (cap_set_proc(capabilities)) /* error */
Обратите внимание, что последние две операции cap_set_flag()
очищают три возможности, которые больше не нужны, так что остается только первая, CAP_SYS_NICE
.
На данный момент дескриптор возможностей больше не нужен, поэтому рекомендуется его освободить.
if (cap_free(capabilities)) /* error */
Скажите ядру, что вы не хотите сохранять возможности для любых дальнейших изменений от root (опять же, просто паранойя)
if (prctl(PR_SET_KEEPCAPS, 0L)) /* error */
Это работает на x86-64 с использованием ядра GCC-4.6.3, libc6-2.15.0ubuntu10.3 и linux-3.5.0-18 на Xubuntu 12.04.1 LTS после установки пакета libcap-dev
.
Отредактировано, чтобы добавить:
Вы можете упростить процесс, полагаясь только на то, что эффективным идентификатором пользователя является root, поскольку исполняемый файл имеет setuid root. В этом случае вам также не нужно беспокоиться о дополнительных группах, поскольку setuid root влияет только на эффективный идентификатор пользователя и ни на что другое. Возвращаясь к исходному реальному пользователю, технически вам нужен только один вызов setresuid()
в конце процедуры (и setresgid()
, если исполняемый файл также помечен как setgid root), чтобы установить как сохраненные, так и действующие идентификаторы пользователя (и группы). реальному пользователю.
Однако случай, когда вы восстанавливаете личность исходного пользователя, встречается редко, а случай, когда вы получаете личность именованного пользователя, встречается часто, и эта процедура здесь изначально была разработана для последнего. Вы должны использовать initgroups()
, чтобы получить правильные дополнительные группы для указанного пользователя и так далее. В этом случае важно заботиться о реальных, эффективных и сохраненных идентификаторах пользователей и групп, а также о дополнительных идентификаторах групп, поскольку в противном случае процесс унаследовал бы дополнительные группы от пользователя, выполнившего процесс.
Процедура здесь параноидальная, но паранойя — это неплохо, когда вы имеете дело с вопросами, чувствительными к безопасности. Для случая возврата к реальному пользователю это можно упростить.
Отредактировано 17 марта 2013 г., чтобы показать простую тестовую программу. Это предполагает, что установлен setuid root, но при этом будут удалены все привилегии и возможности (кроме CAP_SYS_NICE, который требуется для манипуляций с планировщиком сверх обычных правил). Я сократил «лишние» операции, которые предпочитаю выполнять, в надежде, что другим будет легче читать.
#define _GNU_SOURCE
#define _BSD_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <sched.h>
#include <stdio.h>
void test_priority(const char *const name, const int policy)
{
const pid_t me = getpid();
struct sched_param param;
param.sched_priority = sched_get_priority_max(policy);
printf("sched_get_priority_max(%s) = %d\n", name, param.sched_priority);
if (sched_setscheduler(me, policy, ¶m) == -1)
printf("sched_setscheduler(getpid(), %s, { %d }): %s.\n", name, param.sched_priority, strerror(errno));
else
printf("sched_setscheduler(getpid(), %s, { %d }): Ok.\n", name, param.sched_priority);
param.sched_priority = sched_get_priority_min(policy);
printf("sched_get_priority_min(%s) = %d\n", name, param.sched_priority);
if (sched_setscheduler(me, policy, ¶m) == -1)
printf("sched_setscheduler(getpid(), %s, { %d }): %s.\n", name, param.sched_priority, strerror(errno));
else
printf("sched_setscheduler(getpid(), %s, { %d }): Ok.\n", name, param.sched_priority);
}
int main(void)
{
uid_t user;
cap_value_t root_caps[2] = { CAP_SYS_NICE, CAP_SETUID };
cap_value_t user_caps[1] = { CAP_SYS_NICE };
cap_t capabilities;
/* Get real user ID. */
user = getuid();
/* Get full root privileges. Normally being effectively root
* (see man 7 credentials, User and Group Identifiers, for explanation
* for effective versus real identity) is enough, but some security
* modules restrict actions by processes that are only effectively root.
* To make sure we don't hit those problems, we switch to root fully. */
if (setresuid(0, 0, 0)) {
fprintf(stderr, "Cannot switch to root: %s.\n", strerror(errno));
return 1;
}
/* Create an empty set of capabilities. */
capabilities = cap_init();
/* Capabilities have three subsets:
* INHERITABLE: Capabilities permitted after an execv()
* EFFECTIVE: Currently effective capabilities
* PERMITTED: Limiting set for the two above.
* See man 7 capabilities for details, Thread Capability Sets.
*
* We need the following capabilities:
* CAP_SYS_NICE For nice(2), setpriority(2),
* sched_setscheduler(2), sched_setparam(2),
* sched_setaffinity(2), etc.
* CAP_SETUID For setuid(), setresuid()
* in the last two subsets. We do not need to retain any capabilities
* over an exec().
*/
if (cap_set_flag(capabilities, CAP_PERMITTED, sizeof root_caps / sizeof root_caps[0], root_caps, CAP_SET) ||
cap_set_flag(capabilities, CAP_EFFECTIVE, sizeof root_caps / sizeof root_caps[0], root_caps, CAP_SET)) {
fprintf(stderr, "Cannot manipulate capability data structure as root: %s.\n", strerror(errno));
return 1;
}
/* Above, we just manipulated the data structure describing the flags,
* not the capabilities themselves. So, set those capabilities now. */
if (cap_set_proc(capabilities)) {
fprintf(stderr, "Cannot set capabilities as root: %s.\n", strerror(errno));
return 1;
}
/* We wish to retain the capabilities across the identity change,
* so we need to tell the kernel. */
if (prctl(PR_SET_KEEPCAPS, 1L)) {
fprintf(stderr, "Cannot keep capabilities after dropping privileges: %s.\n", strerror(errno));
return 1;
}
/* Drop extra privileges (aside from capabilities) by switching
* to the original real user. */
if (setresuid(user, user, user)) {
fprintf(stderr, "Cannot drop root privileges: %s.\n", strerror(errno));
return 1;
}
/* We can still switch to a different user due to having the CAP_SETUID
* capability. Let's clear the capability set, except for the CAP_SYS_NICE
* in the permitted and effective sets. */
if (cap_clear(capabilities)) {
fprintf(stderr, "Cannot clear capability data structure: %s.\n", strerror(errno));
return 1;
}
if (cap_set_flag(capabilities, CAP_PERMITTED, sizeof user_caps / sizeof user_caps[0], user_caps, CAP_SET) ||
cap_set_flag(capabilities, CAP_EFFECTIVE, sizeof user_caps / sizeof user_caps[0], user_caps, CAP_SET)) {
fprintf(stderr, "Cannot manipulate capability data structure as user: %s.\n", strerror(errno));
return 1;
}
/* Apply modified capabilities. */
if (cap_set_proc(capabilities)) {
fprintf(stderr, "Cannot set capabilities as user: %s.\n", strerror(errno));
return 1;
}
/*
* Now we have just the normal user privileges,
* plus user_caps.
*/
test_priority("SCHED_OTHER", SCHED_OTHER);
test_priority("SCHED_BATCH", SCHED_BATCH);
test_priority("SCHED_IDLE", SCHED_IDLE);
test_priority("SCHED_FIFO", SCHED_FIFO);
test_priority("SCHED_RR", SCHED_RR);
return 0;
}
Обратите внимание: если вы знаете, что двоичный файл запускается только на относительно новых ядрах Linux, вы можете положиться на возможности файла. Тогда ваш main()
не нуждается ни в каких манипуляциях с идентификацией или возможностями — вы можете удалить все в main()
, кроме функций test_priority()
, — и вы просто даете своему двоичному файлу, скажем, ./testprio
, приоритет CAP_SYS_NICE:
sudo setcap 'cap_sys_nice=pe' ./testprio
Вы можете запустить getcap
, чтобы увидеть, какие приоритеты предоставляются при выполнении двоичного файла:
getcap ./testprio
который должен отображать
./testprio = cap_sys_nice+ep
Файловые возможности пока мало используются. В моей собственной системе только gnome-keyring-daemon
имеет файловые возможности (CAP_IPC_LOCK для блокировки памяти).
person
Nominal Animal
schedule
01.11.2012
seteuid(geteuid());
используйте явныйseteuid(0);
и используйтеseteuid()
во всем коде, кроме самого первого вызоваsetuid(0);
. - person   schedule 01.11.2012setuid(0);
, чтобы не получать ошибки, связанные с недостаточным уровнем привилегий... Вы не это пытаетесь исправить? Или я что-то упускаю? - person   schedule 01.11.2012