在保留CAP_SYS_NICE时删除根UID

时间:2022-01-04 05:02:00

I'm trying to write a daemon that will start as root using a setuid bit, but then quickly revert to the user running the process. The daemon, however needs to retain the ability to set new threads to "realtime" priority. The code that I'm using to set the priority is as follows (runs in a thread once it is created):

我正在尝试编写一个守护进程,它将以一个setuid位开始作为根,然后快速恢复到运行该进程的用户。然而,守护进程需要保留将新线程设置为“实时”优先级的能力。我用来设置优先级的代码如下(创建后在线程中运行):

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"
}

However the part I'm having problems with is setting the uid, while retaining the ability to make the above call to sched_setscheduler.

但是,我遇到的问题是设置uid,同时保留调用sched_setscheduler的能力。

I have some code that runs close to startup in the main thread of my application:

在我的应用程序的主线程中,我有一些接近启动的代码:

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());
}

The problem is that after running this code, I get "Operation not permitted" when calling sched_setscheduler as alluded to in the comment above. What am I doing wrong?

问题是,在运行了这段代码之后,我在调用sched_setscheduler时得到了“不允许的操作”。我做错了什么?

2 个解决方案

#1


23  

Edited to describe the reason for the original failure:

编辑以描述最初失败的原因:

There are three sets of capabilities in Linux: inheritable, permitted, and effective. Inheritable defines which capabilities stay permitted across an exec(). Permitted defines which capabilities are permitted for a process. Effective defines which capabilities are currently in effect.

Linux中有三组功能:可继承的、允许的和有效的。可继承定义了在exec()中允许哪些功能。允许定义流程允许哪些功能。有效定义了当前生效的功能。

When changing the owner or group of a process from root to non-root, the effective capability set is always cleared.

当将进程的所有者或组从根更改为非根时,通常会清除有效的能力集。

By default, also the permitted capability set is cleared, but calling prctl(PR_SET_KEEPCAPS, 1L) before the identity change tells the kernel to keep the permitted set intact.

默认情况下,还可以清除允许的功能集,但是在标识更改通知内核保持允许的设置不变之前,调用prctl(PR_SET_KEEPCAPS, 1L)。

After the process has changed the identity back to the unprivileged user, the CAP_SYS_NICE must be added to the effective set. (It must also be set in the permitted set, so if you clear your capability set, remember to set it also. If you just modify the current capability set, then you know it is already set because you inherited it.)

在进程将标识更改回无特权用户之后,必须将CAP_SYS_NICE添加到有效集(它也必须在允许集中设置,因此如果您清除了功能集,请记住也设置它。如果您只是修改了当前的功能集,那么您就知道它已经设置好了,因为您继承了它。

Here is the procedure I recommend you should follow:

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

  1. Save real user ID, real group ID, and supplemental group IDs:

    保存真实用户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 */
    
  2. Filter out unnecessary and privileged supplementary groups (be paranoid!)

    过滤掉不必要的和有特权的附加组(多疑!)

     n = 0;
     while (n < gids)
         if (gid[n] == 0 || gid[n] == group)
             gid[n] = gid[--gids];
         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.

    因为您无法“清除”补充组id(只请求当前编号),所以请确保列表永不为空。您总是可以将真正的组ID添加到补充列表中,使其非空的。

     if (gids < 1) {
         gid[0] = group;
         gids = 1;
     }
    
  3. Switch real and effective user IDs to root

    将真正有效的用户id切换到root

     if (setresuid(0, 0, 0)) /* error */
    
  4. 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_allowed设置中设置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 */
    
  5. 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 */
    
  6. Set real, effective, and saved group IDs to the initially saved group ID

    将实际的、有效的和已保存的组ID设置为最初保存的组ID

     if (setresgid(group, group, group)) /* error */
    
  7. Set supplemental group IDs

    设置补充组id

     if (setgroups(gids, gid)) /* error */
    
  8. Set real, effective and saved user IDs to the initially saved user ID

    将真实、有效和保存的用户ID设置为最初保存的用户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.

    在这一点上,除了CAP_SYS_NICE功能之外,您还可以有效地删除根特权(不需要重新获得它们)。由于从根用户到非根用户的转换,该功能永远不会有效;内核将始终清除在这种转换上设置的有效能力。

  9. Set the CAP_SYS_NICE capability in the CAP_PERMITTED and CAP_EFFECTIVE set

    将CAP_SYS_NICE功能设置为cap_enable和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 */
    

    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.

    注意,后两个cap_set_flag()操作清除了不再需要的三个功能,因此只保留第一个功能,即CAP_SYS_NICE。

    At this point the capabilities' descriptor is no longer needed, so it's a good idea to free it.

    此时不再需要功能的描述符,所以释放它是个好主意。

     if (cap_free(capabilities)) /* error */
    
  10. 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 */
    

This works on x86-64 using GCC-4.6.3, libc6-2.15.0ubuntu10.3, and linux-3.5.0-18 kernel on Xubuntu 12.04.1 LTS, after installing the libcap-dev package.

这在Xubuntu 12.04.1 LTS上使用了GCC-4.6.3、libc6-2.15.0ubuntu10.3和linux-3.5.0-18内核,安装了libcap-dev包。

Edited to add:

编辑添加:

You can simplify the process by relying only on the effective user ID being root, as the executable is setuid root. In that case, you don't need to worry about the supplementary groups either, as the setuid root only affects the effective user ID and nothing else. Returning back to the original real user, you technically only need the one setresuid() call at the end of the procedure (and the setresgid() if the executable also happens to be marked setgid root), to set both saved and effective user (and group) IDs to the real user.

您可以通过只依赖有效的用户ID作为根来简化过程,因为可执行文件是setuid根。在这种情况下,您不必担心补充组,因为setuid根只会影响有效的用户ID,而不会影响其他的。返回到原始的实际用户,您只需要在过程的末尾调用一个setresuid()调用(如果可执行文件也恰好被标记为setgid root),那么就需要将保存的和有效的用户(和组)id设置为真正的用户。

However, the case where you regain the original users' identity is rare, and the case where you gain the identity of a named user is common, and this procedure here was originally designed for the latter. You would use initgroups() to gain the correct supplementary groups for the named user, and so on. In that case, taking care of the real, effective, and saved user and group IDs and supplementary group IDs this carefully is important, as otherwise the process would inherit supplementary groups from the user that executed the process.

但是,您重新获得原始用户身份的情况很少见,您获得指定用户的身份的情况很常见,而这里的这个过程最初是为后者设计的。您将使用initgroups()为指定的用户获取正确的补充组,等等。在这种情况下,仔细地处理实际的、有效的和保存的用户和组id和补充组id是很重要的,否则这个过程将从执行这个过程的用户那里继承补充组。

The procedure here is paranoid, but paranoia is not a bad thing when you are dealing with security-sensitive issues. For the revert-back-to-real-user case, it can be simplified.

这里的程序是偏执狂的,但是当你在处理安全敏感的问题时,偏执狂并不是一件坏事。对于返回真实用户的情况,可以进行简化。


Edited on 2013-03-17 to show a simple test program. This assumes it is installed setuid root, but it will drop all privileges and capabilities (except CAP_SYS_NICE, which is required for scheduler manipulation above the normal rules). I pared down the "excess" operations I prefer to do, in the hopes that others find this easier to read.

2013-03-17编辑,显示一个简单的测试程序。这假定它已经安装了setuid根,但是它将删除所有特权和功能(除了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;
}

Note that if you know the binary is only run on relatively recent Linux kernels, you can rely on file capabilities. Then, your main() needs none of the identity or capability manipulation -- you can remove everything in main() except the test_priority() functions --, and you just give your binary, say ./testprio, the CAP_SYS_NICE priority:

注意,如果您知道二进制文件只在相对较新的Linux内核上运行,那么您可以依赖于文件功能。然后,您的main()不需要任何标识或功能操作——除了test_priority()函数之外,您可以在main()中删除所有内容——然后您只需给您的二进制文件,例如./testprio, CAP_SYS_NICE优先级:

sudo setcap 'cap_sys_nice=pe' ./testprio

You can run getcap to see which priorities are granted when a binary is executed:

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

getcap ./testprio

which should display

它应该显示

./testprio = cap_sys_nice+ep

File capabilities seem to be little used thus far. On my own system, gnome-keyring-daemon is the only one with file capabilities (CAP_IPC_LOCK, for locking memory).

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

#2


1  

I have some code that runs close to startup in the main thread of my application:

在我的应用程序的主线程中,我有一些接近启动的代码:

You must acquire these capabilities in each thread in which you want to use them, or use the CAP_INHERITABLE set.

您必须在您希望使用它们的每个线程中获得这些功能,或者使用cap_继承性集合。

From capabilities(7):

从功能(7):

Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.

Linux将传统上与超级用户相关的特权划分为不同的单元(称为功能),这些单元可以独立地启用和禁用。功能是每个线程的属性。

#1


23  

Edited to describe the reason for the original failure:

编辑以描述最初失败的原因:

There are three sets of capabilities in Linux: inheritable, permitted, and effective. Inheritable defines which capabilities stay permitted across an exec(). Permitted defines which capabilities are permitted for a process. Effective defines which capabilities are currently in effect.

Linux中有三组功能:可继承的、允许的和有效的。可继承定义了在exec()中允许哪些功能。允许定义流程允许哪些功能。有效定义了当前生效的功能。

When changing the owner or group of a process from root to non-root, the effective capability set is always cleared.

当将进程的所有者或组从根更改为非根时,通常会清除有效的能力集。

By default, also the permitted capability set is cleared, but calling prctl(PR_SET_KEEPCAPS, 1L) before the identity change tells the kernel to keep the permitted set intact.

默认情况下,还可以清除允许的功能集,但是在标识更改通知内核保持允许的设置不变之前,调用prctl(PR_SET_KEEPCAPS, 1L)。

After the process has changed the identity back to the unprivileged user, the CAP_SYS_NICE must be added to the effective set. (It must also be set in the permitted set, so if you clear your capability set, remember to set it also. If you just modify the current capability set, then you know it is already set because you inherited it.)

在进程将标识更改回无特权用户之后,必须将CAP_SYS_NICE添加到有效集(它也必须在允许集中设置,因此如果您清除了功能集,请记住也设置它。如果您只是修改了当前的功能集,那么您就知道它已经设置好了,因为您继承了它。

Here is the procedure I recommend you should follow:

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

  1. Save real user ID, real group ID, and supplemental group IDs:

    保存真实用户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 */
    
  2. Filter out unnecessary and privileged supplementary groups (be paranoid!)

    过滤掉不必要的和有特权的附加组(多疑!)

     n = 0;
     while (n < gids)
         if (gid[n] == 0 || gid[n] == group)
             gid[n] = gid[--gids];
         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.

    因为您无法“清除”补充组id(只请求当前编号),所以请确保列表永不为空。您总是可以将真正的组ID添加到补充列表中,使其非空的。

     if (gids < 1) {
         gid[0] = group;
         gids = 1;
     }
    
  3. Switch real and effective user IDs to root

    将真正有效的用户id切换到root

     if (setresuid(0, 0, 0)) /* error */
    
  4. 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_allowed设置中设置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 */
    
  5. 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 */
    
  6. Set real, effective, and saved group IDs to the initially saved group ID

    将实际的、有效的和已保存的组ID设置为最初保存的组ID

     if (setresgid(group, group, group)) /* error */
    
  7. Set supplemental group IDs

    设置补充组id

     if (setgroups(gids, gid)) /* error */
    
  8. Set real, effective and saved user IDs to the initially saved user ID

    将真实、有效和保存的用户ID设置为最初保存的用户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.

    在这一点上,除了CAP_SYS_NICE功能之外,您还可以有效地删除根特权(不需要重新获得它们)。由于从根用户到非根用户的转换,该功能永远不会有效;内核将始终清除在这种转换上设置的有效能力。

  9. Set the CAP_SYS_NICE capability in the CAP_PERMITTED and CAP_EFFECTIVE set

    将CAP_SYS_NICE功能设置为cap_enable和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 */
    

    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.

    注意,后两个cap_set_flag()操作清除了不再需要的三个功能,因此只保留第一个功能,即CAP_SYS_NICE。

    At this point the capabilities' descriptor is no longer needed, so it's a good idea to free it.

    此时不再需要功能的描述符,所以释放它是个好主意。

     if (cap_free(capabilities)) /* error */
    
  10. 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 */
    

This works on x86-64 using GCC-4.6.3, libc6-2.15.0ubuntu10.3, and linux-3.5.0-18 kernel on Xubuntu 12.04.1 LTS, after installing the libcap-dev package.

这在Xubuntu 12.04.1 LTS上使用了GCC-4.6.3、libc6-2.15.0ubuntu10.3和linux-3.5.0-18内核,安装了libcap-dev包。

Edited to add:

编辑添加:

You can simplify the process by relying only on the effective user ID being root, as the executable is setuid root. In that case, you don't need to worry about the supplementary groups either, as the setuid root only affects the effective user ID and nothing else. Returning back to the original real user, you technically only need the one setresuid() call at the end of the procedure (and the setresgid() if the executable also happens to be marked setgid root), to set both saved and effective user (and group) IDs to the real user.

您可以通过只依赖有效的用户ID作为根来简化过程,因为可执行文件是setuid根。在这种情况下,您不必担心补充组,因为setuid根只会影响有效的用户ID,而不会影响其他的。返回到原始的实际用户,您只需要在过程的末尾调用一个setresuid()调用(如果可执行文件也恰好被标记为setgid root),那么就需要将保存的和有效的用户(和组)id设置为真正的用户。

However, the case where you regain the original users' identity is rare, and the case where you gain the identity of a named user is common, and this procedure here was originally designed for the latter. You would use initgroups() to gain the correct supplementary groups for the named user, and so on. In that case, taking care of the real, effective, and saved user and group IDs and supplementary group IDs this carefully is important, as otherwise the process would inherit supplementary groups from the user that executed the process.

但是,您重新获得原始用户身份的情况很少见,您获得指定用户的身份的情况很常见,而这里的这个过程最初是为后者设计的。您将使用initgroups()为指定的用户获取正确的补充组,等等。在这种情况下,仔细地处理实际的、有效的和保存的用户和组id和补充组id是很重要的,否则这个过程将从执行这个过程的用户那里继承补充组。

The procedure here is paranoid, but paranoia is not a bad thing when you are dealing with security-sensitive issues. For the revert-back-to-real-user case, it can be simplified.

这里的程序是偏执狂的,但是当你在处理安全敏感的问题时,偏执狂并不是一件坏事。对于返回真实用户的情况,可以进行简化。


Edited on 2013-03-17 to show a simple test program. This assumes it is installed setuid root, but it will drop all privileges and capabilities (except CAP_SYS_NICE, which is required for scheduler manipulation above the normal rules). I pared down the "excess" operations I prefer to do, in the hopes that others find this easier to read.

2013-03-17编辑,显示一个简单的测试程序。这假定它已经安装了setuid根,但是它将删除所有特权和功能(除了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;
}

Note that if you know the binary is only run on relatively recent Linux kernels, you can rely on file capabilities. Then, your main() needs none of the identity or capability manipulation -- you can remove everything in main() except the test_priority() functions --, and you just give your binary, say ./testprio, the CAP_SYS_NICE priority:

注意,如果您知道二进制文件只在相对较新的Linux内核上运行,那么您可以依赖于文件功能。然后,您的main()不需要任何标识或功能操作——除了test_priority()函数之外,您可以在main()中删除所有内容——然后您只需给您的二进制文件,例如./testprio, CAP_SYS_NICE优先级:

sudo setcap 'cap_sys_nice=pe' ./testprio

You can run getcap to see which priorities are granted when a binary is executed:

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

getcap ./testprio

which should display

它应该显示

./testprio = cap_sys_nice+ep

File capabilities seem to be little used thus far. On my own system, gnome-keyring-daemon is the only one with file capabilities (CAP_IPC_LOCK, for locking memory).

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

#2


1  

I have some code that runs close to startup in the main thread of my application:

在我的应用程序的主线程中,我有一些接近启动的代码:

You must acquire these capabilities in each thread in which you want to use them, or use the CAP_INHERITABLE set.

您必须在您希望使用它们的每个线程中获得这些功能,或者使用cap_继承性集合。

From capabilities(7):

从功能(7):

Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.

Linux将传统上与超级用户相关的特权划分为不同的单元(称为功能),这些单元可以独立地启用和禁用。功能是每个线程的属性。