介绍-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权限集。
capabilites | process capabilities | permitted |
effective | ||
inheritable | ||
bounding | ||
ambient | ||
file capabilities | permitted | |
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官方说明可参考