什么是SELinux
SELinux 即Security-Enhanced Linux, 是一套强制性安全审查机制,Linux Kernel 2.6 版本后, 有直接整合进入SELinux, 搭建在Linux Security Module(LSM)基础上
SELinux 基本架构与原理.
SELinux 是典型的MAC-Mandatory Access Controls 实现, 对系统中每个对象都生成一个安全上下文(Security Context), 每一个对象访问系统的资源都要进行安全上下文审查。审查的规则包括类型强制检测(type enforcement), 多层安全审查(Multi-Level Security), 以及基于角色的访问控制(RBAC: Role Based Access Control).
SELinux 搭建在Linux Security Module(LSM)基础上,关于 LSM 架构的详细描述请参见文章 “Linux Security Modules: General Security Support for the Linux Kernel”, 该文章在 2002 年的 USENIX Security 会议上发表。有完整的实现LSM 的所有hook function.
SELinux 的整体结构如下图所示:
SELinux 包含五个基本组成:
- 用于处理文件系统的辅助模块, 即SELinuxFS.
- 集成Linux Security Modules 的hooks sets.
- Security Policy Database.
- Security Label 验证模块.
- Access Vector Cache (AVC), 访问向量缓存,以便提高验证速度.
基本的访问流程如下图所示:
流程如下:
- 进程通过系统调用(System Call) 访问某个资源, 进入Kernel 后, 先会做基本的检测, 如果异常则直接返回.
- Linux Kernel DAC 审查, 如果异常则直接返回.
- 调用Linux Kernel Modules 的相关hooks, 对接到SELinux 的hooks, 进而进行MAC 验证, 如果异常则直接返回.
- 访问真正的系统资源.
- 返回用户态, 将结构反馈.
DAC(Discretionary Access Control):自主访问控制,基于“用户-用户组-其他/读-写-执行”的权限检查,进程理论上所拥有的权限与执行它的 用户的权限相同,该管理过于宽松,如果获得 root 权限,可以在 Linux 系统内做任何事情。
MAC(Mandatory Access Control):强制访问控制,基于安全上下文和安全策略的安全机制,用于补充 DAC 检查。访问系统资源时,会先进行 DAC 检查,DAC 检查通过,才能进行 MAC 检查,如果 MAC 检查通过,才能获得资源访问权限。SELinux 是 MAC 机制的一种实现
Core SELinux Components
SELinux 的核心组件可以参考下面的图:
- Subject 通常是指触发访问行为的对象, 在Linux 里面通常是一个进程(Process).
- Object Manager 即是对象访问管理器, 即可以知道Subject 需要访问哪些资源,并且触发验证机制
- Security Server 即安全服务器, 用来验证某个Subject 是否可以真正的访问某个Object, 而这个验证机制是基于定义好的Security Policy.
- Security Policy 是一种描述SELinux Policy 的语言.
- Access Vector Cache (AVC) 是访问缓存, 用来记录以往的访问验证情况, 以便提供效率,快速处理.
审查机制
SELinux 对MAC支持两种机制:
-
Type Enforcement (TE)
顾名思义, Type Enforcement 是根据Security Label 中的 type 进行权限审查, 审查 subject type 对 object type 的某个class 类型中某种permission 是否具有访问权限,是目前使用最为广泛的MAC 审查机制, 简单易用。 -
Multi-Level Security (MLS)
多层安全机制, 是基于Bell-La Padula (BLP) 模型, 将Subject 和 Object 定义成多层次的安全等级, 不同安全等级之间有相关的访问约束, 常见的访问约束是 “no write down” 和 “no read up”. 它是根据Security Label 里面的最后一个字段label 进行确认的.
目前在Android 中,重点启用了Type Enforcement 机制.
TE中控制语句的格式:rule_name source_type target_type : class perm_set
- rule: 控制类型, 分成两方面 allow 以及 audit
- source_type:也叫subject,通常是domain。
- Target_type: 代表请求的资源的类型
- class perm_set: 代表对资源访问的操作
TE中的rule:
- allow:赋予某项权限。
- auditallow:audit含义就是记录某项操作。默认SELinux只记录那些权限检查失败的操作。 auditallow则使得权限检查成功的操作也被记录。注意,allowaudit只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用allow语句。
- dontaudit:对那些权限检查失败的操作不做记录。
- neverallow:用来检查安全策略文件中是否有违反该项规则的allow语句
举个栗子:下面是谷歌原生策略中init.te中的一部分
# Create /data/property and files within it.
allow init property_data_file:dir create_dir_perms;
allow init property_data_file:file create_file_perms;
# Set any property.
allow init property_type:property_service set;
# init can only find the APEX service
neverallow init { service_manager_type -apex_service }:service_manager { find };
# init can never add binder services
neverallow init service_manager_type:service_manager { add };
# init can never list binder services
neverallow init servicemanager:service_manager list;
Security Context
SELinux 给Linux 的所有对象都分配一个安全上下文(Security Context), 描述成一个标准的字符串。
两种类型
- Subject 主体, linux通常以进程为单位
- Object 访问对象, linux 通常以文件为单位
标准格式
user:role:type:[range] - User: 用户, 非Linux UID。android 只定义了一个user u
- Role: 角色,一个user可以属于多个role,不同的role具有不同的权限。它是SELinux中一种比较高层次,更方便的权限管理思路,即Role Based Access Control(基于角色的访问控制,简称为RBAC, SELinux 不推荐使用)。android 只定义了一个role r,以及file system 使用object_r。因为user u 只有一个 role r, 所以没有进行角色访问约束(RBAC)。
- Type: Subject或者Object的类型。 MAC的基础管理思路其实不是针对上面的RBAC,而是所谓的Type Enforcement Access Control(简称TEAC,一般用TE表示:根据Security Label 中的 type 进行权限审查, 审查 subject type 对 object type 的某个class 类型中某种permission 是否具有访问权限)。对进程来说,Type就是Domain。
- Range: Multi-Level Security(MLS)的级别。MLS将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问。
如何查看文件的SELinux Context:ls -lZ
如何查看进程的SELinux Context: ps -Z 或者cat proc/PID/attr/current, 查看当前进程可用id
也可以使用ps -AZ,查看所有进程。
SELinux Mode
SELinux 分成两种模式, 即Permissve Mode(宽容模式), 和 Enfocing mode(强制模式).
Permissive Mode 只通过Kernel Audit System 记录LOG, 但不真正拦截访问.
Enforcing Mode 在记录LOG 的同时,还会真正的拦截访问.
查看SELinux 模式
- 使用命令: adb shell getenforce
- 通过AVC log 查看,在log结尾有 permissive=1/0 的标示: 1表示Permissive, 0表示Enforcing
切换SELinux模式
- 使用命令: adb root; adb shell setenforce 0 (0表示Permissive模式, 1表示Enforcing模式),重启后失效
- 修改init:修改system/core/init/SELinux.cpp文件里的IsEnforcing()函数,将该函数直接返回false
AVC log:
0181D <5> [ 94.720314][01-21 11:16:38.720] audit: type=1400 audit(1611198999.579:468): avc: denied { read } for pid=3949 comm=“ww6.temperature” name=“calibration” dev=“sysfs” ino=20396 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:sysfs:s0 tclass=file permissive=0
ww6.temperature 进程缺少对标签为sysfs,类型为file,名称为calibration文件的 read权限
0057D <11> [ 2.746412][12-31 19:05:22.746] selinux: avc: denied { set } for property=ro.sf.lcd_width pid=1 uid=0 gid=0 scontext=u:r:vendor_init:s0 tcontext=u:object_r:default_prop:s0 tclass=property_service permissive=0\x0a
AVC log说明:
- <5> : kernel log level
- [ 94.720314] [01-21 11:16:38.720]: kernel time
- audit: log TAG 表示此log是通过audit打印的
- type=1400: SYSCALL type=AVC - for kernel events type=USER_AVC - for user-space object manager events
- audit(1611198999.579:468):audit(time:serial_number)
- avc: denied: 表示当前操作被拒绝
- { read } : 表示被拒绝的操作,{}中含有实际尝试的操作
- for pid=3949 : 表示被拒的是一个进程,进程ID 3949
- comm=“ww6.temperature” : 发生avc:denied的进程的进程名,即主体进程名
- name=“calibration”: 尝试操作的目标文件或者目录的路径,即客体资源名称
- dev=“sysfs” : 含有这个文件系统的设备节点,客体资源在该文件系统中
- ino=20396 : 目标文件或目录的节点号
- scontext=u:r:untrusted_app:s0 : 主体进程的安全上下文(例子中多了一个’:c512,c768’,说明了主体和客体安全级别不同(MLS规则))
- tcontext=u:object_r:sysfs:s0: 客体资源的安全上下文
- tclass=file:访问资源所属类别
- permissive=0:当前是 Enforcing 模式,permissive=1 时为 Permissive 模式
SELinux 策略
SELinux 策略类型:(由于system image和vendor image的分离导致有了sepolicy的分离)
- Public 公共策略(基础部分):导出的策略是非平台策略,开发人员可以在该策略上编写附加策略。类型和属性被版本化的策略包含在已交付的非平台策略中,非平台策略将与平台策略组合在一起。(定义的类型和属性system和vendor都可以使用)
- Private 私有策略(基础部分):平台功能需要的策略,不会导出给其他供应商策略开发人员,因此可以假定不存在。(仅system image内部使用,只会编译到system image)
- Vendor 供应商通用组件策略(外扩组件部分):供应商功能需要“仅供应商策略”。该政策可以参考公共政策,但不能参考私人政策。此策略适用于从核心/非供应商树生成并放置到供应商分区的组件。(只会编译到vendor image,能够引用到public目录下的定义并设置策略)
谷歌原生策略:
- /system/sepolicy:谷歌原生策略,不要在此路径下进行修改
- /system/sepolicy/private:谷歌原生 system 分区 private sepolicy
- /system/sepolicy/public:谷歌原生 system 分区 public sepolicy
- /system/sepolicy/vendor:谷歌原生 vendor 分区 sepolicy
- /system/sepolicy/prebuilts:版本兼容 sepolicy
谷歌原生策略不能修改,我们可以修改平台的补充策略,各个平台策略存放位置有所不同。
安全上下文分类
安全上下文的描述文件一般是以xxx_contexts命名,可以在文件中为对象指定Security Context,具体分类如下:
- file_contexts:用于为文件分配安全上下文
eg: /system/bin/ims_bridged u:object_r:ims_bridged_exec:s0 - property_contexts: 为Android系统属性分配上下文
eg: vendor.bw.foreground.package u:object_r:system_prop:s0 - genfs_contexts: 用于为不支持扩展属性的文件系统(例如,proc 或 vfat)分配上下文
eg: genfscon proc /sprd_minidump/ u:object_r:proc_minidump_gesture:s0 - seapp_contexts: 用于为应用进程和/data/data目录分配上下文
eg: user=system seinfo=platform name=com.bw.engineermode domain=bwic_engineermode_app type=app_data_file levelFrom=user
问题实践
尝试解决上述avc log报出的权限问题:
- 在哪儿修改呢?
scontext=u:r:untrusted_app:s0: log中主体进程上下文告诉了我们,因此我们应该要去修改untrusted_app.te(可以使用find命令查找一下,应该会出现很多,这时候我们要考虑修改哪个目录下的) - 怎么修改?
格式:allow scontext tcontext:tclass permission;
allow untrusted_app sysfs:file {read open}; - 编译验证
模块编译:mmm system/sepolicy/
编译后会在out目录下生成两个目录:
\vendor\etc\selinux
\system\etc\selinux
将这两个目录push到设备中重启后生效
编译验证时编译报错了:
libsepol.report_failure: neverallow on line 91 of system/sepolicy/private/coredomain.te (or line 32356 of policy.conf) violated by allow untrusted_app sysfs:file { read write open };
libsepol.report_failure: neverallow on line 95 of system/sepolicy/private/app_neverallows.te (or line 29605 of policy.conf) violated by allow untrusted_app sysfs:file { read write open };
libsepol.report_failure: neverallow on line 92 of system/sepolicy/private/app_neverallows.te (or line 29586 of policy.conf) violated by allow untrusted_app sysfs:file { write };
libsepol.report_failure: neverallow on line 514 of system/sepolicy/public/app.te (or line 9547 of policy.conf) violated by allow untrusted_app sysfs:file { write };
libsepol.check_assertions: 4 neverallow failures occurred
报错原因是被google用neverallow禁止了。Google 默认禁止app , 包括system app, radio app 等直接写/sys 目录以u:object_r:sysfs:s0 为标签的文件以及/proc 目录以u:object_r:proc:s0文件。如果直接放开SELinux 权限, 会导致CTS 无法通过
如何处理与Google 定义neverallow 冲突
当我们的权限被neverallow 后,我们不能直接去删掉neverallow ,会导致CTS无法通过,因此我们只能绕道,两种方式:
- 通过system server service 或者 init 启动的service 读写操作, 然后app 通过binder/socket 等方式连接APP 访问. 此类安全可靠, 并且可以在service 中做相关的安全审查
- 修改对应节点的SELinux Security Label, 为特定的APP,具体做法(以上述AVC报错为栗子):
- 参考原来的 sysfs 类型(type sysfs, fs_type, sysfs_type, mlstrustedobject;)定义一种新 type,
在file.te中添加:type sysfs_xxx, fs_type, sysfs_type, mlstrustedobject; - 使用新定义的 type 为目标文件配置安全上下文
在file_context中添加:/xxx/xxx (目标文件路径) u:object_r:sysfs_xxx:s0 - 重新赋予主体进程访问新类型文件的权限
在untrusted_app.te中添加:allow untrusted_app sysfs_xxx:file {read open};
- 参考原来的 sysfs 类型(type sysfs, fs_type, sysfs_type, mlstrustedobject;)定义一种新 type,
新增属性配置安全上下文
Android 属性 property 在系统中存放在一块共享内存中,每个进程都能读,但是只有 init 进程能够写,请
求 init 进程帮助写属性值是需要 SELinux 权限的。
Android 属性名称前缀必须用 system\core\init\property_service.c 中定义的:
- vendor 属性名前缀
- persist.vendor.**
- vendor.**
- ro.vendor.**
属性名不包含 vendor,则为系统属性。
Android 11.0 属性标签有了更严格的限制,在 vendor 域属性所打的标签必须以 vendor_开头,否则会导致
vts 测试失败。
根据属性的命名规范,选择定义在 system 分区还是 vendor 分区,以 vendor 为例,新增属性步骤如下:
- 在 property.te 中添加属性的 type 定义。
type vendor_xxx_prop, property_type; - property_contexts 中对属性设定安全上下文。
xxx.xxx. u:object_r:vendor_xxx_prop:s0
xxx.xxx 后的“.”表示匹配以 xxx.xxx 开头的属性。 - xxx.te 文件中赋予对应进程访问该类型属性的权利。
set_prop(xxx, vendor_xxx_prop)