原文地址: http://rk700.github.io/2016/10/26/linux-capabilities/html
在传统的Linux的权限控制机制中,SUID(Set User ID on execution)是一个比较有意思的概念。例如,文件/bin/passwd
设置了SUID的flag,因此普通用户在运行passwd
命令时,便以passwd
的所属者,即root用户的身份运行,从而修改密码。linux
可是,这也带来了安全隐患。咱们运行SUID的命令时,一般只是须要使用一小部分特权,可是使用SUID,却能够拥有root用户的所有权限。因此,一旦SUID的文件存在漏洞,即可能被利用,以root身份执行其余操做。所以,SUID机制,在某种程度上增大了安全***面。android
为了缓解SUID带来的安全风险,设置SUID须要谨慎,并且这些二进制文件也会经过下降自身euid,并只在须要特权时再恢复为root,以此来缩小以root身份运行的窗口。git
而仔细反思,咱们即可以看到,SUID的问题,主要在于权限控制太粗糙。为了对root身份进行更加精细的控制,Linux增长了另外一种机制,即capabilities。本文,便将对这一机制进行介绍。github
Capabilities机制,是在Linux内核2.2以后引入的。它将root用户的权限细分为不一样的领域,能够分别启用或禁用。从而,在实际进行特权操做时,若是euid不是root,便会检查是否具备该特权操做所对应的capabilities,并以此为依据,决定是否能够执行特权操做。安全
例如,下表列出了一些常见的特权操做及其对应的capability:app
改变文件的所属者(chown()) | CAP_CHOWN |
向进程发送信号(kill(), signal()) | CAP_KILL |
改变进程的uid(setuid(), setreuid(), setresuid()等) | CAP_SETUID |
trace进程(ptrace()) | CAP_SYS_PTRACE |
设置系统时间(settimeofday(), stime()等) | CAP_SYS_TIME |
Capabilities是细分到线程的,即每一个线程能够有本身的capabilities。而完整的capabilities实现,除了对线程的capabilities具备如下相关功能:ide
进行特权操做时,检查该线程是否拥有该操做的capability函数
提供系统调用,用于获取或修改线程的capabilityui
还应该包含对于文件的capabilities的支持,即:
文件系统支持文件附加属性,使得可执行文件具备必定的capabilities,从而在运行时肯定其capabilities
对于文件capabilities的支持,直到内核2.6.24以后才完成。
如下,将分别介绍线程(进程)的capabilities和文件的capabilities。
每个线程,具备3个capabilities的集合,分别是:
Permitted
Inheritable
Effective
每一个集合中,能够包含零个或多个capabilities。这3种集合的具体含义以下:
Permitted
这个集合定义了线程所可以拥有的特权的上限。换句话说,若是某个capability不在Permitted集合中,那么该线程便不能进行这个capability所对应的特权操做。Permitted集合是Inheritable和Effective集合的的超集。
Inheritable
当执行exec()
系运行其余命令时,可以被新命令继承的capabilities,被包含在Inheritable集合中。
Effective
内核检查该线程是否能够进行特权操做时,检查的对象即是Effective集合。如以前所说,Permitted集合定义了上限。线程能够删除Effective集合中的某capability,随后在须要时,再从Permitted集合中恢复该capability,以此达到临时禁用capability的功能。
(Linux 4.3以后,增长了一种集合Ambient。详情可见相关manual)
文件的capabilities,是保存在文件的扩展属性中。修改这些扩展属性,须要具备CAP_SETFCAP
的capability。文件与线程的capabilities,共同决定了经过exec
运行该文件后的capabilities。
文件的capabilities功能,须要文件系统的支持。若是文件系统使用了nosuid
选项进行挂载,那么文件的capabilities将被忽略。
相似于线程的capabilities,文件的capabilities也包含了3个集合:
Permitted
这个集合中包含的capabilities,在文件被执行时,被加入其Permitted集合。
Inheritable
这个集合与线程的Inheritable集合的交集,是执行完exec
后实际继承的capabilities。
Effective
这仅仅是一个bit。若是设置开启,那么在运行exec
后,Permitted集合中新增的capabilities会自动出如今Effective集合中;不然不会出如今Effective集合中。对于一些旧的可执行文件,因为其不会调用capabilities相关函数设置自身的Effective集合,因此能够将该可执行文件的Effective bit开启,从而将Permitted集合中的capabilities自动添加到Effective集合中。
exec
后capabilities的变化上面介绍了线程和文件的capabilities,可能会以为有些抽象难懂。下面将使用具体的计算公式,来讲明执行exec
后capabilities是如何肯定的。
咱们使用P表明执行exec
前的capabilities,P’表明执行exec
后的capabilities,F表明exec
执行的文件的capabilities。那么:
P’(Permitted) = (P(Inheritable) & F(Inheritable)) | (F(Permitted) & cap_bset)
P’(Effective) = F(Effective) ? P’(Permitted) : 0
P’(Inheritable) = P(Inheritable)
其中的cap_bset是capability bounding set。经过与文件的Permitted集合计算交集,可进一步限制某些capabilities的获取,从而下降了风险。
而正如介绍文件的Effective bit时所说,文件能够将其Effective bit关闭。由此,在经过exec
执行该文件后,实际的Effective集合为空集。随后,在须要进行特权操做时,可再将Permitted集合中的capabilities加入Effective集合中。
系统调用capget(2)
和capset(2)
,可被用于获取和设置线程自身的capabilities。此外,也可使用libcap中提供的接口cap_get_proc(3)
和cap_set_proc(3)
。固然,Permitted集合默认是不能增长新的capabilities的,除非CAP_SETPCAP在Effective集合中。
若是要查看线程的capabilities,能够经过/proc/<PID>/task/<TID>/status
文件,三种集合分别对应于CapPrm, CapInh和CapEff。但这种的显示结果是数值,不适合人类阅读。为此,可以使用包libcap
中的命令getpcaps <PID>
获取该进程的主线程的capabilities。
相似的,若是要查看和设置文件的capabilities,可使用命令getcap
或者setcap
。
使用getcap
命令,咱们能够看到ping
文件的capabilities:
root@debian:~# getcap /bin/ping /bin/ping = cap_net_raw+ep
即该文件的capabilities,设置了Effective bit,并且Permitted集合中包含了CAP_NEW_RAW,从而能够发送raw packet。
另外一个例子,是Android系统中的run-as
命令。根据源码中的注释,run-as
具备CAP_SETUID和CAP_SETGID,从而能够设置自身的resuid和resgid。由此,能够达到run-as
的效果,即以特定uid运行命令。
DirtyCow漏洞在Android上的利用,即是修改run-as
文件的内容,并利用CAP_SETUID设置resuid为root,实现权限提高。
根据官方说明,Android 4.3以后,便再也不使用SUID/SGID,而是使用capabilities机制,以减小***面。
可是,虽然Android源码中有包含库libcap-ng的代码,但并未编译相关代码。咱们下载libcap-ng,并修改以下:
$ diff -u libcap-ng-0.7.8/src/cap-ng.c ~/src/libcap-ng-0.7.8/src/cap-ng.c --- libcap-ng-0.7.8/src/cap-ng.c 2016-10-27 10:09:12.000000000 +0800 +++ /Users/liuruikai756/src/libcap-ng-0.7.8/src/cap-ng.c 2016-07-24 23:47:34.000000000 +0800 @@ -25,9 +25,7 @@ #include <string.h> #include <stdarg.h> #include <stdio.h> -#if !defined(ANDROID) #include <stdio_ext.h> -#endif #include <stdlib.h> #include <sys/prctl.h> #include <pwd.h> @@ -278,9 +276,7 @@ f = fopen(buf, "re"); if (f == NULL) return -1; -#if !defined(ANDROID) __fsetlocking(f, FSETLOCKING_BYCALLER); -#endif while (fgets(buf, sizeof(buf), f)) { if (strncmp(buf, "CapB", 4)) continue;
随后,复制Android源码中的external/libcap-ng/config.h
和external/libcap-ng/Android.mk
。在config.h
中须要定义HAVE_SYS_XATTR_H
,并修改Android.mk
以编译filecap
。
随后,运行命令ndk-build
进行编译,并将相关文件push到设备:
$ ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_PLATFORM=android-19 $ adb -e push libs/x86/libcap-ng.so /system/lib/ $ adb -e push libs/x86/filecap /data/local/tmp/
运行/data/local/tmp/filecap
即可查看文件的capabilities了。
例如,查看以前提到的文件/system/bin/run-as
的capabilities以下:
root@generic_x86:/ # /data/local/tmp/filecap /system/bin p/filecap /system/bin/run-as < file capabilities /system/bin/run-as setgid, setuid
参考文献