深入理解SELinux SEAndroid
发布日期:2021-06-30 21:52:11 浏览次数:2 分类:技术文章

本文共 44582 字,大约阅读时间需要 148 分钟。

按哥的习惯,应该是全部洗剪吹完后再发,不过今年是马年,什么都强调 马上。所以 现在就先奉献 马上有第一部分  祝各位同仁,朋友 马年快乐。

 

深入理解SELinux SEAndroid

SEAndroidGoogleAndroid 4.4上正式推出的一套以SELinux为基础于核心的系统安全机制。而SELinux则是由美国NSA(国安局)和一些公司(RedHatTresys)设计的一个针对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正式入驻内核,成为标配。

思考:

同样是政府部门,差别咋这么大?

同样涉及操作系统和安全相关,NSA为何敢用Linux,为什么想方设法要开源?

由于Linux有多种发行版本,所以各家的SELinux表现形式也略有区别。具体到Android平台,Google对其进行了一定得修改,从而得到SEAndroid

本文将先对SELinux相关知识进行介绍,然后看看Android是如何实现SELinux的(咱们只看用户空间)。

要求:最好能下到Android 4.4源码,可

目标:学完本文,读者应该可以轻松修改相关安全策略文件,以进一步在安全方面定制自己的Android系统。

 

一 SELinux背景知识

1.  DAC和MAC

SELinux出现之前,Linux上的安全模型叫DAC,全称是Discretionary Access Control,翻译为自主访问控制。DAC的核心思想很简单,就是:

  • 进程理论上所拥有的权限与执行它的用户的权限相同。比如,以root用户启动Browser,那么Browser就有root用户的权限,在Linux系统上能干任何事情。

显然,DAC太过宽松了,所以各路高手想方设法都要在Android系统上搞到root权限。那么SELinux如何解决这个问题呢?原来,它在DAC之外,设计了一个新的安全模型,叫MACMandatory Access Control),翻译为强制访问控制。MAC的处世哲学非常简单:即任何进程想在SELinux系统中干任何事情,都必须先在安全策略配置文件中赋予权限。凡是没有出现在安全策略配置文件中的权限,进程就没有该权限。来看一个SEAndroid中设置权限的例子:

[例子1]

/*

  from external/sepolicy/netd.te

 下面这条SELinux语句表示 允许(allow )netd域(domain)中的进程  ”写(write)“ 

 类型为proc的文件

 注意,SELinux中安全策略文件有自己的一套语法格式,下文我们将详细介绍它

*/

allow netd proc:file write

如果没有在netd.te中使用上例中的权限配置allow语句,则netd就无法往/proc目录下得任何文件中写数据,即使netd具有root权限。

显然,MACDAC在权限管理这一块要复杂,要严格,要细致得多。

那么,关于DACMAC,此处笔者总结了几个知识点:

  • Linux系统先做DAC检查。如果没有通过DAC权限检查,则操作直接失败。通过DAC检查之后,再做MAC权限检查。
  • SELinux中也有用户的概念,但它和Linux中原有的user概念不是同一个东西。什么意思呢?比如,Linux中的超级用户rootSELinux中可能就是一个没权限,没地位,打打酱油的”路人甲“。当然,这一切都由SELinux安全策略的制定者来决定。

通过上述内容,读者应该能感觉到,在SELinux中,安全策略文件是最重要的。确实如此。事实上,对本文的读者而言,学习SELinux的终极目标应该是:

  • 看懂现有的安全策略文件。
  • 编写符合特定需求的安全策略文件。

前面也曾提到,SELinux有自己的一套规则来编写安全策略文件,这套规则被称之为SELinux Policy语言。它是掌握SELinux的重点。

2.  SELinux Policy语言介绍

Linux中有两种东西,一种死的(Inactive),一种活的(Active)。死的东西就是文件(Linux哲学,万物皆文件。注意,万不可狭义解释为File),而活的东西就是进程。此处的“死”和“活”是一种比喻,映射到软件层面的意思是:进程能发起动作,例如它能打开文件并操作它。而文件只能被进程操作。

SELinux中,每种东西都会被赋予一个安全属性,官方说法叫Security ContextSecurity Context(以后用SContext表示)是一个字符串,主要由三部分组成。例如SEAndroid中,进程的SContext可通过ps -Z命令查看,如图1所示:

1  Nexus 7 ps -Z结果图

1中最左边的那一列是进程的SContext,以第一个进程/system/bin/logwrapperSContext为例,其值为u:r:init:s0,其中:

  • uuser的意思。SEAndroid中定义了一个SELinux用户,值为u
  • rrole的意思。role是角色之意,它是SELinux中一种比较高层次,更方便的权限管理思路,即Role Based Access Control(基于角色的访问控制,简称为RBAC)。简单点说,一个u可以属于多个role,不同的role具有不同的权限。RBAC我们到最后再讨论。
  • init,代表该进程所属的DomaininitMAC的基础管理思路其实不是针对上面的RBAC,而是所谓的Type Enforcement Accesc Control(简称TEAC,一般用TE表示)。对进程来说,Type就是Domain。比如init这个Domain有什么权限,都需要通过[例子1]allow语句来说明。
  • S0SELinux为了满足军用和教育行业而设计的Multi-Level SecurityMLS)机制有关。简单点说,MLS将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问。后文还将详细介绍MLS

再来看文件的SContext,读者可通过ls -Z来查看,如图2所示:

2 Nexus 7 ls -Z结果图

2中,倒数第二列所示为Nexus 7根目录下几个文件和目录的SContext信息,以第一行root目录为例,其信息为u:object_r:rootfs:s0

  • u:同样是user之意,它代表创建这个文件的SELinux user
  • object_r:文件是死的东西,它没法扮演角色,所以在SELinux中,死的东西都用object_r来表示它的role
  • rootfs:死的东西的Type,和进程的Domain其实是一个意思。它表示root目录对应的Typerootfs
  • s0MLS的级别。

根据SELinux规范,完整的SContext字符串为:

user:role:type[:range]

注意,方括号中的内容表示可选项。s0属于range中的一部分。下文再详细介绍range所代表的Security Level相关的知识。

看,SContext的核心其实是前三个部分:user:role:type

刚才说了,MAC基本管理单位是TEACType Enforcement Accesc Control),然后是高一级别的Role Based Accesc ControlRBAC是基于TE的,而TE也是SELinux中最主要的部分。

下面来看看TE

2.1  TE介绍

在例子1中,大家已经见过TEallow语句了,再来细致研究下它:

[例子2]

allow netd proc:file write

这条语句的语法为:

  • allowTEallow语句,表示授权。除了allow之外,还有allowauditdontauditneverallow等。
  • netdsource type。也叫subjectdomain
  • proctarget type。它代表其后的file所对应的Type
  • file:代表Object Class。它代表能够给subject操作的一类东西。例如FileDirsocket等。在Android系统中,有一个其他Linux系统没有的Object Class,那就是Binder
  • write:在该类Object Class中所定义的操作。

根据SELinux规范,完整的allow相关的语句格式为:

rule_name source_type target_type : class perm_set

我们直接来看几个实例:

[例子3]

//SEAndroid中的安全策略文件policy.conf

#允许zygote域中的进程向init type的进程(Object Class为process)发送sigchld信号

allow zygote init:process sigchld;

#允许zygote域中的进程search或getattr类型为appdomain的目录。注意,多个perm_set

#可用{}括起来

allow zygote appdomain:dir { getattr search };

#来个复杂点的:

#source_type为unconfineddomain target_type为一组type,由

#{ fs_type dev_type file_type }构成。object_class也包含两个,为{ chr_file file }

#perm_set语法比较奇特,前面有一个~号。它表示除了{entrypoint relabelto}之外,{chr_file #file}这两个object_class所拥有的其他操作

allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file }   \

 ~{entrypoint relabelto};

#特殊符号除了~外,还有-号和*号,其中:

# 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语句中所涉及到的object classperm set

(1)  Object class和Perm Set

Object class很难用语言说清楚它到底是怎么定义的,所以笔者也不废话,直接告诉大家常见的Object class有哪些。见下面的SEPolicy示例:

[external/sepolicy/security_classes示例]

.......

#此文件定义了Android平台中支持的Object class

#根据SELinux规范,Object Class类型由class关键字申明

# file-related classes

class filesystem

class file  #代表普通文件

class dir   #代表目录

class fd    #代表文件描述符

class lnk_file  #代表链接文件

class chr_file  #代表字符设备文件

 ......

 

# network-related classes

class socket   #socket

class tcp_socket

class udp_socket

......

class binder   #Android平台特有的binder

class zygote   #Android平台特有的zygote

 

#Android平台特有的属性服务。注意其后的userspace这个词

class property_service # userspace和用户空间中的SELinux权限检查有关,下文再解释

上述示例展示了SEAndroidObject Class的定义,其中:

  • Object Class需要通过class语句申明。这些申明一般放在一个叫security_class的文件中。
  • 另外,这些classkernel中相关模块紧密结合。

据说:在kernel编译时会根据security_class文件生成对应的头文件。从这里可以看出,SELinux需要根据发行平台来做相应修改。同时可以看出,该文件一般也不需要我们去修改。

再来看Perm setPerm set指得是某种Object class所拥有的操作。以file这种Object class而言,其拥有的Perm set就包括readwriteopencreate,execute等。

Object class一样,SELinuxSEAndroid所支持的Perm set也需要声明,来看下面的例子:

[external/sepolicy/access_vectors]

#SELinux规范中,定义perm set有两种方式,一种是使用下面的common命令

#其格式为:common common_name { permission_name ... } common定义的perm set能

#被另外一种perm set命令class所继承

#以下是Android平台中,file对应的权限(perm set)。其大部分权限读者能猜出是干什么的。

#有一些权限需要结合文后的参考文献来学习

common file {

      ioctl read write create getattr setattr lock relabelfrom relabelto

      append unlink link rename execute swapon quotaon mounton }

 

#除了common外,还有一种class命令也可定义perm set,如下面的例子:

#class命令的完整格式是:

#class class_name [ inherits common_name ] { permission_name ... }

#inherits表示继承了某个common定义的权限  注意,class命令定义的权限其实针对得就是

#某个object class。它不能被其他class继承

class dir inherits file {

   add_name  remove_name reparent search rmdir open audit_access execmod

}

#来看SEAndroid中的binder和property_service这两个Object class定义了哪些操作权限

class binder {

      impersonate  call set_context_mgr transfer }

class property_service { set }

提示:Object classPerm set的具体内容(SELinux中其实叫Access Vector)都和Linux系统/Android系统密切相关。所以,从知识链的角度来看,Linux编程基础很重要。

(2)  type,attribute和allow等

现在再来看type的定义,和type相关的命令主要有三个,如下面的例子所示:

[external/sepolicy相关文件]

#type命令的完整格式为:type type_id [alias alias_id,] [attribute_id]

#其中,方括号中的内容为可选。alias指定了type的别名,可以指定多个别名。

#下面这个例子定义了一个名为shell的type,它和一个名为domain的属性(attribute)关联

type shell, domain; #本例来自shell.te,注意,可以关联多个attribute

 

#属性由attribute关键字定义,如attributes文件中定义的SEAndroid使用的属性有:

attribute domain

attribute file_type

 

#可以在定义type的时候,直接将其和某个attribute关联,也可以单独通过

#typeattribue将某个type和某个或多个attribute关联起来,如下面这个例子

#将前面定义的system类型和mlstrustedsubject属性关联了起来

typeattribute system mlstrustedsubject

特别注意:对初学者而言,attributetype的关系最难理解,因为“attribute”这个关键词实在是没取好名字,很容易产生误解:

  • 实际上,typeattribute位于同一个命名空间,即不能用type命令和attribute命令定义相同名字的东西。
  • 其实,attribute真正的意思应该是类似type(或domain group这样的概念。比如,将type Aattribute B关联起来,就是说type A属于group B中的一员。

使用attribute有什么好处呢?一般而言,系统会定义数十或数百个Type,每个Type都需要通过allow语句来设置相应的权限,这样我们的安全策略文件编起来就会非常麻烦。有了attribute之后呢,我们可以将这些Type与某个attribute关联起来,然后用一个allow语句,直接将source_type设置为这个attribute就可以了:

  • 这也正是typeattribute位于同一命名空间的原因。
  • 这种做法实际上只是减轻了TE文件编写者的烦恼,安全策略文件在编译时会将attribute拓展为其包含的type。如例子4所示:

[例子4]

#定义两个type,分别是A_t和B_t,它们都管理到attribute_test

type A_t attribute_test;

type B_t attribute_test;

 

#写一个allow语句,直接针对attribute_test

allow attribute_test C_t:file {read write};

#上面这个allow语句在编译后的安全策略文件中会被如下两条语句替代:

allow A_t C_t:file {read write};

allow B_t C_t:file {read write};

前面讲过,TE的完整格式为:

rule_name source_type target_type : class perm_set

所以,attribute可以出现在source_type中,也可以出现在target_type中。

提示:一般而言,定义type的时候,都会在名字后添加一个_t后缀,例如type system_t。而定义attribute的时候不会添加任何后缀。但是Android平台没使用这个约定俗成的做法。不过没关系,SEAndroid中定义的attribute都在external/sepolicy/attribute这个文件中,如果分不清是type还是attribute,则可以查看这个文件中定义了哪些attribute

最后我们来看看TE中的rule_name,一共有四种:

  • allow:赋予某项权限。
  • allowauditaudit含义就是记录某项操作。默认情况下是SELinux只记录那些权限检查失败的操作。allowaudit则使得权限检查成功的操作也被记录。注意,allowaudit只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用allow语句。
  • dontaudit:对那些权限检查失败的操作不做记录。
  • neverallow:前面讲过,用来检查安全策略文件中是否有违反该项规则的allow语句。如例子5所示:

[例子5]

#来自external/sepolicy/netd.te文件

#永远不允许netd域中的进程 读写 dev_type类型的 块设备文件(Object class为blk_file)

neverallow netd dev_type:blk_file { read write }

(3)  RBAC和constrain

绝大多数情况下,SELinux的安全配置策略需要我们编写各种各样的xx.te文件。由前文可知,.te文件内部应该包含包含了各种allowtype等语句了。这些都是TEAC,属于SELinux MAC中的核心组成部分。

TEAC之上,SELiunx还有一种基于Role的安全策略,也就是RBACRBAC到底是如何实施相关的权限控制呢?我们先来看SEAndroidRoleUser的定义。

[external/sepolicy/roles]

#Android中只定义了一个role,名字就是r

role r; 

#将上面定义的r和attribute domain关联起来

role r types domain

再来看user的定义。

[external/sepolicy/users]

#支持MLS的user定义格式为:

#user seuser_id roles role_id level mls_level range mls_range;

#不支持MLS user定义格式为:

#user seuser_id roles role_id;

#SEAndroid使用了支持MLS的格式。下面定义的这个user u,将和role r关联。

#注意,一个user可以和多个role关联。

#level之后的是该user具有的安全级别。s0为最低级,也就是默认的级别,mls_systemHigh

#为u所能获得的最高安全级别(security level)。此处暂且不表MLS

user u roles { r } level s0 range s0 - mls_systemhigh;

那么,RolesUser中有什么样的权限控制呢?

1)首先,我们应该允许从一个role切换(SELinuxTransition表达切换之意)到另外一个role,例如:

#注意,关键字也是allow,但它和前面TE中的allow实际上不是一种东西

#下面这个allow允许from_role_id切换到to_role_id

allow from_role_id to_role_id;

2) 角色之间的关系。SELinux中,RoleRole之间的关系和公司中的管理人员的层级关系类似,例如:

#dominance语句属于deprecated语句,MLS中有新的定义层级相关的语句。不过此处要介绍的是

#selinux中的层级关系

#下面这句话表示super_r dominate(统治,关键词dom) sysadm_r和secadm_r这两个角色

#反过来说,sysadm_r和secadm_r dominate by (被统治,关键词 domby) super_r

#从type的角度来看,super_r将自动继承sysadm_r和secadm_r所关联的type(或attribute)

dominance { role super_r {role sysadm_r; role secadm_r; }

3)其他内容,由于SEAndroid没有使用,此处不表。读者可阅读后面的参考文献。

话说回来,怎么实现基于RoleUser的权限控制呢?SELinux提供了一个新的关键词,叫constrain,来看下面这个例子:

[例子6]

#constrain标准格式为:

# constrain object_class_set perm_set expression ;

#下面这句话表示只有source和target的user相同,并且role也相同,才允许

#write object_class为file的东东

constrain file write (u1 == u2 and r1 == r2) ;

前面已经介绍过object_classperm_set了,此处就不再赘述。constrain中最关键的是experssion,它包含如下关键词:

  • u1,r1,t1:代表sourceuserroletype
  • u2,r2,t2:代表targetuser,roletype
  • ==!===表示相等或属于,!=表示不等或不属于。对于u,r来说,==!=表示相等或不等,而当诸如t1==!=”某个attribute时,表示源type属于或不属于这个attribute
  • dom,domby,incomp,eq:仅针对role,表示统治,被统治,没关系和相同(和==一样)

关于constrain,再补充几个知识点

  • SEAndroid中没有使用constrain,而是用了MLS中的mlsconstrain。所以下文将详细介绍它。
  • constrain是对TEAC的加强。因为TEAC仅针对TypeDomain,没有针对userrole的,所以constrainTEAC的基础上,进一步加强了权限控制。在实际使用过程中,SELinux进行权限检查时,先检查TE是否满足条件,然后再检查constrain是否也满足条件。二者都通过了,权限才能满足。

关于RBACconstrain,我们就介绍到此。

提示:笔者花了很长时间来理解RBACconstrain到底是想要干什么。其实这玩意很简单。因为TEType Enforcement,没userrole毛事,而RBAC则可通过constrain语句来在userrole上再加一些限制。当然,constrain也可以对type进行限制。如此而已!

2.2  Labeling介绍

前面陆陆续续讲了些SELinux中最常见的东西。不过细心的人可能会问这样一个问题:这些SContext最开始是怎么赋给这些死的和活的东西的?Good Question

提示:SELinux中,设置或分配SContext给进程或文件的工作叫Security Labeling,土语叫打标签。

(1)  sid和sid_context

这个问题的回答嘛,其实也蛮简单。Android系统启动后(其他Linux发行版类似),init进程会将一个编译完的安全策略文件传递给kernel以初始化kernel中的SELinux相关模块(姑且用Linux Security Module:LSM来表示它把),然后LSM可根据其中的信息给相关Object打标签。

提示:上述说法略有不准,先且表述如此。

LSM初始化时所需要的信息以及SContext信息保存在两个特殊的文件中,以Android为例,它们分别是:

  • initial_sids:定义了LSM初始化时相关的信息。SIDSELinux中一个概念,全称是Security IdentifierSID其实类似SContextkey值。因为在实际运行时,如果老是去比较字符串(还记得吗,SContext是字符串)会严重影响效率。所以SELinux会用SID来匹配某个SContext
  • initial_sid_context:为这些SID设置最初的SContext信息。

来看这两个文件的内容:

[external/sepolicy/initial_sidsinitial_sid_context]

#先看initial_sids

sid kernel  #sid是关键词,用于定义一个sid

sid security

sid unlabeled

sid fs

sid file

sid file_labels

sid init

......

#再来看initial_sid_context

sid kernel u:r:kernel:s0   #将initial_sids中定义的sid和初始的SContext关联起来

sid security u:object_r:kernel:s0

sid unlabeled u:object_r:unlabeled:s0

sid fs u:object_r:labeledfs:s0

sid file u:object_r:unlabeled:s0

sid file_labels u:object_r:unlabeled:s0

sid init u:object_r:unlabeled:s0

提示sid的细节需要查看LSM的实现。此处不拟深究它。另外,这两个文件也是和Kernel紧密相关的,所以一般不用修改它们。

(2)  Domain/Type Transition和宏

SEAndroid中,init进程的SContextu:r:init:s0,而init创建的子进程显然不会也不可能拥有和init进程一样的SContext(否则根据TE,这些子进程也就在MAC层面上有了和init一样的权限)。那么这些子进程的SContext是怎么被打上和其父进程不一样的SContext呢?

SELinux中,上述问题被称为Domain Transtition,即某个进程的Domain切换到一个更合适的Domain中去。Domain Transition也是需要我们在安全策略文件中来配置的,而且有相关的关键词,来看例子7

[例子7-1]

#先要使用type_transition语句告诉SELinux

#type_transition的完整格式为:

# type_transition source_type target_type : class default_type;

#对Domain Transition而言有如下例子:

type_transition init_t apache_exec_t : process apache_t;

上面这个例子的解释如下,请读者务必仔细:

  • init_t Domain中的进程执行typeapache_exec_t类型的可执行文件(forkexecv)时,其class(此处是process)所属Domain(对process而言,肯定是指Domain)需要切换到apache_t域。

明白了吗?要做DT,肯定需要先fork一个子进程,然后通过execv打开一个新的可执行文件,从而进入变成那个可执行文件对应的活物!所以,在type_transition语句中,target_type往往是那个可执行文件(死物)的typedefault_type则表示execv执行后,这个活物默认的Domain。另外,对DT来说,class一定会是process

请注意,DT属于Labeling一部分,但这个事情还没完。因为打标签也需要相关权限。所以,上述type_transition不过是开了一个头而已,要真正实施成功这个DT,还需要下面至少三个allow语句配合:

[例子7-2]

#首先,你得让init_t域中的进程能够执行type为apache_exec_t的文件

allow init_t apache_exec_t : file execute;

#然后,你还得告诉SELiux,允许init_t做DT切换以进入apache_t域

allow init_t apache_t : process transition;

#最后,你还得告诉SELinux,切换入口(对应为entrypoint权限)为执行apache_exec_t类型

#的文件

allow apache_t apache_exec_t : file entrypoint;

为什么会需要上述多达三个权限呢?这是因为在Kernel中,从forkexecv一共设置了三处Security检查点,所以需要三个权限。

提示:读者不必纠结这个了,按照规范做就完了。不过...,这导致我们写TE文件时候会比较麻烦啊!

确实比较麻烦,不过SELinux支持宏,这样我们可以定义一个宏语句把上述4个步骤全部包含进来。在SEAndroid中,系统定义的宏全在te_macros文件中,其中和DT相关的宏定义如下:

[external/sepolicy/te_macros]

#定义domain_trans宏。$1,$2等等代表宏的第一个,第二个....参数

define(`domain_trans', `

# SEAndroid在上述三个最小权限上,还添加了自己的一些权限

allow $1 $2:file { getattr open read execute };

allow $1 $3:process transition;

allow $3 $2:file { entrypoint read execute };

allow $3 $1:process sigchld;

dontaudit $1 $3:process noatsecure;

allow $1 $3:process { siginh rlimitinh };

')

#定义domain_auto_trans宏,这个宏才是我们在te中直接使用的

#以例子7而言,该宏的用法是:

#domain_auto_trans(init_tapache_exec_tapache_t)

define(`domain_auto_trans', `

# 先allow相关权限

domain_trans($1,$2,$3)

# 然后设置type_transition

type_transition $1 $2:process $3;

')

external/sepolicy/init_shell.te中就有上述宏的用法:

./init_shell.te:4:domain_auto_trans(init, shell_exec, init_shell)

除了DT外,还有针对TypeTransition。举个例子,假设目录ASContextu:r:dir_a,那么默认情况下在该目录下创建的文件都具有u:r:dir_a这个SContext。所以我们也要针对死得东西进行打标签。

DT类似,TT的语句也是type_transition,而且要顺利完成Transition,也需要申请相关权限。废话不再多说,我们直接看te_macros是怎么定义TT所需要的宏的:

[external/sepolicy/te_macros]

# 定义file_type_trans(domain, dir_type, file_type)宏

#

define(`file_type_trans', `

# ra_dir_perms是一个宏,由global_macros文件定义,其值为:

#define(`ra_dir_perms', `{ r_dir_perms add_name write }')

allow $1 $2:dir ra_dir_perms;

# create_file_perms也是一个宏,定义在global_macros文件中,其值为:

# define(`create_file_perms', `{ create setattr rw_file_perms

#                link_file_perms }')

#而r_dir_perms=define(`r_dir_perms', `{ open getattr read search ioctl }

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)宏

#该宏的含义是:当domain域中的进程在某个Type为dir_type的目录中创建文件时,该文件的

#SContext应该是file_type

define(`file_type_auto_trans', `

file_type_trans($1, $2, $3)

type_transition $1 $2:dir $3;

#notdevfile_class_set也是一个宏,由global_macros文件定义,其值为

# define(`notdevfile_class_set', `{ file lnk_file sock_file fifo_file }')

type_transition $1 $2:notdevfile_class_set $3;

')

WoWSEAndroid太这两个宏定义太复杂了,来看看官方文档中的最小声明是什么:

[例子8]

type_transition acct_t var_log_t:file wtmp_t;

allow acct_t var_log_t:dir { read getattr lock search ioctl

                                   add_name remove_name write };

allow acct_t wtmp_t:file { create open getattr setattr read

                                 write append rename link unlink ioctl lock };

SEAndroidapp.te中,有如下TT设置:

./app.te:86:file_type_auto_trans(appdomain, download_file, download_file)

DTTT就介绍到这,翻来覆去就这么点东西,多看几遍就“柜”(用柜字,打一成语,参考2014年中国首次猜谜大会)了

 

=======未完,待续========

接第一部分的内容()。

今天公司年会,哥高兴,所以发布第二部。SELinux/SEAndroid一共分三部分。第一和第二部分是SELinux的基础知识,第三部分是SEAndroid的工作源码分析。

        深入理解SELinux SEAndroid 第二部分

3)  File/File System 打label

前面一节中,读者见识到了DTTT。不过这些描述的都是Transition,即从某种TypeDomain进入另外一种TypeDomain,而上述内容并没有介绍最初的Type怎么来。在SELinux中,对与File相关的死货(比“死东西”少些一个字)还有一些特殊的语句。

直接看SEAndroid中的文件吧。

[external/sepolicy/file_contexts]

#从file_contexts这个文件名也可看出,该文件描述了死货的SContext

#果然:SEAndroid多各种预先存在的文件,目录等都设置了初始的SContext

#注意下面这些*,?号,代表通配符

/dev(/.*)?        u:object_r:device:s0

/dev/akm8973.*        u:object_r:akm_device:s0

/dev/accelerometer    u:object_r:accelerometer_device:s0

/dev/alarm        u:object_r:alarm_device:s0

/dev/android_adb.*    u:object_r:adb_device:s0

/dev/ashmem        u:object_r:ashmem_device:s0

/dev/audio.*        u:object_r:audio_device:s0

/dev/binder        u:object_r:binder_device:s0

/dev/block(/.*)?    u:object_r:block_device:s0

......

#注意下面的--号,SELinux中类似的符号还有:

#‘-b’ - Block Device ‘-c’ - Character Device

#‘-d’ - Directory ‘-p’ - Named Pipe

#‘-l’ - Symbolic Link ‘-s’ - Socket

#‘--’ - Ordinary file

/system(/.*)?        u:object_r:system_file:s0

/system/bin/ash        u:object_r:shell_exec:s0

/system/bin/mksh    u:object_r:shell_exec:s0

/system/bin/sh        --    u:object_r:shell_exec:s0

/system/bin/run-as    --    u:object_r:runas_exec:s0

/system/bin/app_process    u:object_r:zygote_exec:s0

/system/bin/servicemanager    u:object_r:servicemanager_exec:s0

/system/bin/surfaceflinger    u:object_r:surfaceflinger_exec:s0

/system/bin/drmserver    u:object_r:drmserver_exec:s0

上面的内容很简单,下面来个面生的:

[external/sepolicy/fs_use]

#fs_use中的fs代表file system.fs_use文件描述了SELinux的labeling信息

#在不同文件系统时的处理方式

#对于常规的文件系统,SContext信息存储在文件节点(inode)的属性中,系统可通过getattr

#函数读取inode中的SContext信息。对于这种labeling方式,SELinux定义了

#fs_use_xattr关键词。这种SContext是永远性得保存在文件系统中

fs_use_xattr yaffs2 u:object_r:labeledfs:s0;

fs_use_xattr jffs2 u:object_r:labeledfs:s0;

fs_use_xattr ext2 u:object_r:labeledfs:s0;

fs_use_xattr ext3 u:object_r:labeledfs:s0;

fs_use_xattr ext4 u:object_r:labeledfs:s0;

fs_use_xattr xfs u:object_r:labeledfs:s0;

fs_use_xattr btrfs u:object_r:labeledfs:s0;

 

#对于虚拟文件系统,即Linux系统运行过程中创建的VFS,则使用fs_use_task关键字描述

#目前也仅有pipefssockfs两种VFS格式

fs_use_task pipefs u:object_r:pipefs:s0;

fs_use_task sockfs u:object_r:sockfs:s0;

 

#还没完,还有一个fs_use_trans,它也是用于Virtual File System,但根据SELinux官方

#描述,好像这些VFS是针对pseudo terminal和临时对象。在具体labeling的时候,会根据

#fs_use_trans以及TT的规则来来决定最终的SContext

#我们以下面这个例子为例:

fs_use_trans devpts u:object_r:devpts:s0;

#假设还有一条TT语句

#type_transition sysadm_t devpts : chr_file sysadm_devpts_t:s0;

#表示当sysadm_t的进程在Type为devpts下创建一个chr_file时,其SContext将是

#sysadm_devpts_t:s0。如果没有这一条TT,则将使用fs_use_trans设置的SContext:

#u:object_r:devpts:s0 注意,和前面的TT比起来,这里并不是以目录为参考对象,而是

#以FileSystem为参考对象

fs_use_trans tmpfs u:object_r:tmpfs:s0;

fs_use_trans devtmpfs u:object_r:device:s0;

fs_use_trans shm u:object_r:shm:s0;

fs_use_trans mqueue u:object_r:mqueue:s0;

到此,我们介绍了fs_use_xattrfs_use_taskfs_use_trans,那么这三种打标签的方法是否涵盖了所有情况呢?答案肯定是否,因为我们还有一个兄弟没出场呢。

[external/sepolicy/genfs_context]

#genfs中的gen为generalized之意,即上述三种情况之外的死货,就需要使用genfscon

#关键词来打labeling了。一般就是/目录,proc目录,sysfs等

genfscon rootfs / u:object_r:rootfs:s0

genfscon proc / u:object_r:proc:s0

genfscon proc /net/xt_qtaguid/ctrl u:object_r:qtaguid_proc:s0

......

到此,绝大部分能想到的死货怎么打标签就介绍完了。

(4)  给网络数据包/端口打标签

不过,从知识完整性角度看,还有对网络数据包打标签的工作,这也是SELinux新增的功能。不过,它涉及到与iptables相关的工作,所以笔者也不想过多讨论。在SEAndroid中,selinux-network.sh脚本就是来干这个事情的,其内容如图4所示:

网络数据包打标签

由图4可以看出,SEAndroid暂时也没放开网络数据包打标签的功能。"-j SECMARK --selctx SContext"iptables(需要支持SELinux功能)新增选项,用来给各种数据包也打上标签。

除了数据包外,还可以给端口打标签,这是由portcon关键词来完成的。此处不再详述,读者有个概念即可。

 

2.3  Security Level和MLS

(1)  Security Level

上文介绍的TERBAC基本满足了“平等社会”条件下的权限管理,但它无法反映现实社会中等级的概念。为此,SELinux又添加了一种新的权限管理方法,即Multi-Lever Security,多等级安全。多等级安全信息也被添加到SContext中。所以,在MLS启用的情况下(注意,你可以控制SELinux启用用MLS还是不启用MLS),完整的SContext

  • MLS未启用前:user_u:role_r:type_t
  • MLS启用后,user:role:type:sensitivity[:category,...]- sensitivity [:category,...]

看,MLS启用后,SContext type后面的字段变得非常复杂,看着有些头晕(至少笔者初学它时是这样的)。下面马上来解释它。

[Security-level解析]

|-->low security level<--| -  |-->high security level<--|

sensitivity[:category,...]  - sensitivity [:category,...]

上述字符串由三部分组成:

  •  low security level:表明当前SContext所对应的东西(活的或死的)的当前(也就是最小)安全级别。
  • 连字符“-”,表示range
  • high security level:表明当前SContext所对应的东西(活的或死的)的最高可能获得的安全级别(英文叫clearance,不知道笔者的中文解释是否正确)。

security level由两部分组成,先来看第一部分由sensitivity关键字定义的sensitivity,其用法见如下例子:

[例子9]

#用sensitivity定义一个sens_id,alias指定别名。

sensitivity sens_id alias alias_id [ alias_id ];

#比如:

sensitivity s0 alias unclassified

sensitivity s1 alias seceret

sensitivity s2 alias top-seceret

.....

#Question:从alias看,似乎so的级别<s1的级别<s2的级别。但是

#alias并不是sensitivity的必要选项,而且名字可以任取。

#在SELinux中,真正设置sensitivity级别的是由下面这个关键词表示

dominance {s0 s1 s2.....sn}

#在上述dominance语句中,括号内最左边的s0级别最低,依次递增,直到最右边的sn级别最高

再来看security level第二部分,即category关键字及用法,如例10所示:

[例子10]

#category cat_id alias alias_id;

#比如:

category c0

category c1 #等

#category和sensitivity不同,它定义的是类别,类别之间是没有层级关系的。比如,

#小说可以是一中cagetory,政府公文是另外一种category,

SEAndroid中:

  • sensitivity只定义了s0
  • category定义了从c0c1023,共1024category

senstivitycategory一起组成了一个security level(以后简称SLevel),SLevel由关键字level声明,如下例所示:

[例子11]

#level sens_id [ :category_id ];

#注意,SLevel可以没有category_id。看一个例子:

#sensitivity为s0,category从c0,c1,c2一直到c255,注意其中的.号

level s0:c0.c255;

#没有category_id,如:

level s0

Role类似,SL1SL2之间的关系有:

  • dom:如果SL1 dom SL2的话,则SL1sensitivity >= SL2senstivitySL1category包含SL2category(Category of SL1Category of SL2的超集)

例如:

SL1="s2:c0.c5" dom SL2="s0:c2,c3"

  • domby:和dom相反。
  • eqsensitivity相等,category相同。
  • incomp:不可比。sensitivity不可比,category也不可比。

现在回过头来看SContext,其完整格式为:

user:role:type:sensitivity[:category,...]- sensitivity [:category,...]

#前面例子中,我们看到Android中,SContext有:

u:r:init:s0 #在这种case中,Low SLevel等于High SLevel,而且SLevel没有包含Category

好了,知道了SLevel后,下面来看看它如何在MAC中发挥自己的力量。和constrain类似,MLS在其基础上添加了一个功能更强大的mlsconstrain关键字。

(2)  mlsconstrain和no read down/write up

mlsconstrain语法和constrain一样一样的:

mlsconstrain class perm_set expression;

constrain不一样的是,expression除了u1,u2,r1,r2,t1,t2外还新增了:

  • l1,l2:小写的Ll1表示源的low senstivity levell2表示targetlow sensitivity
  • h1,h2:小写的Hh1表示源的high senstivity levelh2表示targethigh sensitivity
  • lh的关系,包括dom,domby,eqincomp

mlsconstrain只是一个Policy语法,那么我们应该如何充分利用它来体现多层级安全管理呢?来看图5

5  MLS的作用

MLS在安全策略上有一个形象的描述叫no write downno read up

  • 高级别的东西不能往低级别的东西里边写数据:这样可能导致高级别的数据泄露到低级别中。如图4中,Process的级别是Confidential,它可以往同级别的File B中读写数据,但是只能往高级别的File A(级别是Secret)里边写东西。
  • 高级别的东西只能从低级别的东西里边读数据:比如Process可以从File CFile D中读数据,但是不能往File CFile D上写数据。

反过来说:

低级别的东西只能往高级别的东西里边写数据

-----我和小伙伴们解释这一条的时候,小伙伴惊呆了,我也惊呆了。他们的想法是”低级别往高级别里写,岂不是把数据破坏了?“。晕!这里讨论的是泄不泄密的问题,不是讨论数据被破坏的事情。破坏就破坏了,只要没泄密就完了。

低级别的东西不能从高级别的东西那边读数据

(3)  MLS in SEAndroid

再来看看SEAndroid中的MLS

  • 首先,系统中只有一个sensitivity level,即s0
  • 系统中有1024category,从c0c1023

读者通过mmm external/sepolicy --just-print可以打印出sepolicymakefile执行情况,其中有这样的内容:

#m4用来处理Policy文件中的宏

m4 -D mls_num_sens=1 -D mls_num_cats=1024

external/sepolicy/mls文件中有:

[external/sepolicy/mls]

#SEAndroid定义的两个和MLS相关的宏,位于mls_macro文件中

gen_sens(mls_num_sens)  #mls_num_sens=1

gen_cats(mls_num_cats)  #mls_num_cats=1024

#下面这个宏生成SLevel

gen_levels(mls_num_sens,mls_num_cats)

没必要解释上面的宏了,最终的policy.conf中(2.4节将介绍它是怎么来的),我们可以看到:

[out/target/product/generic/obj/ETC/sepolicy_intermediates/policy.conf]

sensitivity s0;

dominance { s0  }

category c0;

......#目前能告诉大家的是,policy.conf文件中,宏,attribute等都会被一一处理喔!

category c1023

level s0:c0.c1023; #定义SLevel

#SEAndroid中,mls_systemlow宏取值为s0

#mls_systemhigh宏取值为s0:c0.c1023

user u roles { r } level s0 range s0 - s0:c0.c1023; #定义u

最后,来看一下mlsconstain的例子:

[例子12]

mlsconstrain dir search

(( l1 dom l2 ) or

(( t1 == mlsfilereadtoclr ) and ( h1 dom l2 )) or

( t1 == mlsfileread ) or

( t2 == mlstrustedobject ));

#上述标粗体的都是attribute

不解释!

2.4  编译安全策略文件

到此,SELinux Policy语言中的基本要素都讲解完毕,相信读者对着真实的策略文件再仔细研究下就能彻底搞明白。

不过,我们前面反复提到的安全策略文件到底是什么?我们前面看到的例子似乎都是文本文件,难道就它们是安全策略文件吗?

拿个例子说事,来看图6Android的策略文件:

6  Android策略文件

Android中,SELinux的安全策略文件如图6所示。这么多文件,如何处理呢?来看图7

7  SElinux安全配置文件生成

由图7可知:

  • 左边一列代表安全配置的源文件。也即是大家在图6中看到的各种te文件,还有一些特殊的文件,例如前文提到的initial_sidinitial_sid_contextsaccess_vectorsfs_use,genfs_contexts等。在这些文件中,我们要改的一般也是针对TE文件,其他文件由于和kernel内部的LSM等模块相关,所以除了厂家定制外,我们很难有机会去修改。
  • 这些文件都是文本文件,它们会被组合到一起(图7中是用cat命令,不同平台处理方法不相同,但大致意思就是要把这些源文件的内容搞到一起去)。
  • 搞到一起后的文件中有使用宏的地方,这时要利用m4命令对这些宏进行拓展。m4命令处理完后得到的文件叫policy.conf。前面我们也见过这个文件了,它是所有安全策略源文件的集合,宏也被替换。所以,读者可以通过policy.conf文件查看整个系统的安全配置情况,而不用到图6中那一堆文件中去找来找去的。
  • policy.conf文件最终要被checkpolicy命令处理。该命令要检查neverallow是否被违背,语法是否正确等。最后,checkpolicy会将policy.conf打包生成一个二进制文件。在SEAndroid中,该文件叫sepolicy,而在Linux发行版本上,一般叫policy.26等名字。26表示SELinux的版本号。
  • 最后,我们再把这个sepolicy文件传递到kernel LSM中,整个安全策略配置就算完成。

提示:请读者务必将上述步骤搞清楚。

8所示为SEAndroidsepolicy makefile的执行情况:

8  sepolicy makefile执行情况

看明白了吗?

提示

想知道如何打印make命令的执行情况?请使用“--just-print”选项

进阶阅读

1)上述做法是将所有源文件打包生成一个单一的安全策略文件,这种方式叫Monolithic

     policy。显然,在什么都模块化的今天,这种方式虽然用得最多,但还是比较土。

     SELinux还支持另外一种所谓的模块化Policy。这种PolicyBase PolicyModule

     Policy两个。BasePolicy为基础,先加载,然后可以根据情况动态加载Module Policy

     目前SEAndroid还没有该功能,不过以后可能会支持。相信有了它,开发定制企业级

     安全管理系统就更方便些。

2  安全策略源文件非常多。基本上,我们都会在一个参考源文件上进行相应修改,

     而不会完全从头到尾都自己写。所以,在发行版上有一个Reference Policy,里边

     涵盖了普适的,常用的策略。很明显,AOSP 4.4中的sepolicy也提供了针对Android

     平台的Reference Policy

2.5  拓展讨论

最后,作为拓展讨论,我们来看看SELinux作为一套复杂的系统安全模块增强,其实现架构如图9所示:

9  SELinux Component组成

其中:

  • Subject:代表发起操作的对象,一般是ProcessSELinux需要检查Subject是否满足权限要求
  • Object Manager:管理着Object及相应的SContextOM将向Access Vector Cache查询所要求的操作是否有权限。
  • AVC主要起一个加速的作用,它将缓存一些权限检查的结果。当相同的权限检查请求过来时,直接从AVC中返回所缓存的结果。
  • 如果AVC没有这条权限检查的结果,那么它将向Security Server去查询。SS内部保存有SePolicy,它可以根据SEPolicy计算出权限检查的结果。

9中所示的SELinux Component可以:

  • 上述这些模块全部运行在Kernel中,它们也是LSM SElinux的核心模块。
  • OMAVC可以存在于UserSpace中,这种caseSELinux awareapplication。说白了,就是一个使用SELinux的安全监管系统。在Android中,KernelUserspaceSELinux都使用了。对于userspaceSELinux相关app来说,需要使用开源动态库libselinux。在Android平台中,该库位于external/libselinuxUserspaceSElinux APP也会和Kernel中的LSM Selinux交互,所以不能在没有Kernel SELinux的系统中单独使用SELinux app

10展示了一个完整的SELinux系统结构:

10  SELinux系统结构

10比较复杂,很大的原因是它包含了其他Linux发行版本上的一些和SELinux相关的工具,我们从上往下看:

  • 最顶上,Reference Policy, checkmodule, semodule_package,semodule等讲得都是Policy编译相关的工具和参考文件。这些东西编译完后,会生成最右边那个圆柱体SELinux Policy
  • 中间的SELinux-aware APP,Linux Commands, policycoreutils, file Labeling utils, semanage等,都是Linux发行版中常用的SELinux管理工具。
  • SELinux-aware APP借助libselinux库,将最右边的SELinux Policy配置文件传递到kernel中。这其实是通过往系统一些特殊的文件中写数据来完成的。例如/selinux/sys/fs/selinux等。
  • 然后我们进入最下边的Kernel中的SElinux,它包含AVC,LSM挂钩的LSM HookSecurity Server等等。

2.6  参考文献介绍

SELinux比较复杂,对于初学者,建议看如下几本书:

1  SELinux NSA’s Open Source Security Enhanced Linux

下载地址:

评价:讲得SELinux版本比较老,不包括MLS相关内容。但是它是极好的入门资料。如果你完全没看懂本文,则建议读本文。

2  SELinux by Example Using Security Enhanced Linux

 下载地址:

评价:这本书比第1本书讲得SELinux版本新,包括MLS等很多内容,几乎涵盖了目前SELinux相关的所有知识。读者可跳过1直接看这本书。

3  The_SELinux_Notebook_The_Foundations_3rd_Edition

下载地址:

评价:这是官方网站上下的文档,但它却是最不适合初学者读的。该书更像一个汇总,解释,手册文档。所以,请务必看完1或2的基础上再来看它。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

接第二部分的内容()

SEAndroid最后一部分

全文PDF下载地址为:

 

     深入理解SELinux SEAndroid(结局)

 

 

二  SEAndroid源码分析

有了上文的SELinux的基础知识,本节再来看看Google是如何在Android平台定制SELinux的。如前文所示,Android平台中的SELinuxSEAndroid

先来看SEAndroid安全策略文件的编译。

 

1.  编译sepolicy

Android平台中:

  • external/sepolicy:提供了Android平台中的安全策略源文件。同时,该目录下的tools还提供了诸如m4,checkpolicy等编译安全策略文件的工具。注意,这些工具运行于主机(即不是提供给Android系统使用的)
  • external/libselinux:提供了Android平台中的libselinux,供Android系统使用。
  • external/libsepol:提供了供安全策略文件编译时使用的一个工具checkcon

对我们而言,最重要的还是external/sepolicy。所以先来看它。

读者还记得上文提到的如何查看make命令的执行情况吗?通过:

mmm external/sepolicy  --just-print

,我们可以看到sepolicy编译时都干了些什么。

#以后用SEPOLICY_TEMP代替

#     out/target/product/generic/obj/ETC/sepolicy_intermediates字符串

 

#创建临时目录

mkdir -p out/target/product/generic/obj/ETC/sepolicy_intermediates/

 

#----->处理一堆输入源文件,最终输出为policy.conf

#执行m4命令,用来生成plicy.conf文件。m4命令将扩展SEAndroid定义的一些宏

m4 -D mls_num_sens=1 -D mls_num_cats=1024 -s

#m4的输入文件。下面标黑体的是SEAndroid一些系统相关的文件,一般不会修改它

security_classes   initial_sids access_vectors

global_macros mls_macros mls

policy_capabilities te_macros attributes

#Android系统中的te文件。

adbd.te app.te bluetoothd.te  bluetooth.te   clatd.te dbusd.te debuggerd.te device.te dhcp.te dnsmasq.te domain.te drmserver.te file.te gpsd.te hci_attach.te healthd.te hostapd.te init_shell.te init.te installd.te isolated_app.te kernel.te keystore.te media_app.te mediaserver.te mtp.te netd.te net.te nfc.te ping.te platform_app.te ppp.te property.te qemud.te racoon.te radio.te release_app.te rild.te runas.te sdcardd.te servicemanager.te shared_app.te shell.te surfaceflinger.te su.te system.te tee.te ueventd.te unconfined.te untrusted_app.te vold.te watchdogd.te wpa_supplicant.te zygote.te

#其他文件

roles  users initial_sid_contexts fs_use genfs_contexts port_contexts

#m4:将上述源文件处理完后,生成policy.conf

> SEPOLICY_TEMP/policy.conf

 

 

#下面这个命令将根据policy.conf中的内容,再生成一个policy.conf.dontaudit文件

sed '/dontaudit/d'

           SEPOLICY_TEMP/policy.conf >

           SEPOLICY_TEMP/policy.conf.dontaudit

 

mkdir -p SEPOLICY_TEMP/

#------>根据policy.conf文件,生成二进制文件。SEAndroid中,它叫sepolicy

#执行checkpolicy,输入是policy.conf,输出是sepolicy

#-M选项表示支持MLS

checkpolicy -M -c 26 -o SEPOLICY_TEMP/sepolicy

                             SEPOLICY_TEMP/policy.conf

#执行checkpolicy,输入是policy.conf.dontaudit,输出是sepolicy.dontaudit

checkpolicy -M -c 26 -o

            SEPOLICY_TEMP/sepolicy.dontaudit    

           SEPOLICY_TEMP/policy.conf.dontaudit

 

#--->将sepolicy拷贝到对应目标平台的root目录下

echo "Install: out/target/product/generic/root/sepolicy"

acp -fp SEPOLICY_TEMP/sepolicy

                  out/target/product/generic/root/sepolicy

 

#---->生成file_context文件

#用FILE_CONTEXT_TEMP代替

#    out/target/product/generic/obj/ETC/file_contexts_intermediates字符串

mkdir -p FILE_CONTEXT_TEMP/

m4 -s  external/sepolicy/file_contexts  > FILE_CONTEXT_TEMP/file_contexts

checkfc  SEPOLICY_TEMP/sepolicy

                  FILE_CONTEXT_TEMP/file_contexts

echo "Install: out/target/product/generic/root/file_contexts"

acp -fp FILE_CONTEXT_TEMP/file_contexts

                     out/target/product/generic/root/file_contexts

 

#--->生成seapp_context文件,这个是Android平台特有的,其作用我们下文再介绍

#用SEAPP_CONTEXT_TEMP代替

#     out/target/product/generic/obj/ETC/seapp_contexts_intermediates

mkdir -p SEAPP_CONTEXT_TEMP/

checkseapp -p SEPOLICY_TEMP /sepolicy

-o SEAPP_CONTEXT_TEMP/seapp_contexts SEAPP_CONTEXT_TEMP/seapp_contexts.tmp

echo "Install: out/target/product/generic/root/seapp_contexts"

acp -fp SEAPP_CONTEXT_TEMP/seapp_contexts

                        out/target/product/generic/root/seapp_contexts

 

#---->和Android平台中的属性相关。SEAndroid中,设置属性也需要相关权限

#用PROPERTY_CONTEXT_TMP代替:

#         out/target/product/generic/obj/ETC/property_contexts_intermediates

mkdir -p PROPERTY_CONTEXT_TMP/

m4 -s  external/sepolicy/property_contexts  >

                         PROPERTY_CONTEXT_TMP/property_contexts

checkfc -p TARGET_SEPOLICY_TEMP/sepolicy

               PROPERTY_CONTEXT_TMP/property_contexts

echo "Install: out/target/product/generic/root/property_contexts"

acp -fp PROPERTY_CONTEXT_TMP/property_contexts

                      out/target/product/generic/root/property_contexts

上面展示了sepolicy编译的执行情况,读者最好自己尝试一下。注意,checkfccheckseapp等都是SEAndroid编译时使用的工具,它们用来做策略检查,看看是否有规则不符合的地方。

总结:

  • sepolicy的重头工作是编译sepolicy安全策略文件。这个文件来源于众多的te文件,初始化相关的文件(initial_sid,initial_sid_context,users,roles,fs_context等)。
  • file_context:该文件记载了不同目录的初始化SContext,所以它和死货打标签有关。
  • seapp_context:和Android中的应用程序打标签有关。
  • property_contexts:和Android系统中的属性服务(property_service)有关,它为各种不同的属性打标签。

下面我们来看看和SEAndroid相关的代码,故事从init开始。

2.  initSEAndroid定制

Android平台中,SEAndroid的初始化由进程的祖先initmain函数完成,相关代码如下所示:

[-->init.c:main]

 

  process_kernel_cmdline();

  //向SELinux设置两个回调函数,主要是打印log

 union selinux_callback cb;

 cb.func_log = klog_write;

 selinux_set_callback(SELINUX_CB_LOG, cb);

  cb.func_audit = audit_callback;

  //selinux_set_callback由libselinux提供。读者可google libselinux各个API

  //的作用

  selinux_set_callback(SELINUX_CB_AUDIT, cb);

   //①初始化SEAndroid

    selinux_initialize(); 

   //②给下面几个目录打标签!

   restorecon("/dev"); 

   restorecon("/dev/socket");

   restorecon("/dev/__properties__");

   restorecon_recursive("/sys");

上述代码中的两个重要函数:

  • selinux_initialize:初始化SEAndroid
  • 一堆的restoercon,全称应该是restore context:就是根据file_contexts中的内容给一些目录打标签。

先来看selinux_initialize

2.1  selinux_initialize分析

[-->init.c:: selinux_initialize]

static void selinux_initialize(void)

{

   /*判断selinux功能是否启用。方法是:

   1) /sys/fs/selinux 是否存在。或者

   2)  ro.boot.selinux 属性不为disabled

  */

   if (selinux_is_disabled()) return;

 

      //加载sepolicy文件

    if (selinux_android_load_policy() < 0) {......}

 

    selinux_init_all_handles();

   /*selinux有两种工作模式

    “permissive”:所有操作都被允许(即没有MAC),但是如果有违反权限的话,会记录日志

    “enforcing”:所有操作都会进行权限检查

    */

    bool is_enforcing = selinux_is_enforcing();

    //设置SELinux的模式

    security_setenforce(is_enforcing);

}

来看上述代码中的两个函数:

  • selinux_android_load_policy:加载sepolicy文件。
  • selinux_init_all_handles:初始化file_contextseapp_contextproperty_context相关内容。
(1)  selinux_android_load_policy

来看selinux_android_load_policy,其代码如下所示:

[-->external/libselinux/src/android.c:: selinux_android_load_policy]

int selinux_android_load_policy(void)

{

    char *mnt = SELINUXMNT;// 值为/sys/fs/selinux

    int rc;//挂载/sys/fs/selinux,SELINUXFS值为"selinuxfs"

    rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);

     ......

    // /sys/fs/selinux为userpace和kernel中的SELinux模块交互的通道

    set_selinuxmnt(mnt);//此函数定义在selinux.h中,属于libselinux API.

 

    return selinux_android_reload_policy(); //加载SEAndroid中的policy文件

}

11展示了Nexus 7/sys/fs/selinux的内容:

11  /sys/fs/selinux的内容

用户空间进程可同读写/sys/fs/selinux的各个文件或其中的子目录来通知Kernel中的SELinux完成相关的操作。

我们此处此处举一个例子,如图11下方红框中的booleans文件夹:

  • 我们可以SELinux的安全配置文件中写一些类似if/else的语句。if中的是布尔判断条件。比如booleans文件夹下有一个in_qemu文件,这个就是sepolicy安全配置文件中的一个布尔变量。in_qemu定义在domain.te文件中,关键词是bool
  • cat booleans/in_qemu:打印in_qemu布尔变量的取值。读者会发现它的值为“0 0”。为什么有两个零呢,这第一个0是它的当前值,第二个零代表pending取值。即还没有赋值给当前值的一个中间变量。如果我们通过 echo "1" > booleans/in_qemu的话,第二个零将变成1
  • 为什么需要有中间变量呢?读者注意图11的右上方有一个commit_pending_bools文件。原来,通过在布尔变量中设置一个pending变量,我们可以实现批处理操作。即先修改1个或多个布尔变量的pending变量,然后往commit_pending_bools1,这样这些一个或多个的布尔变量将使用pending变量取代当前值。

接下来看看selinux_android_reload_policy函数:

 [-->external/libselinux/src/android.c:: selinux_android_reload_policy]

int selinux_android_reload_policy(void)

{

    int fd = -1, rc;  struct stat sb;   void *map = NULL;

    int i = 0;

   // sepolicy_file指明sepolicy文件的路径。Android中有两处,第一个是

  // /data/security/current/sepolicy。第二个是root目录下的sepolicy文件。

  //下面这段逻辑可知,SEAndroid只使用其中的一个,如果/data/目录下有sepolicy文件,则

  //优先使用它

    while (fd < 0 && sepolicy_file[i]) {

        fd = open(sepolicy_file[i], O_RDONLY | O_NOFOLLOW);

        i++;

    }

    ......

    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    ......

   //假设使用根目录下的sepolicy文件。下面这个函数由selinux.h定义,它将此文件加载到

   //内核中

    rc = security_load_policy(map, sb.st_size);

    ......

    munmap(map, sb.st_size);

    close(fd);

     return 0;

}

init通过mmap的方式,将sepolicy文件传递给了kernelinit使用了libselinux提供的API函数来完成相关操作。而libselinux则是通过操作/sys/fs/selinux下的文件来完成和KernelSELinux模块的交互。libselinux库的API不是我们研究的重点,感兴趣的兄弟请自己研究源码。

总之,selinux_android_load_policy干得最重要的一件事情就是将sepolicy文件传递给Kernel,这样Kernel就有了安全策略配置文件,后续的MAC才能开展起来。

在此,请读者注意sepolicy文件的位置:

  • 首先查看/data/security/current/sepolicy:笔者自定义的策略文件一般放在这里,init优先使用它。
  • /sepolicyroot根目录下的sepolicy,如果data目录下没有sepolicy,则使用它。系统默认的sepolicy在此。
(2)  selinux_init_all_handles

前面讲过,init要给一些死货和property打标签,为了完成这个工作,根据libselinuxAPIinit需要先创建两个handler,代码在selinux_init_all_handles中:

[-->init.c:: selinux_init_all_handles]

void selinux_init_all_handles(void)

{

    sehandle = selinux_android_file_context_handle();

    sehandle_prop = selinux_android_prop_context_handle();

}

创建两个handler,主要为后续做labeling控制。我们来看看propcontext

[-->init.c::selinux_android_prop_context_handle]

struct selabel_handle* selinux_android_prop_context_handle(void)

{

    int i = 0;

    struct selabel_handle* sehandle = NULL;

   //setopts_prop也有两个值:

   //第一个是/data/security/property_contexts。第二个是/property_contexts

    while ((sehandle == NULL) && seopts_prop[i].value) {

        sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, &seopts_prop[i], 1);

        i++;

    }

  //假设采用的是根目录下的property_context文件

  ......

  return sehandle;

}

handler其实就是为了打开xxx_context文件。由于它们和restorecon有关,下面直接来看restorecon函数,看看这些handler是怎么个用法。

2.2  restorecon分析

[-->init.c::restorecon]

int restorecon(const char *pathname)

{

    char *secontext = NULL;

    struct stat sb;

    int i;

 

    if (is_selinux_enabled() <= 0 || !sehandle)

        return 0;

 

    if (lstat(pathname, &sb) < 0)   return -errno;

    //查找file_context文件中是否包含有pathname路径的控制选项

    if (selabel_lookup(sehandle, &secontext, pathname, sb.st_mode) < 0)

        return -errno;

   //设置patchname目录的security_context,lsetfilecon的实现非常简单,就是调用

   //

    if (lsetfilecon(pathname, secontext) < 0) {

        freecon(secontext);

        return -errno;

    }

    freecon(secontext);

    return 0;

}

想知道selinux是如何labeling一个文件或目录的吗?答案在lsetfilecon中:

[-->external/libselinux/src/lsetfilecon.c:: lsetfilecon]

int lsetfilecon(const char *path, const security_context_t context)

{

 //设置文件系统的属性

 return lsetxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1,0);

}

2.3  property权限检查

一般而言,SELinux权限检查都是由kernel来完成的,不过对于Android平台中的Property而言,这却完全是一个用户空间的内容。所以,我们看看init是如何使用libselinux来完成用户空间的权限检查的。

每当其他进程通过setprop函数设置属性时,property_service中有一个叫check_

[system/core/init/property_service.c:: check_mac_perms]

static int check_mac_perms(const char *name, char *sctx)

{

    if (is_selinux_enabled() <= 0)  return 1;

 

    char *tctx = NULL;

    const char *class = "property_service";

    const char *perm = "set";

    int result = 0;

    ......

    //检查property_context中是否定义了目标SContext,即tctx。

    if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0) goto err;

   //将源SContext和目标SContext进行比较,判断是否有相关权限。name是属性的名字

   //源SContext是调用setprop进程的SContext。目标SContext是property_context

   //文件中定义的SContext。

    if (selinux_check_access(sctx, tctx, class, perm, name) == 0)

        result = 1;

    freecon(tctx);

 err:

    return result;

}

怎么样?理解起来并不困难吧?用户空间的权限检查主要就是通过selinux_check_access完成,其输入参数包括:

  • 源的SContext:它就是调用setprop的进程的SContext
  • 目标的SContext:不同的属性有不同的SContext,这是在property_context中定义的。
  • 要检查的Object class(系统所支持的类在external/sepolicy/security_classes文件中定义)。
  • 操作名称(perm,由access vector定义。对Property这种Object class而言,其唯一需要做权限检查的操作就是set。读者可参考external/sepolicy/access_vectors这个文件)。

具体的哪一个属性(name参数指定,就是具体指明哪一文件)。

提示:关于这些API的说明,读者请参考中的Manual pages文档。

下面我们来看Android中应用程序是如何使用SELinux的。

3.  应用程序中的SELinux

对应用程序而言,最重要的工作就是管理它们的DTTT

  • 所有AndroidApplication对应的进程都是从zygote进程中fork出来的。从前文介绍DT的知识可知,在做DT时,可以根据所执行的不同Type的文件来转换到不同的DT。但这个对Android而言不可行。因为zygotefork子进程后,并没有执行execv
  • apk在安装后,都会在/data/data/目录下建立自己对于的文件夹,这个工作是由installd来完成的。同样,installd应该给这些不同的文件夹打上对应的label

我们先来看应用程序的DT

3.1  Java应用程序的DT

Android中应用进程(就是APK所在的进程)的DT转换其实很简单,它及其具有Android特色:

  • 普通的DT是根据所execv文件的Type来设置DT转换条件。
  • Android中则根据该APK签名信息来讲最终的进程转换到几种预设值的Domain中。
(1)  mac_permissions.xml的用途

我们先来看PackageManagerService

[-->PackageManagerService.java::PackageManageService]

......

/*下面这个函数将尝试解析

 1)/data/security/mac_permissions.xml 或

 2)/system/etc/security/mac_permissions.xml 中的内容。

*/

mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

注意,mac_permissions.xml位于external/sepolicy中,图12所示为该文件的原始:

12  mac_permissions.xml的内容

在系统过程中,图12中的@RELEASE@PLATFORM等内容会被RELEASEPLATFORM签名信息替换。图13所示为Nexus 7中该文件的内容。

13  Nexus7mac_permissions.xml内容

mac_permissions.xml保存了不同签名所对应的seinfo:如seinfoplatform时的签名是什么,seinfomedia的时候签名又是什么。那么,这些信息有啥用呢?来看下文。

(2)  扫描APK

APK安装时,也就是APKPKMGS扫描的时候,有如下的代码:

[-->PackageManagerService.java::ScanPackageLI]

if (mFoundPolicyFile) {

     //下面这个函数将根据签名信息赋值seinfo值给对应的apk

     SELinuxMMAC.assignSeinfoValue(pkg);

   }

[-->SELinuxMMAC.java::assignSeinfoValue]

public static void assignSeinfoValue(PackageParser.Package pkg) {

 

     //对于系统app(预装的,位于system目录下的)

     if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||

          ((pkg.applicationInfo.flags &

                         ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {

   

        for (Signature s : pkg.mSignatures) {

                if (s == null)  continue;

              //sSigSeinfo存储了mac_permissions.xml中seinfo标签的内容

              if (sSigSeinfo.containsKey(s)) {

                    String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(s);

                    return;

                }

            }

            //sPackageSeinfo存储了xml中package标签下seinfo子标签的内容

            if (sPackageSeinfo.containsKey(pkg.packageName)) {

                String seinfo = pkg.applicationInfo.seinfo =

                              sPackageSeinfo.get(pkg.packageName);

                 return;

            }

        }

        //default标签中seinfo的值

        String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(null);

        ......

  }

assignSeinfoValue的功能如上代码所示,它根据apk的签名信息来赋值不同的seinfo,也就是诸如"platform",”media“之类的值。

提示:大家能想出为什么要设置seinfo吗?恩,它就是AndroidApp定义的SContext中的Domain的值。

(3)  App的DT转换

ActivityManagerService负责启动目标应用进程,相关代码如下所示:

[-->ActivityManagerService.java:: startProcessLocked]

Process.ProcessStartResult startResult =

                     Process.start("android.app.ActivityThread",

                    app.processName, uid, uid, gids, debugFlags, mountExternal,

                    app.info.targetSdkVersion, app.info.seinfo, null);

根据《深入理解AndroidI》第4章对zygote的介绍,zygote进程将fork一个子进程,相关函数在:

[-->ZygoteConnection.java::runOnce]

pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,

                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal,

                    parsedArgs.seInfo,parsedArgs.niceName);

该函数由JNI实现,代码在dalvik/vm/native/ dalvik_system_Zygote.cpp中,其中最重要的是内部所调用的forkAndSpecializeCommon

[-->dalvik_system_Zygote.cpp:: forkAndSpecializeCommon]

pid = fork();

if (pid == 0) {

  ......

    err = setSELinuxContext(uid, isSystemServer, seInfo, niceName);

  .....}

 

[-->external/libselinux/android.c::selinux_android_setcontext]

int selinux_android_setcontext(uid_t uid,int isSystemServer,

                   const char *seinfo,const char *pkgname)

{

    char *orig_ctx_str = NULL, *ctx_str;  context_t ctx = NULL;

    int rc = -1;

    if (is_selinux_enabled() <= 0)  return 0;

   

    //重要函数:seapp_context_init,内部将调用selinux_android_seapp_context_reload

   //以加载seapp_contexts文件。

   // 1) /data/security/current/seapp_contexts 或者

   // 2) /seapp_contexts   本例而言,就是根目录下的这个seapp_context文件

    __selinux_once(once, seapp_context_init);

   

    rc = getcon(&ctx_str);

    ctx = context_new(ctx_str);

    orig_ctx_str = ctx_str;

    //从zygote进程fork出来后,最初的SContext取值为u:r:zygote:s0

   //下面这个函数将根据uid,pkgname等设置最终的SC。例如u:r:system_app:s0等

    rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname,

     ctx);

   ctx_str = context_str(ctx);

   rc = security_check_context(ctx_str);

   if (strcmp(ctx_str, orig_ctx_str)) {

        rc = setcon(ctx_str);

    }

 

    rc = 0;

    ......

    return rc;

}

14所示为seapp_context的内容,非常简单:

14  seapp_context内容

上面代码中的seapp_context_lookup将根据图14的内容,通过不同的apk所对应的seinfo,找到他们的目标domain,然后再设置为它们新的SContext。例如图15Nexus 7ps -Z的结果图。

15  ps -Z查看apk进程的SContext

seapp_context_lookup是完成从seapp_context文件内容映射到具体对应为哪个Domain的关键函数,该函数第一次看起来吓死人,其实蛮简单。这里就不再多说。

anywaySEAndroid中,不同应用程序将根据它们的签名信息得到对应的SContext(主要是DomainMLS其实没用上,但以后可以用上,这是通过图14中的levelFrom语句来控制的,具体可参考seapp_context_lookup的实现)。

DT完成后,我们看系统如何为它们的对应文件夹打标签

3.2  App data目录的TT

还是在PackageManagerServicescanPackageLI函数中,

[-->PackageManagerService.java:: scanPackageLI]

int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,

                                           pkg.applicationInfo.seinfo);

createDataDirsLI最终会调用installd实现的函数:

[-->installd/commands.c::install]

//内部调用selinux_android_setfilecon2,它和上文的selinux_android_setcontext

//几乎一样。最终它将设置pkgdir的SContext。注意,它主要根据seapp_context文件中的

//type字段来确定最终的Type值。

if (selinux_android_setfilecon2(pkgdir, pkgname, seinfo, uid) < 0) {

     ......

 }

16展示了ls -Z /data/data目录下的结果。

16  /data/data目录下ls -Z的结果

是不是和图14seapp_context文件的type字段描述一样一样的?

 

4  小试牛刀

下面,笔者将通过修改shell的权限,使其无法设置属性。

先来看shellte,如下所示:

[external/sepolicy/shell.te]

# Domain for shell processes spawned by ADB

type shell, domain;

type shell_exec, file_type;

#shell属于unconfined_domain,unconfined即是不受限制的意思

unconfined_domain(shell)

 

# Run app_process.

# XXX Split into its own domain?

app_domain(shell)

unconfied_domain是一个宏,它将shell和如下两个attribute相关联:

[external/sepolicy/te_macros]

#####################################

# unconfined_domain(domain)

# Allow the specified domain to do anything.

#

define(`unconfined_domain', `

typeattribute $1 mlstrustedsubject; #这个和MLS有关

typeattribute $1 unconfineddomain;

')

unconfineddomain权限很多,它的allow语句定义在unconfined.te中:

[external/sepolicy/unconfined.te]

......

allow unconfineddomain property_type:property_service set;

从上面可以看出,shell所关联的unconfineddomain有权限设置属性。所以,我们把它改成:

allow {unconfineddomain -shell} property_type:property_service set;

通过一个“-”号,将shell的权限排除。

然后:

  • 我们mmm external/sepolicy,得到sepolicy文件。
  • 将其push/data/security/current/sepolicy目录下
  • 接着调用setprop selinux.reload_policy 1,使得init重新加载sepolicy,由于/data目录下有了sepolicy,所以它将使用这个新的。

17所示为整个测试的例子:

17  测试结果

根据图17

  • 重新加载sepolicy之前,笔者可通过"setprop wlan.driver.status test_ok"设置该属性的值为test_ok。注意,这个值笔者瞎设的。
  • 当通过setprop selinux.reload_policy 1的命令后,init重新加载了sepolicy
  • 从此,笔者再setprop wlan.driver.status 都不能修改该属性的值。

18所示为dmesg输出,可以看出,当selinux使用了data目录下这个新的sepolicy后,shellsetprop权限就被否了!

18  dmesg输出

提示:前面曾提到过audit,日志一类的事情。恩,这个日志由kernel输出,可借助诸如audit2allowhost上的工具查看哪些地方有违反权限的地方。系统会将源,目标SContext等信息都打印出来。

 

  全文总结

本文对SELinux的核心知识进行了介绍。从入门角度来说,有了这些内容,SELinux大概80%左右的知识都已经介绍,剩下来的工作就是不断去修改和尝试不同的安全配置文件。

然后我们对SEAndroid进行了相关介绍,这部分基本上反映了Android是如何利用这些安全配置文件来构造自己的安全环境的。

从目前AOSP SEAndroid安全配置源文件来看,很多te文件中都使用了如下这样的语句:

19  permissive定义

其中,permissive关键词表示不用对上述这些type/domain进行MAC监管。permissive一般用于测试某个策略,看是否对整个系统有影响。一旦测验通过,就可以把permissve语句移掉,以真正提升安全。

基于SEAndroid,广大搞机人可以:

  • 针对行业,开发更加安全的安全策略。
  • 定制MLS,针对企业级或军用级的多层权限管理。
  • 不知道google有没有意向实现Modular Policy,这样的话,SELinux灵活性更高。

另外,要提醒读者的是,安全配置需要考虑的东西非常多,稍有不甚,就会影响系统其他模块的运行。比如笔者在研究SELinux时,不小心把Ubuntu的图像界面系统启动不了,后来只能移除SELinux后才解决。这也是为什么SELinux出来这么多年,但是大家好像碰到它的机会很少的原因,因为它的配置实在是太麻烦,很容易出错!

最后,反复提醒读者,一旦修改了策略文件,务必进行全方位,多层面测试。

关于SEAndroid的更多官方说明,请参考

转载地址:https://loongembedded.blog.csdn.net/article/details/50845557 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android 5.0 SEAndroid下如何获得对一个内核节点的访问权限
下一篇:ARM Linux内核Input输入子系统浅解

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月23日 02时14分55秒