版本基于:Andorid16
1. 前言
SELinux ( Security-Enhanced Linux **)**是由美国NSA(国安局)和一些公司(RedHat、Tresys)设计的一个针对Linux的安全加强系统。
NSA 最初设计的安全模型叫 FLASK ,全称为 Flux Advanced Security Kernel (由Uta大学和美国国防部开发,后来由NSA将其开源),当时这套模型针对 DTOS系统。后来,NSA觉得Linux更具发展和普及前景,所以就在Linux系统上重新实现了FLASK,称之为 SELinux。
Linux Kernel中,SELinux通过Linux Security Modules实现。在2.6之前,SElinux通过Patch方式发布。从2.6开始,SELinux正式入驻内核,成为标配。
由于Linux有多种发行版本,所以各家的SELinux表现形式也略有区别。具体到Android平台,Google对其进行了一定得修改,从而得到SEAndroid。
本文将先对 SELinux 相关知识进行介绍,然后看看 Android 是如何实现SELinux的。
2. 基础知识
2.1 访问控制 (DAC & MAC)
Linux内核资源访问控制分为 DAC (Discretionary Access Control ,自主访问控制)和 MAC (Mandatory Access Control,强制访问控制)两类。任何访问控制都要实现真实域向安全域的映射。
- DAC
SELinux 出现之前,Linux 上的安全模型为 DAC,即自主访问控制。
UGO属于典型的DAC级别访问控制,基于"用户-用户组-其他"的"读、写、执行"的权限检查,进程理论上所拥有的权限与执行它的用户的权限相同,该管理过于宽公,如果获得**root权限**,可以在Linux系统内做任何事情。
- MAC
SELinux 属于MAC级别访问控制,基于SELinux安全上下文(简称SELinux标签)和安全策略的安全机制,用于补充细化DAC级别的访问控制。访问系统资源时,会先进行DAC级别的访问检查,DAC级别的访问检查通过/后,继续进行MAC级别的访问检查,如果MAC级别的访问检查通过,才允许进行资源访问操作。
allow netd proc:file write
这是一条 SELinux 的安全策略,当进程 netd 想要对带有**proc 标签** 的文件进行**写操作**时,首先不仅需要该文件的UGO 拥有 w 权限,更要求开发者指定上面这一安全策略。
2.2 SELinux 模式
SELinux 按照默认拒绝的原则运行:任何未经明确允许的行为都会被拒绝。SELinux 可按两种全局模式运行:
- Permissive-宽容模式
权限拒绝事件会被记录下来,但不会被强制执行。代表SELinux访问检查运作中,如果访问操作违反了SELinux规则,不会直接限制资源访问行为,只会打印警告信息**(avc:denied)**到日志中。这种模式可用于调试SELinux访问所需权限,查看违反SELinux访问检查的原因。
- Enforcing强制模式
权限拒绝事件会被记录下来并强制执行。代表SELinux访问检查运作中,如果访问操作违反了SELinux规则,那么访问行为将被阻止,并打印警告信息(avc:denied)到日志中。
2.3 SELinux 模式的查看和切换
- 命令行方式查看 (getenforce)
会直接输出 Permissive 和 Enforcing
- avc log 查看
在avc log 结尾会有permissive=1/0的标识:
permissive=1,说明是Permissive模式
permissive=0,说明是Enforcing模式
- 命令行方式切换 (setenforce)
bash
adb root ##需要有root权限
adb shell setenforce 0 # 0->permissive,1->enforcing
- 修改 bootconfig 参数切换
该方式适用于userdebug版本,系统重启修改仍生效
类似修改:
bash
#Disable SELinux
ifeq ($(TARGET_BUILD_VARIANT),userdebug)
BOARD_BOOTCONFIG += androidboot.selinux=permissive
endif
- 修改init 代码切换
该方式适用于user和userdebug版本,系统重启修改仍生效。
修改**system/core/init/selinux.cpp** 文件里的**IsEnforcing()**函数,将该函数直接返回 false值:
cpp
Bool IsEnforcing() {
return false;
......
}
user版本还需在kernel config中配置:
bash
CONFIG_SECURITY_SELINUX_DEVELOP=y
修改完成后重新编译打包,下载软件包到手机即可。
3. 基础语法
3.1 SContext (Security Context)
SELinux中,每种东西都会被赋予一个**安全属性** ,官方说法叫**Security Context**。Security Context(以后用SContext表示)是一个字符串。
例如:
bash
$ ps -AZ |grep system_server
u:r:system_server:s0 system 1793 1170 26001560 400624 0 0 S system_server
$ ps -AZ | grep surface
u:r:surfaceflinger:s0 system 1293 1 12021912 50940 0 0 S surfaceflinger
$ ls -lZ
-rwxr-xr-x 1 root shell u:object_r:system_file:s0 320 2009-01-01 08:00 am
-rwxr-xr-x 1 root shell u:object_r:tradeinmode_exec:s0 150 2009-01-01 08:00 tradeinmode
$ getprop -Z
[gsm.sim1.type]: [u:object_r:radio_prop:s0]
[external_storage.casefold.enabled]: [u:object_r:storage_config_prop:s0]
例子中分三部分:进程SContext 、文件SContext、属性SContext;
这些 SContext 在MAC 的策略中,会被分**scontext** 、tcontext 两种type,即源头上下文、目标上下文,在TE 中的安全策略中scontext 为**发起访问的一方** ,tcontext 为**被访问的一方**。
SContext 格式如下:
bash
user:role:type:mls_level
- user: 指用户,该用户非DAC中的用户,而是在MAC中定义的。目前android只定义了 **"u"**这一个user。
- role: 表示角色,android目前只定义了一个role即 "r" 。一个user可以属于多个role,每个role有不同的权限。Android中,主体(subject)的role固定为 "r" ,对象(object)的role固定为**"object_r"**。
- **type:**Source 或者Target 的类型;MAC 的管理都是基于 type 来限制的,即所谓的 Type Enforcement Access Control(简称TEAC,一般用 TE 表示)
- **mls_level:**MLS级别,即多层安全机制,SELinux为了满足军用和教育行业而设计的机制,目前android基本上都配置的是s0。
说明:
- **scontext:**主体,发起访问的一方,linux通常以进程为单位,比如 init、StorageManagerService等等。
- **tcontext:**客体,访问对象,被访问的一方,linux 通常以文件为单位,还包括属性、端口等。
3.2 TE
MAC 基本管理单位是TEAC (Type Enforcement Accesc Control ),然后是高一级别的**Role Based Accesc Control**。RBAC是基于TE的,而TE也是SELinux中最主要的部分。
TE 的文件格式包括:
- file.te
- file_contexts
- genfs_contexts
- property.te
- property_contexts
- service.te
- services_contexts
- "scontext".te
当然,contexts 的文件还有很多,例如 hwservice_contexts、port_contexts、seapp_contexts,更多文件类型可以查看:system/sepolicy/private
3.2.1 file.te
制定新增文件SContext 的类型,例如:
bash
type mylog_data_file, file_type, data_file_type;
type mylog_device, file_type, dev_type;
分两部分:
- 固定指令;
- 文件SContex 标签,以及其类型,之间是逗号分隔;
3.2.2 file_contexts
配置文件,用以配置文件的SContext 类型,例如:
bash
/data/vendor/shiftlog(/.*)? u:object_r:mylog_data_file:s0
/dev/mylog_kernel u:object_r:tranlog_device:s0
分两部分:
- 前面是文件的完成路径,支持正则表达式
- 后面是SContext,用以指定这些文件的安全上下文,这些类型可能是新增的,也可能是系统已有的;
当然,文件通常会被当做被访问的一方,也就是 tcontext。
更多使用规则可以查看:system/sepolicy/private/file_contexts
**首次加载:**在系统构建阶段(编译固件时)应用标签
运行时加载: 系统启动时,初始化完成后,不会自动加载,必须手动执行 restorecon 命令才会生效,这也是为何 rc 文件中一般都会添加 restorecon 的原因。
3.2.3 genfs_contexts
sysfs、debugfs、tracefs等和procfs一样,都属于虚拟文件系统,给这类文件配置权限需要注意,既可以使用 file_contexts,也可以使用 genfs_contexts。
bash
genfscon proc /mm_collect u:object_r:my_mm_collect:s0
genfscon proc /pages_prefiltered u:object_r:my_mm_collect:s0
genfscon sysfs /class/leds/vibrator/gain u:object_r:sysfs_vibrator:s0
genfscon usbfs / system_u:object_r:usbfs_t:s0
分三部分:
- **genfscon:**固定指令;
- **fs type:**文件系统类型,包括:proc、sysfs、debugfs、tracefs、selinuxfs等等;
- **SContxt:**文件所属的安全上下文;
更多使用规则可以查看:system/sepolicy/private/genfs_contexts
Android原生不会使用 file_contexts 给虚拟文件配置上下文,建议使用 genfs_contexts。
genfs_contexts 配置的安全上下文比 file_contexts 加载得更早,如果文件/目录的创建在启动早期使用,请务必使用genfs_contexts;
genfs_contexts无法使用正则表达式,file_contexts支持正则表达式,如果文件/目录的创建在启动晚期使用,又有使用正则表达式的需求,可以使用file_contexts;
**加载时机:**在内核挂载虚拟文件系统时自动加载
触发条件: 系统启动过程中挂载 /proc, /sys 等虚拟文件系统时;动态设备创建时(如热插拔);
**自动生效:**无需任何命令;内核直接读取策略并应用标签;
3.2.4 property.te
同file.te,这里是制定新增属性SContext 的类型,例如:
bash
system_vendor_config_prop(wifi_config_prop)
type tr_trace_prop, property_type, system_property_type;
typeattribute vendor_mm_state_prop vendor_property_type;
3.2.5 property_contexts
配置文件,用以为prop 指定其 SContext 类型,例如:
bash
vendor.media.wfd.debug. u:object_r:system_video_framework_debug_log_prop:s0
init.svc.odm. u:object_r:vendor_default_prop:s0
分两部分:
- 前面是prop 的名称,注意最后的点号 ".",表示以这个prop 为前缀的属性;
- 后面是property 的SContext,用以指定该prop 的安全上下文,这些类型可能是新增的,也可能是系统已有的;
更多的使用规则可以查看:system/sepolicy/private/property_contexts
由于系统属性资源可以划分为 System 域和 vendor 域,部分属性是可以被跨域使用的,部分属性不可以。因此在定义系统属性type时需要明确它的 attribute,是否私有,是否可写,可使用如下辅助宏进行配置:
| 宏 | 宏说明 |
|---|---|
| system_internal_prop | 声明type:仅在 /system 中使用的属性 |
| system_restricted_prop | 声明type:/system 外无法写入的属性 |
| system_public_prop | 声明type:/system 没有限制的属性 |
| system_vendor_config_prop | 声明type:只能被vendor_init写入的属性 |
| vendor_internal_prop | 声明type:仅在 /vendor 中使用的属性 |
| vendor_restricted_prop | 声明type:/vendor 外无法写入的属性 |
| vendor_public_prop | 声明type:/vendor 没有限制的属性 |
3.2.6 service.te
同file.te,这里是制定新增服务SContext 的类型,例如:
bash
type logcat_service, system_server_service, service_manager_type;
type stats_service, service_manager_type;
type app_binding_service, system_server_service, service_manager_type;
通常这里定义的都是 service_manager_type,用以在Android Binder 中注册的binder service。
3.2.7 service_contexts
配置文件,为service manager 类型的的service 指定SContext 类型,例如:
bash
logcat u:object_r:logcat_service:s0
powerstats u:object_r:powerstats_service:s0
app_binding u:object_r:app_binding_service:s0
3.2.8 "scontext".te
通常像domain、service 等SContext 类型都会作为访问者,当其访问被访问者 (例如file、prop) tcontext 时都需要取得 tcontext 制定的 SContext 类型的权限,从而形成 scontext te 策略。
例如 lmkd.te:
bash
typeattribute lmkd coredomain;
init_daemon_domain(lmkd)
allow lmkd fs_bpf:file read;
allow lmkd cgroup_v2:dir { remove_name rmdir };
allow lmkd cgroup_v2:file r_file_perms;
lmkd 被定义为一个 coredomain 的SContext,作为domain 想要访问 cgroup_v2 标签的文件或目录时必须指定 MAC 的策略,取得 删除、读取的权限。
3.3 SELinux 宏的使用
Android在SELinux定义了很多宏给开发使用,以提高权限配置的效率,同时增加可读性。在权限配置,type定义时都可以使用对应宏来代替冗杂的语句。
宏定义主要在代码仓库的**system/sepolicy/public**下,可以在如下文件中查看具体使用。
例如,global_macros、te_macros、ioctl_macros 等。
3.3.1 global_macros 文件
该文件中定义全局的权限集合,例如:r_file_perms、w_file_perms、rw_file_perms、rw_dir_perms 等。
bash
define(`x_file_perms', `{ getattr execute execute_no_trans map }')
define(`r_file_perms', `{ getattr open read ioctl lock map watch watch_reads }')
define(`w_file_perms', `{ open append write lock map }')
define(`rx_file_perms', `{ r_file_perms x_file_perms }')
define(`ra_file_perms', `{ r_file_perms append }')
define(`rw_file_perms', `{ r_file_perms w_file_perms }')
define(`rwx_file_perms', `{ rw_file_perms x_file_perms }')
define(`create_file_perms', `{ create rename setattr unlink rw_file_perms }')
define(`r_dir_perms', `{ open getattr read search ioctl lock watch watch_reads }')
define(`w_dir_perms', `{ open search write add_name remove_name lock }')
define(`ra_dir_perms', `{ r_dir_perms add_name write }')
define(`rw_dir_perms', `{ r_dir_perms w_dir_perms }')
define(`create_dir_perms', `{ create reparent rename rmdir setattr rw_dir_perms }'
3.3.2 te_macros 文件
bash
define(`r_dir_file', `
allow $1 $2:dir r_dir_perms;
allow $1 $2:{ file lnk_file } r_file_perms;
')
define(`set_prop', `
unix_socket_connect($1, property, init)
allow $1 $2:property_service set;
get_prop($1, $2)
')
define(`get_prop', `
allow $1 $2:file { getattr open read map };
')
3.4 SELinux SContext 的调试
3.4.1 chcon (修改安全上下文)
SELinux安全上下文的修改可以使用 chcon 命令来实现,用于文件(可以批量),格式如下:
bash
$ chcon [选项] files
选项:
- -R:递归,当前目录和目录下所有的子文件同时恢复
- -h:更改符号链接,而不是它们指向的内容
- -v:为处理的所有文件显示诊断信息
3.4.1 restorecon (恢复安全上下文)
SELinux安全上下文的恢复可以使用 restorecon 命令来实现,用于文件(可以批量),格式如下:
bash
$ restorecon [选项] [SContext] files
选项:
- -R:递归,当前目录和目录下所有的子文件同时恢复
- -F:强制恢复
- -n:不做任何修改,与 -v 一起使用查看加载文件的上下文有哪些
4. SELinux 策略分布
**Public公共策略:**谷歌原生策略中的public部分以及开发人员自行添加策略中的public部分,vendor域和system域共用。
**Private私有策略:**谷歌原生策略中的private部分以及开发人员自行添加策略中的private部分,仅system域可用。
**Vendor供应商通用组件策略:**谷歌原生策略中的vendor部分以及开发人员自行添加策略中的vendor,仅vendor域可用。
|------------|---------------------------|-----------------------------------|
| Google原生策略 | system/sepolicy | Google原生策略,不要在此路径下进行修改 |
| Google原生策略 | system/sepolicy/private | Google原生system分区 private sepolicy |
| Google原生策略 | system/sepolicy/public | Google原生system分区 public sepolicy |
| Google原生策略 | system/sepolicy/vendor | Google原生vendor分区sepolicy |
| Google原生策略 | system/sepolicy/prebuilts | 版本兼容sepolicy |
vendor 供应商也可以按照 Google 这种分布方式分private、public、vendor 管理。
由于SELinux策略分为Vendor域和System域,在标签定义和策略声明时须满足如下要求:
- 定义的标签既在system域使用,又在vendor域使用:该标签应当定义在system_ext/public/路径下
- 定义的标签只在system域使用:该标签应当定义在system_ext/private/路径下
- 定义的标签只在vendor域使用:该标签应当定义在vendor/或odm/路径下
- system_ext/public/路径下定义的标签对system_ext/private/、system_ext/public/、vendor/、odm/下的allow均可见
- system_ext/private/路径下定义的标签只对system_ext/private/路径下的allow可见
- vendor/路径下定义的标签只对vendor/路径下的allow可见
因此:
- SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS 指定的权限跟随system编译,可仅配置在system侧
- BOARD_VENDOR_SEPOLICY_DIRS 指定的权限跟随vendor编译,可仅配置在vendor侧
- SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS 指定的权限需要跟随system编译,但其类型/属性会被vendor的编译所依赖,大多数情况下两侧都需要添加
5. 使用举例
5.1 init.rc 中添加内核节点的写操作
bash
on property:sys.boot_completed=1
write /proc/pages_pre/inodes_protect_enabled 1
需要在initrc 中对节点写操作,需要满足下面几点:
- file.te 中指定一个SContext 类型名;(当然可以使用已有的)
- file_contexts 中将该节点对应的 SContext 添加上;(建议使用genfscon 添加到genfs_contexts)
- init.te 中增加写权限申请;
bash
allow init proc_my_temp:file rw_file_perms;
目前该 init.te 添加到 vendor 目录也是可以实现的。
遇到问题:
平台 /vendor/etc/selinux/vendor_sepolicy.cil 文件中已经在看该权限成功添加了,但user 版本会打印 avc 提示没有write 权限:
bash
03-12 17:57:48.872 1 1 W init : type=1400 audit(0.0:340): avc: denied { write } for name="inodes_protect_enabled" dev="proc" ino=4026534030 scontext=u:r:init:s0 tcontext=u:object_r:proc_my_temp:s0 tclass=file permissive=0
非常奇怪的是user_root 版本又是可行的。
目前解决办法:
修改除了编译system、system_ext、vext以外,还需要编译 odm分区,可以解决该问题。
只能怀疑是否和 initrc执行的操作时 主体的type有变化,是不是切成root了