SEAndroid 介绍

Posted by feizaipp on March 28, 2019

我的博客

1. 介绍

SEAndroid 是 Google 在 Android 4.4 上正式推出的一套以 SELinux 为基础核心的系统安全机制。 SELinux 是针对 Linux 设计的安全加强系统。

2. SELinux 背景知识

Google 针对 SELinux 进行了一定的修改,从而得到了 SEAndroid ,所以我们先了解下 SELInux 相关知识。

2.1. DAL 和 MAC

SELinux 出现之前, Linux 上的安全模型叫 DAC ,全称是 Discretionary Access Control ,翻译为自主访问控制。 DAC 的核心思想很简单,就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以 root 用户启动 Browser ,那么 Browser 就有 root 用户的权限,在 Linux 系统上能干任何事情。

显然, DAC 太过宽松了。 SELinux 为了解决这个问题设计了一个新的安全模型,叫 MAC(Mandatory Access Control) ,翻译为强制访问控制。 MAC 的核心思想是:任何进程想在 SELinux 系统中干任何事情,都必须先在安全策略配置文件中赋予权限,凡是没有出现在安全策略配置文件中的权限,进程都没有该权限。例如:在 external/sepolicy/netd.te 中有下面语句: allow netd proc:file write ,该语句表示允许 netd 域中的进程写类型为 proc 的文件。如果在 net.te 中没有使用以上语句,则 netd 就无法往 /proc 目录下的任何文件中写数据,即使 netd 具有 root 权限。

2.2. SELinux Policy 语言简介

通过以上的介绍,在 SELinux 中,安全策略文件是最重要的,所以了解 SELinux Policy 语言是很有必要的。

Linux 中有两种东西,文件和进程。进程能发起动作,例如它能打开文件并操作它,而文件只能被进程操作。 SELinux 中,每种东西都会被赋予一个安全属性,官方说法叫 Security Context(SContext) , SContext 是一个字符串。

在 SEAndroid 中,进程的 SContext 可通过 ps –Z 命令查看,如下图所示:

进程的安全标签

上图中最左侧的那一列是进程 SContext 。以第一个进程 /init 为例,其值为 u:r:init:s0 ,其中:

  • u 为 user 的意思。 SEAndroid 中定义了一个 SELinux 用户,值为 u ;
  • r 为 role 的意思。 role 是角色的意思,它是 SELinux 中一种比较高层的东西,更方便的权限管理思路,即 Role Based Access Control ,翻译为基于角色的访问控制,简称 RBAC 。一个 u 可以属于多个 role ,不同的 role 具有不同的访问权限
  • init 代表该进程所属的 Domain 为 init 。 MAC 的基础管理思路是针对 Type Enforcement Access Control ,简称 TEAC ,一般用 TE 表示。对进程来说 Type 就是 Domain 。比如 init 这个 Domain 有什么权限,都需要通过 allow 语句说明, allow 语句写在安全策略配置文件里。
  • s0 与 Multi-Level Security(MLS)机制有关。 MLS 将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问。

在 SEAndroid 中,文件的的 SContext 可以通过 ls –Z 来查看,如下图所示:

文件的安全标签

上图中倒数第二列所示为文件和目录的 SContext 信息,以第一行 ueventd.qcom.rc 为例,其信息为 u:object_r:rootfs:s0 。

  • u 表示 user,它代表创建这个文件的 SELinux 用户
  • object_r 表示文件对应的 role。文件是死的东西,它没办法扮演角色,所以 SELinux 中,死的东西都用 object_r 来表示 role
  • rootfs 表示文件对应的 Type,和进程的 Domain 是一个意思。它表示 ueventd.qcom.rc 文件对应的 Type 为 rootfs
  • s0 表示 MLS 的级别

根据 SELinux 规范,完整的 SContext 字符串为: user:role:type:[rang] , rang 是可选项。

2.3. TE 介绍

前面已经介绍了 TE 的 allow 语句,再来分析一下它:

allow netd proc:file write

这条语句的语法为:

  • allow : TE 的 allow 语句,表示授权。除了 allow 之外,还有 allowaudit 、 dontaudit 、 neveraudit 等
  • netd : source type ,也叫 subject 或 domain
  • proc : target type ,它代表其后的 file 所对应的 type
  • file :代表 Object Class ,它代表能够给 subject 操作的一类东西。例如 file 、 dir 、 socket 等。 Android 系统存在其他 Linux 系统没有的 Object Class ,即 binder 和 zygote
  • write :在该类Object Class中所定义的操作

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

rule_name source_type target_type:object_class perm_set
  • 多个 perm_set 用 {} 括起来,中间用空格隔开,例如, allow zygote appdomain:dir { getattr search }; 表示允许 zygote 域中的进程 search 或 getattr 类型为 appdomain 的目录
  • allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } ~{entrypoint relabelto}; 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 所拥有的其他操作
  • 特殊符号出了 ‘~’ 之外,还有-号和 ‘*’ 号,其中 ‘-‘ 号表示去除某项内容, ‘*’ 号表示所有内容
  • neverallow { appdomain -unconfineddomain } self:capability2 *; 这条语句中, source_type 为 appdomain ,但不属于 unconfineddomain 的进程,而 * 号表示所有和 capability2 相关的权限, neverallow 表示决不允许
  • 特别注意,权限必须显示声明,没有声明的话默认就没有权限。那 neverallow 语句就没有必要存在了,因为无权限是不需要声明的。确实如此, neverallow 语句的作用只是生成安全策略文件时进行检查,判断是否有违法 neverallow 语句的 allow 语句

2.3.1. rule_name

TE 中的 rule_name 有四种:

  • allow :赋予某项权限
  • allowaudit : audit 含义是记录某项操作,默认情况下 SELinux 只记录那些权限检查失败的操作, allowaudit 则使得权限检查成功的操作也被记录
  • dontaudit :对那些权限检查失败的操作不做记录
  • neverallow :用来检查安全策略文件中是否有违反该项规则的 allow 语句

2.3.2. Object Class

根据 SELinux 的规范, Object Class 类型由 class 关键字声明。 Android 平台支持的 Object Class 在 external/sepolicy/security_classes

# 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 这个词和用户空间中的 SELinux 权限检查有关
class property_service 	# userspace

2.3.3. Perm Set

根据 SELinux 规范, Perm Set 也需要声明,在 external/sepolicy/access_vectors 文件中。

SELinux 规范中,定义 Perm Set 有两种方式,一种是使用下面的 common 命令,其格式为: common common_name {permission_name…} 。另一种是使用 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 }

2.3.4. type 和 attribute

type 命令的完整格式为: type type_id [alias alias_id,] [attribute_id] ;其中方括号中的内容是可选的, alias 指定了 type 的别名,可以指定多个别名。例如: type shell,domain 定义了一个名为 shell 的 type ,它和一个名为 domain 的属性关联,可以关联多个属性。属性由 attribute 关键字定义, attribute 文件中定义的 SEAndroid 使用的属性有: attribute domain 、 attribute file_type 等。

可以在定义 type 的时候直接和某个 attribute 关联,也可以单独通过 typeattribute 将某个 type 和某个或多个 attribute 关联。例如: typeattribute system mlstrustedsubject 表示将 system 类型和 mlstrustedsubject 属性关联起来。 type 和 attribute 位于同一命名空间,不能用 type 命令和 attribute 命令定义相同名字的东西。 attribute 真正的意思应该是类似 type group 这样的概念,比如, type A 和 attribute B 关联起来,就是说 type A 属于 group B 中的一员。那么,使用 attribute 有什么好处呢?一般而言,系统会定义数十个或数百个 type ,每个 type 都需要通过 allow 语句来设置权限,这样安全策略文件编起来就会非常麻烦。有了 attribute 后,我们可以将这些 type 与某个 attribute 关联起来,然后用 allow 语句,直接将 source_type 设置为这个 attribute 就可以了。

2.4. RBAC 和 constrain

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

在TEAC之上还有一种基于 Role 的安全策略,也就是 RBAC 。 RBAC 是如何实施相关的权限控制呢?我们先来看 SEAndroid 中 Role 和 User 的定义。 Android 中只定义了一个 role ,名字是 r ,在 external/sepolicy/roles 文件中: role r;

将上面定义的 r 与 attribute domain 关联起来:

role r types domain;

再来看看 user 的定义,支持 MLS 的 user 定义格式为:

user seuser_id roles role_id level mls_level range mls_range

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

user seuser_id roles role_id;

在 SEAndroid 中使用了支持 MLS 的格式。在 external/sepolicy/users 定义了 user:

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

上面定义的 user u 将和 role r 关联,一个 user 可以和多个 role 关联。 level 后的是该 user 具有的安全级别。 s0 为最低级别,也就是默认的级别, mls_systemhigh 为 u 所能获得的最高安全级别。

那么, role 和 user 有什么样的权限控制呢?

  • 允许从一个 role 切换到另一个 role ,例如: allow from_role_id to_role_id
  • 角色之间的关系。 Dominance {role super_r {role sysadm_r;role secadm_r;}} 这句话表示 super_r dominance sysadm_r 和 secadm_r 这两个角色。从 type 的角度看, super_r 将自动继承 sysadm_r 和 secadm_r 所关联的 type 或 attribute

怎样实现基于 role 和 user 的权限控制呢?

SELinux提供了一个关键字, constrain 。 constrain 的标准格式是: constrain object_class_set perm_set expression; 例如: constrain file write (u1==u2 and r1==r2); 表示只有 source 和 target 的 user 相同,并且 role 也相同,才允许 write object_class 为 file 的客体。 Constrain 中最关键的是 expression ,它包含如下关键词:

  • u1 r1 t1 :代表 source 的 user role 和 type
  • u2 r2 t2 :代表 target 的user role 和 type
  • == 和 != : == 表示相等或属于, != 表示不等或不属于。对于 r,u 来说, == 和 != 表示相等或不相等。而当 t1== 或 t1!= 某个 attribute 时,表示源 type 属于或不属于这个 attribute
  • dom domby incomp eq ;仅仅针对 role,表示统治,被统治,没关系和相等

SEAndroid 中没有使用 constrain,而是使用 mlsconstrain。

2.5. Labeling 介绍

上面介绍了 SELinux 中最常见的东西,那么,这些 SContext 最开始是怎么赋值这些进程和文件的呢? SELinux 中,设置或分配 SContext 给进程或文件的工作叫 Security Labeling 。 Android 系统启动后, init 进程会将一个编译完的安全策略文件传递给 kernel 以初始化 kernel 中的 SELinux 相关的模块(姑且用 Linux Security Module:LSM 来表示它),然后 LSM 可根据其中的信息给相关 object 打标签。

2.5.1. sid 和 sid_context

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

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

在 external/sepolicy/initial_sids 中有如下内容:

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

在 external/sepolicy/initial_sid_context 中有如下内容:

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

2.5.2. Domain/Type Transition

SEAndroid 中, init 进程的 SContext 为 u:r:init:s0 ,而 init 创建的子进程显然不会也不可能拥有和 init 进程一样的 SContext 。那么这些子进程是怎么打上和其父进程不一样的 SContext 的呢? SELinux 中,上述问题被称为 Domain Transition ,即某个进程的 Domain 切换到一个更合适的 Domain 中去。 Domain Transition 也是需要我们在安全策略文件中来配置的,使用 type_transition 关键字, type_transition 的完整格式为:

type_transition source_type target_type:class default_type

例如:

type_transition init_t apache_exec_t:process apache_t

上面的例子解释如下:当 init_t Domain 中的进程执行 type 为 apache_exec_t 类型的可执行文件(fork 并 execv)时,其 class(此处是 process)所属的 Domain 需要切换到 apache_t 域。因为 kernel 中,从 fork 到 execv 一共设置了三处 Security 检查点,所以执行上面语句,还需要至少三个 allow 语句配合:

  • 首先,得让 init_t 域中的进程能够执行 type 为 apache_exec_t 的文件 : allow init_t apache_exec_t:file execute;
  • 然后,告诉 SELinux ,允许 init_t 做 Domain Transition 切换进入 apache_t 域 : allow init_t apache_t:process transition
  • 最后,告诉 SELinux ,切换入口为执行 apache_exec_t 类型的文件 allow apache_t apache_exec_t:file entrypoint

2.5.3. 宏

按照以上规范编写 TE 文件会比较麻烦,还好 SELinux 支持宏,这样我们可以定义一个宏语句把上述 4 个步骤全部包含进来。在SEAndroid 中,系统定义的宏全在 te_macros 文件中,其中和 Domain Transition 相关的宏定义如下:

# external/sepolicy/te_macros 文件中
define(‘domain_trans’, ’
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 {signh rlimitinh};
’)

上面定义了 domain_trans 宏。$1,$2…等代表宏的第一个、第二个…参数。

define(‘domain_auto_trans’, ‘
domain_trans($1,$2,$3)
type_transition $1 $2:process $3;
’)

上面定义 domain_auto_trans 宏,这个宏才是我们在 .te 文件中直接使用的,上面的例子可以使用下面的宏:

domain_auto_trans(init_t,apache_exec_t,apache_t)

2.5.4. File/File System 打 label

2.5.4.1 file_contexts 文件

在 SEAndroid 中,关于 file 的 SContext 在 external/sepolicy/file_contexts 文件中。在该文件中注意 * 号, ? 号,代表通配符, * 号代表 0 个或多个字符, ? 号代表一个字符。注意 -- 号, SELinux 中类似的符号还有:

  • ‘-b’ - Block Device
  • ‘-c’ - Character Device
  • ‘-d’ - Directory
  • ‘-p’ - Named Pipe
  • ‘-l’ - Symbolic Link
  • ‘-s’ - Socket
  • ‘–’ - Ordinary file

    2.5.4.2 fs_use 文件

external/sepolicy/fs_use 文件,该文件描述了 SELinux 的 labeling 信息在不同的文件系统时的处理方式。对于常规的文件系统, SContext 信息存储在文件节点的属性中,系统可以通过 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;

对于虚拟文件系统,即 Linux 运行过程中创建的 VFS ,则使用 fs_use_task 关键字描述,目前仅有 pipefs 和 socketfs 两种 VFS 格式,例如:

fs_use_task pipefs u:object_r:pipefs:s0;
fs_use_task sockfs u:object_r:sockfs:s0;

还有一个 fs_use_trans ,它也是用于 VFS ,但根据 SELinux 官方描述,这些 VFS 是针对 pesudo terminal 和临时对象。在具体 labeling 时,会根据 fs_use_trans 以及 Type Transition 的规则来决定最终的 SContext 。例如:

fs_use_trans devpts u:object_r:devpts:s0;
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;

假设有下面一条策略,表示当 sysadm_t 的进程在 Type 为 devpts 下创建一个 chr_file 时,其 SContext 将是 sysadm_devpts_t:s0 。如果没有这一条 TE ,则将使用 fs_use_trans 设置的 SContext 为 u:object_r:devpts:s0 。

type_transition sysadm_t devpts:chr_file sysadm_devpts_t:s0;

2.5.4.3 genfs_context 文件

external/sepolicy/genfs_context 文件,一般就是根目录、proc目录、sysfs目录,需要使用 genfscon 关键字来打 labeling 。

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

2.6. Security Level 和 MLS

2.6.1. Security Level

SELinux 中添加了多等级安全管理,即 Multi-Level Security 。多等级安全信息也被添加到 SContext 中。所以,在 MLS 启用的情况下,完整的 SContext 规范如下:

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

上面 sensitivity[:category,...]- sensitivity [:category,...] 表示 Security-level 范围 [low security level] - [high security level] ,由三部分组成:

  • low security level :表明当前 SContext 所对应的当前(也就是最小)安全级别
  • 连字符 “-” ,表示 range
  • high security level :表明当前 SContext 所对应最高可能获得的安全级别

security level 由两部分组成,先来看第一部分由 sensitivity 关键字定义的 sensitivity ,用 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

alias 并不是 sensitivity 的必要选项,且名字可以任取。在 SELinux 中,真正设置 sensitivity 级别的是由下面这个关键词表示: dominance {s0 s1 s2.....sn} ,在 dominance 语句中,括号内最左边的 s0 级别最低,依次递增,直到最右边的 sn 级别最高。

再来看 security level 第二部分,即 category 关键字及用法。 Category 语法规范为: category cat_id alias alias_id; 例如:

category c0
category c1

category 和 sensitivity 不同,它定义的是类别,类别之间是没有层级关系的。 SEAndroid 中, sensitivity 只定义了 s0 , category 定义了 c0-c1023 。 senstivity 和 category 一起组成了一个 security level(以后简称SLevel), SLevel 由关键字level 声明,如下例所示: level sens_id [ :category_id ];

举例: level s0:c0.c255; sensitivity 为 s0 , category 从 c0,c1,c2 一直到 c255 ,注意其中的 . 号。 SLevel 可以没有 category_id 。看一个例子: level s0 。和 Role 类似, SL1 和 SL2 之间的关系有:

  • dom :如果 SL1 dom SL2 的话,则 SL1 的 sensitivity >= SL2 的 senstivity , SL1 的 category 包含 SL2 的 category (即 Category of SL1 是 Category of SL2 的超集)。例如: SL1=”s2:c0.c5” dom SL2=”s0:c2.c3”
  • domby :和 dom 相反
  • eq : sensitivity 相等, category 相同
  • incomp :不可比。 sensitivity 不可比, category 也不可比

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

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

前面例子中,我们看到 Android 中, SContext 有: u:r:init:s0,在这种情况下, Low SLevel 等于 High SLevel ,而且 SLevel 没有包含 Category 。

2.6.2. mlsconstrain

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

mlsconstrain class perm_set expression;

和 constrain 不一样的是, expression 除了 u1,u2,r1,r2,t1,t2 外还新增了: l1,l2 , l1 表示 source 的 low senstivity level 。 l2 表示 target 的 low sensitivity ; h1,h2 : h1 表示 source 的 high senstivity level 。 h2 表示 target 的 high sensitivity 。 l 和 h 的关系,包括 dom,domby,eq 和 incomp 。 mlsconstrain 只是一个 Policy 语法,那么我们应该如何充分利用它来体现多层级安全管理呢?看下图:

MLS 图例

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

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

2.6.3. MLS in SEAndroid

再来看看 SEAndroid 中的 MLS :系统中只有一个 sensitivity level ,即 s0 。系统中有 1024 个 category ,从 c0 到 c1023 。

3. 编译安全策略文件

在 Android 源码的 external/sepolicy/ 目录下有各种文件,这些文件会被组合到一起,组合到一起后的文件中有使用宏的地方,需要利用 m4 命令对这些宏进行扩展。 m4 命令处理完成后得到的文件叫 policy.conf ,它是所有安全策略源文件的集合,宏也被替换了。所以,我们可以通过该文件查看整个系统的安全配置情况。 policy.conf 文件最终要被 checkpolicy 命令处理,该命令要检查 neverallow 是否被违背,语法是否正确等。最后, checkpolicy 会将 policy.conf 打包成二进制文件。在 SEAndroid 中,该文件叫 sepolicy 。最后,我们再把这个 sepolicy 文件传递到 kernel LSM 中,整个安全策略配置就算完成了。


评论与讨论

LET’S TALK

有问题或新的想法?欢迎交流。使用 GitHub 账号登录后即可留言,支持 Markdown。

评论将在滚动到这里时加载。

在 GitHub 查看讨论 ↗