我正在尝试编写一个使用setuid位以root身份启动的守护程序,但随后会快速恢复为运行该进程的用户。但是,守护程序需要保留将新线程设置为“实时”优先级的能力。我用来设置优先级的代码如下(创建后在一个线程中运行):
struct sched_param sched_param;
memset(&sched_param, 0, sizeof(sched_param));
sched_param.sched_priority = 90;
if(-1 == sched_setscheduler(0, SCHED_FIFO, &sched_param)) {
// If we get here, we have an error, for example "Operation not permitted"
}
但是,我遇到问题的部分是设置uid,同时保留对sched_setscheduler
进行上述调用的功能。
我有一些代码在我的应用程序的主线程中运行接近启动:
if (getgid() != getegid() || getuid() != geteuid()) {
cap_value_t cap_values[] = {CAP_SYS_NICE};
cap_t caps;
caps = cap_get_proc();
cap_set_flag(caps, CAP_PERMITTED, 1, cap_values, CAP_SET);
cap_set_proc(caps);
prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
cap_free(caps);
setegid(getgid());
seteuid(getuid());
}
问题是在运行此代码后,在上面的评论中提到的sched_setscheduler
调用时,我得到“不允许操作”。我做错了什么?
答案 0 :(得分:24)
编辑描述原始失败的原因:
Linux中有三组功能:可继承,允许和有效。可继承定义exec()
中允许的功能。允许定义进程允许的功能。有效定义了当前有效的功能。
将进程的所有者或组从root更改为非root时,始终会清除有效的功能集。
默认情况下,也会清除允许的功能集,但在身份更改之前调用prctl(PR_SET_KEEPCAPS, 1L)
会告诉内核保持允许的设置不变。
在流程将身份更改回非特权用户后,必须将CAP_SYS_NICE
添加到有效集。 (它也必须在允许的集合中设置,所以如果你清除你的能力集,记得也设置它。如果你只是修改当前的能力集,那么你知道它已经设置,因为你继承了它。)
以下是我建议您遵循的程序:
保存真实用户ID,真实组ID和补充组ID:
#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++;
因为你无法清除&#34;补充组ID(只请求当前号码),确保列表永远不会为空。您始终可以将实际组ID添加到补充列表中,以使其非空。
if (gids < 1) {
gid[0] = group;
gids = 1;
}
将真实有效的用户ID切换为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 */
将实际,有效和已保存的组ID设置为最初保存的组ID
if (setresgid(group, group, group)) /* error */
设置补充组ID
if (setgroups(gids, gid)) /* error */
将真实,有效和已保存的用户ID设置为最初保存的用户ID
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
。
此时的能力&#39;不再需要描述符,所以释放它是一个好主意。
if (cap_free(capabilities)) /* error */
告诉内核你不希望保留对来自root的任何进一步更改的能力(再次,只是偏执狂)
if (prctl(PR_SET_KEEPCAPS, 0L)) /* error */
在安装libcap-dev
软件包后,在x86-64上使用GCC-4.6.3,libc6-2.15.0ubuntu10.3和linux-3.5.0-18内核在Xubuntu 12.04.1 LTS上运行。
编辑添加:
您可以仅依赖有效的用户ID为root来简化流程,因为可执行文件是setuid root。在这种情况下,您也不必担心补充组,因为setuid root只会影响有效的用户ID而不会影响其他任何内容。返回原始真实用户,从技术上讲,只需要在过程结束时调用setresuid()
(如果可执行文件也恰好标记为setgid root,则需要setresgid()
),以设置两者都保存和真实用户的有效用户(和组)ID。
但是,重新获得原始用户的情况&#39;身份是罕见的,并且您获得命名用户的身份的情况很常见,此处的此过程最初是为后者设计的。您可以使用initgroups()
为指定用户获取正确的补充组,依此类推。在这种情况下,仔细处理真实,有效和保存的用户和组ID以及补充组ID非常重要,否则该过程将从执行该过程的用户继承补充组。
这里的程序是偏执狂,但在处理安全敏感问题时,偏执并不是一件坏事。对于恢复到真实用户的情况,可以简化它。
于2013-03-17编辑,展示一个简单的测试程序。这假设它已安装setuid root,但它将删除所有特权和功能(CAP_SYS_NICE除外,这是调度程序操作高于正常规则所必需的)。我减少了多余的&#34;我喜欢做的操作,希望其他人觉得这更容易阅读。
#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()
个函数之外的所有内容 - 而且您只需提供二进制文件,例如{{1 ,CAP_SYS_NICE优先级:
./testprio
您可以运行sudo setcap 'cap_sys_nice=pe' ./testprio
以查看执行二进制文件时授予的优先级:
getcap
应该显示
getcap ./testprio
到目前为止,文件功能似乎很少使用。在我自己的系统上,./testprio = cap_sys_nice+ep
是唯一具有文件功能的(CAP_IPC_LOCK,用于锁定内存)。
答案 1 :(得分:1)
我有一些代码在我的应用程序的主线程中运行接近启动:
您必须在要使用它们的每个线程中获取这些功能,或使用CAP_INHERITABLE
集。
Linux传统上划分权限 与超级用户关联成不同的单位,称为能力,其中 可以独立启用和禁用。 功能是每个线程 属性。强>