编辑以描述原始故障的原因:
Linux 中有三组功能:可继承、可允许和有效。可继承定义了哪些功能在 exec()
期间保持被允许。可允许定义了一个进程可以使用哪些功能。有效定义了当前生效的功能。
将进程的所有者或组从 root 更改为非 root 时,有效能力集总是被清除。
默认情况下,也会清除可允许的能力集,但在标识更改之前调用 prctl(PR_SET_KEEPCAPS, 1L)
告诉内核保持可允许集合不变。
在进程将身份更改回未经特权的用户后,必须将 CAP_SYS_NICE
添加到有效集中。(它也必须在可允许的集合中设置,因此如果您清除了您的能力集,请记得也设置它。如果您只修改当前的能力集,则已经设置了它,因为您继承了它。)
以下是我建议您应该遵循的程序:
Save real user ID, real group ID, and supplemental group IDs:
#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)
gid = malloc((gids + 1) * sizeof *gid);
if (!gid)
gids = getgroups(gids, gid);
if (gids < 0)
Filter out unnecessary and privileged supplementary groups (be paranoid!)
n = 0;
while (n < gids)
if (gid[n] == 0 || gid[n] == group)
gid[n] = gid[
else
n++;
Because you cannot "clear" the supplementary group IDs (that just requests the current number), make sure the list is never empty. You can always add the real group ID to the supplementary list to make it non-empty.
if (gids < 1) {
gid[0] = group;
gids = 1;
}
Switch real and effective user IDs to root
if (setresuid(0, 0, 0)) /* error */
Set the CAP_SYS_NICE
capability in the CAP_PERMITTED
set.
I prefer to clear the entire set, and only keep the four capabilities that are required for this approach to work (and later on, drop all but 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))
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 4, capability, CAP_SET))
if (cap_set_flag(capabilities, CAP_PERMITTED, 4, capability, CAP_SET))
if (cap_set_proc(capabilities))
Tell the kernel you wish to retain the capabilities over the change from root to the unprivileged user; by default, the capabilities are cleared to zero when changing from root to non-root identity
if (prctl(PR_SET_KEEPCAPS, 1L)) /* error */
Set real, effective, and saved group IDs to the initially saved group ID
if (setresgid(group, group, group))
Set supplemental group IDs
if (setgroups(gids, gid)) /* error */
Set real, effective and saved user IDs to the initially saved user ID
if (setresuid(user, user, user)) /* error */
At this point you effectively drop root privileges (without the ability to gain them back anymore), except for the CAP_SYS_NICE
capability. Due to the transition from root to non-root user, the capability is never effective; the kernel will always clear the effective capability set on such a transition.
Set the CAP_SYS_NICE
capability in the CAP_PERMITTED
and CAP_EFFECTIVE
set
if (cap_clear(capabilities))
if (cap_set_flag(capabilities, CAP_PERMITTED, 1, capability, CAP_SET))
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 1, capability, CAP_SET))
if (cap_set_flag(capabilities, CAP_PERMITTED, 3, capability + 1, CAP_CLEAR))
if (cap_set_flag(capabilities, CAP_EFFECTIVE, 3, capability + 1, CAP_CLEAR))
if (cap_set_proc(capabilities))
Note that the latter two cap_set_flag()
operations clear the three capabilities no longer needed, so that only the first one, CAP_SYS_NICE
remains.
At this point the capabilities' descriptor is no longer needed, so it's a good idea to free it.
if (cap_free(capabilities)) /* error */
Tell the kernel you don't wish to retain the capability over any further changes from root (again, just paranoia)
if (prctl(PR_SET_KEEPCAPS, 0L)) /* error */
在使用GCC-4.6.3、libc6-2.15.0ubuntu10.3和linux-3.5.0-18内核的Xubuntu 12.04.1 LTS上,x86-64可行。在安装了libcap-dev
软件包后,这个过程可以运行。
编辑以添加:
您可以简化该过程,只需依靠有效用户ID为root,因为可执行文件是setuid root设置的。在这种情况下,您不需要担心附加组,因为setuid root仅影响有效用户ID而不影响其他任何内容。从技术上讲,回到原始的真实用户,您只需要在过程结束时进行一次 setresuid()
调用(如果可执行文件也恰好被标记为setgid root,则还需要进行setresgid()
调用),将保存和有效用户(和组)ID设置为真实用户。
但是,重新获得原始用户身份的情况很少见,而获得命名用户身份的情况很常见,此处的过程最初是为后者设计的。您将使用initgroups()
来获取命名用户的正确附加组等。在这种情况下,仔细处理真实、有效和保存的用户和组ID以及附加组ID非常重要,否则进程将从执行该进程的用户继承附加组。
这里的过程很谨慎,但当您处理安全敏感问题时,谨慎并不是一件坏事。对于恢复到真实用户的情况,可以简化它。
在2013-03-17进行编辑以显示一个简单的测试程序。假设已将其安装为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;
user = getuid();
if (setresuid(0, 0, 0)) {
fprintf(stderr, "Cannot switch to root: %s.\n", strerror(errno));
return 1;
}
capabilities = cap_init();
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;
}
if (cap_set_proc(capabilities)) {
fprintf(stderr, "Cannot set capabilities as root: %s.\n", strerror(errno));
return 1;
}
if (prctl(PR_SET_KEEPCAPS, 1L)) {
fprintf(stderr, "Cannot keep capabilities after dropping privileges: %s.\n", strerror(errno));
return 1;
}
if (setresuid(user, user, user)) {
fprintf(stderr, "Cannot drop root privileges: %s.\n", strerror(errno));
return 1;
}
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;
}
if (cap_set_proc(capabilities)) {
fprintf(stderr, "Cannot set capabilities as user: %s.\n", strerror(errno));
return 1;
}
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,用于锁定内存)。
seteuid(geteuid());
,而是使用显式的seteuid(0);
并在代码中始终使用seteuid()
,除了对setuid(0);
的第一次调用。 - user529758setuid(0);
重新获得root权限,以避免与权限不足相关的错误...这不就是你想要解决的问题吗?还是我漏掉了什么? - user529758