前言
早年期间,Linux基于用户身份/用户组的DAC(Discretionary Access Control作为访问控制策略: 每个进程都有所属的UID, 每个文件都有所属的UID/GID以及文件模式(读写执行等), 一个进程是否可以访问某个文件就是基于UID/GID/文件模式来管理的.换句话说,只要某个资源序属于该用于或该用户组, 则该用户对该资源具有绝对控制权力, 这样一旦用户获得了root权限, 那么整个系统就成了肉鸡. 可见, DAC的安全控制策略比较粗放。
SELinux最初是由美国Utah大学与NSA(National Security Agency)的安全小组研究出来的安全框架FLASK演变而来, 后被合入到Linux 2.6版本.相较于DAC, SELinux采用的是更细粒度的MAC(Mandatory Access Control).对于DAC而言, 资源的权限是由每个用户自己控制的, 而MAC则将所有的权限收拢, 由一个统一的管理者(SELinux)统一来分配所有的资源权限, 如果访问者没有事先分配到某个资源的权限, 则不会允许访问.这样即使是root用户也要收到安全策略的约束. Android在4.3开始引入SELinux, 到了5.0版本之后, 则开始全面支持了.在SELinux机制下, Android中所有的对象(进程/文件/socket/property)都打上了标签(label), 进程访问对象时, SELinux根据事先配置好的安全策略(security policy)判断访问者是否有权限.
-
SeLinux机制:在SELinux机制下,每个进程都被打上了label(称为process label),所有的对象(进程/文件/目录/网络口/socket/property/设备等对象)都打上了标签(称为Object label)。
-
MAC:SeLinux通过编写规则来控制一个process label对一个Object label的访问,这个规则称之为策略(Policy)。SeLinux的这种安全机制称为Mandatory Access Control (MAC),是在内核层实现的。
-
DAC:在标准的Linux上,也就是未实施SeLinux的Linux上,传统的访问控制是通过owner/group + permission flags(比如rwx)实现的,称之为Discretionary Access Control (DAC).
SeLinux和传统的DAC不是替代的关系,而是并行的关系。两者同时在起作用。之所以出现SeLinux,是因为传统DAC的安全机制过于粗粝,而Selinux提供了更为细致和安全的访问控制。简言之,传统DAC机制下,一旦你获得了root权限,将无所不能,但在SeLinux的机制下,即使获得了root权限,也仍然需要遵循已经设置好的访问策略,只有指定的进程才可以访问指定的文件。Android系统是同时支持DAC与SELinux的, 就是说, 在一个进程访问某个资源时,会按照如下规则进行权限控制:
- 首先根据DAC规则, 检查进程权限, 是否具有对应资源的读写/执行权限, 如果没有则拒绝执行;
- 如果DAC规则检查通过, 则执行SELinux安全规则的检查, 如果不通过,则拒绝访问。
初识SELinux
SELinux的首要原则是: 任何未被声明允许执行的都会被拒绝, 其有两种运行模式:
- Permissive模式: 访问控制的策略不会强制执行, 但是会被日志记录下来
- Enforcing模式: 访问控制策略会被强制执行并被记录下来
在Android下可以通过 adb getenforce来查看当前SELinux处于何种模式。
也可以通过adb setenforce来设置SELinux的模式,USER版本默认是enforcing, USERDEBUG/ENG可以设置模式:
adb setenforce 0 // permissive模式
adb setenforce 1 // enforcing模式
在SELinux中, 主要有Subject/Object/Object Manager/Security Server等几个核心的组成部分(见下图):
- Subject: 在SELinux中, Subject是一个进程, 每个Subject都有与之关联的一个安全上下文(security context); Subject负责发起访问某个对象的请求,比如读文件/建立socket链接
- Object: 一个对象就是一个资源, 比如文件, socket, pipes以及网络接口;每个对象都由一个类型标识其用途(file, socket), 并且与一个权限(permissions)集合关联, 该权限集合描述了对象能提供什么样的服务(比如read/write/send等)
- Object Manager: 对象管理者负责管理所有对象以及这些对象上能够执行的动作
- Access Vecctor Cache: 用于缓存Security Server的访问决策,以改善系统性能
- Security Server: 安全服务器根据安全策略来决定某个对象上的动作是否被执行
- Security Policy: 用于描述SELinux的访问规则
接下来我们看看SELinux具体是如何给每个对象打标签以及实现安全策略规则的.
标签(label)与策略规则
SELinux是通过标签(label)来匹配执行动作以及策略的.标签决定了何种动作是允许被执行的, socket/文件/进程都有自己的标签. SELinux的访问控制就是根据各个对象上的的标签来决定的, 而策略文件则定义了这些对象是如何相互交互的.
一个标签通常有如下的形式:
user:role:type:mls_level
这样一个标签也通常被成为Security Context。在Android中, 通常不用关心user/role/msl_level, user一般只有u, role对于进程来说是r, 对其他对象是object_r, msl_level是s0, 而type则用来标识对象的类型, 其决定了该对象的所具备的能力, 因此Android中的SELinux又称为基于TE(Type Enforcement)的安全机制, 在Android中, 所有的SELinux策略文件都以te结尾。
通过ls/ps指令中加入-Z参数, 可以查看文件/进程的SELinux状态, 如输入ls -aZ /init*查看init文件夹的标签:
上图中的init可执行程序以及文件夹都是在/system/sepolicy/private/file_contexts中定义的:
# Root
/ u:object_r:rootfs:s0
# Data files
/adb_keys u:object_r:adb_keys_file:s0
/build\.prop u:object_r:rootfs:s0
/default\.prop u:object_r:rootfs:s0
/fstab\..* u:object_r:rootfs:s0
/init\..* u:object_r:rootfs:s0
/res(/.*)? u:object_r:rootfs:s0
/selinux_version u:object_r:rootfs:s0
/ueventd\..* u:object_r:rootfs:s0
/verity_key u:object_r:rootfs:s0
# Executables
/init u:object_r:init_exec:s0
/sbin(/.*)? u:object_r:rootfs:s0
同样输入ps -Z可以查看进程的标签:
策略规则(policy rules)决定了进程是如何访问对象的, 其通常是如下格式:
allow domains types:classes permissions
- Domain: 域是一个进程或一组进程的标签,也被成为域类型
- Type: 对象的标签(如file/socket等)或者一个对象集合
- Class: 访问对象的类型
- Permission: 请求的权限(read/write)
例如:
allow appdomain app_data_file:file rw_file_perms;
这个规则的意思是允许所有应用域的进程访问标签为app_data_file的文件. 所有这些规则需要依赖于global_macros/te_macros的宏定义(位于/system/sepolicy目录下). 除了像上面的规则指定某个特定的域或类型, 也可以通过指定一个属性(attribute)来表示一组域或类型;当通过一个规则有属性时, 会被自动扩展成为了相应的域或类型. 按照上述方式写成的规则如下:
RULE_VARIANT SOURCE_TYPES TARGET_TYPES: CLASSES PERMISSIONS
在这个规则下, 只要一个Subject标识了SOURCE_TYPES就可以有权在类型为CLASSES/标签为TAEGET_TYPES的对象上执行任何在PERMISSONS中声明的操作.例如:
allow domain null_device:chr_file {
getattr open read ioctl write}
这个条规则意思是允许任何有domain域的进程访问null_device类型(对应/dev/null)的字符设备.最后我们来看下Android是如何应用SELinux的.
SELiunx在Android中的应用
Android的SELinux的配置在Android源码中有两个目录:
- /system/sepolicy
- /device///sepolicy
而/system/sepolicy主要是Android原生已有的SELinux文件, 包括所有SELinux标签以及策略文件.te的定义, 一般不做修改;/device目录下的SELinux配置通过编译宏BOARD_SEPOLICY_DIRS引入, 所有SELinux相关的编译都要依靠/system/sepolicy/Android.mk这个makefile. 具体来说, SELinux的配置大致有如下几个部分
配置目录 | 说明 |
---|---|
/system/sepolicy/public | 包含了系统sepolicy相关的API |
/system/sepolicy/private | 包含了系统sepolicy的具体实现(与vendor无关) |
/system/sepolicy/vendor | 提供给厂商(vendor)自由实现的配置 |
BOARD_SEPOLICY_DIRS | 包含厂商sepolicy的定制化配置 |
所有以.te结尾的都是安全策略文件, 其定义了对象的域(domain)和类型(types); 而SELinux标签文件(也称为SELinux context文件), 大致有如下几种:
- file_contexts: 为用户空间的文件分配标签
- genfs_contexts: 为不支持扩展属性的文件分配标签(如proc/vfat)
- property_contexts: 为Android所有属性分配标签,init进程在初始化时会读取该配置
- service_contexts: 为Android所有binder服务分配标签, 用于控制哪些进程可以注册/查找这些服务
- seapp_contexts: 为/data/data目录下的应用分配标签, 应用启动时zygote进程以及在应用安装时installd都会读取该配置
- mac_permissions.xml: 根据应用的签名(也可能包括包名)分配seinfo tag;seinfo tag在seapp_contexts文件中可以当作一个密钥用于分配特定的标签给所有的应用. 该配置在system_sever启动时会被读取。
那么, 这些SELinux的标签配置以及策略文件是如何编译的? 大致有两个编译路径,所有的file_contexts标签文件都会编译生成一个file_contexts.bin;而其他的如security_classes/*.te/genfs_contexts/port_contexts等文件都会编译生成一个sepolicy的二进制文件, 整体的编译逻辑如下图所示:
有关SELinux在Android的编译可以参考https://source.android.com/security/selinux/build.
有了SELinux的基础知识, 要如何修改或者添加SELinux规则? 一般, 通过dmesg | grep avc或则logcat | grep avc查看系统当前的SELinux访问的记录, 如果出现avc: denied等字样, 说明有进程违反了安全策略:。
- 案例1
[ 42.357295] selinux: avc: denied {
set } for property=net.usb0.dns1 pid=473 uid=0 gid=0 scontext=u:r:network_manager:s0 tcontext=u:object_r:system_prop:s0 tclass=property_service permissive=1
这个访问拒绝的提示说明, 标签为u:r:network_manager:s0的进程473(network_manager)不具备设置标签为u:object_r:system_prop:s0的属性值。
需要添加安全规则:
system/sepolicy/private/network_manager.te
set_prop(network_manager, system_prop)
prebuilts/api/{当前系统版本}/private/network_manager.te
set_prop(network_manager, system_prop)
再次编译验证后就不会出现访问拒绝的日志了.
- 案例2
avc: denied {
set } for property=com.af.test pid=2026 uid=10115 gid=10115 scontext=u:r:platform_app:s0 tcontext=u:object_r:default_prop:s0 tclass=property_service permissive=1
这个访问拒绝的提示说明, 标签为u:r:platform_app:s0的进程2026(platform_app)不具备设置标签为u:object_r:default_prop:s0的属性值。
system/sepolicy/private/property_contexts
#声明属性变量的类型为system_prop
com.af. u:object_r:system_prop:s0
prebuilts/api/{当前系统版本}/private/property_contexts
#声明属性变量的类型为system_prop
com.af. u:object_r:system_prop:s0
system/sepolicy/private/network_manager.te
#set_prop(platform_app , system_prop)
allow platform_app system_prop:property_service {
set };
prebuilts/api/{当前系统版本}/private/network_manager.te
#set_prop(platform_app , system_prop)
allow platform_app system_prop:property_service {
set };
再次编译验证后就不会出现访问拒绝的日志了.
参考:https://sniffer.site/2019/12/07/selinux%E5%9C%A8android%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/#/%E6%A0%87%E7%AD%BE-label-%E4%B8%8E%E7%AD%96%E7%95%A5%E8%A7%84%E5%88%99