selinux 学习
ROOT 的本质是什么
所谓 root 的本质,就是 当前任务访问系统资源的能力。
在一般的实时操作系统中,这里的任务(task)指的就是线程,是计算机 CPU 对程序进行调度的最小单位。
如果我们要执行一条命令
cat /etc/passwd
,当前用户运行了可执行文件/bin/cat
,并且新生成一个子进程,在该进程中读取了/etc/passwd
文件。要看所执行的
cat /etc/passwd
是否能够成功,其实是看在内核中下面这些条件能否满足:
- 当前的线程是否可访问、执行
cat
可执行文件,是否可以创建新进程;- 新进程如何继承当前进程的属性,这决定了新线程是否可以访问
/etc/passwd
;
访问控制是什么
在计算机安全领域,访问控制表示操作系统对某个主体(subject)访问或者执行某种操作的约束,主体可以是线程或者进程,操作可以是访问文件、目录、TCP/UDP 端口、共享内存段、IO 设备等对象。这类约束可以抽象成两大类,一类可以由对象的属主对自己的访问者进行管理,称为自主访问控制(DAC);另外一类由操作系统统一管理,称为强制访问控制(MAC)。
DAC
DAC 即 Discretionary Access Control,这种权限管理机制的主体是用户,因为权限的控制是自主的,因此称为自主访问控制。
在没有使用 SELinux 的操作系统中,决定一个资源是否能被访问的因素是:某个资源是否拥有对应用户的权限(读、写、执行),只要访问这个资源的进程符合以上的条件就可以被访问,而最致命问题是,root 用户不受任何管制,系统上任何资源都可以无限制地访问。
DAC 有两种自主访问控制策略,分别是文件权限码和访问控制列表 ACL (Access Control List)。
文件权限码就是我们常说的9位权限码,分别表示当前用户(user/owner)、用户组(group) 和其他用户(other) 对应的 读、写、执行 (rwx) 访问权限,可以参考 chmod。实际上在 Linux 操作系统中在前面还增加了三位,分别是:
- S_ISUID (04000): SETUID 位,用于在 exeve 系统调用时设置进程的有效用户ID(effective user ID);
- S_ISGID (02000): SETGID 位,和 SETUID 类似,从父目录中继承;
- S_ISVTX (01000): sticky bit,即防删除位,防止其他用户删除公共文件,通常用于
/tmp
目录下;
通过文件权限码可以实现一定程度上的自主访问控制,但是对于多用户系统而言只能通过用户组去管理,无法控制某个文件可以让用户A访问而不让用户B访问。ACL 就是为了实现这个目标而出现的。例如,需要单独给某个用户添加文件的读权限如下:
1 | $ setfacl -m u:evilpan:r /etc/passwd |
具体命令可以参考 setfacl,值得一提的是,ACL需要内核和文件系统的支持。
MAC
MAC 即 Mandatory Access Control,这种权限管理机制的主体是进程,是用于将系统中的信息分密级和类进行管理,以保证每个用户只能访问到那些被标明可以由他访问的信息的一种访问约束机制。
通俗的来说,在强制访问控制下,用户(或其他主体)与文件((其他客体)都被标记了固定的安全属性(如安全级、访问权限等),在每次访问发生时,系统检测安全属性以便确定一个用户是否有权访问该文件。其中 SELinux 和 AppArmor 就是 Linux 中典型的强制访问控制实现。
在使用了 SELinux 的操作系统中,决定一个资源是否能被访问的因素除了某个资源是否拥有对应用户的权限(读、写、执行),还需要判断每一类进程是否拥有对某一类资源的访问权限,这样一来,即使进程是以 root 身份运行的,也需要判断这个进程的类型以及允许访问的资源类型才能决定是否允许访问某个资源。进程的活动空间也可以被压缩到最小。即使是以 root 身份运行的服务进程,一般也只能访问到它所需要的资源。即使程序出了漏洞,影响范围也只有在其允许访问的资源范围内。安全性大大增加。
UID
访问控制策略是根据用户和组去进行管理的。对于操作系统而言,为了方便管理,用户和组都分别对应数字 ID,即 UID 和 GID。
一般情况下 su 是一个设置了 SETUID 位的程序,并且 owner 是 root 用户。普通用户执行该程序只是上是对该文件执行了execve系统调用,也就是说,内核会根据 SETUID 位来调整当前进程的权限,这主要是通过有效用户ID去实现的,如果文件名所指的程序文件设置了 set-user-ID 位,并且底层文件系统没有以 nosuid 方式挂载(即挂载时没有使用 MS_NOSUID
标志),并且调用进程没有被 ptrace 调试,那么调用进程的有效用户 ID 将被更改为该程序文件所有者的用户 ID。
Linux中的用户ID分为 real user id 和 effective user id,前者用来表示进程的真实用户,后者用来表示当前所表示的有效用户。
在内核里的 task_struct 中有一个 struct cred 字段,该字段对应的结构就包含了当前任务的安全相关上下文信息,其中就有 uid
1 | struct cred { |
Capabilities
传统 Linux 执行权限检测主要是基于 UID,而且只有两个分类,即 (effective) UID 为 0 的超级用户和其他普通用户。这样一来就会面临权限划分粒度太粗的问题,比如只想让普通用户可以访问 ping 程序,就需要给 ping 文件加上 SETUID 位,如果该可执行文件的实现存在漏洞,就可能被利用造成权限提升。
因此,从 Linux 2.2 开始,就引入了 capabilities,将超级用户的权限进行切分,并且按需要给普通用户进行分配,解决了传统 UID-0 的局限性。
capabilities 以任务(线程)为单位,还是在上面内核的 struct cred
结构体中,其相关的字段为:
1 | struct cred { |
从定义上看,一共有三类 capability,分别是 effective、permitted 和 inheritable,这和 UID 的设计初衷是类似的,因为进程可以被复制(fork),因此增加了 inheritable 的控制。对于每一类 capabilities,由于其类型是__u32
,每项 capability 通过位与方式进行组合,因此最多可以支持 32 种 capability,其中一些常见的包括:
- CAP_NET_RAW: 创建和使用 RAW/PACKET socket 的权限以及绑定透明代理地址的权限;
- CAP_NET_ADMIN: 各类网关相关的操作,比如网卡接口配置、路由表修改等;
- CAP_SETUID: 设置和修改进程 UID 的权限;
- CAP_SYS_PTRACE: 使用 ptrace 跟踪任意其他进程的能力;
- ….
完整的权限列表可以参考 capabilities(7)。
对于系统管理员而言,更多是使用 capsh、getcap、setcap 等命令行工具,不过本质上都是通过 libcap 对系统调用进行封装实现的。
SELinux
SELinux 即 Security Enhanced Linux,是 Linux 中强制访问控制的两大实现之一(另一个是 AppArmor),作为 Linux 的拓展,最初由 NSA 开发,后集成到了开源内核主线中。
用户态
在 SELinux 中,访问控制通过 context 来描述访问权限,例如对于文件系统,可以使用 ls -Z
查看文件对应的标签:
1 | generic:/ # ls -lZ / |
对于网络端口的标签,可以用 netstat -Z
查看;对于进程标签,则可以通过ps -Z
查看:
1 | generic:/ # ps -Z |
context 可以分为几个部分,使用冒号:
分隔,分别是:
- user: 表示 SELinux 用户账号,与 Linux 用户账号不同,前者在 policy 中定义,包含多层级权限;
- role: 定义了主体(subject)在特定域(domain)中可以对客体(object)进行的操作;
- type: 定义了文件的类型;
- sensitivity: 即最后一个字段,表示涉密等级,范围可以从c0到c1023,c3表示
Top Secret
。该字段仅在 MLS 模式中使用,用于高敏感度的国防军事机构,对于客户端或者一般数据服务器而言只需保留默认值。
对一系列系统资源增加标签后,系统就可以根据标签来判断访问是否应该允许,一个示例的访问拒绝日志如下:
1 | type=1400 audit(18.250:15): avc: denied { getattr } for pid=939 comm="ls" path="/ueventd.rc" dev="rootfs |
访问权限的判断是在内核中实现的,但是访问规则可以动态生成和更新,内核中只预置了一系列触发点。SELinux 规则(policy)通常使用自定义的高级语言去描述,目前正在开发的是 CIL(Common Intermediate Language),但使用更多的是传统的 MLS Statements,比如访问规则的定义如下:
1 | rule_name source_type target_type:class perm_set; |
一个具体的例子:
1 | allow initrc_t acct_exec_t:file { getattr read execute }; |
表示允许拥有initrc_t
标签类型的主体访问带有acct_exec_t
标签的目标文件
,访问权限为 getattr、read和write。其中类型是使用type
关键字定义的,一般使用单独的file_contexts
文件记录。MLS 的完整语法见 Kernel Policy Language Definition Links。
对于系统管理员而言,常用的相关命令有:
- chcon: 修改目标文件的 SELinux 标签;
- resotrecon: 重新加载(恢复)系统文件的 SELinux 标签;
- semanage: 实时修改当前系统的 SELinux 规则;
- …
内核态
前面说 SELinux 是在内核中进行检查的,那么就以打开文件的操作为例来简单分析下 SELinux 的校验过程。打开文件使用的系统调用是openat
,该系统调用在内核中的大致调用路径如下:
- sys_openat
- do_sys_open
- do_filp_open
- path_openat
- do_last
- may_open
- inode_permission
- do_inode_permission -> generic_permission
- devcgroup_inode_permission
- security_inode_permission
inode_permission 是在文件打开之前检查文件系统 inode 权限的操作,其中包含常规的 DAC 检查、cgroup 权限检查以及我们所关心的 SELinux 检查:
1 |
|
struct security_hook_heads 是一个结构体,其中包含一系列链表,每个链表都对应一类 SELinux hook:
1 | struct security_hook_heads { |
每个链表都是在内核启动时进行初始化的,inode_permission
也不例外。在security/linux/hooks.c
中定义了静态数组selinux_hooks
:
1 | static struct security_hook_list selinux_hooks[] = { |
因此,selinux_inode_permission 就是实际进行 SELinux 检查的函数:
1 | static int selinux_inode_permission(struct inode *inode, int mask) |
这里有几个值得注意的地方,一个是 selinux_hooks 中注册了很多回调列表,这些模块就是内核中预置的检查点;另外,在 selinux_inode_permission
函数中,使用 file_mask_to_av
来将打开文件的 flag 转换成 SELinux 对应的访问动作(Access Vector):
1 | /* Convert a Linux mode and permission mask to an access vector. */ |
这些宏定义在 <build>/security/selinux/av_permissions.h
中,是编译内核时自动生成的。在确认该次访问需要审计后,就接着调用 audit_inode_permission -> slow_avc_audit 进行实际的判断了。因为这类访问控制判断需要频繁调用,出于性能考虑判断过程所使用的访问规则预先编译好并已经加载到内核缓存中,称为 avc (Access Vector Cache),这也是前面日志中 avc 的来源。
SEAndroid
Google 在 Android 4.4 上正式添加以 SELinux 为基础的系统安全机制,命名为SEAndroid。SEAndroid 在架构和机制上与 SELinux 完全一样,基于移动设备的特点,SEAndroid 的只是所以移植 SELinux 的一个子集。
模式
- Disable模式:此种模式关闭SELinux检测,不进行任何SELinux权限检查,畅通无阻。
- Permissive模式:宽容模式,当权限检查不通过时,不决绝资源访问,只打印avc log日志。
- Enforceing模式:强制模式,此种模式下权限检查不通过时,拒绝资源访问,并打出avc log,这个是最狠模式。
1 | #查看SeLinux状态 |
SEAndroid app分类
ELinux(或SEAndroid)将app划分为主要三种类型(根据user不同,也有其他的domain类型):
untrusted_app:第三方app,没有Android平台签名,没有system权限
platform_app:有android平台签名,没有system权限
system_app:有android平台签名和system权限
untrusted_app_25:第三方app,没有Android平台签名,没有system权限,其定义为
This file defines the rules for untrusted apps running with targetSdkVersion <= 25
从上面划分,权限等级,理论上:untrusted_app < platform_app < system_app
按照这个进行排序 property_contexts(系统属性)主要描述系统属性相关
seaapp_context 定义
seapp_contexts定义在system/sepolicy/seapp_contexts数据文件,如下
1 | isSystemServer=true domain=system_server |
从上面可以看出,domain和type由user和seinfo两个参数决定。
比如:
user=system seinfo=platform,domain才是system_app
user=_app,可以是untrusted_app或platform_app,如果seinfo=platform,则是platform_app。
参考链接
Android SELinux开发入门指南之SELinux基础知识_selinux书-CSDN博客
Android/Linux Root 的那些事儿 - 有价值炮灰 (evilpan.com)
一文彻底明白linux中的selinux到底是什么 - 知乎 (zhihu.com)
正确姿势临时和永久开启关闭Android的SELinux_setenforce: couldn’t set enforcing status to ‘0’: -CSDN博客