基于Android10 源码分析
权限控制介绍
SEAndroid是android中一种权限控制机制,android中的权限控制机制发展经历MAC和DAC。
DAC 权限控制
SElinux 之前, linux 采用的是 DAC (Discretionary Access Control,自主访问控制系统)基本概念就是 系统只提供基本的验证,完整的访问控制由开发者自己控制(直接 chmod/chmod 就可以修改权限),DAC 采用了一 种非常简单的策略(policy),存在资源访问者,资源,资源的访问权限:
- 资源访问者分别:Owner, Group,Other,访问者包含各种用户(uid/gid),各种进程;
- 资源一般都是文件;
- 资源的访问权限分成 read、 write、 execute;
MAC 权限控制
在DAC中,每个用户都有uid/gid, 进程理论上所拥有的权限与执行它的用户的权限相同,如果进程是由root 超级用户启动,该进程就会拥有 root 权限, 该进程就可以在系统上干任何事情,其实是比较危险的。所以就出 现了 MAC(Mandatory Access control 强制性访问控制),即要求系统对每一项访问都进行严格的限制和验证, 具体的限制和验证策略(policy)由开发者给出,即需要明确的指明某个进程可以访问哪些资源(文件、网络端口, 属性等)。
MAC 可以明显弥补 DAC 的缺陷。如可限制 Root 权限,即使你有 root 权限(将军),如果无法通过 MAC 验证(圣 旨授权),那么一样的无法真正执行相关的操作。另外 MAC 对每一项权限进行了更加完整的细化,可限制用户对 资源的访问行为。在 Linux Kernel,所有的 MAC 机制都是搭建在 Linux Security Modules(LSM)基础上,目前 SELinux 已经成了事实上的行业标准。
注意:Linux系统先做DAC检查。如果没有通过DAC权限检查,则操作直接失败。通过DAC检查之后,再做MAC权限检查。
SEAndroid 和 SELinux 关系
SELinux(Security Enhanced Linux)就是 linux 系统中对 MAC 的实现。由于 Android 系统有着独特的用户空 间运行时,因此 SELinux 不能完全适用于 Android 系统。为此,NSA(美国国家安全局)同 Google 一起针对 Android 系统,在 SELinux 基础上开发了 SEAndroid(Security Enhancements (SE) for Android), Android 从 5.0 开始 普遍使用 SEAndroid。
SEAndroid框架
参考罗升阳大神的这张图 SEAndroid 框架是由用户空间和内核空间组成, 用户空间安全保护机制中主要包含:
- 安全上下文(Security Context)
- 安全策略(Security Policy)
- 安全服务(Security Server)等模块
内核空间由 LSM,AVC 组成,LSM(linux security module)负责内核资源的安全访问控, 由它来决定一个主体访问一个客体是否是合法的,这个主体一般是指进程,而客体主要指资源, 而 selinux 是属于 LSM 中实现的一种。
用户空间的 Security Server(一种统称)主要根据用户空间提供的安全上下文对资源进行配置,它由4个部分构成:
- 应用程序安装服务 PackageManagerService
- 应用安装守护进程 installd
- 应用程序进程孵化器 Zygote 进程
- init进程
其中PackageManagerService 和 installd 负责创建 App 数据目录的安全上下文,Zygote 进程负责创建 App 进程的安全上下文,而 init 进程负责控制系统属性的安全访问。
上面这个框架图说明了大体结构,但是整个机制的工作流描述的还不太清晰,因此使用下面这张流程图讲其工作流阐述清楚。
如上图所示,当一个进程想要访问一个文件时(读写操作等),需要解决 3 个问题:
- 谁来做裁判,评判进程是否有权限访问这个文件,这个裁判就是LSM
- 裁判是依据什么来做判断,判断依据就是"安全策略"
- "安全策略"中如何才能对号入座的描述清楚进程对文件的访问权限呢?就比如大家的身份证号码,只有有了身份标识,做相关检查时才能对号入座;在SEAndroid中这个身份标识就是"安全上下文"。
整个流程大致如下:
- LSM如何监控操作?
LSM 会在相应的内核对象子系统中会加入一些 Hook 代 码(内核 LSM 驱动代码:security/selinux.c),例如我们调用系统接口 open 函数来读取一个文件的时候,就会进入到内 核的文件子系统中。在文件子系统中负责读取文件函数 vfs_open 就会调用 LSM 加入的 Hook 代码。这些 Hook 代码就会调用之前 SELinux 注册进来的回调函数(security/selinux/hooks.c),以便后者可以进行安全检查。
- 进程、文件等的安全上下文哪儿来?安全上下在源码中有统一的定义,由用户空间的 Security Server进行统一分配的。
- 安全策略怎么来的?安全策略在源码中有统一的定义,然后在源码编译阶段和init进程在初始化阶段将整个安全策略组织成LSM所需要的格式,写入内核空间,这样一来LSM就有了判断依据
当一个进程访问文件时,首先会进行系统调用调用的内核空间,经过一些检查后,会调用LSM的Hook代码,在Hook代码中会依据"安全策略"进行权限访问判断。
安全上下文
可以通过ls -Z 可以查看文件上下文:
js
u:object_r:cgroup:s0 acct
u:object_r:adb_keys_file:s0 adb_keys
u:object_r:apex_mnt_dir:s0 apex
u:object_r:rootfs:s0 bin
u:object_r:rootfs:s0 bugreports
u:object_r:cache_file:s0 cache
u:object_r:rootfs:s0 charger
u:object_r:configfs:s0 config
u:object_r:rootfs:s0 d
u:object_r:system_data_file:s0 data
u:object_r:tmpfs:s0 debug_ramdisk
u:object_r:rootfs:s0 default.prop
u:object_r:device:s0 dev
u:object_r:rootfs:s0 dsp
u:object_r:rootfs:s0 etc
u:object_r:firmware_file:s0 firmware
u:object_r:init_exec:s0 init
u:object_r:rootfs:s0 init.environ.rc
通过 ps -Z 查看进程上下文
js
LABEL UID PID PPID C STIME TTY TIME CMD
u:r:init:s0 root 1 0 0 52:25 ? 00:00:00 init second_stage
u:r:qemu_props:s0 root 204 1 0 52:26 ? 00:00:00 qemu-props
u:r:ashmemd:s0 nobody 211 1 0 52:26 ? 00:00:00 ashmemd
u:r:audioserver:s0 audioserver 212 1 0 52:26 ? 00:00:00 audioserver
u:r:gpuservice:s0 gpu_service 213 1 0 52:26 ? 00:00:00 gpuservice
u:r:lmkd:s0 lmkd 214 1 0 52:26 ? 00:00:00 lmkd
u:r:kernel:s0 root 218 2 0 52:26 ? 00:00:00 [psimon]
u:r:surfaceflinger:s0 system 219 1 2 52:26 ? 00:19:48 surfaceflinger
u:r:logpersist:s0 logd 221 1 0 52:26 ? 00:00:00 logcat -Q
u:r:adbd:s0 shell 224 1 0 52:26 ? 00:00:06 adbd --root_secl+
u:r:traced:s0 nobody 225 1 0 52:26 ? 00:00:00 traced
u:r:traced_probes:s0 nobody 227 1 0 52:26 ? 00:00:00 traced_probes
u:r:cameraserver:s0 cameraserver 229 1 0 52:26 ? 00:00:00 cameraserver
u:r:drmserver:s0 drm 230 1 0 52:26 ? 00:00:00 drmserver
u:r:idmap:s0 system 231 1 0 52:26 ? 00:00:00 idmap2d
u:r:incidentd:s0 incidentd 232 1 0 52:26 ? 00:00:00 incidentd
u:r:installd:s0 root 233 1 0 52:26 ? 00:00:00 installd
u:r:keystore:s0 keystore 235 1 0 52:26 ? 00:00:00 keystore /data/m+
u:r:mdnsd:s0 mdnsr 237 1 0 52:26 ? 00:00:00 mdnsd
u:r:mediadrmserver:s0 media 238 1 0 52:26 ? 00:00:00 mediadrmserver
u:r:mediaextractor:s0 mediaex 239 1 0 52:26 ? 00:00:00 media.extractor +
u:r:mediametrics:s0 media 240 1 0 52:26 ? 00:00:00 media.metrics di+
u:r:mediaserver:s0 media 241 1 0 52:26 ? 00:00:00 mediaserver
u:r:statsd:s0 statsd 242 1 0 52:26 ? 00:00:15 statsd
u:r:storaged:s0 root 243 1 0 52:26 ? 00:00:01 storaged
u:r:wificond:s0 wifi 244 1 0 52:26 ? 00:00:03 wificond
u:r:mediacodec:s0 mediacodec 245 1 0 52:26 ? 00:00:00 media.codec hw/a+
u:r:rild:s0 radio 246 1 0 52:26 ? 00:00:01 rild
实际由四部分内容组成:SELinux 用户、SELinux 角色、类型、安全级别。
格式 | user:role:type:mls |
---|---|
User 用户 | 代表创建这个文件的 SELinux user, Android 不使用 SELinux 用户。唯一定义的用户是 u, android 源码中有定义:system/sepolicy/private/users |
Role 角色 | 为角色 role 的意思, 不同角色有不同的权限, Android 不使用 SELinux 角色,定义并使用了两个默认角色:如果是进程(活的), 会定义为 r, 如果是文件(死的),会被定义为 object_r。Android 源码中有定义:system/sepolicy/private/roles_decl |
Type 类型 | 如果是进程的话, 这一栏就是 domain(域),表示进程属于某某 Domain 并具有某某权限, 在策略文件中,domain 可以通过 allow 语句来赋予不同的权限, 当某个进程属于这个域,也就有了这个域的权限, 比如 zygote 域拥有对 init 进程发送 SIGCHLD信号等权限, 而属于 zygote 的所有进程也就有了相同权限, 这栏如果是文件的话,那这一栏就是 type(文件类型,如 device 类型,camera 类型等),文件有了类型, 这样进程才能对知道对哪些类型文件拥有什么访问权限, 对进程来说,Type 就是 Domain |
Mls 多级别安全 | MLS 将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问,主要是为了满足军用和教育行业而设计的, Android 不使用 SELinux 敏感度,只有 s0 敏感度。 |
其中"Type 类型"最为重要,在 Android 源码中会存在几个重要的上下文文件, 用来描述系统和属性,服务等的上下文信息, 路径在system/sepolicy
文件名 | 描述 |
---|---|
file_contexts | 根系统中所有文件的安全上下文, 如/system/bin, /system/etc 等文件,源码路径如: system/sepolicy/private/file_contexts |
property_contexts | 属性的安全上下文,源码路径如:system/sepolicy/private/property_contexts |
genfs_contexts | Generalized filesystem 虚拟文件系统安全上下文,如 proc, sysfs,debugfs 文件系统,源码路径如: system/sepolicy/private/genfs_contexts |
service_contexts | service 文件安全上下文,用于为 Android Binder 服务分配标签,以便控制哪些进程可以为相应服务添加(注册)和查找(查询)Binder 引用。在启动期间,servicemanager进程会读取此配置,HIDL 机制出现后,就有两个,源码路径:system/sepolicy/private/hwservice_contextssystem/sepolicy/private/service_contexts |
mac_permission.xml | 用于根据应用签名和应用软件包名称(后者可选)为应用分配 seinfo 标记。随后,分配的 seinfo 标记可在 seapp_contexts 文件中用作密钥,以便为带有该 seinfo 标记的所有应用分配特定标签。在启动期间,system_server 会读取此配置, 源码路径为: system/sepolicy/private/mac_permissions.xml |
seapp_contexts | app 安全上下文,用于描述 apk 安装之后的目录文件和 /data/data 目录分配标签。在每次应用启动时,zygote 进程都会读取此配置;在启动期间,installd 会读取此配置。源码路径如: system/sepolicy/private/seapp_contexts |
有了上述文件,系统中的文件、属性、进程等就有了对应的安全上下文。
如下为file_contexts中部分内容示例,可以看到其对android根文件系统中每一个文件和目录都定义了对应的安全上下文
js
###########################################
# 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
# For kernel modules
/lib(/.*)? u:object_r:rootfs:s0
# Empty directories
/lost+found u:object_r:rootfs:s0
/acct u:object_r:cgroup:s0
/config u:object_r:rootfs:s0
/debug_ramdisk u:object_r:tmpfs:s0
/mnt u:object_r:tmpfs:s0
/postinstall u:object_r:postinstall_mnt_dir:s0
/postinstall/apex u:object_r:postinstall_apex_mnt_dir:s0
/proc u:object_r:rootfs:s0
/sys u:object_r:sysfs:s0
/apex u:object_r:apex_mnt_dir:s0
如下为service_contexts中部分内容示例,其对系统中binder服务定义了对应的安全上下文
less
accessibility u:object_r:accessibility_service:s0
account u:object_r:account_service:s0
activity u:object_r:activity_service:s0
activity_task u:object_r:activity_task_service:s0
adb u:object_r:adb_service:s0
alarm u:object_r:alarm_service:s0
android.os.UpdateEngineService u:object_r:update_engine_service:s0
android.security.keystore u:object_r:keystore_service:s0
android.service.gatekeeper.IGateKeeperService u:object_r:gatekeeper_service:s0
app_binding u:object_r:app_binding_service:s0
app_prediction u:object_r:app_prediction_service:s0
apexservice u:object_r:apex_service:s0
ashmem_device_service u:object_r:ashmem_device_service:s0
gsiservice u:object_r:gsi_service:s0
appops u:object_r:appops_service:s0
appwidget u:object_r:appwidget_service:s0
assetatlas u:object_r:assetatlas_service:s0
attention u:object_r:attention_service:s0
audio u:object_r:audio_service:s0
在file_contexts、service_contexts等文件中定义的安全上下文的"Type 类型"字段,例如:rootfs、sysfs、tmpfs、accessibility_service、activity_service等也是有统一定义的地方的,它们在system/sepolicy下面的.te文件中(后面会具体讲解)。
安全策略
为了让大家快速理解, 来看一个 SEAndroid 中设置安全策略(设置权限)的例子:
js
/*system/sepolicy/public/vold.te */
allow vold tmpfs:dir create_dir_perms
解释: 允许 vold 进程在 tmpfs 文件系统(如/mnt 目录下)中创建文件夹。如果没有在 vold.te 中使用上例中的权限配置 allow 语句,则 vold 进程就无法往 tmpfs 文件系统中创建目录,即使 vold 具有 root 权限。
完整的 allow 相关的语句格式为:
js
rule_name source_type target_type :object_class perm_set
- rule_name:规则名,分别有 allow,dontaudit,auditallow,neverallow 等
- source_type:源类型,主要作用是用来填写一个域(domain),一般都是一个进程, 一般也叫做 scotonext
- target_type:目标的类型,一般都是客体的上下文标签,当然进程也是可以做为客体,比如一个进程给另外一个进 程发送信号,获取另外一个进程 pid 等, object_class:类别,目标(客体)是哪种类别,主要有 file,dir,socket, process, SEAndroid 还有 binder 等,在这些 基础上又细分出设备字符类型(chr_file),链接文件(lnk_file)等。
- perm_set:权限集合, 如 read, write 等。
更加复杂的格式为:
js
rule_name {source_type1 source_type2 ...} { target_type1 target_type2 ... } :object_class {perm_set}
安全策略语句格式涉及的相关内容定义在如下文件中:
- 源文件
文件路径 | 描述 |
---|---|
system/sepolicy/private/security_classes | 描述 te 策略文件中的 object_class 目标类型,如 class file,class dir |
system/sepolicy/private/access_vectors | 描述 te 策略文件中的 perm_set 权限集合.如 common file{ioctl read ...} |
system/sepolicy/public/te_macros | 包含了 Selinux 所有的宏定义文件,如最常用的 init_daemon_domain() |
system/sepolicy/public/global_macros | 定义了各种 object_class 和 permissions 的集合名,如 devfile_class_set,ipc_class_set 等, 权限集合如 x_file_perms,里面都是宏定义 |
system/sepolicy/public/attributes | 定义系统中各种属性,属性是一种可进行关联的, 如 attribute domain;attribute exec_type; |
system/sepolicy/*/file.te | 定义特定 type 的文件, 目录 vendor,private,public,prebuilts 都有该文件, |
- 根文件系统文件
文件路径 | 描述 |
---|---|
/system/etc/selinux | 平台策略文件编译之后生成到根文件系统中存放目录 |
/vendor(odm/product)/etc/selinux | 厂商(odm/product)定制策略文件编译之后生成到根文件系统中存放目录 |
/sys/fs/selinux | 内核为用户空间提供了 selinux 文件系统的挂载路径 |
rule_name
内容 | 描述 |
---|---|
allow | 允许权限操作,必须显示说明 |
allowaudit | 允许记录,重点是记录, 允许权限规则必须使用 allow,此处允许记录是指允许对权限检查成功和失败的结果进行记录 |
dontaudit | 对权限检查失败的操作不做记录 |
neverallow | logd dev_type:blk_file { read write };此处意思是在其他策略文件中就不要出现 allow logd dev_type:blk_file { read write }的语句,否则编译会报错。类似于在考试出题时,上级要求作文中不允许写议论文, 而在下级自主出题时,就出现了允许写议论文的要求, 这样这个"允许"就明显不合规。 |
object_class
object_class 类别作用是用于描述目标(客体)的类别,如 dir, file 类别, 不同的文件都可以设置相同的上下文,加上 object_class 类别之后,可以更精准的去描述目标(客体), 比如/data/test/(目录)和 /data/testfile(普通文件),都是可以设置安全上下文为 u:object_r:test:s0, 假如 allow 语句只想给定访问 testfile 普通文件时权限,而不想放开目录的权限, 就可以在在 allow 语句中将 object_class 设置进来, 如:
js
allow myprocess test:file read;
一般 object_class 都需要在 system/sepolicy/private/security_classes 中声明, 而对应 classs 涉及到的具体权限项目是在 system/sepolicy/private/access_vectors 中文件中声明的,如:
js
# file-related classes
class filesystem
class file
class dir
class fd
class lnk_file
class chr_file
class blk_file
class sock_file
class fifo_file
# network-related classes
class socket
class tcp_socket
class udp_socket
class rawip_socket
class node
class netif
.... class binder
.... # Property service
class property_service # userspace
# Service manager
class service_manager # userspace
perl
common file
{
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
map
unlink
link
rename
execute
quotaon
mounton
}
#
# Define a common prefix for socket access vectors.
#
common socket
{
# inherited from file
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
map
# socket-specific
bind
connect
listen
accept
getopt
setopt
shutdown
recvfrom
sendto
name_bind
}
perm_set
Perm set 表示操作权限集合,system/sepolicy/public/global_macros 会定义权限集宏定义, 如例子:
js
define(`x_file_perms', `{ getattr execute execute_no_trans map }')
define(`r_file_perms', `{ getattr open read ioctl lock map }')
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 }')
当然在 perm_set 这个语法位置上也是可以使用上一节中的 access_vectors 文件中出现的权限字符串。
type
- 在安全策略格式中type为source_type和target_type
js
rule_name source_type target_type :object_class perm_set
- 在安全上下文格式中type为第三项
js
user:role:type:mls
其实安全策略中的source_type和target_type就是安全上下文中的第三项,type的定义在如下相关文件中:
文件 | 描述 |
---|---|
system/sepolicy/public/file.te | 系统中各种文件类型的声明, 如 type sockfs, fs_type; |
system/sepolicy/public/device.te | 系统中各种设备类型的声明, 如 type camera_device, dev_type; 很多时候我们在开发驱动的时候,会新建设备节点如一个串口设备,这个设备节点的 type 一般都习惯放在 device.te 中声明 |
system/sepolicy/public/service.te | 声明系统中各种后台服务,如 type audioserver_service, service_manager_type; |
各个进程的 te 文件 | 如 zygote.te,init.te 等文件, 主要是用于声明 domain, 如 type vold, domain; |
type的完整格式如下:
js
type type_id [, attribute_id,][attribute_id,] ...;
方括号表示可选, attribute_id 表示和 type_id 进行关联的属性,可以是多个,之间用逗号隔开
例子如下:
js
type device, dev_type, fs_type;
此处表示声明(或理解新建)一个 device 类型, 该类型关联文件系统属性,以及设备属性
type graphics_device, dev_type;
此处表示声明(或理解新建)一个 graphics_device 类型, 该类型关联另外一个设备属性
attribute
直接翻译过来叫属性, 主要用于在声明 type 的时候进行关联。 属性类似一个组,type 关联属性之后, 当前新 增的 type 就可以加入到这个属性组中,这个组拥有的权限, 新增的 type 也就自然拥有了这个权限),举例:
js
type system_server, coredomain, domain;
type performanced, domain, coredomain;
{ system_server performanced }都关联(加入)了 domain 属性组
文件 public/domain.te +66
# Device accesses. allow domain device:dir search;
就可以等同于(伪代码):
allow { system_server performanced } device:dir search;
attribute其实就是为了方便书写te规则,我们可以用attribute找到所有关联了属性的type集合,而不用每一个都写出来。
属性语法:
js
attribute attribute_id;
声明属性的文件 system/sepolicypublic/attributes:
js
attribute dev_type;
attribute domain;
其实 attribute 也可以理解成一种 type(集合),意味着属性也是可以用在安全上下文和安全策略中, 如:
js
u:object_r:某属性:s0
allow 某属性 device:dir search;
allow adbd 某属性:dir search;
不同属性所拥有的权限会在不同的 te 的文件中声明, 比如属性 domain 会在以下文件中定义 system/sepolicy/public/domain.te:
js
allow domain self:fd use;
allow domain proc:dir r_dir_perms;
allow domain proc_net_type:dir search;
r_dir_file(domain, self)
allow domain self:{ fifo_file file } rw_file_perms;
allow domain self:unix_dgram_socket { create_socket_perms sendto };
allow domain self:unix_stream_socket { create_stream_socket_perms connectto };
宏
ini
#public/adbd.te文件
# adbd seclabel is specified in init.rc since
# it lives in the rootfs and has no unique file type.
type adbd, domain;
type adbd_exec, exec_type, file_type, system_file_type;
# Only init is allowed to enter the adbd domain via exec()
neverallow { domain -init } adbd:process transition;
neverallow * adbd:process dyntransition;
# Allow adbd start/stop mdnsd via ctl.start
set_prop(adbd, ctl_mdnsd_prop)
在如上示例的te文件中有set_prop,这就是一个宏,其完整定义如下:
js
#public/te_macros文件
# set_prop(sourcedomain, targetproperty)
# Allows source domain to set the
# targetproperty.
#
define(`set_prop', `
unix_socket_connect($1, property, init)
allow $1 $2:property_service set;
get_prop($1, $2)
')
大部分宏都是在 system/sepolicy/public/te_macros 中定义:
宏 | 描述 |
---|---|
type_transition source_type target_type : class default_type 例子: type_transition vold storage_file:dir storage_stub_file; 场景解释: Vold 进程会在打了 vold storage_file 标签(上下文)的目录中创建一个文件时, 子文件的上下文应该变成 storage_stub_file 标签(上下文) | 当主体进程域source_type对target_type类型的客体进行 class 定义的操作时,主体或者客体会切换到 default_type。意味着该声明是可以描述主体和客体的类型切换的, 比如客体文件类型切换, 或者主体进程域的 type 切换(上下文切换), 注意:此时只是描述了从哪里切换到哪里的问题。 |
domain_trans(olddomain, target_type, newdomain) 缩写: DT 例子: domain_trans(vold, shell_exec, blkid); | 定义允许在 olddomain 操作 target_type 时,从 olddomain 到 newdomain 转化规则。主要针对进程上下文的转换,大部分进程都是从init 进程调动 fork 和 execv,此时子进程就需要从 init 的进程安全上下文切换到对应的子进程自己应有的进程安全上下文。此时就需要用该宏来切换。注意点, 该宏重点只是申请允许切换。也可以理解为 type_transition()申请权限。 |
domain_auto_trans(olddomain, target_type, newdomain) 例子: domain_auto_trans(init, charger_exec, charger)当 init 进程在执行 charger_exec 可执行程序时,自动切换到 charger进程上下文。 | 等同于 type_transition 和 domain_trans 两个,既描述从哪里切换到哪里, 也描述切换所需要的允许规则。 |
file_type_trans(domain, dir_type, file_type)缩写: TT | 在 某 个 domain( 进 程 ) 在 安 全上 下 文 为dir_type 类型的目录下创建子文件, 子文件会自动继承父目录的文件安全上下文,如果想子文件的上下文创建时变为 file_type, 可通过 file_type_trans 申请权限, 注意,该宏重点是也只是申请允许切换的权限。 |
file_type_auto_trans(domain, dir_type, file_type)例子:一般在实际例子用的少 | 等同于 type_transition 和 file_type_trans 两个,既描述从哪里切换到哪里, 也描述切换所需要的允许规则。 |
r_dir_file(domain, type) | 允许 domain 访问安全上下文为 type 的目录下的文件和符号连接文件 |
unconfined_domain(domain) | 使指定的 doamin 获得更多的许可(这一宏只是 让 指 定 的 进 程 安 全 上 下 文 继 承mlstrustedsubject(在作为主体时, 能绕过MLS 检查相关)和 unconfineddomain 属性(在作为客体时, 能绕过 MLS 检查相关)) |
binder_use(domain) | 允许 domain 使用 binder 通信 |
binder_call(clientdomain, serverdomain) | 允许clientdomain通过binder调用serverdomain |
binder_service(domain) | 定义 domain 作为 binder service 的相关许可 |
app_domain(domain) | 为 domain 定义所有 android app 所需的基本许可(实际上是继承 appoamin 属性) |
net_domain(domain) | 为 doamin 定义访问网络所需的基本许可(实际上是继承 netdoamin 属性) |
set_prop(domain, prop) | 允许 domain 对 prop 属性有设置权限 |
get_prop(domain, prop) | 允许 domain 对 prop 属性有获取权限 |
编译
整个编译过程大致如下:
Android系统将源码部分分为平台和非平台,因为Android系统的开发除了google,还有硬件厂商(包括vendor、odm、oem等),google自己的代码为平台代码,硬件厂商的代码为非平台代码,如下图所示
所以SEAndroid的源码位置分为平台和非平台 2 处存放。
这些文件会经历如下编译步骤:
- transform-policy-to-conf(m4 宏处理)---> 得到 conf 宏展开并合并文件
- checkpolicy 编译 conf 文件--->得到 cil 通用中间语言文件
- secilc 工具编译 cil 文件---> 得到目标文件 precompiled_sepolicy 二进制
这个编译过程使用到的相关编译工具位于external/selinux目录:外部 SELinux 项目,用于构建主机环境中编译 SELinux 政策和标签所需的各种工具,如提供 libselinux 库函数,checkpolicy 编译器(policy.conf 编译成 cil 文件),secilc 编译器(cil 文件编译成二进制),getenfore/setenfore 命令。
js
external/selinux/
├── Android.bp
├── checkpolicy : policy.conf 编译成 cil 文件
├── CleanSpec.mk
├── dbus
├── gui
├── libselinux : 提供 libselinux 库函数, getenfore/setenfore 命令
├── libsemanage
├── libsepol: 提供检查上下文语法的工具和库函数。
├── Makefile
├── mcstrans
├── MODULE_LICENSE_GPL
├── NOTICE
├── OWNERS
├── policycoreutils: 提供 load_policy,chcon 等命令
├── prebuilts : 提供 audit2allow,sediff 等命令
├── python
├── README
├── README.android
├── restorecond
├── sandbox
├── scripts
├── secilc : cil 文件编译成二进制
└── semodule-utils
最终这些文件会打包到根文件系统的如下目录:
- /system/etc/selinux
- /vendor/etc/selinux
- /odm/etc/selinux
- /oem/etc/selinux
这些文件在后续的权限检查判断过程中会被:安全服务(Security Server)相关模块以及内核LSM所使用到。
Init初始化之SEAndroid
初始化代码位于源码目录system/core/init/selinux.cpp
java
int SetupSelinux(char** argv) {
SelinuxInitialize();
}
void SelinuxInitialize() {
Timer t;
LOG(INFO) << "Loading SELinux policy";
//SEAndroid安全策略加载
if (!LoadPolicy()) {
LOG(FATAL) << "Unable to load SELinux policy";
}
//获取selinux模式,其实就是从/sys/fs/selinux/enforce文件中去读取。
bool kernel_enforcing = (security_getenforce() == 1);
//从内核启动的参数中androidboot.selinux=xxx获取用户需要设置的selinux模式
bool is_enforcing = IsEnforcing();
//如果当前模式和用户设置的不一直, 那么就重新设置
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
}
}
//写入0,表示权限检查由内核来决定,1表示由用户空间来决定。默认android都写0
if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
}
// init's first stage can't set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}
从Android8.0之后,因为Project Treble,system和vendor策略分离,所以Android 10上走的是LoadSplitPolicy
java
bool LoadPolicy() {
return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}
java
bool LoadSplitPolicy() {
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
bool use_userdebug_policy =
((force_debuggable_env && "true"s == force_debuggable_env) &&
AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);
if (use_userdebug_policy) {
LOG(WARNING) << "Using userdebug system sepolicy";
}
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
// must match the platform policy on the system image.
std::string precompiled_sepolicy_file;
// use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
// Thus it cannot use the precompiled policy from vendor image.
if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {
unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
if (fd != -1) {
if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {
LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
return false;
}
return true;
}
}
// No suitable precompiled policy could be loaded
LOG(INFO) << "Compiling SELinux policy";
// We store the output of the compilation on /dev because this is the most convenient tmpfs
// storage mount available this early in the boot sequence.
char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
if (compiled_sepolicy_fd < 0) {
PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
return false;
}
// Determine which mapping file to include
std::string vend_plat_vers;
if (!GetVendorMappingVersion(&vend_plat_vers)) {
return false;
}
std::string plat_mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");
std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
product_policy_cil_file.clear();
}
std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil");
if (access(product_mapping_file.c_str(), F_OK) == -1) {
product_mapping_file.clear();
}
// vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
// nonplat_sepolicy.cil.
std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
// For backward compatibility.
// TODO: remove this after no device is using nonplat_sepolicy.cil.
vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
plat_pub_versioned_cil_file.clear();
} else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
return false;
}
// odm_sepolicy.cil is default but optional.
std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
odm_policy_cil_file.clear();
}
const std::string version_as_string = std::to_string(SEPOLICY_VERSION);
// clang-format off
std::vector<const char*> compile_args {
"/system/bin/secilc",
use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N",
"-c", version_as_string.c_str(),
plat_mapping_file.c_str(),
"-o", compiled_sepolicy,
// We don't care about file_contexts output by the compiler
"-f", "/sys/fs/selinux/null", // /dev/null is not yet available
};
// clang-format on
if (!product_policy_cil_file.empty()) {
compile_args.push_back(product_policy_cil_file.c_str());
}
if (!product_mapping_file.empty()) {
compile_args.push_back(product_mapping_file.c_str());
}
if (!plat_pub_versioned_cil_file.empty()) {
compile_args.push_back(plat_pub_versioned_cil_file.c_str());
}
if (!vendor_policy_cil_file.empty()) {
compile_args.push_back(vendor_policy_cil_file.c_str());
}
if (!odm_policy_cil_file.empty()) {
compile_args.push_back(odm_policy_cil_file.c_str());
}
compile_args.push_back(nullptr);
if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
unlink(compiled_sepolicy);
return false;
}
unlink(compiled_sepolicy);
LOG(INFO) << "Loading compiled SELinux policy";
if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
return false;
}
return true;
}
LoadSplitPolicy函数功能如下:
- 查找上一节"编译"产物中是否有precompiled_sepolicy二进制文件
- 如果没有开始Compiling SELinux policy,它会收集编译产物中contexts文件和cil文件,然后将其编译后写入/dev/sepolicy.XXXXXX
查找precompiled_sepolicy
java
bool FindPrecompiledSplitPolicy(std::string* file) {
file->clear();
// If there is an odm partition, precompiled_sepolicy will be in
// odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
static constexpr const char vendor_precompiled_sepolicy[] =
"/vendor/etc/selinux/precompiled_sepolicy";
static constexpr const char odm_precompiled_sepolicy[] =
"/odm/etc/selinux/precompiled_sepolicy";
if (access(odm_precompiled_sepolicy, R_OK) == 0) {
*file = odm_precompiled_sepolicy;
} else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
*file = vendor_precompiled_sepolicy;
} else {
PLOG(INFO) << "No precompiled sepolicy";
return false;
}
std::string actual_plat_id;
if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) {
PLOG(INFO) << "Failed to read "
"/system/etc/selinux/plat_sepolicy_and_mapping.sha256";
return false;
}
std::string actual_product_id;
if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
&actual_product_id)) {
PLOG(INFO) << "Failed to read "
"/product/etc/selinux/product_sepolicy_and_mapping.sha256";
return false;
}
std::string precompiled_plat_id;
std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256";
if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) {
PLOG(INFO) << "Failed to read " << precompiled_plat_sha256;
file->clear();
return false;
}
std::string precompiled_product_id;
std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256";
if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) {
PLOG(INFO) << "Failed to read " << precompiled_product_sha256;
file->clear();
return false;
}
if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id ||
actual_product_id.empty() || actual_product_id != precompiled_product_id) {
file->clear();
return false;
}
return true;
}
FindPrecompiledSplitPolicy函数功能:
- 从/odm/etc/selinux/和/vendor/etc/selinux/依次查找precompiled_sepolicy,找到哪个就使用
- 如果找到文件,校验文件的sha256,这个校验文件在编译的时候也是会一并生成的
然后文件找到后续会调用selinux_android_load_policy_from_fd函数(后面会讲)。
编译precompiled_sepolicy
如果没有找到二进制precompiled_sepolicy文件,就开始编译
java
LOG(INFO) << "Compiling SELinux policy";
std::vector<const char*> compile_args {
"/system/bin/secilc",
use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N",
"-c", version_as_string.c_str(),
plat_mapping_file.c_str(),
"-o", compiled_sepolicy,
// We don't care about file_contexts output by the compiler
"-f", "/sys/fs/selinux/null", // /dev/null is not yet available
};
使用/system/bin/secilc编译工具进行编译 编译所需源文件放在compile_args中,最后会fork一个进程进行单独编译,并等待进程处理完返回
java
if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
unlink(compiled_sepolicy);
return false;
}
unlink(compiled_sepolicy);
当编译玩成后会调用函数selinux_android_load_policy_from_fd。
selinux_android_load_policy_from_fd
java
int selinux_android_load_policy_from_fd(int fd, const char *description)
{
set_selinuxmnt(SELINUXMNT);
....
map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
....
rc = security_load_policy(map, sb.st_size);
return 0;
}
首先会设置一个SELINUXMNT宏,它定义如下
c
#/external/selinux/libselinux/src/policy.h
#define SELINUXMNT "/sys/fs/selinux"
#/external/selinux/libselinux/src/init.c
void set_selinuxmnt(const char *mnt)
{
selinux_mnt = strdup(mnt);
}
结果就是selinux_mnt="/sys/fs/selinux"
mmap将fd进行了映射,根据前面的分析fd指向如下 2 处文件之一:
- /odm/etc/selinux/或/vendor/etc/selinux/的precompiled_sepolicy二进制文件
- 文件/dev/sepolicy.XXXXXX 这两个文件的内容其实是一样的,都是编译后提供给内核使用的二进制数据。
c
#/external/selinux/libselinux/src/load_policy.c
int security_load_policy(void *data, size_t len)
{
char path[PATH_MAX];
int fd, ret;
if (!selinux_mnt) {
errno = ENOENT;
return -1;
}
snprintf(path, sizeof path, "%s/load", selinux_mnt);
fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0)
return -1;
ret = write(fd, data, len);
close(fd);
if (ret < 0)
return -1;
return 0;
}
最后会将二进制数据写入/sys/fs/selinux/load文件中,后续内核会读取这些数据作为安全策略进行权限的判断工作(后内核代码就不进一步分析了)。