一、概述
1.1 SELinux由来
SEAndroid是Google在Android 4.4上正式推出的一套以SELinux为基础于核心的系统安全机制。而SELinux则是由美国NSA(国家安全局)和一些公司(如 RedHat)设计的一个针对Linux的安全加强系统。
NSA最初设计的安全模型叫FLASK(Flux Advanced Security Kernel),最初这套模型针对DTOS系统。后来,NSA觉得Linux更具发展和普及前景,所以就在Linux系统上重新实现了FLASK,称之为SELinux(Security-Enhanced Linux)。
由于Linux有多种发行版本,所以各家的SELinux表现形式也略有区别。具体到Android平台,Google对其进行了一定得修改,从而得到SEAndroid。
那么,Google为什么要引入这套安全机制呢?
1.2 为什么需要SELinux
1.2.1 缺陷
在引入SEAndroid安全机制之前,Android系统的安全机制分为应用程序和内核两个级别。
应用程序级别的安全机制就是我们常说的Permisson机制。需要在AndroidManifest.xml配置文件中进行权限申请,系统在应用程序安装的时候会提示该应用申请了哪些权限,用户确认后,进行安装,并且安装过程中由系统决定(如根据应用程序签名)是否赋予应用程序申请的那些权限。一旦申请成功,后续应用就可以随意使用这些权限了。
内核级别的安全机制就是传统的Linux UID/GID机制,Linux就是通过用户、进程、文件的UID/GID来进行权限管理的。Linux将文件的权限划分为读、写和执行三种,分别用字母r、w和x表示。每一个文件有三组读、写和执行权限,分别是针对文件的所有者、文件所有者所属的组以及除前两种之外的其它用户。这样,如果一个用户想要将一个自己创建的文件交给另外一个用户访问,那么只需要相应地设置一下这个文件的其它用户权限位就可以了。
由上可知,文件的权限控制在所有者手中,这种权限控制方式就称为自主式权限控制,即Discretionary Access Control,简称为DAC。
理想情况下DAC不会有什么问题,但在现实中,一个用户可能会不小心将自己创建的文件的权限位错误地修改为允许其它用户访问。如果这个用户是一个特权用户,并且它错误操作的文件是一个敏感的文件,那么就会产生严重的安全问题。
1.2.2 改进
在访问控制安全模型中,与DAC相对的是MAC(Mandatory Access Control,强制访问控制)。在MAC机制中,用户、进程或者文件的权限是由管理策略决定的,而不是由它们自主决定的。例如,我们可以设定这样的一个管理策略,不允许用户A将它创建的文件F授予用户B访问。这样无论用户A如何修改文件F的权限位,用户B都是无法访问文件F的。这种安全访问模型可以强有力地保护系统的安全。
SELinux就是采用的MAC机制。SELniux是在FLASK基础上重新实现的,FLASK架构如下:
SELinux在内核中以LSM(Linux Security Modules)模块的形式实现。
从上图可知,SEAndroid安全机制与传统的Linux UID/GID安全机制是并存关系的,它们同时用来约束进程的权限。当一个进程访问一个文件的时候,首先要通过基于UID/GID的DAC安全检查,接着才有资格进入到基于SEAndroid的MAC安全检查,只要其中的一个检查不通过,那么进程访问文件的请求就会被拒绝。
二、SELinux安全策略
SEAndroid是一种基于安全策略的MAC安全机制。这种安全策略又是建立在对象的安全上下文的基础上的。这里所说的对象分为两种类型:主体(Subject,通常就是指进程)和客体(Object,指进程所要访问的资源,例如文件、系统属性等)。
SEAndroid安全机制中的安全策略就是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。
2.1 安全上下文
安全上下文实际上就是一个附加在对象上的标签(label)。这个标签是一个字符串,它由四部分内容组成:SELinux用户、SELinux角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:sensitivity”。
文件init.rc的安全上下文类似如下:( $ls -Z /init.rc)
-rwxr-x--- root root u:object_r:rootfs:s0 init.rc
进程init的安全上下文类似如下:($ps -Z)
LABEL USER PID PPID NAME
u:r:init:s0 root 1 0 /init
其实在安全上下文中,只有类型(Type)才是最重要的,SELinux用户、SELinux角色和安全级别都几乎可以忽略不计的。正因为如此,SEAndroid安全机制又称为是基于TE(Tyoe Enforcement)策略的安全机制。
2.1.1 用户和角色
对于进程来,SELinux用户和SELinux角色只是用来限制进程可以标注的类型。对于文件来说,SELinux用户和SELinux角色就可以完全忽略不计。通常:
进程的安全上下文中,用户固定为 u,角色固定为 r。
文件的安全上下文中,用户固定为 u,角色固定为 obejct_r。
2.1.2 安全级别
在SELinux中,安全级别是可选的,也就是说,可以选择启用或者不启用。通常在进程及文件的安全上下文中安全级别都设置为s0。
2.1.3 类型
进程的安全上下文的类型称为domain,文件的安全上下文中的类型称为file_type。
上面我们看到进程init的安全上下文为u:r:init:s0,type为init,这也是合法的,为什么呢?在Android中我们可以通过如下语句定义type:
type init domain;
即将domain设置为init的属性,这样就可以用init为type来描述进程的安全上下文了。
2.2 安全上下文描述
安全上下文根据主体对象不同,有如下几种,在Android O上一般都存放在/system/sepolicy/private/目录下。
2.2.1 mac_permissions.xml
用于给不同签名的App分配不同的seinfo字符串,例如,在AOSP源码环境下编译并且使用平台签名的App获得的seinfo为“platform”。这个seinfo描述的是其实并不是安全上下文中的Type,它是用来在另外一个文件seapp_contexts中查找对应的Type的。
2.2.2 Seapp_contexts
用于声明APP进程和创建数据目录的安全上下文,O上将该文件拆分为plat和nonplat 前缀的两个文件,plat前缀的文件用于声明system app,nonplat前缀的文件用于声明vendor app。
从前面的分析可知,对于使用平台签名的App来说,它的seinfo为“platform”。这样我们就可以知道,使用平台签名的App所运行在的进程domain为“platform_app”,并且它的数据文件的file_type为“platform_app_data_file”。
2.2.3 File_contexts
用于声明文件的安全上下文,plat前缀的文件用于声明system、rootfs、data等与设备无关的文件。Nonplat 用于声明vendor、data/vendor 等文件。
如上,/system目录包括子目录和文件的安全上下文为u:object_r:system_file:s0,这意味着只有有权限访问Type为system_file的资源的进程才可以访问这些文件。
2.2.4 Service_contexts
用于声明java service 的安全上下文, O上将该文件拆分为plat和nonplat 前缀的两个文件,但nonplat前缀的文件并没有具体的内容(vendor和system java service不允许binder 操作)。
如上,service为netd的安全上下文为u:object_r:netd_service:s0,这意味着只有有权限访问Type为netd_service的资源的进程才可以访问这些service。
2.2.5 Property_contexts
用于声明属性的安全上下文,plat 前缀的文件用于声明system属性,nonplat前缀的文件用于声明vendor 属性。
如上,ril.开头的属性的安全上下文为u:object_r:radio_prop:s0,这意味着只有有权限访问Type为radio_prop的资源的进程才可以访问这些属性。
2.2.6 Hwservice_contexts
O 上新增文件,用于声明HIDL service 安全上下文。
如上,android.frameworks.sensorservice::ISensorManager的hw service的安全上下文为u:object_r:fwk_sensor_hwservice:s0,这意味着只有有权限访问Type为fwk_sensor_hwservice的资源的进程才可以访问这些hw service。
2.6 安全策略
上面我们分析了SEAndroid安全机制中的对象安全上下文,接下来我们就继续分析SEAndroid安全机制中的安全策略。
前面提到,SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。所有安全策略都存放在.te结尾的文件中,一般放在 /system/sepolicy/private/,厂商定制的一般放在/device/xxx/common/sepolicy/下,如/system/sepolicy/private/app.te:
一个Type所具有的权限是通过allow语句来描述的,SEAndroid使用的是最小权限原则,也就是说,只有通过allow语句声明的权限才是允许的,而其它没有通过allow语句声明的权限都是禁止,这样就可以最大限度地保护系统中的资源。
语句格式为:allow scontex tcontex:class action
scontex 主体安全上下文
tcontex 客体安全上下文
class 客体对应的资源
action 赋给主体的权限
例1:allow appdomain zygote_tmpfs:file read;
即允许appdomain的app对zygote_tmpfs类型的文件进行读操作。
例2:allow { appdomain -isolated_app } rootfs:lnk_file r_file_perms;
即允许除去isolated_app的appdomain的app对rootfs类型的链接文件进行r_file_perms操作。
除了allow还有:neverallow (检查并记录是否有违反这条策略的)、allowaudit(检查记录权限成功及失败的操作,默认只记录检查失败的)、dontaudit(不记录权限检查失败的记录),后三个仅是检查及记录,并不赋予或禁止权限。
至此,我们知道SELinux是基于安全策略的MAC安全机制,用来弥补DAC的不足,安全策略是在安全上下文基础上来描述的,安全上下文中重点是类型Type,因此SELinux又叫基于TE的安全机制。
三、SELinux架构
下面我们来简单看一下SELinux的整体架构。
从图中可以看到,SEAndroid安全机制包含有内核空间(Kernel Space)和用户空间(User Space)两部分支持,以SELinux文件系统(SELinux File system)接口为边界。在内核空间,主要涉及到一个SELinux LSM模块。而在用户空间中,涉到安全上下文(Security Context)、安全服务(Security Server)和安全策略(SEAndroid Policy)等模块。这些内核空间模块和用户空间模块的作用以及交互如下:
1. 内核空间的SELinux LSM模块负责内核资源的安全访问控制。
2. 用户空间的Security Context描述的是资源安全上下文。
从上文我们知道,SEAndroid的安全访问策略就是在资源(进程、文件等)的安全上下文基础上实现的。
3. 用户空间的SEAndroid Policy描述的是资源安全访问策略。
系统在启动的时候,用户空间的Security Server会将这些安全访问策略加载内核空间的SELinux LSM模块中去。这是通过SELinux文件系统接口实现的。
4. 用户空间的Security Server一方面需要到用户空间的Security Context去检索对象的安全上下文,另一方面也需要到内核空间去操作对象的安全上下文。
5. 用户空间的libselinux库封装了对SELinux文件系统接口的读写操作。
用户空间的Security Server访问内核空间的SELinux LSM模块时,都是间接地通过libselinux进行的。这样可以将对SELinux文件系统接口的读写操作封装成更有意义的函数调用。
用户空间的Security Server到用户空间的Security Context去检索对象的安全上下文时,同样也是通过libselinux库来进行的。
四、常见问题
1、Selinux 模式有两种:
Enforcing:强制模式,SELinux 运作中,且已经正确的开始限制 domain/type
Permissive:宽容模式,SELinux 运作中,仅会有警告讯息并不会限制 domain/type 的存取
userdebug 版本开机后,可以通过adb命令来查看或设置SELinux模式(设置后需重启shell才真正生效,手机重启后恢复),可以用此方法排除问题:
adb shell getenforce,会返回 Enforcing 或 Permissive
adb shell setenforce 1或0,设置其为 Enforcing 或 Permissive
代码中1,开关SELinux在dts的bootargs 中:如/kernel/arch/arm64/boot/dts/xxx/yyy.dts
bootargs = "earlycon=s_serial,0x70100000,115200n8 console=ttyS1,115200n8 loglevel=8 maxcpus=1 init=/init root=/dev/ram0 rw androidboot.hardware=s9999_haps androidboot.selinux=permissive";
代码中2,或者 system/core/init/init.cpp 的函数 bool selinux_is_enforcing() 中直接返回false。
2、常见错误修改
出现违反SELinux安全策略的错误时一般会有如下log输出:
avc:denied { write } for pid=2646 comm="gfslog" name="/" dev="mmcblk0p21" ino=2 scontext=u:r:gfslog:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=0
一般按照规则 allow scontex tcontex:tclass action 来改即可。即:
allow gfslog system_data_file:dir write
3、违反规则的同时又neverallow问题修改
比如recovery升级中,有如下错误:
avc: denied { read } for name="mmcblk0p15" dev="tmpfs" ino=3364 scontext=u:r:install_recovery:s0 tcontext=u:object_r:block_device:s0 tclass=blk_file permissive=0
但是domain.te文件中,明确domain中是没有对block_device的读写权限,除了recovery等。
neverallow { domain -kernel -init -recovery } block_device:blk_file { open read write };
方法一:将 install_recovery 添加到上述neverallow的domain例外中,但后续可能会有cts问题
方法二:将要操作的文件定义为其他type,然后允许install_recovery来读写这个新type的文件
4、Android O源码中 SELinux 相关文件位置
如下位置放置上下文定义文件及各种资源具体的访问策略。
/system/sepolicy/private/ system分区private
/system/sepolicy/public/ system分区public
/system/sepolicy/vendor/ vendor分区
/system/sepolicy/reqd_mask/
/vendor/xxx/sepolicy/ vendor分区
/device/xxx/sepolicy/ 厂商定制
/system/sepolicy/prebuilts/api/26.0/ 版本兼容
/build/make/target/board/generic/sepolicy/
/build/target/board/generic/sepolicy/
5、Android O机器上 SELinux 相关文件位置
其中plat_sepolicy.cil、nonplat_sepolicy.cil汇总了所有的te中定义的安全策略。