NotePublic/Software/Development/System/Android/SELinux/Android_SELinux_详解.md

25 KiB
Raw Blame History

Android SELinux 详解

1.Kernel SELinux 模块

2.模式控制

SELinux 有三种工作模式强制模式enforcing、宽容模式permissive和关闭模式disabled

  • enforcing代表 SELinux 运作中,且会执行实际的限制;
  • permissive代表 SELinux 运作中,但只会记录警告讯息并,不执行实际的限制;
  • disabled代表 SELinux 不会实际运作。

临时设置 SELinux 为 permissive 的方法如下:

adb root
adb shell setenforce <0|1>

在 Android 系统下,可通过设置 Bootargs 来改变 SELinux 的工作模式,参数、取值范围及说明如下:

# 设置 SELinux 为强制模式
androidboot.selinux=enforcing
# 设置 SELinux 为宽容模式
androidboot.selinux=permissive
# 设置 SELinux 为关闭模式
androidboot.selinux=disabled

可以通过:

adb shell getenforce

获取当前的 SELinux 工作模式。

3.SEAndroid

SEAndroid 是在 Android 系统中基于 SELinux 推出的强制访问控制模型,来完善自主访问模型中只要取得 root 权限就可以为所欲为的情况。

SEAndroid 工作大致分为两部分:打标签和根据标签做权限控制。

3.1.打标签

SEAndroid 会给所有的文件(包括进程贴上标签),这些标签称为 Security Context。我们可以通过 ls -Z 来查看文件的 Security Context 或者通过 ps -Z 来查看进程的 Security Context。

Security Context 由四部分组成,每一部分用冒号隔开,来看一个例子:

u:r:init:s0
u:object_r:rootfs:s0
  • 第一部分 u 代表用户,在 SEAndroid 中只有一个用户,就是 u
  • 第二部分 r 或者 object_r 代表用户的角色,如果角色是 r 代表的是进程,如果角色是 object_r 代表的是文件;
  • 第三部分 init 或者 rootfs 等,代表类型或者域,一般管文件的这部分叫类型,管进程的这部分叫域。不同的域拥有不同的权限;
  • 第四部分 s0 代表安全等级,我们不用关注。

3.2.根据标签做权限控制

这一部分主要是编写 Policy 文件了,最常见的 Policy 就是 TE 文件,举个栗子:

allow netd proc:file write

简单说一下语法:

action domain type:object_class operation
  • allow 是 TE 中定义的动作词,表示允许,类似的还有 neverallow 表示不允许;
  • netd 表示域,就是进程所属的域,代表某一类进程;
  • proc 表示 type就是 file 的 type表示一类文件
  • file 表示 object class是 type 下的某一个具体类型,是具体给进程操作的东西,这些 object class 是操作系统预定义好的,在 security_class 文件中,每一个 object class 都用 class 关键字声明;
  • write 表示操作,允许进行的操作也是系统定义好的,在 access_vector 文件中,每一种 object class 都对应一组操作,比如 common file {write read},或者 class bind {read write},其中 common 和 class 的区别是 common 可以被继承,而 class 定义的操作就是针对于某种 object class 的,不能被继承。

上面的 TE 语句的意思是:允许标签为 netd 的进程去向标签为 proc 的 file 写入数据。

接下来看一下如何定义 type 或者 domain使用 type 关键字定义:

type type_name, attribute_name

attribute_name 表示 type 所属的属性组,一个 type 可以属于多个属性组。这样我们在编译 SELinux 策略的时候通过对属性组编写,就可以应用到该属性组之下的所有 type 了。Android 系统定义的属性组一般都放在 attribute 文件中。

3.3.SELinux 中用户和角色的定义

在 roles 文件下定义了角色,每一个角色都有不同的功能,所以每一个角色都和一个域相关联,比如:

role r types domain

就把 r 这个角色和名称为 domain 的这个域关联起来了。一个用户可以拥有多个角色:

user u roles {r}

这样就把用户 u 和角色 r 关联起来了。多个角色之间有层级关系比如老板比总经理大SELinux 用 dominance 来表示,比如:

dominance{role 老板 {role 总经理;role 总监}}

这就表明老板比总经理和总监都大,从 type 来说就是老板会继承总经理和总监所关联的属性组。

3.4.基于角色的控制

在 SEAndroid 中定义的 u 和 r 是怎么做到基于角色的控制呢? 使用 constrain 关键字,形式如下:

constrain object_class operation expression

比如

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

表示只有用户相同并且角色相同的时候,才允许对 file 这个 class 执行 write 操作。其中 expression 中可以使用的符号有 ==!=domdomby就是前面说的上下级关系

3.5.打标签以及 transition

这涉及到两个话题:一个是系统是如何给文件还有进程打标签的。另一个是我们知道进程是一个可执行文件的执行过程,系统可以给某个可执行文件打标签,但是当这个可执行文件运行起来变成进程的时候,这个进程的标签是怎么样的。另外,进程都可以 fork那么一个进程 fork 出来的进程的标签是怎么样的?这就需要 transition 了。

给进程和文件打标签涉及到两个内核文件initial_sids 和 initial_sid_context.

initial_sids 算是给我们的标签起别名,因为我们知道一个标签是由四部分组成的字符串,直接使用起来不方便,这个就是给标签起别名,举个例子:

sid security
sid init

然后 initial_sid_context 是把标签的别名和标签联系起来,举个例子:

sid security u:object_r:kernel:s0
sid init u:object_r:unlabeled:s0

接下来重点看一下 transition使用 tryp_transition 关键字:

type_transition source_type target_type:object_class result_type

举个例子:

type_transition init_t apache_exec_t : process apache_t;

这句话是说当一个 type 为 init_t 的进程去执行一个 type 为 apache_exec_t 的文件时生成的object_class 为 process这个 process 的类型为 apache_t。这样当我们的 init 进程去执行 apache 文件的时候,生成的进程 type 就是 apache_t。

理想是美好的,但是要达到上述目的,还需要额外三条语句来配合:

# 允许 type 为 init_t 的进程去执行 type 为 apache_exec_t 的文件
allow init_t apache_exec_t : file execute;
# 允许 type 为init_t 的进程切换为 type 为 apache_t 的
allow init_t apache_t : process transition;
# 允许 type 为 apache_t 的进程由 type 为 apache_exec_t 的文件生成
allow apache_t apache_exec_t : file entrypoint;

由于这样使用起来很麻烦,所以 SEAndroid 在 te_macros 中定义了很多宏来帮助我们实现, 比如上面的四句就可以用以下一句宏来实现:

domain_auto_trans(init_t, apache_exec_t, apache_t)

SEAndroid 中还有一个常见的宏就是给一个目录下的新建的文件打标签的宏,比如:

file_type_auto_trans(appdomain, download_file, download_file)

3.6.Android 系统预先给哪些文件打了标签

Android 预先打的标签在 XXX_context 文件中,比如:

/dev/binder u:object_r:binder_device:s0

给 /dev/binder 文件打的标签是 u:object_r:binder_device:s0

一般 XXX_context 文件都是与预置好的给某些文件或者目录打标签。

3.7.apk 以及其使用的 data 目录是如何被 SEAndroid 打标签的

在 PMS 启动的时候会去读取 mac_permissions.xml 文件,该文件中保留了签名和 seinfo 的对应关系,比如 platform 签名对应的 seinfo 是 platform这个 seinfo 信息在安装 app 的时候会用到。

然后在安装 app 的时候PMS 会根据 app 的签名,给该 app 赋予一个 seinfo。这个 seinfo 就是 SELinux 给这个 app 打标签时候的第三个参数type。

在启动 app 的时候PMS 会使用到 seapp_contexts 文件,这个文件定义了不同 seinfo 的 app 启动之后的dominSELinux 就是根据这个文件为 app 进程和其使用的 data 目录打标签。

4.TE 文件

在 Android 系统中,可在系统源码中,通过 TE 文件设置程序/文件的 SELinux 属性。

SELinux 分为文件属性和进程属性(运行属性),普通文件属性定义在 file_contexts 中:

# file_contexts
/(vendor|system/vendor)/bin/demo                            u:object_r:demo_exec:s0

可通过 ls -Z 查看谋文件的 SELinux 文件属性,通过 ps -Z 查看谋运行中程序的进程属性,属性分为 4 组,如:

u:r:init:s0

上述的 u 为 Android 的唯一 user有待确认第二个参数 r 表示进程object_r 表示文件;第三个参数是这个进程的 type在 Android 里面,定义了 100 多个 type第四个参数 s0 是一个安全等级。

以下是一个 TE 文件示例:

type demo, domain;
type demo_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(demo)

allow demo self:capability { sys_admin };
allow demo sysfs:file { create open read write };
allow demo device:dir { create open read write };

下面将对该文件的各部分进行讲解。

4.1.基本语法

在 TE 文件中,我们一般遇到的语法是这样的:

# rule_name   :规则名称,除了有 allow 还有 dontauditauditallow 和 neverallow
# source_type :源类型,主要作用是用来填写一个域(domain)
# target_type 目标的类型即安全上下文SELinux 一个重要的判断对象
# class       :类别,目标(客体)是哪种类别,主要有 File、Dir、Socket、SEAndroid 还有 Binder 等在这些基础上又细分出设备字符类型chr_file链接文件lnk_file等。可以通过 ls -l 查看文件类型
# perm_set    :动作集
rule_name source_type target_type:class perm_set

解读为:为 source_type 设置一个 rule_name 的规则,规则是对 target_type 的 class 进行 perm_set 的操作,如:

# 用中文来表述是:允许 factory 域里的进程或服务对类型为 ttyMT_device 的类别为文件file进行 open,read,write,ioctl 操作
allow factory ttyMT_device:chr_file { read write open ioctl};

TE 表达式基本上就是这样,下文将按顺序介绍 rule_name、source_type、target_type、class 和 perm_set。

4.2.rule_name

allow允许某个进程执行某个动作。

auditallowaudit 含义就是记录某项操作。默认 SELinux 只记录那些权限检查失败的操作。 auditallow 则使得权限检查成功的操作也被记录。注意allowaudit 只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用 allow 语句。

dontaudit对那些权限检查失败的操作不做记录。

neverallow没有被 allow 到的动作默认就不允许执行的。neverallow 只是显式地写出某个动作不被允许,如果添加了该动作的 allow则会编译错误。

4.3.source_type

与 source context 对应指定一个“域”domain一般用于描述进程该域内的的进程受该条 TE 语句的限制。用 type 关键字,把一个自定义的域与原有的域相关联。

最简单地定义一个新域的方式为:

type shell, domain

上面这句话的意思是,将 domain 的全部属性赋予 shell。如果 domain 集合里有一个 allow domain xxxxx那么 shell 将会继承它。

4.4.target_type

与 target context 对应,指定进程需要操作的客体(文件,文件夹等)类型(安全上下文),同样是用 type 与一些已有的类型,属性相关联

以上面的 ttyMT_device 为例:

# 定义一个类型,属于 dev_type 属性
type ttyMT_device, dev_type;
# 属性 dev_type 在 external/sepolicyattributes 的定义如下: attribute dev_type;
# 以上 attribute 关键字用于定义一个属性

type 可以与一个或多个属性关联,如

type usb_device, dev_type, mlstrustedobject;

另外,还有一个关键字 typeattributetype 有两个作用,定义(声明)并关联某个属性。可以把这两个作用分开:用 type 进行定义;用 typeattribute 进行关联。示例如下:

# 定义 httpd_user_content_t并关联两个属性
type httpd_user_content_t, file_type, httpdcontent;  
# 分成两条语句进行表述:
# 定义 httpd_user_content_t
type httpd_user_content_t;
# 关联属性
typeattribute httpd_user_content_t file_type, httpdcontent;

这些类型(安全上下文)会显示地与一个“文件”相关联,如:

file_contexts 里面显式定义了哪些文件属于 ttyMT_device 类型,即用 ls -Z 显示出来文件的安全上下文

/dev/ttyMT.* u:object_r:ttyMT_device:s0

虚拟文件系统的标识方式与普通的文件系统文件标识方式不一样,用 genfscon 来配置。

# genfscon 的语法是:
genfscon fs_type pathprefix [-file_type] context

例如,把 /proc/mtk_demo/demo_file 文件的安全上下文设置成 demo_context

genfscon proc /mtk_demo/demo_file u:object_r:demo_context:s0

4.4.1.网络对象上下文

# 例1定义端口的上下文
portcon tcp 80 system_u:object_r:http_port_t
portcon tcp 8080 system_u:object_r:http_port_t
# 例2定义网络接口的上下文
netifcon eth0 system_u:object_r:netif_eth0_t system_u:object_r:netmsg_eth0_t
# 例3定义节点的上下文
nodecon 10.34.10.66 255.255.255.255 system_u:object_r:node_zeus_t;
nodecon 10.34.10.0 255.255.255.0 system_u:object_r:node_any_t

然后你会有一个疑问,这么多属性,这些属性有什么作用,这些属性会有一个地方显式地说明这个属性拥有什么权限,在 external/sepolicy/domain 里就有非常详细的描述。另个在 external/sepolicy/attributes 里定义了很多属性,下面截取了一些常见的定义。

# All types used for devices.
attribute dev_type;
# All types used for processes.
attribute domain;
# All types used for filesystems.
attribute fs_type;
# All types used for files that can exist on a labeled fs.
# Do not use for pseudo file types.
attribute file_type;
# All types used for domain entry points.
attribute exec_type;
# All types used for property service
attribute property_type;
# All service_manager types created by system_server
attribute system_server_service;
# All domains that can override MLS restrictions.
# i.e. processes that can read up and write down.
attribute mlstrustedsubject;
# All types that can override MLS restrictions.
# i.e. files that can be read by lower and written by higher
attribute mlstrustedobject;
# All domains used for apps.
attribute appdomain;
# All domains used for apps with network access.
attribute netdomain;
# All domains used for binder service domains.
attribute binderservicedomain;

4.5.Class 和 Permission

Class 指客体的具体类别。用 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

Permission 指具体的操作,系统的定义在 external/sepolicy/access_vectors。有两种定义方法。

用 common 命令定义:

# 格式为:
common common_name { permission_name ... }
# common 定义的 perm set 能被另外一种 perm set 命令 class 所继承
# 如:
common file {
      ioctl read write create getattr setattr lock relabelfrom relabelto
      append unlink link rename execute swapon quotaon mounton }

用 class 命令定义:

class class_name [ inherits common_name ] { permission_name ... }
# inherits 表示继承了某个 common 定义的权限  
# 注意class 命令它不能被其他 class 继承
# 继承一个 common如继承了 file common

class dir
inherits file
{
    add_name
    remove_name
    reparent
    search
    rmdir
    open
    audit_access
    execmod
}
# 不继承任何 common
class binder
{
    impersonate
    call
    set_context_mgr
    transfer
}

然后是一些特殊的配置文件:

# 所有定义的 attributes 都在这个文件
external/sepolicy/attributes
# 对应了每一个 class 可以被允许执行的命令
external/sepolicy/access_vectors
# Android 中只定义了一个 role名字就是 r将 r 和 attribute domain 关联起来
external/sepolicy/roles
# 其实是将 user 与 roles 进行了关联,设置了 user 的安全级别s0 为最低级是默认的级别mls_systemHigh 是最高的级别
external/sepolicy/users
# 指的是上文命令中的 class个人认为这个 class 的内容是指在 android 运行过程中,程序或者系统可能用到的操作的模块
external/sepolicy/security_classes
# 系统定义的宏全在 te_macros 文件
external/sepolicy/te_macros
# 一些配置的文件,包含了各种运行的规则
external/sepolicy/***.te

一般常用的 Class 和 Permission 如下表:

Class Permission
file ioctl read write create getattr setattr lock relabelfrom relabelto append unlink link rename execute swapon quotaon mounton
directory add_name remove_name reparent search rmdir open audit_access execmod
socket ioctl read write create getattr setattr lock relabelfrom relabelto append bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind
filesystem mount remount unmount getattr relabelfrom relabelto transition associate quotamod quotaget
process fork transition sigchld sigkill sigstop signull signal ptrace getsched setsched getsession getpgid setpgid getcap setcap share getattr setexec setfscreate noatsecure siginh setrlimit rlimitinh dyntransition setcurrent execmem execstack execheap setkeycreate setsockcreate
security compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot read_policy
capability chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap
tcp_socket create connect name_connect lock append bind name_bind listen accept recvfrom sendto read write getattr setattr getopt setopt
sock_file open read write
chr_file create open read write map ioctl

4.6.TE 的正则表达式和集合

TE 文件支持正则表达式,从下面可以看到,通配符是常用的通配符,可以度娘

/sys/devices/system/cpu(/.*)?    u:object_r:sysfs_devices_system_cpu:s0
/sys/power/wake_lock -- u:object_r:sysfs_wake_lock:s0
/sys/power/wake_unlock -- u:object_r:sysfs_wake_lock:s0
/sys/kernel/uevent_helper --    u:object_r:usermodehelper:s0
/sys/module/lowmemorykiller(/.*)? -- u:object_r:sysfs_lowmemorykiller:s0
#############################
# asec containers
/mnt/asec(/.*)?             u:object_r:asec_apk_file:s0
/mnt/asec/[^/]+/[^/]+\.zip  u:object_r:asec_public_file:s0

需要注意的是上面的"--",这里的“--”表示二进制文件,类似的还有

#-b - Block Device -c - Character Device
#-d - Directory -p - Named Pipe
#-l - Symbolic Link -s - Socket
#-- - Ordinary file

TE 表达式里可以用“{}”来表示一个集合,如:

# 允许user_t对bin_t类型的文件和文件夹执行read,getattr操作
allow user_t bin_t : { file dir } { read getattr };
# 允许domain对exec_type,sbin_t类型的文件执行execute的动作
allow domain { exec_type sbin_t } : file execute;

可以在集合里使用“*”,“-” 和 “~” 三个通配符

# 允许user_t对bin_t类型的文件和文件夹执行所有操作
allow user_t bin_t : { file dir } *;
# 允许user_t对bin_t类型的文件和文件夹执行除了read,getattr以外的所有操作
allow user_t bin_t : { file dir } ~{ read getattr };
# 允许domain对exec_type类型的文件执行execute的动作除了sbin_t以外
allow domain { exec_type -sbin_t } : file execute;

4.7.TE 的类型转换规则

为什么要转换类型?

init 进程拥有系统的最高权限,如果由 Init 进程 forkexec 出来的进程默认是与 init 相同的权限,这肯定是不安全的。另一个场景是,由 init 生成的文件,默认也是 init 的读写权限,不方便其他低权限的文件进行访问。

类型转换有两种类型转换:

  1. 主体的域的转换
  2. 客体的转换

4.7.1.域的转换

type_transition 的完整格式为:

type_transition source_type target_type : class default_type;

举个例子

type_transition init_t apache_exec_t : process apache_t;

init_t 进程执行 type 为 apache_exec_t 的可执行文件时,新的进程转换到 apache_t 域

但是上面只是告诉了转换的过程,却没有说明,有转换的权限,如果要上面的转换成功,还需要下面的语句:

# 首先,你得让 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;

4.7.2.客体的转换

例子:

type_transition passwd_t tmp_t : file passwd_tmp_t;

passwd_t 在 tmp_t 目录下创建文件时,该文件的类型转化为 passwd_tmp_t。这里默认隐含了一个 tmp_t 类型 dir因为 file 的容器只能是个 dir。同样的如果要上面的语句运行成功与需要有相应的权限说明。

对应的必须有两个前提条件:

  • The source domain needs permission to add file entries into the directory(这个 process 必须有在这个目录下添加文件的权限).
  • The source domain needs permission to create file entries(这个 process 必须有在这个目录下创建以这个 Security Context 为 Label 的文件权限).

如果每个转换之前都需要这样繁锁地权限声音实在很麻烦。TE里允许把这些相同的重复使用的语句定义成一个宏类似于函数一样。

4.8.TE 宏

如果把上面 domain 转换的例子定义成一个宏,应该定义如下:

# 定义 domain_auto_trans 宏,$1,$2 等等代表宏的第一个,第二个....参数
define('domain_auto_trans', '
    # 先 allow 相关权限, domain_trans 宏定义在后面
    domain_trans($1,$2,$3)
    # 然后设置 type_transition
    type_transition $1 $2:process $3;
')
# 定义 domain_trans 宏
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 };
')

上面的宏定义在 external/sepolicy/te_macros 里。客体的转换定义如下:

#####################################
# 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;
')

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;
')

TE 的集合也可以定义成一个宏代替,如读写文件操作集的宏:

define('x_file_perms', '{ getattr execute execute_no_trans }')
define('r_file_perms', '{ getattr open read ioctl lock }')
define('w_file_perms', '{ open append write }')
define('rx_file_perms', '{ r_file_perms x_file_perms }')
define('ra_file_perms', '{ r_file_perms append }')
define('rw_file_perms', '{ r_file_perms w_file_perms }')
define('rwx_file_perms', '{ rw_file_perms x_file_perms }')
define('create_file_perms', '{ create rename setattr unlink rw_file_perms }')

使用方式是:

allow demo demo_device:chr_file rw_file_perms;