在保留CAP_SYS_NICE的同时删除根UID

bro*_*s94 10 c linux pthreads scheduler linux-capabilities

我正在尝试编写一个守护进程,它将使用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"
}
Run Code Online (Sandbox Code Playgroud)

但是,我遇到问题的部分是设置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());
}
Run Code Online (Sandbox Code Playgroud)

问题是在运行此代码后,我sched_setscheduler在上面的评论中提到了"不允许操作" .我究竟做错了什么?

Nom*_*mal 24

编辑描述原始失败的原因:

Linux中有三组功能:可继承,允许和有效.可继承定义允许跨越的功能exec().允许定义进程允许的功能.有效定义了当前有效的功能.

将进程的所有者或组从root更改为非root时,始终会清除有效的功能集.

默认情况下,也会清除允许的功能集,但prctl(PR_SET_KEEPCAPS, 1L)在身份更改之前调用会告诉内核保持允许的集不变.

在该过程将身份更改回非特权用户后,CAP_SYS_NICE必须将其添加到有效集.(它也必须在允许的集合中设置,所以如果你清除你的能力集,记住也要设置它.如果你只修改当前的功能集,那么你知道它已经设置,因为你继承了它.)

以下是我建议您遵循的程序:

  1. 保存真实用户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 */
    
    Run Code Online (Sandbox Code Playgroud)
  2. 过滤掉不必要的和特权的补充组(偏执!)

     n = 0;
     while (n < gids)
         if (gid[n] == 0 || gid[n] == group)
             gid[n] = gid[--gids];
         else
             n++;
    
    Run Code Online (Sandbox Code Playgroud)

    因为您无法"清除"补充组ID(仅请求当前号码),请确保列表永远不会为空.您始终可以将实际组ID添加到补充列表中,以使其为非空.

     if (gids < 1) {
         gid[0] = group;
         gids = 1;
     }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将真实有效的用户ID切换为root

     if (setresuid(0, 0, 0)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)
  4. CAP_SYS_NICECAP_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 */
    
    Run Code Online (Sandbox Code Playgroud)
  5. 告诉内核您希望保留从root到非特权用户的更改的功能; 默认情况下,从root用户身份更改为非root用户身份时,功能将清除为零

     if (prctl(PR_SET_KEEPCAPS, 1L)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)
  6. 将实际,有效和已保存的组ID设置为最初保存的组ID

     if (setresgid(group, group, group)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)
  7. 设置补充组ID

     if (setgroups(gids, gid)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)
  8. 将真实,有效和已保存的用户ID设置为最初保存的用户ID

     if (setresuid(user, user, user)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)

    此时,除了CAP_SYS_NICE功能外,您还可以有效地删除root权限(无法再获取它们).由于从root用户到非root用户的转换,该功能永远不会有效; 内核将始终清除在此类转换上设置的有效功能.

  9. 设置CAP_SYS_NICE性能在CAP_PERMITTEDCAP_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 */
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,后两个cap_set_flag()操作清除了不再需要的三个功能,因此只剩下第一个功能CAP_SYS_NICE.

    此时不再需要功能描述符,因此释放它是个好主意.

     if (cap_free(capabilities)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)
  10. 告诉内核你不希望保留来自root的任何进一步更改的能力(再次,只是偏执狂)

     if (prctl(PR_SET_KEEPCAPS, 0L)) /* error */
    
    Run Code Online (Sandbox Code Playgroud)

在安装libcap-dev软件包之后,这适用于x86-64,在Xubuntu 12.04.1 LTS上使用GCC-4.6.3,libc6-2.15.0ubuntu10.3和linux-3.5.0-18内核.

编辑添加:

您可以仅依赖有效的用户ID为root来简化流程,因为可执行文件是setuid root.在这种情况下,您也不需要担心补充组,因为setuid root只会影响有效的用户ID而不会影响其他任何内容.返回原始真实用户,从技术上讲,只需要在setresuid()过程结束时进行一次调用(setresgid()如果可执行文件也恰好标记为setgid root),则将已保存和有效的用户(和组)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, &param) == -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, &param) == -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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您知道二进制文件仅在相对较新的Linux内核上运行,则可以依赖文件功能.然后,您main()不需要身份或功能操作 - 您可以删除main()test_priority()函数之外的所有内容- 并且您只需提供二进制文件,例如./testprioCAP_SYS_NICE优先级:

sudo setcap 'cap_sys_nice=pe' ./testprio
Run Code Online (Sandbox Code Playgroud)

您可以运行getcap以查看执行二进制文件时授予的优先级:

getcap ./testprio
Run Code Online (Sandbox Code Playgroud)

哪个应该显示

./testprio = cap_sys_nice+ep
Run Code Online (Sandbox Code Playgroud)

到目前为止,文件功能似乎很少使用.在我自己的系统上,gnome-keyring-daemon是唯一一个具有文件功能的系统(CAP_IPC_LOCK,用于锁定内存).