SELinux在Android中的应用

SELinux(Security Enhanced Linux)是Linux下的安全控制机制, 为进程访问系统资源提供了访问控制(access control)策略. 早期, 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 policy concepts

另外, Android系统是同时支持DAC与SELinux的, 就是说, 在一个进程访问某个资源时,会按照如下规则进行权限控制:

  • 首先根据DAC规则, 检查进程权限, 是否具有对应资源的读写/执行权限, 如果没有则拒绝执行;
  • 如果DAC规则检查通过, 则执行SELinux安全规则的检查, 如果不通过,则拒绝访问.

初识SELinux

SELinux的首要原则是: 任何未被声明允许执行的都会被拒绝, 其有两种运行模式:

  • Permissive模式: 访问控制的策略不会强制执行, 但是会被日志记录下来
  • Enforcing模式: 访问控制策略会被强制执行并被记录下来

在Android下可以通过 adb getenforce来查看当前SELinux处于何种模式, 也可以通过adb setenforce来设置SELinux的模式(USER版本默认是enforcing, USERDEBUG/ENG可以设置模式):

1
2
3

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 components

接下来我们看看SELinux具体是如何给每个对象打标签以及实现安全策略规则的.

标签(label)与策略规则

SELinux是通过标签(label)来匹配执行动作以及策略的.标签决定了何种动作是允许被执行的, socket/文件/进程都有自己的标签. SELinux的访问控制就是根据各个对象上的的标签来决定的, 而策略文件则定义了这些对象是如何相互交互的.

一个标签通常有如下的形式:

1
2

user:role:type:mls_level

这样一个标签也通常被成为Security Context. 在Android中, 通常不用关心user/role/msl_level, user一般只有u, role对于进程来说是r, 对其他对象是object_r, msl_levels0, 而type则用来标识对象的类型, 其决定了该对象的所具备的能力, 因此Android中的SELinux又称为基于TE(Type Enforcement)的安全机制, 在Android中, 所有的SELinux策略文件都以te结尾.

通过ls/ps指令中加入-Z参数, 可以查看文件/进程的SELinux状态, 如输入ls -aZ /init*查看init文件夹的标签:

ls -z示例

上图中的init可执行程序以及文件夹都是在/system/sepolicy/private/file_contexts中定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 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
/charger u:object_r:rootfs:s0
/init u:object_r:init_exec:s0
/sbin(/.*)? u:object_r:rootfs:s0

同样输入ps -Z可以查看进程的标签:

ps -Z

策略规则(policy rules)决定了进程是如何访问对象的, 其通常是如下格式:

1
2
   
allow domains types:classes permissions

这里,

  • Domain: 域是一个进程或一组进程的标签,也被成为域类型
  • Type: 对象的标签(如file/socket等)或者一个对象集合
  • Class: 访问对象的类型
  • Permission: 请求的权限(read/write)

举个例子:

1
2

allow appdomain app_data_file:file rw_file_perms;

这个规则的意思是允许所有应用域的进程访问标签为app_data_file的文件. 所有这些规则需要依赖于global_macros/te_macros的宏定义(位于/system/sepolicy目录下). 除了像上面的规则指定某个特定的域或类型, 也可以通过指定一个属性(attribute)来表示一组域或类型;当通过一个规则有属性时, 会被自动扩展成为了相应的域或类型. 按照上述方式写成的规则如下:

1
2

RULE_VARIANT SOURCE_TYPES TARGET_TYPES: CLASSES PERMISSIONS

在这个规则下, 只要一个Subject标识了SOURCE_TYPES就可以有权在类型为CLASSES/标签为TAEGET_TYPES的对象上执行任何在PERMISSONS中声明的操作.例如:

1
2

allow domain null_device:chr_file { getattr open read ioctl write};

这个条规则意思是允许任何有domain域的进程访问null_device类型(对应/dev/null)的字符设备.最后我们来看下Android是如何应用SELinux的.

SELiunx在Android中的应用

Android的SELinux配置(以下均以Android P 9.0的代码为例)在Android源码中有两个目录:

  • /system/sepolicy
  • /device/<manufactory>/<device-name>/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 tagseapp_contexts文件中可以当作一个密钥用于分配特定的标签给所有的应用. 该配置在system_sever启动时会被读取

那么, 这些SELinux的标签配置以及策略文件是如何编译的? 大致有两个编译路径,所有的file_contexts标签文件都会编译生成一个file_contexts.bin;而其他的如security_classes/*.te/genfs_contexts/port_contexts等文件都会编译生成一个sepolicy的二进制文件, 整体的编译逻辑如下图所示:

SELinux build logic

有关SELinux在Android的编译可以参考https://source.android.com/security/selinux/build.

有了SELinux的基础知识, 要如何修改或者添加SELinux规则? 一般, 通过dmesg | grep avc或则logcat | grep avc查看系统当前的SELinux访问的记录, 如果出现avc: denied等字样, 说明有进程违反了安全策略, 举个例子:

1
2

[ 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=proper1

这个访问拒绝的提示说明, 进程473(network_manager)的标签u:r:network_manager:s0不具备访问标签为u:object_r:system_prop:s0, 类型为system_prop的属性值, 需要添加安全规则:

1
2

set_prop(network_manager, system_prop)

再次编译验证后就不会出现访问拒绝的日志了.

参考文献