介绍-Linux capability机制

1. 权限

1)传统的Unix系统,从权限的角度可以把用户分为特权用户root(privileged user)和普通用户(unprivileged user)。通过这两种用户执行的进程,就是对应的privileged processes和unprivileged processes。

2)root进程拥有所有的权限。而普通用户进程则要进行权限的检查,这种检查一般是基于文件的UID、GID进行检查的。例如,普通用户进程要向串口ttyS0写一个字符,那么该进程就要write一个字符到文件(设备节点)/dev/ttyS0,此时,kernel就会检查进程的UID、GID是什么,文件ttyS0的User读写权限、Group读写权限和Other读写权限,匹配上以后才允许普通进程写数据到串口。

3)以上是一种基于文件读写执行(rwx)权限的机制,但有很多权限是不需要操作文件的,例如给某个进程发送信号、创建socket并绑定某个网卡等等,因此,从Linux 2.2开始引入了capability权限机制。capability机制相比文件权限检查更完善、更灵活。

4)capability把权限进行了拆分,可以把部分权限赋予给普通用户进程,而不需要切换到root。

2. capability

1)capability具体的权限,可以命令行查看

    man capabilities

目前较新的kernel支持最多41种权限。

2)capability权限分类

Linux capabilities 分为进程 capabilities 和文件 capabilities。

  • 进程 capabilities

对于进程来说,capabilities 是细分到线程的,即每个线程可以有自己的capabilities。

  • 文件capabilities

对于文件(可执行文件)来说,capabilities 保存在文件的扩展属性security.capability中。

3)capability权限集

在进程capabilities下,又把权限分成了5个权限集合,分别是:

  • Permitted

This is a limiting superset for the effective capabilities that the thread may assume. It is also a limiting superset for the capabilities that may be added to the inheritable set by a thread that does not have the CAP_SETPCAP capability in its effective set.

解析:permitted权限集就是进程所拥有的权限的集合。

  • Inheritable

This is a set of capabilities preserved across an execve(2). Inheritable capabilities remain inheritable when executing any program, and inheritable capabilities are added to the permitted set when executing a program that has the corresponding bits set in the file inheritable set.

解析:inheritable权限集就是可继承权限集,当通过execv系列函数执行一个新的程序时,新程序会继承inheritable权限集。

  • Effective

This is the set of capabilities used by the kernel to perform permission checks for the thread.

解析:effective权限集就是有效权限集,kernel最终检查进程是否有权限执行某个操作,检查的就是这个权限集里面的值。只要这个权限集里面没有这个权限,即使其他权限集里面有,kernel也不允许执行该操作。与permitted权限集的区别,permitted权限集是所拥有的权限,即使拥有某个权限,但也可以不使能(effective)这个权限。就好像你会降龙十八掌,但你却不使出来一样。

  • Bounding (per-thread since Linux 2.6.25)

The capability bounding set is a mechanism that can be used to limit the capabilities that are gained during execve(2).

解析:Bounding是一套机制,在执行execv系列函数执行一个新的程序时,bounding可以限制新程序获得的权限。

  • Ambient (since Linux 4.3)

This is a set of capabilities that are preserved across an execve(2) of a program that is not privileged.  The ambient capability set obeys the invariant that no capability can ever be ambient if it is not both permitted and inheritable.

解析:当非特权程序A调用execv系列函数执行新的程序B时,程序A的ambient权限集就会被进程B所继承。同时,ambient权限集有以下特性:

a)如果permitted或者inheritable中的某个权限P被清空,在ambient中该权限P也自动被清空;

b)当程序调用setuid改变UID或者setgid改变GID时,ambient中的所有权限会被清空;

c)当调用execv系列函数执行一个bin程序时,如果该bin文件曾经被设置了任何的文件capabilities(非进程capabilities),ambient中的所有权限会被清空;

d)当调用execv系列函数执行一个bin程序时,如果该bin文件没有设置过任何的文件capabilities(非进程capabilities),ambient中的所有权限就会被赋予给新程序的permited和effective权限集。

capabilitesprocess capabilitiespermitted
effective
inheritable
bounding
ambient
file capabilitiespermitted
effective
inheritable

3. 新进程capability的计算

1)fork

当程序执行fork()创建子进程时,子进程全部继承父进程的capabilities,包括permittd、effective、inheritable、imabient 4个权限集。

2)execv系列函数

当程序执行execv系列函数运行新的程序时,新程序的权限会被系统自动修改,这个修改过程是遵循一套公式的。

  • 为方便描述,定义以下表示方式:

P         表示进程在执行execv系列函数前,该进程的权限,例如P(permitted),表示该进程中permitted权限集中所有的权限。

P'         表示进程在执行execv系列函数后,该进程的权限,例如P'(permitted),表示该进程中permitted权限集中所有的权限。

F         表示文件的权限,例如F(permitted),表示该文件中permitted权限集中所有的权限。

cap_bset         表示bounding权限集中所有的权限。

  • 计算公式

P'(ambient) = (file is privileged) ? 0 : P(ambient)

解析:如果是特权(root)进程,则执行execv()后新程序的ambient权限集被清空;如果是普通进程,则执行execv()后新程序的ambient权限集跟原来的P(ambient)一致,即P'(ambient) = P(ambient)。

P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient)

解析:执行execv()后新程序的permitted权限集是三部分结果“或”运算的结果。

对于普通进程而言,如果没有设置bin文件的权限集,那么想要新程序P'(permitted)具有某项权限,则需要在执行execv()前先设置好P(ambient)。

P'(effective) = F(effective) ? P'(permitted) : P'(ambient)

解析:如果bin文件的effective权限集具有某项权限A,那么新程序P'(effective)中的权限A就等于P'(permitted)中的权限A的值;如果bin文件的effective权限集不具有某项权限A,那么新程序P'(effective)中的权限A就等于P'(ambient)中的权限A的值。

对于普通进程而言,如果没有设置bin文件的权限集,那么想要新程序P'(effective)具有某项权限A,则需要在执行execv()前先设置好P(ambient)。

P'(inheritable) = P(inheritable)

解析:新程序的P'(inheritable)跟原来的P(inheritable) 一致,不会被kernel改变。

4. 普通进程获取某项权限A

首先,所谓的获取某项权限A,其实就是进程的effective权限集中,某项权限A被置位。其次,由于普通进程不能无中生有地给自己设置权限,并且所有的新进程都是通过fork和execv所运行起来的,所以普通进程要获取权限A,必须是由

  • 方法一

root进程对bin文件设置好各个文件权限集,那么bin文件被运行起来后,就能具有相应的权限集;

对于方法一,直接通过setcap命令,设置bin文件的F(effective)即可。

    sudo setcap cap_chown,cap_kill=e /path/to/bin/file
  • 方法二

root进程在执行setuid切换成普通进程之前,设置好自己的各个进程权限集,例子如下:

#include <sys/capability.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/prctl.h>
#include <string.h>


#define nitems(x) (sizeof(x) / sizeof(x[0]))

static const char *_cap_name[] = {
    "cap_chown",
    "cap_dac_override",
    "cap_dac_read_search",
    "cap_fowner",
    "cap_fsetid",
    "cap_kill",
    "cap_setgid",
    "cap_setuid",
    "cap_setpcap",
    "cap_linux_immutable",
    "cap_net_bind_service",
    "cap_net_broadcast",
    "cap_net_admin",
    "cap_net_raw",
    "cap_ipc_lock",
    "cap_ipc_owner",
    "cap_sys_module",
    "cap_sys_rawio",
    "cap_sys_chroot",
    "cap_sys_ptrace",
    "cap_sys_pacct",
    "cap_sys_admin",
    "cap_sys_boot",
    "cap_sys_nice",
    "cap_sys_resource",
    "cap_sys_time",
    "cap_sys_tty_config",
    "cap_mknod",
    "cap_lease",
    "cap_audit_write",
    "cap_audit_control",
    "cap_setfcap",
    "cap_mac_override",
    "cap_mac_admin",
    "cap_syslog",
    "cap_wake_alarm",
    "cap_block_suspend",
    "cap_audit_read",
    NULL
};
static const int _cap_size = nitems(_cap_name) - 1;

static void _cap_eip_dump(cap_t cap)
{
    cap_value_t cap_list[CAP_LAST_CAP+1];
    cap_flag_value_t cap_flags_value;
    /* temporary use for cap_get_flag calls */
    struct {
        const char *str;
        cap_flag_t flag;
    } const flags[3] = {
        {"EFFECTIVE", CAP_EFFECTIVE},
        {"PERMITTED", CAP_PERMITTED},
        {"INHERITABLE", CAP_INHERITABLE}
    };
    int i, j;

    /* dump them */
    for (i=0; i < _cap_size && i < CAP_LAST_CAP; i++) {
        cap_from_name(_cap_name[i], &cap_list[i]);
        printf("%-20s %d\t\t", cap_to_name(cap_list[i]), cap_list[i]);
        printf("flags: \t\t");
        for (j=0; j < nitems(flags); j++) {
            cap_get_flag(cap, cap_list[i], flags[j].flag, &cap_flags_value);
            printf(" %s %-4s ", flags[j].str, (cap_flags_value == CAP_SET) ? "OK" : "NOK");
        }
        printf("\n");
    }
    printf("\n");
}

static void cap_eip_dump(void)
{
    pid_t pid;
    cap_t cap;

    pid = getpid();
    cap = cap_get_pid(pid);
    if (cap == NULL) {
        perror("cap_get_pid");
        exit(-1);
    }

    /* dump them */
    printf("dump proc capabilities:\n");
    _cap_eip_dump(cap);

    cap_free(cap);
}


static int _cap_eip_set(const cap_flag_t *cap_flag, int cap_flag_size,
                    const cap_value_t *cap_list, int cap_list_size,
                    int set)
{
    cap_t cap;
    int i;

    for (i = 0; i < cap_flag_size; ++i)
    {
        if (cap_flag[i] != CAP_EFFECTIVE
                && cap_flag[i] != CAP_PERMITTED
                && cap_flag[i] != CAP_INHERITABLE)
        {
            perror("invalid argument");
            return -1;
        }
    }
    for (i = 0; i < cap_list_size; ++i)
    {
        if (cap_list[i] > CAP_LAST_CAP)
        {
            perror("invalid argument");
            return -1;
        }
    }

    //1. get cap
    cap = cap_get_proc();
    if (cap == NULL) {
        perror("cap_get_proc");
        return -1;
    }

    //2. set cap flag
    set = set ? CAP_SET : CAP_CLEAR;
    for (i = 0; i < cap_flag_size; ++i)
    {
        /* set CAP_EFFECTIVE/CAP_PERMITTED/CAP_INHERITABLE cap */
        if (cap_set_flag(cap, cap_flag[i], cap_list_size, cap_list, set) == -1) {
            perror("cap_set_flag CAP_KILL");
            cap_free(cap);
            return -1;
        }
    }

    //3. set cap to kernel
    if (cap_set_proc(cap) < 0) {
        perror("cap_set_proc fail");
        cap_free(cap);
        return -1;
    }

    //4. clean
    cap_free(cap);

    return 0;
}
static int cap_eip_set(const cap_flag_t *cap_flag, int cap_flag_size,
                    const cap_value_t *cap_list, int cap_list_size)
{
    return _cap_eip_set(cap_flag, cap_flag_size, cap_list, cap_list_size, 1);
}

static void cap_ambient_dump(void)
{
    cap_value_t cap_list[CAP_LAST_CAP+1];
    int value;
    int i;

    if (!CAP_AMBIENT_SUPPORTED()) {
        printf("kernel NOT support AMBIENT!\n");
        return;
    }

    printf("dump ambient capabilities:\n");

    /* dump them */
    for (i=0; i < _cap_size; i++) {
        cap_from_name(_cap_name[i], &cap_list[i]);
        value = cap_get_ambient(cap_list[i]);
        if (value < 0) {
            continue;
        }

        printf("%-20s %d\t\t", cap_to_name(cap_list[i]), cap_list[i]);
        printf("flags: \t\t");
        printf(" %s %-4s ", "AMBIENT", (value == CAP_SET) ? "OK" : "NOK");
        printf("\n");
    }
    printf("\n");
}

static int cap_ambient_set(const cap_value_t *cap_list, int cap_list_size)
{
    int i;

    for (i = 0; i < cap_list_size; ++i)
    {
        if (cap_list[i] > CAP_LAST_CAP)
        {
            perror("invalid argument");
            return -1;
        }
    }

    for (i = 0; i < cap_list_size; ++i)
    {
        if (cap_set_ambient(cap_list[i], CAP_SET) < 0) {
            perror("cap_set_ambient fail");
            return -1;
        }
    }

    return 0;
}


static int test_cap[] = {CAP_KILL};
static void child_runner(void *userp)
{
    char path[128] = {'\0'};
    char *arg[8] = {NULL};
    int ret;

    ret = readlink("/proc/self/exe", path, sizeof(path));
    if (ret) {
        //make compiler happy
    }

    arg[0] = path;
    arg[1] = "--test";
    arg[2] = userp;
    execv(path, arg);
}
static void child_handler(void *userp)
{
    /* 作为子进程被execv()所运行 */
    /* 验证execv()后进程是否还具有我们测试的CAP_KILL权限 */
    char *parent_pid = userp;
    printf("after execv(), I am child bin\r\n");
    cap_eip_dump();
    cap_ambient_dump();
    printf("========\n");

    sleep(1);
    kill(atoi(parent_pid), SIGINT);
}

int main(int argc, char *argv[])
{
    cap_value_t cap_list[CAP_LAST_CAP+1];
    cap_flag_t cap_flag[3];
    pid_t parent_pid;
    pid_t child_pid;

    if (argc > 2 && !strcmp(argv[1], "--test"))
    {
        child_handler(argv[2]);
        return 0;
    }

    parent_pid = getpid();
    child_pid = fork();
    if (child_pid < 0)
    {
        exit(-1);
    }
    else if (child_pid > 0)
    {
        //parent
        printf("I am parent, pid=%d, child pid=%d\n", parent_pid, child_pid);
        while (1)
        {
            sleep(1);
        }
    }
    else
    {
        //child
        int i;

        cap_eip_dump();
        cap_ambient_dump();
        printf("========\n");

        /* enable CAP_SETPCAP, or PR_SET_KEEPCAPS will fail */
        cap_flag[0] = CAP_EFFECTIVE;
        cap_flag[1] = CAP_PERMITTED;
        cap_flag[2] = CAP_INHERITABLE;
        memset(cap_list, 0, sizeof(cap_list));
        cap_list[0] = CAP_SETPCAP;
        cap_eip_set(cap_flag, 3, cap_list, 1);

        /* enable testing cap */
        /* 这里把我们想要测试的权限设置进去 */
        cap_flag[0] = CAP_EFFECTIVE;
        cap_flag[1] = CAP_PERMITTED;
        cap_flag[2] = CAP_INHERITABLE;
        memset(cap_list, 0, sizeof(cap_list));
        for (i = 0; i < nitems(test_cap); ++i) {
            cap_list[i] = test_cap[i];
        }
        cap_eip_set(cap_flag, 3, cap_list, nitems(test_cap));

        cap_eip_dump();
        cap_ambient_dump();
        printf("========\n");

        /*! keep caps after setuid */
        /* 必须设置PR_SET_KEEPCAPS, 否则调用setuid之后,所有权限会被清空 */
        prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);

        setuid(1000);

        printf("after setuid\n");
        cap_eip_dump();
        cap_ambient_dump();
        printf("========\n");

        /* 运行execv()前,必须把我们想要的权限设置到ambient权限集中 */
        /* 否则运行execv()后,无法获得该权限 */
        memset(cap_list, 0, sizeof(cap_list));
        for (i = 0; i < nitems(test_cap); ++i) {
            cap_list[i] = test_cap[i];
        }
        cap_ambient_set(cap_list, nitems(test_cap));

        printf("before execv()\n");
        cap_eip_dump();
        cap_ambient_dump();
        printf("========\n");

        {
            char arg[16];
            snprintf(arg, sizeof(arg), "%d", parent_pid);
            child_runner(arg);
        }
    }

    return 0;
}

Linux官方说明可参考

capabilities(7) - Linux manual page

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值