1. DAC和MAC
在了解SELinux之前,我们先来了解一下Linux的两种访问控制策略:DAC和MAC
1.1 Linux DAC
DAC,自主访问控制(Discretionary Access control)。系统只提供基本的验证, 完整的访问控制由开发者自己控制。
DAC将资源访问者分成三类:Owner、Group、Other 。
将访问权限也分成三类:read、write、execute
资源针对资源访问者设置不同的访问权限。访问者通常是各个用户的进程,有自己的uid/gid,通过uid/gid 和文件权限匹配, 来确定是否可以访问。DAC机制下,每一个用户进程默认都拥有该用户的所有权限。
DAC 有两个严重问题:
问题一:
因为Root用户是拥有所有权限的,所以DAC对Root用户的限制是无效的。并且在Linux Kernel 2.1以后,Linux将Root权限根据不同的应用场景划分成许多的Root Capabilities, 普通用户也可以被设置某个Root Capability。普通用户如果被设置了CAP_DAC_OVERRIDE, 也可以绕过 DAC 限制。
问题二:
用户进程拥有该用户的所有权限,可以修改/删除该用户的所有文件资源, 难以防止恶意软件。
可见,DAC 有明显的缺陷,一旦被入侵,取得Root权限的用户进程就可以无法无天,胡作非为,早期android版本就深受其害。
1.2 Linux MAC
MAC, 强制性访问控制(Mandatory Access control)。 系统针对每一项访问都进行严格的限制, 具体的限制策略由开发者给出。
Linux MAC 针对DAC 的不足, 要求系统对每一项访问, 每访问一个文件资源都需要根据已经定义好了的策略进行针对性的验证。系统可以针对特定的进程与特定的文件资源来进行权限的控制。即使是root用户,它所属的不同的进程,并不一定能取得root权限,而得要看事先为该进程定义的访问限制策略。如果不能通过MAC 验证,一样无法执行相关的操作。
与DAC相比,MAC访问控制的“主体”变成了“进程”而不是用户。这样可以限制了Root 权限的滥用,另外要求对每一项权限进行了更加完整的细化, 可以限制用户对资源的访问行为。
SELinux就是目前最好的MAC机制,也是目前的行业标准。
2. SELinux概述
2.1 SELinux简介
SELinux,安全增强Linux(Security-Enhanced Linux),是由美国国家安全局(NSA)发起, 多个非营利组织和高校参与开发的强制性安全审查机制(Mandatory Access control,简称MAC)。SELinux最早于2000年12月采用GPL许可发布。目前,Linux Kernel 2.6 及以上的版本都已经集成了SELinux。
2.2 SELinux模式
SELinux 分成三种模式:
- Disabled(关闭),Selinux并没有实际运行。
- Permissve(宽容模式),宽容模式只通过Kernel Audit System 记录LOG, 但不拦截访问。
- Enfocing mode(强制模式),强制模式在记录LOG 的同时,还会拦截访问。
Android 5.x及以上强制开启,因此,disabled(关闭)模式并没有什么用了。 通常在调试时,我们会启用Permissve(宽容模式), 以便尽可能的发现多的问题, 然后一次修正。 在量产时启用Enfocing mode(强制模式)来保护系统。
查看SELinux模式:adb shell getenforce
设置SELinux模式:adb shell setenforce 1 //0是Permissve,1是Enfocing
2.3 SELinux 在Android上的主要变更历史
- Android 4.3 引入SELinux,但采用宽容模式(Permissive)
- Android 4.4 部分强制模式(Enforcing), 针对netd, installd, zygote, vold 四个原本具有root 权限的process, 以及它们fork 出的子进程启用强制模式(Enforcing)。
- Android 5.x 及更高版本,所有域均处于强制模式(Enforcing)。
- Android 8.0 Google 为了实现vendor.img、system.img可独立升级,不相互影响,将Vendor和System分离,大幅度的增强了SElinux 的限制, 限制System/Vendor 之间的交叉使用。
3. SELinux 基础
3.1 SELinux 基本概念
SELinux 的访问控制示意图:
- Subject:访问主体,通常是指能够产生访问行为的对象, Linux 中通常是一个process。
- Object :访问对象, Linux 中通常是文件,但process 也可能是一个访问对象, 比如另外一个进程对它发送Signal, 比如去对它进行Ptrace 操作。
- Object Manager 对象管理器, 即可以知道Subject 需要访问哪些资源,并且触发验证机制
- Security Server 即安全服务器, 用来验证某个Subject 是否可以真正的访问某个Object, 而这个验证机制是基于定义好的Security Policy.
- Security Policy 是一种描述SELinux Policy 的语言.
- Access Vector Cache (AVC) 是访问缓存, 用来记录以往的访问验证情况, 以便提供效率,快速处理.
通常我们开发的过程中,就是配置Subject、Object、Security Policy。
3.2 Security Context
SELinux 给Linux 的所有对象都分配一个安全上下文(Security Context), 描述成一个标准的字符串。
3.2.1 安全上下文基本概念
安全上下文的标准格式: user:role:type[:range]
- User: 用户, 非Linux UID,在SEAndroid中,只定义了一个SElinux用户那就是:u
- Role: 角色,一个user可以属于多个role,不同的role具有不同的权限。在SEAndroid中,定义了两个role: r 和 object_r。文件或属性的角色通常是:object_r,而进程的角色通常是:r。
- Type: Subject或者Object的类型。每一个对象都有一个类型(Class),每个类型(Class)都会根据实际情况定义权限类型项, 如: read, write, exec, ioctl, append 等等。对于作为Object的文件, 它是File类型(Class)。 而对于作为Subject的进程来说,它的类型就是Domain。
- Range: Multi-Level Security(MLS)的级别。MLS将系统的进程和文件进行了分级,MLS的low_level 是s0, high level 是s0:c0.c1023。
3.2.2 Security Label
Security Label 用来绑定被访问资源和安全上下文,描述它们的对应关系。标准格式为:resource security_context。即:res user:role:type[:range]。这里也可以使用通配符,例如 net.就可以绑定所有以net.开头的属性,除此之外,还有类似正则表达式的*、?等等通配符。Security Label 都定义在type_contexts当中,例如file的定义在file_contexts中,service定义在service_contexts中,property定义在property_contexts中。
举例:
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
service_contexts:
accessibility u:object_r:accessibility_service:s0
account u:object_r:account_service:s0
activity u:object_r:activity_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
appops u:object_r:appops_service:s0
appwidget u:object_r:appwidget_service:s0
3.2.3 查看安全上下文
查看进程安全上下文:ps -AZ。例如,查看Settings进程的安全上下文,ps -AZ | grep settings:
u:r:system_app:s0 system 1381 585 4234504 201072 0 0 S com.android.settings
查看文件安全上下文:ls -Z。例如,查看文件build.prop的安全上下文:
u:object_r:system_file:s0 build.prop
3.3 Type Enforcement Access Control
Type Enforcement (TE) 是根据Security Context中的 type 进行权限审查, 审查 subject type 对 object type 的某个class 类型中某种permission 是否具有访问权限,是目前使用最为广泛的MAC 审查机制, 简单易用。
3.3.1 Type Enforcement基本概念
TE控制语句格式 : rule_name source_type target_type : class perm_set
- rule: 控制类型, 分为:allow 以及 audit
- source_type:也叫subject,通常是domain,可以是一组subject,用{}包含起来。
- target_type: 代表请求的资源的类型,可以是一组target,用{}包含起来。
- class perm_set: 代表对资源访问的操作,perm_set可以是一组,用{}包含起来。
3.3.2 Type Enforcement规则
Type Enforcement规则说明:
- allow:赋予某项权限。
- auditallow:audit含义就是记录某项操作。默认SELinux只记录那些权限检查失败的操作。 auditallow则使得权限检查成功的操作也被记录。注意,allowaudit只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用allow语句。
- dontaudit:对那些权限检查失败的操作不做记录。
- neverallow:用来检查安全策略文件中是否有违反该项规则的allow语句
举个例子,logd.te、tombstoned.te中定义的TE规则:
allow logd runtime_event_log_tags_file:file rw_file_perms;
dontaudit domain runtime_event_log_tags_file:file { open read };
auditallow tombstoned anr_data_file:file { append write };
neverallow logd { app_data_file system_data_file }:dir_file_class_set write;
3.3.3 Attribute and Type
SELinux 中每一个进程或者文件都对应一个type, 而每一个type 都对应有一个或几个attribute。所有常见的attribute定义在以下文件中:
system/sepolicy/public/attributes
system/sepolicy/prebuilts/api/[build version]/public/attributes
system/sepolicy/prebuilts/api/[build version]/private/attributes
其中的[build version]即为android版本号,例如android O为28.0。常见的attribute定义:
######################################
# Attribute declarations
#
# All types used for devices.
# On change, update CHECK_FC_ASSERT_ATTRS
# in tools/checkfc.c
attribute dev_type;
# All types used for processes.
attribute domain;
# All types used for filesystems.
# On change, update CHECK_FC_ASSERT_ATTRS
# definition in tools/checkfc.c.
attribute fs_type;
# All types used for context= mounts.
attribute contextmount_type;
# All types used for files that can exist on a labeled fs.
# Do not use for pseudo file types.
# On change, update CHECK_FC_ASSERT_ATTRS
# definition in tools/checkfc.c.
attribute file_type;
# All types used for domain entry points.
attribute exec_type;
# All types used for /data files.
attribute data_file_type;
expandattribute data_file_type false;
# All types in /data, not in /data/vendor
attribute core_data_file_type;
expandattribute core_data_file_type false;
# All types in /vendor
attribute vendor_file_type;
# All types used for procfs files.
attribute proc_type;
expandattribute proc_type false;
# All types used for sysfs files.
attribute sysfs_type;
# All types use for debugfs files.
attribute debugfs_type;
Type对应一个或者几个attribute,Type的定义格式:
type type_name, attribute1, attribute2;
Type的定义通常分散在各个te文件中。例如,常用普通文件的type定义在file.te中:
# Default type for anything under /system.
type system_file, file_type;
# Default type for anything under /data.
type system_data_file, file_type, data_file_type, core_data_file_type;
# /data/data subdirectory for system UID apps.
type system_app_data_file, file_type, data_file_type, core_data_file_type, mlstrustedobject;
# Default type for anything under /data/vendor{_ce,_de}.
type vendor_data_file, file_type, data_file_type;
3.3.4 Classes and Permissions
SEAndroid对于不同的资源类型,定义了不同的Class。比如普通的file、socket等等,比如SELinux 使用的security, 比如针对每个process 参数的process 等定义相关的class。这些class,每一个class 都有相对应的permissions。 比如file 就有 read, write, create, getattr, setattr, lock, ioctl 等等. 比如process 就有fork, sigchld, sigkill, ptrace, getpgid, setpgid 等等。这些相关的class, 以及他们具有那些Permissions都定义在以下文件中:
system/sepolicy/private/access_vectors
system/sepolicy/reqd_mask/access_vectors
system/sepolicy/prebuilts/api/版本号/private/access_vectors
例如:
#
# Define a common prefix for file access vectors.
#
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
}
定义完之后,在以下对应的security_classes 文件中声明定义的classes。
system/sepolicy/private/security_classes
system/sepolicy/reqd_mask/security_classes
system/sepolicy/prebuilts/api/版本号/private/security_classes
例如:
#
# Define the security object classes
#
# Classes marked as userspace are classes
# for userspace object managers
class security
class process
class system
class capability
# 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
注意,Classes 和Permissions的定义与Kernel 中相关API是强相关的,普通用户严禁修改。
3.4 Domain and Object Transitions
3.4.1 Domain Transitions
在SELinux 中, 我们通常称一个进程是一个domain, 一个进程fork 另外一个进程并执行(exec) 一个执行档时, 我们往往会涉及到domain 的切换. 比如init 进程, SELinux 给予了它很大的权限, 而它拉起的服务, 我们要限制这个服务的权限,于是就涉及到从一个domain 切换到另外一个domain, 不然默认就使用init 进程的domain.
在SELinux 里面有专门的一条语法: type_transition statement.
在准备切换前我们先要确保有相关的权限操作:
- 源domain 必须有权限切换到这个目标domain。(The source domain has permission to transition into the target domain.)
- 源domain 必须要有这个执行档的执行权限。(The application binary file needs to be executable in the source domain.)
- 这个执行档必须是目标domain 的入口。(The application binary file needs an entry point into the target domain.)
如下面的demo, init 拉起apache 并且切换到 apache 的domain.
(1). 首先,你得让init_t域中的进程能够执行type为apache_exec_t的文件
allow init_t apache_exec_t : file {read getattr execute};
(2). 然后,你还得告诉SELinux,允许init_t做DT切换以进入apache_t域
allow init_t apache_t : process transition;
(3). 然后,你还得告诉SELinux,切换入口(对应为entrypoint权限)为执行apache_exec_t类型 的文件
allow apache_t apache_exec_t : file entrypoint;
(4).最后,Domain Transition
type_transition init_t apache_exec_t : process apache_t;
可以看到,整个domain切换过程写起来非常麻烦。因此,Google 为了使用方便, 在system/sepolicy/public/te_macros 文件中定义了宏:
#####################################
# domain_trans(olddomain, type, newdomain)
# Allow a transition from olddomain to newdomain
# upon executing a file labeled with type.
# This only allows the transition; it does not
# cause it to occur automatically - use domain_auto_trans
# if that is what you want.
#
define(`domain_trans', `
# Old domain may exec the file and transition to the new domain.
allow $1 $2:file { getattr open read execute map };
allow $1 $3:process transition;
# New domain is entered by executing the file.
allow $3 $2:file { entrypoint open read execute getattr map };
# New domain can send SIGCHLD to its caller.
ifelse($1, `init', `', `allow $3 $1:process sigchld;')
# Enable AT_SECURE, i.e. libc secure mode.
dontaudit $1 $3:process noatsecure;
# XXX dontaudit candidate but requires further study.
allow $1 $3:process { siginh rlimitinh };
')
#####################################
# domain_auto_trans(olddomain, type, newdomain)
# Automatically transition from olddomain to newdomain
# upon executing a file labeled with type.
#
define(`domain_auto_trans', `
# Allow the necessary permissions.
domain_trans($1,$2,$3)
# Make the transition occur by default.
type_transition $1 $2:process $3;
')
#####################################
# init_daemon_domain(domain)
# Set up a transition from init to the daemon domain
# upon executing its binary.
define(`init_daemon_domain', `
domain_auto_trans(init, $1_exec, $1)
')
我们可以使用这些宏来完成domain切换。
举例:
kernel启动init进程切换domain:
domain_auto_trans(kernel, init_exec, init)
init启动netd、vold、zygote、installd切换domain:
init_daemon_domain(netd)
init_daemon_domain(vold)
init_daemon_domain(zygote)
init_daemon_domain(installd)
3.4.2 Target Transitions
一个进程创建在一个目录下创建文件时, 默认是沿用父目录的Security Context, 如果要设置成特定的Label, 就必须进行Object Transitions.
同样是使用:type_transition statement.
对应的必须有两个前提条件:
- 源domain 必须有在这个目录下添加文件的权限。(The source domain needs permission to add file entries into the directory)
- 源domain 必须有在这个目录下创建以这个Security Context 为Label 的文件权限。(The source domain needs permission to create file entries)
下面是一个demo, ext_gateway_t 这个domain 在类型为in_queue_t 的目录下,创建类型为 in_file_t 的文件.
(1). 首先,你得让ext_gateway_t 对in_queue_t 目录具备访问权限
allow ext_gateway_t in_queue_t : dir { write search add_name };
(2). 然后,你还得告诉SELinux,允许ext_gateway_t 访问in_file_t的文件
allow ext_gateway_t in_file_t : file { write create getattr };
(3).最后,Object Transition
type_transition ext_gateway_t in_queue_t : file in_file_t;
同样的,为了方便使用,Google 也在system/sepolicy/public/te_macros 文件中定义了宏:
#####################################
# file_type_trans(domain, dir_type, file_type)
# Allow domain to create a file labeled file_type in a
# directory labeled dir_type.
# This only allows the transition; it does not
# cause it to occur automatically - use file_type_auto_trans
# if that is what you want.
#
define(`file_type_trans', `
# Allow the domain to add entries to the directory.
allow $1 $2:dir ra_dir_perms;
# Allow the domain to create the file.
allow $1 $3:notdevfile_class_set create_file_perms;
allow $1 $3:dir create_dir_perms;
')
#####################################
# file_type_auto_trans(domain, dir_type, file_type)
# Automatically label new files with file_type when
# they are created by domain in directories labeled dir_type.
#
define(`file_type_auto_trans', `
# Allow the necessary permissions.
file_type_trans($1, $2, $3)
# Make the transition occur by default.
type_transition $1 $2:dir $3;
type_transition $1 $2:notdevfile_class_set $3;
')
使用举例:
file_type_auto_trans(factory, system_data_file, factory_data_file)
4. SELinux实战
4.1 sepolicy存放目录
android O 以前sepolicy 集中放在boot image 。前面提到SELinux在Android的主要变更历史时,有提到android O 开始,Google将system image 和 vendor image 分离。因此,sepolicy 也相应的被分离存放到system image 以及 vendor image。与system 相关的sepolicy 就存放system image, 与SoC vendor 相关的sepolicy 就存放在vendor image。
4.1.1 AOSP sepolicy存放目录
对于原生AOSP,Google 设定了不同的存放目录, 以便进行分离, 以Google 默认的sepolicy 为例,sepolicy主目录为 /system/sepolicy,我们主要关注三个子目录:
public:android 和 vendor 共享的sepolicy 定义, 通常情况下, 意味着vendor 开发者可能为此新增一些权限. 一般system/vendor 共用的一些类型和属性的定义, neverallow 限制等会存放于此.
private:通常意义上的仅限于system image 内部的使用, 不对vendor 开放. 这个只会编译到system image 中.
vendor:它仅仅能引用public 目录下的相关定义, 这个只会编译到vendor image 中. 但它依旧可以对system image 里面的module 设定sepolicy(对应module 需要在public 下进行声明); 在很大程度上绕过了Google CTS 测试.
4.1.2 MTK平台
对于不同的平台,不同平台厂商也设定了不同的存放目录,以MTK平台为例:
首先,根据不同的platform共用sepolicy、platform独有、project独有,分为:
- /device/mediatek/sepolicy :下面是ALL platform 都需要.
- /device/mediatek/[platform]/sepolicy :其中platform为某个具体的平台,如mt6763.
- /device/[customer]/[project]/sepolicy :客户和项目自定义配置, 客户可根据更新BroadConfig.mk 中BOARD_SEPOLICY_DIRS 以新增目录。
对应的,不同版本会导入不同目录下的sepolicy配置
- Basic 版本 导入 [common]|[platfrom]/basic
- Bsp 版本 导入 [common]|[platfrom]/basic & bsp
- TK 版本(一般情况) 导入 [common]|[platfrom]/basic & bsp & full
以mt6763平台为例,导入时:
[common] 路径为:/device/mediatek/sepolicy
[platfrom] 路径为:/device/mediatek/mt6763/sepolicy/
具体的定义在BoardConfig.mk文件中:
#SELinux Policy File Configuration
ifeq ($(strip $(MTK_BASIC_PACKAGE)), yes)
BOARD_SEPOLICY_DIRS += \
device/mediatek/mt6763/sepolicy/basic
endif
ifeq ($(strip $(MTK_BSP_PACKAGE)), yes)
BOARD_SEPOLICY_DIRS += \
device/mediatek/mt6763/sepolicy/basic \
device/mediatek/mt6763/sepolicy/bsp
endif
ifneq ($(strip $(MTK_BASIC_PACKAGE)), yes)
ifneq ($(strip $(MTK_BSP_PACKAGE)), yes)
BOARD_SEPOLICY_DIRS += \
device/mediatek/mt6763/sepolicy/basic \
device/mediatek/mt6763/sepolicy/bsp \
device/mediatek/mt6763/sepolicy/full
endif
endif
然后,basic、bsp、full又可以主要细分为:
- non_plat:仅用于vendor 目录的sepolicy 设定. 和Google vendor 目录类似.
- plat_private:仅用于system 目录的sepolicy 设定, 和Google 的private 目录类似.
- plat_public:同时可用于vendor/system 的sepolicy 设定, 和Google 的public 目录类似.
4.2 Sepolicy修改注意事项
Google 在system/sepolicy 中定义了相关的neverallow 规则, 对SELinux Policy 的更新进行了限制, 以防止开发者过度开放权限,从而引发安全问题。并且还会通过CTS测试检测开发者是否有违法相关的规则。
因此,我们需要注意以下几点:
- 不要直接修改system/sepolicy 下面的相关SELinux Policy, 使之保持与Google 一致, 特别是不要直接删除或者修改Google 定义的neverallow 规则。
- 添加的sepolicy按需要提供,需要多大权限就给多大权限,不可将权限过于放大。
- 遇到与Google 定义相违背之处, 只能绕道, 或者修改设计。
出现SELinux Policy Exception时常见的两种解决方案:
(1). 修改对应节点的SELinux Security Label, 为特定的Subject,如system_app、platform_app、priv_app,例如Settings,SystemUI等内置APP开启权限, 但严禁为untrsted app 开启权限。
(2). 通过system server service 或者 init 启动的service 读写操作, 然后app 通过binder/socket 等方式连接访问. 此类安全可靠, 并且可以在service 中做相关的安全审查, 推荐这种方法.
4.3 Sepolicy常用例子
4.3.1 添加由init进程启动的服务
情景: 定义由 init 进程启动的service, factory, 其对应的执行档是 /vendor/bin/factory。
(1). 在device/mediatek/mt6763/sepolicy/basic/non_plat 目录下创建一个factory.te , 然后将te文件加入编译,如放到这种指定目录下不需要额外配置,sytem/sepolicy/Android.mk中定义的build_policy函数会遍历指定目录导入te文件。
(2). 在factory.te 中定义factory类型,init 启动service 时类型转换,
type factory, domain;
type factory_exec, exec_type, file_type, vendor_file_type;
init_daemon_domain(factory)
(3). 在file_contexts中绑定执行档
/(system/vendor|vendor)/bin/factory u:object_r:factory_exec:s0
(4). 根据factory需要访问的文件以及设备, 定义其它的权限在factory.te 中.
#Purpose: For key and touch event
allow factory input_device:chr_file r_file_perms;
allow factory input_device:dir rw_dir_perms;
4.3.2 添加property
情景: 添加一个自定义的system property: persist.demo,并为platform_app设置读写权限
(1). 在property.te中定义system property类型
type demo_prop, property_type
(2). 在property_contexts中绑定system property的安全上下文。
persist.demo u:object_r:demo_prop:s0
(3). 在platform_app.te 中新增写权限,可以使用set_prop宏。
set_prop(platform_app, demo_prop)
(4). 在platform_app.te 中新增读权限,可以get_prop 宏。
get_prop(platform_app, demo_prop)
4.3.3 访问设备节点
情景: 有一个设备节点/dev/demo,有一个platform_app进程需要读写这个设备节点。
(1). 在device.te中定义device 类型
type demo_device dev_type;
(2). 在 file_contexts中绑定demo_device
/dev/demo u:object_r:demo_device:s0
(3). 在platform_app.te中,允许platform_app使用demo device 的权限
allow platform_app demo_device:chr_file rw_file_perms;
4.3.4 添加系统服务
情景: 有一个扩展的系统服务demo_service供APP调用。
(1). 在service.te 中定义service 类型
type demo_service, app_api_service, system_server_service, service_manager_type;
(2). 在service_contexts 中绑定service
demo u:object_r:demo_service:s0
(3). 在frameworks/base/core/java/android/content/Context.java中定义服务常量
public static final String DEMO_SERVICE = "demo";
(4). 在frameworks/base/core/java/android/app/SystemServiceRegistry.java中,参照其它系统服务注册demo_service
registerService(Context.DEMO_SERVICE, DemoManager.class,
new CachedServiceFetcher<DemoManager>() {
@Override
public DemoManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder binder = ServiceManager.getServiceOrThrow(Context.DEMO_SERVICE);
IDemoService service = IDemoService.Stub.asInterface(binder);
return new DemoManager(ctx.getOuterContext(), service);
}});
(5). 在frameworks/base/services/java/com/android/server/SystemServer.java中,启动DemoService,添加到service_manager进行管理。
try {
mSystemServiceManager.startService(DemoService.class);
} catch (Throwable e) {
reportWtf("starting DemoService", e);
}
(6). 最后一步,参考其它系统服务,实现DemoManager、DemoService,并定义如IDemoService等等的AIDL接口。
4.3.5 使用Local socket
情景: 一个native service 通过init 创建一个socket 并绑定在 /dev/socket/demo, 并且允许某些process 访问.
(1). 在file.te中定义socket 的类型
type demo_socket, file_type;
(2). 在file_contexts中绑定socket 的类型
/dev/socket/demo_socket u:object_r:demo_socket:s0
(3). 允许所有的process 访问,使用宏unix_socket_connect(clientdomain, socket, serverdomain)
unix_socket_connect(appdomain, demo, demo)
4.3.6 init fork新进程
(1). 在device/mediatek/mt6763/sepolicy/basic/non_plat目录下创建一个demo.te。
(2). 在demo.te 中定义demo 类型,init 启动service 时类型转换。并可以根据demo 需要访问的文件以及设备, 定义其它的权限在demo.te 中。
type demo, domain;
type demo_exec, exec_type, file_type;
init_daemon_domain(demo)
(3). 绑定执行档 file_context 类型
/vendor/bin/demo u:object_r:demo_exec:s0
(4). 创建demo的入口执行档demo_exec、并配置相应的权限。
4.4 SELinux常见问题分析
4.4.1 如何判断是否与SELinux 相关?
(1). 将SELinux 调整到Permissive 模式复测
使用eng/userdebug 版本,adb shell setenforce 0 将SELinux 模式调整到Permissive 模式,然后复测。如果还能复现问题,则与SELinux 无关; 如果原本很容易复现, 而Permissive mode 不能再复现, 那么就可能与SELinux相关。
(2). 查看LOG 中是否有标准的SELinux Policy Exception.
在Kernel LOG / Main Log 中查询关键字 "avc: denied" 看看是否有与目标进程相关的SELinux Policy Exception, 并进一步确认这个异常是否与当时的逻辑相关。
4.4.2 如何根据SELinux exception log配置权限
一般情况我们在符合Google sepolicy策略及neverallow策略的前提下,根据LOG中的内容,需要什么权限就加什么权限。例如LOG:
2020-03-27 14:11:02.596 1228-1228/com.android.systemui W/FaceIdThread: type=1400 audit(0.0:481): avc: denied { read } for name="als_ps" dev="tmpfs" ino=10279 scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:als_ps_device:s0 tclass=chr_file permissive=0
LOG说明如下:
2020-03-27 14:11:02.596:[time]。
1228-1228/com.android.systemui I/FaceIdThread:[uid]/[package] [log level]/[thread]。
type=1400:type=[1400|AVC|USER_AVC],1400表示SYSCALL,AVC表示kernel events,USER_AVC表示user-space object manager events。
audit(0.0:481): audit(time:serial_number)
avc: denied { read }: read是被拒绝的权限,即permission
for name="als_ps" dev="tmpfs" ino=10279 :访问目标的名称、设备节点等相关信息。
scontext=u:r:platform_app:s0:c512:主体的安全上下文,platform_app就是source type
tcontext=u:object_r:als_ps_device:s0:目标资源的安全上下文,als_ps_device就是target type
tclass=chr_file:目标资源的class类型即target class
permissive=0:当前的selinux模式,1表示permissive,0表示enforcing
一般我们需要重点关注的是四个:permission、source type、target type、target class
根据这四个就可以配置出的所需要的selinux权限:
allow [source type] [target type]: [target class] [permission]
例1:
03-27 03:45:22.632 2958 2958 W Camera: type=1400 audit(0.0:314): avc: denied { read } for name="u:object_r:graphics_debug_prop:s0" dev="tmpfs" ino=2649 scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:graphics_debug_prop:s0 tclass=file permissive=0
解决方案:
按正常的套公式,应该是这样修改platform_app.te,增加:
allow platform_app graphics_debug_prop:file r_file_perms;
这里我们利用system/sepolicy/te_macros中定义的宏get_prop:
define(`get_prop', `
allow $1 $2:file r_file_perms;
')
更多相关的宏定义请参考:system/sepolicy/public/te_macros。
所以最终简化后,修改platform_app.te,增加:
get_prop(platform_app, graphics_debug_prop)
例2:
03-27 14:11:02.596 1228-1228/com.android.systemui W/BackThread: type=1400 audit(0.0:481): avc: denied { read } for name="als_ps" dev="tmpfs" ino=10279 scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:als_ps_device:s0 tclass=chr_file permissive=0
解决方案:
修改platform_app.te增加:
allow platform_app als_ps_device:chr_file r_file_perms;
4.3.2 修改或者增加一些SELinux Policy 后无法编译通过或者CTS测试项failed
(1). 不符合neverallow规则或者修改了neverallow规则
编译报错:
neverallow check failed at xxx
CTS测试项failed:
android.cts.security.SELinuxNeverallowRulesTest#testNeverallowRulesXXX
这类问题在android O vendor和system分离之后,尤其容易出现。基本上这类问题都是因为修改或者增加的te配置不符合neverallow规则,导致编译报错。而为了解决编译报错,又修改了neverallow规则,最终在跑CTS时,没法通过相关的测试项。
解决思路:
- 尽量不修改neverallow规则;
- 不符合neverallow规则的te配置,要注意vendor sepolicy和system sepolicy分离,是否为跨分区使用。另外,如果是因为权限被过于放大,导致编译报错,可细化权限后再试。
- 其它情况可参考4.2节中的两种参考方案修改。
(2). init进程fork新进程没有做domain切换
CTS测试项failed:
android.security.cts.SELinuxDomainTest # testInitDomain
解决思路:
fork进程时,参考3.4节中做domain切换。
5.参考
本文主要参考了MTK-Online的Quick-start中《SELinux 问题快速分析》的内容,感谢原作者们的辛勤付出。另外,结合源码和自身开发实践,增加了一些自身理解和实践内容。