Linux的capability深入分析(1)

本文深入解析Linux内核2.1版引入的能力(capability)机制,包括概念介绍、进程能力的设定与清除方法、进程能力掩码获取等核心内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一)概述:


1)从2.1版开始,Linux内核有了能力(capability)的概念,即它打破了UNIX/LINUX操作系统中超级用户/普通用户的概念,由普通用户也可以做只有超级用户可以完成的工作.
2)capability可以作用在进程上(受限),也可以作用在程序文件上,它与sudo不同,sudo只针对用户/程序/文件的概述,即sudo可以配置某个用户可以执行某个命令,可以更改某个文件,而capability是让某个程序拥有某种能力,例如:
capability让/tmp/testkill程序可以kill掉其它进程,但它不能mount设备节点到目录,也不能重启系统,因为我们只指定了它kill的能力,即使程序有问题也不会超出能力范围.
3)每个进程有三个和能力有关的位图:inheritable(I),permitted(P)和effective(E),对应进程描述符task_struct(include/linux/sched.h)里面的cap_effective, cap_inheritable, cap_permitted,所以我们可以查看/proc/PID/status来查看进程的能力.
4)cap_effective:当一个进程要进行某个特权操作时,操作系统会检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0.
例如,如果一个进程要设置系统的时钟,Linux的内核就会检查cap_effective的CAP_SYS_TIME位(第25位)是否有效.
5)cap_permitted:表示进程能够使用的能力,在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集.
6)cap_inheritable:表示能够被当前进程执行的程序继承的能力.



二)capability的设定与清除

我们在下面的程序中给当前的进程设定能力,最后我们清除掉所设定的能力,源程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
 
#undef _POSIX_SOURCE
#include <sys/capability.h>
 
extern int errno;
  
void whoami(void)
{
	printf("uid=%i  euid=%i  gid=%i\n", getuid(), geteuid(), getgid());
}
 
void listCaps()
{
	cap_t caps = cap_get_proc();
	ssize_t y = 0;
	printf("The process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
	fflush(0);
	cap_free(caps);
}
  
int main(int argc, char **argv)
{
	int stat;
	whoami();
	stat = setuid(geteuid());
	pid_t parentPid = getpid();

	if(!parentPid)
	return 1;
	cap_t caps = cap_init();

	cap_value_t capList[5] ={ CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
	unsigned num_caps = 5;
	cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
	cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
	cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
 
	if (cap_set_proc(caps)) {
		perror("capset()");
 
		return EXIT_FAILURE;
	}
	listCaps();

	printf("dropping caps\n");
	cap_clear(caps);  // resetting caps storage

	if (cap_set_proc(caps)) {
		perror("capset()");
		return EXIT_FAILURE;
	}
	listCaps();

	cap_free(caps);
	return 0;
}

编译:
gcc capsettest.c -o capsettest -lcap

运行:
./capsettest 
uid=0  euid=0  gid=0
The process 2383 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
dropping caps
The process 2383 was give capabilities =

注:
1)我们对该进程增加了5种能力,随后又清除了所有能力.
2)首先通过cap_init()初始化存放cap能力值的状态,随后通过cap_set_flag函数的调用,将三种位图的能力设置给了变量caps,再通过cap_set_proc(caps)设定当前进程的能力值,通过cap_get_proc()返回当前进程的能力值,最后通过cap_free(caps)释放能力值.
3)cap_set_flag函数的原型是:
int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap,const cap_value_t *caps, cap_flag_value_t value);

我们这里的调用语句是:cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
第一个参数cap_p是存放能力值的变量,是被设定值.这里是caps.
第二个参数flag是是三种能力位图,这里是CAP_PERMITTED.
第三个参数ncap是要设定能力的个数,这里是num_caps,也就是5.
第四个参数*caps是要设定的能力值,这里是capList数组,也就是CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP.
第五个参数value是决定要设定还是清除,这里是CAP_SET.

4)cap_set_proc函数的原型是:int cap_set_proc(cap_t cap_p);
cap_set_proc函数通过cap_p中的能力值设定给当前的进程.

5)cap_get_proc函数的原型是:cap_t cap_get_proc(void);
cap_get_proc函数返回当前进程的能力值给cap变量.

6)cap_free函数的原型是:cap_free(caps);
cap_free函数清理/释放cap变量.

7)如果我们fork()了子进程,那么子进程继承父进程的所有能力.

8)不能单独设定CAP_EFFECTIVE,CAP_INHERITABLE位图,必须要和CAP_PERMITTED联用,且CAP_PERMITTED一定要是其它两个位图的超集.

9)如果两次调用cap_set_proc函数,第二次调用的值力值不能少于或多于第一次调用.如第一次我们授权chown,setuid能力,第二次只能是chown,setuid不能是其它的能力值.

10)普通用户不能给进程设定能力.


三)进程的能力掩码:
我们可以通过下面的程序获取当前进程的掩码,它是通过capget函数来获取指定进程的能力掩码,当然我们也可以用capset来设定掩码,下面获取掩码的体现:
#undef _POSIX_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/capability.h>
#include <errno.h>

int main()
{
	struct __user_cap_header_struct cap_header_data;
	cap_user_header_t cap_header = &cap_header_data;

	struct __user_cap_data_struct cap_data_data;
	cap_user_data_t cap_data = &cap_data_data;

	cap_header->pid = getpid();
	cap_header->version = _LINUX_CAPABILITY_VERSION_1;

	if (capget(cap_header, cap_data) < 0) {
		perror("Failed capget");
		exit(1);
	}
	printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,cap_data->permitted, cap_data->inheritable);
}

gcc capget0.c -o capget0 -lcap

普通用户:
./capget0 
Cap data 0x0, 0x0, 0x0

超级用户:
/home/test/capget0 
Cap data 0xffffffff, 0xffffffff, 0x0

这也说明了默认情况下,root运行的进程是什么权限都有,而普通用户则什么权限都没有.


我们可以将本程序与上面的程序进行整合,如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
 
#undef _POSIX_SOURCE
#include <sys/capability.h>
 
extern int errno;
  
void whoami(void)
{
	printf("uid=%i  euid=%i  gid=%i\n", getuid(), geteuid(), getgid());
}
 
void listCaps()
{
	cap_t caps = cap_get_proc();
	ssize_t y = 0;
	printf("The process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
	fflush(0);
	cap_free(caps);
}
 
 
int main(int argc, char **argv)
{
	int stat;
	whoami();
	stat = setuid(geteuid());
	pid_t parentPid = getpid();

	if(!parentPid)
	return 1;
	cap_t caps = cap_init();

	cap_value_t capList[5] ={ CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
	unsigned num_caps = 5;
	cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
	cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
	cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);

	if (cap_set_proc(caps)) {
		perror("capset()");
		return EXIT_FAILURE;
	}
	listCaps();

	cap_free(caps);

	struct __user_cap_header_struct cap_header_data;
	cap_user_header_t cap_header = &cap_header_data;

	struct __user_cap_data_struct cap_data_data;
	cap_user_data_t cap_data = &cap_data_data;

	cap_header->pid = getpid();
	cap_header->version = _LINUX_CAPABILITY_VERSION_1;

	if (capget(cap_header, cap_data) < 0) {
		perror("Failed capget");
		exit(1);
	}
	printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,cap_data->permitted, cap_data->inheritable);
	sleep(60);
	return 0;
}

编译并执行:
gcc capsettest.c -o capsettest -lcap

./capsettest
uid=0  euid=0  gid=0
The process 3101 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
Cap data 0x25c0, 0x25c0, 0x25c0

注:0x25c0=10 0101 1100 0000(二进制)
对映的能力如下:
cap_setgid=6(位)
cap_setuid=7(位)
cap_setpcap=8(位)
cap_net_bind_service=10(位)
cap_net_raw=13(位)

在程序sleep的时候,我们查看一下进程的status,如下:
cat /proc/`pgrep capsettest`/status

CapInh: 00000000000025c0
CapPrm: 00000000000025c0
CapEff: 00000000000025c0
CapBnd: ffffffffffffffff

我们看到进程的status也反映了它的能力状态.
CapBnd是系统的边界能力,我们无法改变它.
input.c是Linux内核中的一个驱动程序,它的主要作用是对输入设备进行管理和控制。在本文中,我们将对这个文件进行详细的分析和解释。 首先,我们需要了解input.c的结构和组成。该文件包含了一系列的函数和结构体,用于实现对输入设备的控制。其中最重要的结构体是input_dev,它表示一个输入设备的相关信息,包括设备名称、设备类型、事件处理函数等。另外,还有一些其他的结构体和宏定义,用于表示输入事件的不同类型和属性,比如EV_KEY、EV_ABS、BTN_LEFT等。 在input.c中,有很多的函数都是用来处理输入事件的。这些函数通常包括一个参数,表示输入设备的指针(即input_dev结构体的指针)。其中最重要的是input_event函数,用于生成和发送输入事件。该函数的实现比较简单,只需将事件的类型、代码和值写入设备的缓冲区中即可。 除了处理输入事件外,input.c还负责处理输入设备的注册和注销。当一个新的输入设备被添加时,内核需要调用input_register_device函数来注册该设备。该函数将会为该设备创建一个input_dev结构体,并将其加入到内核的输入设备列表中。当输入设备不再需要时,可以调用input_unregister_device函数将其注销。 此外,input.c还支持对输入设备的属性进行设置和获取。例如,可以使用input_set_capability函数设置设备的功能,或使用input_get_drvdata函数获取设备的私有数据。 总的来说,input.c是一个非常重要的驱动程序,它实现了对输入设备的完整管理和控制。通过对该文件的分析,我们可以更深入地了解Linux内核的输入子系统,并学习如何编写和调试驱动程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值