Physical Address:
ChongQing,China.
WebSite:
本来已经做好的一个项目最近又要移植到新的平台上去,花了一段时间移植完成后发现很多功能不正常,从报错日志来看大多都是权限问题,但本身我们的应用具有root权限,是不应该存在这么多的权限问题的。突然想起来很久之前遇到的一个SELinux问题,于是灵机一动:也许是系统开启了SELinux导致的。为了确认这一点,我当即使用adb输入getenforce,当看到enforcing时立马兴奋了起来,也许问题就出在SELinux上。
为了验证这一点,我过滤了日志中的avc报错,发现其中有很多是属于我们进程的报错,对应到程序的出错时间点,也是能一一匹配上的,于是开始自己想着修复这些报错,顺便整理了一下有关于SELinux的知识,记录在这里。
不知道大家有没有想过,为什么要有SELinux(Security-Enhanced Linux)这个东西存在。我们知道Android系统是以Linux作为内核,而Linux是带有权限管理的。不过Linux最初的权限管理之士基于用户、用户组的,那就意味着如果你是root用户,或者你位于root用户组,那就意味着你所拥有的权限非常大。这可能会给系统带来很大的麻烦,比如经典的rm -rf根目录等,只需要一个小命令就可以使你的整个系统崩溃。为了加强权限管控,由美国国家安全局(NSA)发起,众多机构参与的一套安全性审查机制就此诞生了,也就是SELinux。
SELinux将权限管控由传统的基于用户、用户组的管理细化到以进程为最小单位。每一个进程都拥有一个独立的域,将自己的进程资源分为若干个独立的管理对象,并使用安全标签来标记这些管理对象。当该进程要访问外部域(也就是其他进程)的时候,需要编写响应的规则来访问其他进程的管理对象。同理,如果外部进程要访问该进程的时候,也需要编写对应规则来实现权限管理。
在我们讲解如果实现SELinux之前,有一些基本的概念需要给大家简单介绍一下,主要包括域、标签和规则。
域:在Google官方文档中,域(Domain)是一个进程或者一组进程的标签,也称为域类型。安卓系统中有一些公共的、约定好的域,这部分大多都是安卓系统本身自带的SELinux设定。而实际上,我们是可以为自己的进程设定单独的域的,在后缀.te文件中使用如下语法自定义我们自己的域:
type process, domain, coredomain;
在该语法定义中,process为进程/文件名称,后面的写法相对来说是固定的。通过该定义即可为我们的进程自定义一个属于自己的域。
标签:在SELlinux中依靠标签来匹配操作域政策,这句话可能不太好理解。实际上,你大可以将其理解为对某个进程所属资源的权限定义,用于决定允许的事项。套接字、文件和进程在SELinux中都有标签。SELinux在工作时会先参考标签中的定义,再依据政策来做具体决定。
标签的形式如下:
user:role:type:mls_level
其中user表示用户,在安卓系统中统一表示为u,role表示角色,一个user可以属于多个角色,不同的role具有不同的权限;type是访问决定的主要组成部分,表明该对象所属的domain;mls_level是系统中进程或者文件的权限分级不同级别的资源需要对应级别的进程才能访问。通常我们对标签的定义位于file_contexts文件内。
我们可以通过ps -Z命令查看进程的安全标签,使用ls -Z命令查看文件的安全标签。
政策:政策是指对域依据某个标签来对某类资源做访问时所形成的规则。政策的形式如下所示:
rule domain type:class permission
其中rule是指政策的动作,rule的行为包括allow(允许),auditallow(权限检测成功或失败都记录)、dontaudit(即使权限失败也不记录)、neverallow(显式声明不被允许的行为).
domain是一个进程或者一组进程的标签,也称为域类型,domain是可以自行定义的。
type是一个对象或者一组对象的标签,class是type中的一个子项,而perimission表明规定的操作类型,如读取、写入等等。
在Android 8.0及之上的版本,SELinux政策拆分为平台组件和供应商组件,允许平台组件(system.img)与供应商组件(vendor.img and boot.img)各自独立更新,最大程度保证其灵活性。其中平台政策进一步细分为平台公共部分与平台专用部分,平台公共部分的政策位于system/sepolicy/public目录,而专用部分则位于system/sepolicy/private目录。
其实对于大多数安卓开发者而言,不用特意去了解SELinux的深层次知识,一来是我们实际生产环境中大多没有开启SELinux,二来一般我们只需要了解sepolicy的编写规则,出现avc权限报错时知道如何添加相应规则即可,因此就我们日常开发中遇到的SELinux的相关诊断思路做一个记录。
1.如何查看系统是否开启SELinux
使用getenforce命令进行查看,当我们使用getenforce命令时,一般会得到三种回馈。
enforcing:强制模式,所有规则都会被执行
permissive:宽容模式,所有规则不会起实际作用,只会打印错误信息
disabled:关闭模式,SELinux的所有规则都不会生效。
只有当我们得到的反馈为enforcing时,才表示SELinux真正开启。一般而言,当我们处于调试阶段时会将SElinux设为宽容模式,在将所有的avc报错解决后应用到生产应用阶段,此时可将SElinux设为enforcing模式。
2.如何修改系统SElinux状态
与getenfoce相对应,我们可以通过setenforce命令来设定SELinux。需要注意的是,使用setenforce命令只能将SELinux设为enforcing或者permissive,如果要将SELinux设为disabled,则需要修改配置文件并重启。
设为宽容模式:setenforce permissive 或者 setenforce 0
设为强制模式:setenforce enforcing 或者 setenforce 1
setenforce只能够临时地将系统的SELinux状态设定为enforcing或者permissive,在系统重启后临时设定的内容将不再生效。如果想要将SELinux状态设定为disabled或者一直保持设定状态,则需要通过修改/etc/sysconfig/selinux文件来实现。或者通过修改init进程来实现,此处我们贴出通过修改init启动来实现对SELinux实施禁用的代码:
//源码文件位于system/core/init/selinux.cpp
bool IsEnforcing() {
//Modified by FranzKafka
return false;
//if (ALLOW_PERMISSIVE_SELINUX) {
// return StatusFromProperty() == SELINUX_ENFORCING;
//}
//return true;
}
在init启动时,会通过IsEnforcing接口获取SELinux的状态,这里直接在入口处返回false从而禁用SELinux。
3.获取avc报错信息及日志解读
如果我们想要解决SELinux的问题,可以通过以下命令抓取系统中的avc报错信息,其是SELinux政策执行时的具体状态体现。
adb shell “cat /proc/kmsg |grep avc” > avc_log.txt
那么,在抓取了avc报错信息后,我们如何解读呢。其实规则相当简单,以下我们将举例说明:
avc: denied { write } for pid=4965 comm=”evsserver” name=”usernotify” dev=”mmcblk0p50″ ino=131255 scontext=u:r:evsserver:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=0
这一段报错信息表明evsserver域(进程)在对挂载节点为mmcblk0p50的名为usernotify的文件夹的写入控制中失败。如果我们要修正该报错,可以按照一定的规则来修改,比如当前报错,我们对应的应添加的规则为;
#============= evsserver ==============
denied { write } for pid=5943 comm=”evsserver” name=”property_service” dev=”tmpfs” ino=2726 scontext=u:r:evsserver:s0 tcontext=u:object_r:property_socket:s0 tclass=sock_file permissive=0
allow evsserver property_socket: sock_file write;
相信只要稍微观察就能发现,修正该avc报错的规则完全可以根据该报错信息来进行添加,具体该如何根据avc报错来添加规则在此不做细述,上述的例子足够帮助你解决大部分的SELinux报错问题。
实际上,我们还有更方便快捷的办法来得到针对某个域或者某个进程的SELinux规则,即使用audit2allow命令,audit2allow默认会在AOSP源码中,其路径为:
${ANDROID_HOME}/external/selinux/prebuilts/bin/audit2allow
我们打开该文件,可以发现其实际是一个shell脚本,并在内部通过调用python来实现对SELinux报错信息的解析与相应规则的生成;在具体使用时,我们需要得到avc报错的日志,可以使用下面的命令抓取:
adb root && adb remount
#方式一
adb shell "cat /proc/kmsg |grep avc" > avc_log.txt
#方式二
adb shell
dmesg | grep avc > /data/avc_log.txt
adb pull /data/avc_log.txt
之后使用audit2allow -i avc_log.txt -p policy file命令即可自动生成,若我们的环境中没有audit2allow工具,通过apt install policycoreutils-python-utils进行安装,同时需要注意,policy file存在于/sys/fs/selinux中,需要从我们编译好的系统中单独取出使用。如果我们在使用audit2allow过程中提示Python相关报错,一般是由于Python版本问题,这里需要注意audit2allow依赖于python2而非python3,如果存在问题我们需要自行解决。
以上就是这篇博客中所涉及到的全部内容了,当然还有很多知识没有涉及到,关于Android中的SELinux大家也可以到Android的官方开发网站查看更多具体信息。由于本人水平有限,文中难免存在错误,如有问题欢迎各位指出!