在LG项目的开发过程中,客户要求将一些重要数据保存在Android文件系统中,这样数据便可永久保存,避免被格式化。比如将用户通话的累计时长保存在/persist目录下,以避免用户不良退机事件。对Android文件系统中资源进行操作,涉及SELinux机制应用。从Android4.4开始,Android便引入了SELinux机制,SELinux机制下文件系统中的数据受到更严格的保护,应用程序必须明确赋予相应权限后才可读写文件系统中的数据。
一、Android安全机制历程
在SELinux机制引入前,Android是基于传统的Linux数据保护机制,即DAC,全称是DiscretionaryAccessControl,翻译为自主访问控制。DAC的核心思想就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以root用户启动Browser,那么Browser就有root用户的权限,在Linux系统上能干任何事情。因此,只要拥有root权限,系统数据便无安全可言,隐患极大。出于用户数据安全考虑,从Android4.4开始引入SELinux机制,其安全机制叫MAC(MandatoryAccessControl),翻译为强制访问控制。MAC的核心思想就是:即任何进程想在SELinux系统中干任何事情,都必须先在安全策略配置文件中赋予权限。凡是没有出现在安全策略配置文件中的权限,进程就没有该权限。
引入SELinux机制的Android被称为SEAndroid,所添加的实现代码位于external/sepolicy目录下。
来看一个SEAndroid中设置权限的例子:
[例子1]
/*from external/sepolicy/netd.te
下面这条SELinux语句表示:允许(allow)netd域(domain)中的进程 ”写(write)“
类型为proc的文件*/
allownetdproc:file write
如果没有在netd.te中使用上例中的权限配置allow语句,则netd就无法往/proc目录下得任何文件中写数据,即使netd具有root权限。显然,MAC比DAC在权限管理这一块要复杂、要严格、要细致得多。
二、SELinux机制实现
Linux中有两种对象,一种死的(Inactive),一种活的(Active)。死的东西就是文件(Linux哲学:万物皆文件。注意,万不可狭义解释为File),而活的东西就是进程。此处的“死”和“活”是一种比喻,映射到软件层面的意思是:进程能发起动作,如它能打开文件并操作它。而文件只能被进程操作。
1) Security Context
SELinux中所有对象都会被赋予一个安全属性,叫SecurityContext。SecurityContext(后续用SContext表示)是一个字符串,主要由三部分组成。可通过相关命令来查询对象的SContext,
例如,SEAndroid中进程的SContext可通过ps-Z命令查看,如下图所示:
图1 ps -Z结果图
最左边的就是进程的SContext。根据SELinux规范,完整的SContext字符串为:
user:role:type[:range]
注意,方括号中的内容表示可选项。s0属于range中的一部分,SContext的核心其实是前三个部分:
user:role:type。
以第一个进程/system/bin/logwrapper的SContext为例,其值为u:r:init:s0,其中:
u为user的意思。SEAndroid中定义了一个SELinux用户,值为u。
r为role的意思。role是角色之意,它是SELinux中一种比较高层次,更方便的权限管理思路,即Role Based Access Control(基于角色的访问控制,简称为RBAC)。简单地说,一个u可以属于多个role,不同的role具有不同的权限。RBAC因项目开发中不涉及,本文不做介绍。
init,代表该进程所属的Domain为init。这里申明一下,MAC的基础管理思路是所谓的Type Enforcement Accesc Control(简称TEAC,一般用TE表示),而不是上面所说的RBAC。对进程来说,Type就是Domain。比如init这个Domain有什么权限,都需要通过类似[例子1]中的allow语句来说明。
S0和SELinux为了满足军用和教育行业而设计的Multi-Level Security(MLS)机制有关。简单地说,MLS将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问。因项目开发中不涉及,本文不介绍MLS。
再来看文件的SContext,读者可通过ls-Z来查看,如图2所示:
图2ls -Z结果图
图2中倒数第二列所示为文件系统根目录下几个文件和目录的SContext信息,以第一行root目录为例,其信息为u:object_r:rootfs:s0:
u:同样是user之意,它代表创建这个文件的SELinux user。
object_r:文件是死的东西,它没法扮演角色,所以在SELinux中,死的东西都用object_r来表示它的role。
rootfs:死的东西的Type,和进程的Domain其实是一个意思。它表示root目录对应的Type是rootfs。
s0:MLS的级别。
通过进程与文件的两个例子,详细说明了SecurityContext。另外,这里说明一下:
在SELinux中,各类数据的SecurityContext定义所在文件如下:
a. App进程->mac_permissions.xml
b. App数据文件->seapp_contexts
c. 系统文件->file_contexts
d. 系统属性->property_contexts
2) Security policy
由MAC的核心思想可知,其机制实现的核心在于安全策略配置文件。那么,SELinux中安全策略配置文件如何进行编写?下面将介绍它。
由前述可知,MAC基本管理单位是TEAC(TypeEnforcement Accesc Control),然后是高一级别的RoleBased Accesc Control。RBAC是基于TE的,而TE也是SELinux中最主要的部分。因此,
下面来看看TE。
在例子1中,大家已经见过TE的allow语句了,再来细致研究下它:
[例子2]
allownetd proc:file write
这条语句的语法为:
allow:TE的allow语句,表示授权。除了allow之外,还有allowaudit、dontaudit、neverallow等。
netd:source type。也叫subject,domain。
proc:target type。它代表其后的file所对应的Type。
file:代表Object Class。它代表能够给subject操作的一类东西。例如File、Dir、socket等。在Android系统中,有一个其他Linux系统没有的Object Class,那就是Binder。
write:在该类Object Class中所定义的操作。
根据SELinux规范,完整的allow相关的语句格式为:
rule_namesource_type target_type : class perm_set
我们直接来看几个实例:
[例子3]
//SEAndroid中的安全策略文件policy.conf
#允许zygote域中的进程向inittype的进程(ObjectClass为process)发送sigchld信号
allowzygote init:process sigchld;
#允许zygote域中的进程search或getattr类型为appdomain的目录。注意,多个perm_set
#可用{}括起来
allowzygote appdomain:dir { getattr search };
#来个复杂点的:
#source_type为unconfineddomaintarget_type为一组type,由
#{fs_type dev_type file_type }构成。object_class也包含两个,为{chr_file file }
#perm_set语法比较奇特,前面有一个~号。它表示除了{entrypointrelabelto}之外,{chr_file#file}这两个object_class所拥有的其他操作
allowunconfineddomain {fs_type dev_type file_type}:{chr_file file } \
~{entrypointrelabelto};
#特殊符号除了~外,还有-号和*号,其中:
#1):-号表示去除某项内容。
#2):*号表示所有内容。
#下面这条语句中,source_type为属于appdomain,但不属于unconfinedomain的进程。
#而*表示所有和capability2相关的权限
#neverallow:表示绝不允许。
neverallow{ appdomain -unconfineddomain } self:capability2 *;
特别注意,前面曾提到说权限必须显示声明,没有声明的话默认就没有权限。那neverallow语句就没必要存在了。因为”无权限“是不需要声明的。确实如此,neverallow语句的作用只是在生成安全策略文件时进行检查,判断是否有违反neverallow语句的allow语句。例如,笔者修改shell.te中一个语句后,生成安全策略文件时就检测到了冲突,如图3所示:
图3 neverallow的作用
如图3所示,笔者修改shell.te后,意外导致了一条allow语句与neverallow语句冲突,从而生成安全策略文件失败。
下面我们来看上述allow语句中所涉及到的objectclass和permset。
Objectclass代表对象类别,类似面向对象编程中的类定义。这里列举了一些常见的Objectclass,见下面的SEPolicy示例:
[external/sepolicy/security_classes示例]
.......
#此文件定义了Android平台中支持的Objectclass
#根据SELinux规范,ObjectClass类型由class关键字申明
#file-related classes
classfilesystem
classfile #代表普通文件
classdir #代表目录
classfd #代表文件描述符
classlnk_file #代表链接文件
classchr_file #代表字符设备文件
......
#network-related classes
classsocket #socket
classtcp_socket
classudp_socket
......
classbinder #Android平台特有的binder
classzygote #Android平台特有的zygote
#Android平台特有的属性服务。注意其后的userspace这个词
classproperty_service# userspace和用户空间中的SELinux权限检查有关,下文再解释
上述示例展示了SEAndroid中ObjectClass的定义,其中:
Object Class需要通过class语句申明。这些申明一般放在一个叫security_class的文件中。
另外,这些class和kernel中相关模块紧密结合。
据说:在kernel编译时会根据security_class文件生成对应的头文件。从这里可以看出,SELinux需要根据发行平台来做相应修改。同时可以看出,该文件一般也不需要我们去修改。
再来看Permset。Permset指得是某种Objectclass所拥有的操作。以file这种Objectclass而言,其拥有的Permset就包括read,write,open,create,execute等。
和Objectclass一样,SELinux或SEAndroid所支持的Permset也需要声明,来看下面的例子:
[external/sepolicy/access_vectors]
#SELinux规范中,定义permset有两种方式,一种是使用下面的common命令
#其格式为:commoncommon_name { permission_name ... }common定义的permset能
#被另外一种permset命令class所继承
#以下是Android平台中,file对应的权限(permset)。其大部分权限读者能猜出是干什么的。
#有一些权限需要结合文后的参考文献来学习
commonfile {
ioctlread write create getattr setattr lock relabelfrom relabelto
appendunlink link rename execute swapon quotaon mounton }
#除了common外,还有一种class命令也可定义permset,如下面的例子:
#class命令的完整格式是:
#classclass_name [ inherits common_name ] { permission_name ... }
#inherits表示继承了某个common定义的权限 注意,class命令定义的权限其实针对得就是
#某个objectclass。它不能被其他class继承
classdir inherits file{
add_name remove_name reparent search rmdir open audit_access execmod
}
#来看SEAndroid中的binder和property_service这两个Objectclass定义了哪些操作权限
classbinder{
impersonate call set_context_mgr transfer }
classproperty_service{set }
提示:Objectclass和Permset的具体内容(SELinux中其实叫AccessVector)都和Linux系统/Android系统密切相关。所以,从知识链的角度来看,Linux编程基础很重要。
现在再来看type的定义,和type相关的命令主要有三个,如下面的例子所示:
[external/sepolicy相关文件]
#type命令的完整格式为:typetype_id [alias alias_id,] [attribute_id]
#其中,方括号中的内容为可选。alias指定了type的别名,可以指定多个别名。
#下面这个例子定义了一个名为shell的type,它和一个名为domain的属性(attribute)关联
typeshell, domain;#本例来自shell.te,注意,可以关联多个attribute
#属性由attribute关键字定义,如attributes文件中定义的SEAndroid使用的属性有:
attributedomain
attributefile_type
#可以在定义type的时候,直接将其和某个attribute关联,也可以单独通过
#typeattribue将某个type和某个或多个attribute关联起来,如下面这个例子
#将前面定义的system类型和mlstrustedsubject属性关联了起来
typeattributesystem mlstrustedsubject
特别注意:对初学者而言,attribute和type的关系最难理解,因为“attribute”这个关键词实在是没取好名字,很容易产生误解:
实际上,type和attribute位于同一个命名空间,即不能用type命令和attribute命令定义相同名字的东西。
其实,attribute真正的意思应该是类似type(或domain) group这样的概念。比如,将type A和attribute B关联起来,就是说type A属于group B中的一员。
使用attribute有什么好处呢?一般而言,系统会定义数十或数百个Type,每个Type都需要通过allow语句来设置相应的权限,这样我们的安全策略文件编起来就会非常麻烦。有了attribute之后呢,我们可以将这些Type与某个attribute关联起来,然后用一个allow语句,直接将source_type设置为这个attribute就可以了:
这也正是type和attribute位于同一命名空间的原因。
这种做法实际上只是减轻了TE文件编写者的烦恼,安全策略文件在编译时会将attribute拓展为其包含的type。如例子4所示:
[例子4]
#定义两个type,分别是A_t和B_t,它们都管理到attribute_test
typeA_t attribute_test;
typeB_t attribute_test;
#写一个allow语句,直接针对attribute_test
allowattribute_test C_t:file {read write};
#上面这个allow语句在编译后的安全策略文件中会被如下两条语句替代:
allowA_t C_t:file {read write};
allowB_t C_t:file {read write};
前面讲过,TE的完整格式为:
rule_namesource_type target_type : class perm_set
所以,attribute可以出现在source_type中,也可以出现在target_type中。
提示:一般而言,定义type的时候,都会在名字后添加一个_t后缀,例如typesystem_t。而定义attribute的时候不会添加任何后缀。但是Android平台没使用这个约定俗成的做法。不过没关系,SEAndroid中定义的attribute都在external/sepolicy/attribute这个文件中,如果分不清是type还是attribute,则可以查看这个文件中定义了哪些attribute。
最后我们来看看TE中的rule_name,一共有四种:
allow:赋予某项权限。
allowaudit:audit含义就是记录某项操作。默认情况下是SELinux只记录那些权限检查失败的操作。allowaudit则使得权限检查成功的操作也被记录。注意,allowaudit只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用allow语句。
dontaudit:对那些权限检查失败的操作不做记录。
neverallow:前面讲过,用来检查安全策略文件中是否有违反该项规则的allow语句。如例子5所示:
[例子5]
#来自external/sepolicy/netd.te文件
#永远不允许netd域中的进程读写 dev_type类型的块设备文件(Objectclass为blk_file)
neverallownetd dev_type:blk_file { read write }
最后,总结说明一下:
a.external/sepolicy/attributes -> 所有定义的attributes都在这个文件
b.external/sepolicy/access_vectors -> 对应了每一个class可以被允许执行的命令
c.external/sepolicy/roles -> Android中只定义了一个role,名字就是r,将r和attributedomain相关联
d.external/sepolicy/users ->其实是将user与roles进行了关联,设置了user的安全级别,s0为最低级是默认的级别,mls_systemHigh是最高的级别
e.external/sepolicy/security_classes ->指的是上文命令中的class,个人认为这个class的内容是指在android运行过程中,程序或者系统可能用到的操作的模块
f.external/sepolicy/te_macros -> 系统定义的宏全在te_macros文件
g.external/sepolicy/***.te -> 一些配置的文件,包含了各种运行的规则
三、SELinux机制在项目开发中调试
在开发LG需求如通话时长累加等功能时,在向文件系统写入文件时,发现不生效。通过如下步骤来解决:
1、通过如下命令来判定是否为权限受限。
首先,
getenforce; //
获取当前
seLinux
状态
终端显示:
Enforcing
,则表示
SEL
inux
机制打开。
或者,
sestatus
终端显示如下:
SELinux status: enabled
SELinuxfs mount: /selinux
Current mode: enforcing //表示
SEL
inux
机制打开
Mode from config file: enforcing
Policy version: 24
Policy from config file: targeted
selinux
有两种工作模式:
“permissive”:所有操作都被允许(即没有
MAC
),但如有违反权限的话,会记录日志
;
“enforcing”:所有操作都会进行权限检查
;
然后、
setenforce0; //
关闭
seLinux
复测一下功能,调试生效
。
再次、
setenforce1; //
打开
seLinux
再复测一下功能,调试
不
生效,则证明由
SEL
inux
机制受限导致的。
二、搜索如下
kernellog,
可明确判定是否由权限引起。
cat/proc/kmsg | grep ‘avc:denied’
或dmesg| grep ‘avc:denied’
可见到类似如下的相关log,
<36>[50830.234458] (1)[242:logd.auditd]type=1400 audit(1489558059.720:228): avc: denied{ read } for pid=4766comm="debuggerd" path="/dev/block/mmcblk0p1"dev="tmpfs" ino=3256DJ_08.15_A scontext=u:r:debuggerd:s0tcontext=u:object_r:nvram_device:s0 tclass=blk_filepermissive=0
分析过程:
缺少什么权限: {read}权限,
谁缺少权限: scontext=u:r:debuggerd:s0,
对哪个文件缺少权限:tcontext=u:object_r:nvram_device:s0
什么类型的文件: tclass=blk_file
权限问题的log的核心部分可归纳为如下标志性格式:
avc:denied {
操作权限
} for pid=
xxxx
comm=“
进程名”
scontext=u:r:
源类型
:s0tcontext=u:r:
目标类型
:s0 tclass=
访问类型
permissive=0
3
、解决方法
谁缺少权限,就去谁对应的TE文件中去添加权限配置语句。
例如,上述权限问题的log,debuggerd对nvram_device的blk_file缺少read的权限,那就去
debuggerd.te中添加权限配置语句,如下:
allowdebuggerd nvram_device:blk_file read;
修改之后,重新编译并烧入boot.img。
四、SELinux机制在项目开发中客制化
首先,按照Google的官方文档:需要linux内核首先是支持selinux的,且需要android的selinux的配置文件即extern/sepolicy里面的内容。然后就是修改BoardConfig.mk。首先会包含厂商定制的sepolicy的文件夹:BOARD_SEPOLICY_DIRS,然后将规则添加到了sepolicy中:BOARD_SEPOLICY_DIRS,这样,我们编译出来的image就有了selinux的功能。其实如果没有厂商定制的话,也是会编译到external/sepolicy的,这样,就是使用andriod所有默认的sepolicy。MTK平台的sepolicy的支持位于device/mediatek/common/sepolicy。
联系客服