1.前言
SEAndroid是Security-Enhanced Android的缩写,是指将一直在Linux操作系统中使用的MAC强制存取控管套件SELinux移植到Android平台上,以达到强化Android操作系统对APP的存取控管的目的,这样可以建立一个类似于沙箱的执行隔离效果,以确保每一个APP之间的独立运作,并阻止恶意APP对系统或其他应用程序的攻击。
2.SEAndroid概述
在引入SEAndroid安全机制之前,Android系统的安全机制分为应用程序和内核两个级别。应用程序的安全机制就是我们通常所说的Permission机制,一个应用程序如果需要访问一些系统敏感或者特权资源,那么就必须在AndroidManifest.xml中进行申请,并在安装的过程中向用户确定权限。内核的安全机制是通过Linux UID/GID实现的。但是这两种方式都会受到DAC问题的困扰。因此需要一种新的安全机制来保证系统的安全,即MAC机制,全称为Mandatory Access Control强制访问控制,用户、进程或者文件的权限室友管理策略决定的,而不是由他们自主决定的。例如,可以设定一个如下所示的管理策略:
不允许用户A将其创建的文件F授予用户B访问
SEAndroid等于“SELinux+Android”,通过MAC的方式管控应用程序,从而提升原生Android系统的安全性。下面是SEAndroid机制的整体框架:
SEAndroid机制以SELinux文件系统为边界,分为内核空间和用户空间两部分。在内核空间中,主要涉及一个称为SELinux LSM的模块。而用户空间中,涉及l了Security Context、Security Server和SEAndroid Policy等模块
内核空间中的SELinux LSM模块:负责内核资源的安全访问控制。
用户空间中的SEAndroid Policy:描述了资源安全访问策略。在启动系统是,用户空间的Security Server通过SELinux文件系统接口,将这些安全策略加载到内核空间的SELinux LSM模块中。
用户空间的Security Context:描述资源安全上下文,SEAndroid的安全访问策略就是在资源的安全上下文基础上实现的。
用户空间中的Security Server:不但需要用户空间的Security Context去检索对象的安全上下文,而且也需要到内核空间去操作对象的安全上下文.
用户空间的SELinux库:封装了对SELinux文件系统接口的读写操作.
2.1 内核空间
在内核空间上有一个SELinux LSM模块,此模块包含一个访问向量缓冲(Access Vector Cache)和一个安全服务(Security Server).Security Server负责安全访问控制逻辑,即由它来决定一个主题访问一个课题是否是合法的.
SELinux,LSM和内核中的子系统是如何相互交互的呢?如下:
I.首先,SELinux会在LSM中注册相应的回调函数.
II.LSM会在相应的内核对象子系统中加入一些Hook代码.例如,在调用系统接口函数read读取一个文件时,会进入到内核的文件子系统中.在文件子系统中,读取文件函数vfs_read会调用加入到内核中的Hook代码,这些代码会调用之前SELinux注册回来的回调函数。
III.当SELinux在做安全检查时,会去访问向量缓冲中检查是否有存在。如果有,则直接返回内核子系统,否则通过Security Server去查找相应的安全策略,并保存到访问向量缓冲中。
2.2 用户空间
在用户空间上,包含三个主要的模块:安全上下文、安全策略、Security Server。
2.2.1 安全上下文
SEAndorid安全策略又是建立在对象安全上下文的基础上的,是一种基于安全策略的MAC安全机制。这里的对象安全有两种类型。
主体(Subject):通常指进程。
客体(Objcet):指通常要访问的资源,如文件、进程、socket等。
而安全上下文其实就是附加在对象上的一个标签,包括四部分,SELinux用户,SELinux角色,类型,安全级别。如下为一个上下文格式:
user:info:type:sensitivity
可通过ls –Z知道文件的上下文,ps –Z知道进程的上下文。在安全上下文中user,info,sensitivity基本上是固定的,我们可以上述命令查看上下文,发现SELinux用户全是u,角色全是r,于是我们只是关心type,因此SEAndroid安全机制又被称作基于TE(Type Enforcement)策略的安全机制。
在Android5.0代码的external/sepolicy/users和/external/sepolicy/roles中看到SEAndroid定义的SELinux用户和SELinux角色( a
上面代码中申明了一个用户u,角色为r,安全级别是s0,变化范围为s0~mls-systemhigh。
在上述代码汇中,第一个语句申明了一个SELinux角色r,第二个语句允许SELinux角色r和类型domain关联。
我们接下来讨论安全上下文中的类型是怎么定义的。
在SEAndroid机制中通常将用来标注文件的安全上下文类型称为file_type,而用来标注进程的安全上下文的类型称为domain,并且每个用来描述文件的安全上下文的类型都将有file_type设置为属性,每一个进程安全上下文的类型都将是domain设置为其属性。
将一个类型的属性设置为另一个类型的属性是通过type语句实现的。例如我们在/external/sepolicy/init.te文件中定义了对init进程的属性。如下:
上述代码中将domain属性设置为init的属性,说明init是用来描述进程的上下文。
同样的我们可以再file.te中查看文件类型的定义,如下:
上述代码定义了apk数据文件的上下文类型,它是用来描述文件的上下文类型。
我们将讨论4种类型的安全上下文,分别为App进程,APP数据文件、系统文件和系统属性。这4中类型的安全上下文通过4个文件来描述:mac_permission.xml,seapp_contexts,file_-
Contexts和property_contexts,他们均在external/sepolicy目录中,如下为mac_permissio
.xml的内容:
在系统中定义了两种app,一种签名是属于平台APP的,其他的属于第三方签名的。此文件中的seinfo并不是APP的安全上下文,安全上下文定义在seapp_context中,内容如下:
Seinfo为platform的应用分配的domain为platform_app,而seinfo为default的应用被分配的是属于untrusted_app。所以,当属于platform_app的应用在运行时,所属的安全上下文为platform_app,default应用启动时上下文为untrusted_app。
然后我们再看看系统文件的安全上下文定义是如何的,如下为file_contexts的内容:
上面是部分内容,定义了不同目录下,系统文件所属的安全上下文,如在/data/app(/.*)?目录下的文件安全上下文为u:object_r:apk_data_file:s0
在Android系统中,因为APP可以通过读写属性能够获得相应的信息和控制系统的行为,所以SEAndroid也需要对Android的系统属性进行保护,关联安全上下文。下面是property_conte-
xts文件的部分内容:
2.2.2 安全策略(SEAndroid Policy)
在上一节中我们详细说明了安全上下文的定义,然后我们将讨论SEAndroid怎样在安全上下文的基础上对安全策略进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。SEAndroid安全机制主要是使用对象安全上下文中的类型来定义安全策略,这种安全策略就称Type Enforcement,简称TE,因此可以发现在/external/policy文件目录下有很多.te文件,每一个这样的文件在经过编译后就会生成一个sepolicy文件,在编译后会打包到ROM中,并保持在根目录下的/sepolicy目录下。
我们打开app.te文件,部分内容如下:
如最后一个allow appdomain zygote:fd use;意思为允许上下文类型为appdomain的程序使用上下文类型为zygote(类型为fd)。
2.2.3 安全策略的加载
上面我们陈述了安全上下文的定义已经安全策略的定义,但安全策略是怎样加载到系统中的呢,我们将研究安全策略是如何加载到系统中的。
系统中启动的第一个进程为init进程,在启动init进程时做了很多操作,其中就包括加载安全策略,加载安全策略到SELinux LSM中去,我们可以在/system/core/init/init.c找到如下内容:
其中调用了selinu_android_load_policy函数实现了加载安全策略,此函数的定义在/external/libselinux/src/android.c中,我们继续分析函数中做了什么,内容如下:
上面代码中可以看到,先是安装一个selinux文件系统,然后挂载到/sys/fs/selinux上,用来和内核空间的SELinux LSM模块通信。如果安装失败,则再以/selinux为安装点,安装SELinux文件系统。在安装成功后,会调用selinux_android_load_policy_helper方法将系统中的安全策略加载到内核空间的SELinux LSM模块中去,如下:
上面会出逐步加载安全策略文件,然后将打开的sepolicy文件内容映射到内存中,并得到他的起始地址map。
然后调用security_load_policy将已经映射到内存的sepolicy内容加载到内核空间的SELinux LSM模块中去。
通过上面的过程就将SEAndroid的安全策略加载到了系统中。
2.2.4 Security Server
用户空间的Security Server的功能是保护用户空间资源,并操作内核空间对象的安全上下文。Security Server由应用程序安装服务PackageManagerService、应用程序安装守护进程installd、应用程序进程孵化器Zygote进程和init进程组成。其中PackageManageServi-
ce和installd负责创建APP数据目录的安全上下文,Zygote进程负责创建APP进程的安全上下文,init进程负责控制系统属性的安全访问。
在接下来的一节中我们将讨论Security Server是如何管理安全上下文的。
2.3 应用程序数据和进程的安全上下文分配机制
我们在上节最后知道了应用程序的数据的安全上下文会由PackageManagerService和installd具体分配,然后应用程序进程的安全上下文会由zygote和init管理,这一节我们将深入讨论。
2.3.1 应用程序数据文件的安全上下文
在Android系统中,每一个应用程序在/data/data/目录下都有一个用包名命名的目录,用来保存,这个数据目录是在应用程序安装的时候有守护进程installd创建的。当守护进程installd创建应用程序数据目录时,会同时设置它的安全上下文,以便可以对它进行保护。
PackageManagerService负责安装Android应用程序,在PackageManagerService的构造函数中它在启动时会通过类SELinuxMMAC的静态成员函数readInstallPolicy读取上一章中的mac_permission.xml文件,代码如下:
下面是SELinuxMMAC中的readInstallPolicy方法的具体调用:
在这个函数中我们可以看到它读取了mac_permission文件,然后解析了这个xml文件,由前面知道这个xml中保存的是不同app所对应的seinfo信息,当读取成功后会保存在sSigSeinfo sDefaultSeinfo中,供后续使用:
然后在应用程序安装时,PackageManagerService会抵用scanPackageLI解析应用程序的信息,然后调用SELinuxMMAC中的assignSeinfoVlaue函数会去查找当前包名对应的seinfo字符信息。
查询成功后,会将pkg.applicatioInfo.seinfo设置成对应的seinfo。
当PackageManagerService解析完成要安装的应用信息后,接卸来会调用PackageManagerService中的createDataDirsLI来给正在安装的应用程序创建数据目录,代码如下:
上面函数调用Installer中的install方法,如下:
在install方法中通过一些参数,uid,gid,seinfo构建一条执行命令,然后调用execute方法去生成目录。这里的mInstaller是一个与守护进程installd相连的socket,把命令传递给installd后,由/framework/native/cmds/installd.c中的do_install方法,代码如下:
然后install方法在/framework/native/cmds/commands.c中,代码如下:
在这个函数中,我们只关心两个方法create_pkg_path和selinux_android_setfilecon
在这里,共创建了三个目录,包目录,包动态链接库源路径和包动态链接库目的路径。
然后是selinux_android_setfilecon函数,此函数在/external/libselinux/src/android.c中,如下:
函数中首先判断的是是否启用了selinux功能,然后调用getfilecon方法获取目录原有的安全上下文并保存在ctx_str中。
然后根据获取的安全上下文创建新的上下文,然后根据seinfo通过seapp_context_lookup在seapp_contexts文件中对应的type,并设置新的安全上下文ctr的type。
然后根据新设置的安全上下文获取对应的字符串描述,以便后面可以调用security_check_context来验证新创建的安全上下文的正确性。
最后比较原来的安全上下文和现在安全上下文的描述,如果不相同的话,则调用setfilecon方法设置当前目录的安全上下文。
在上面的seapp_context_lookup方法调用中,显示调用了seapp_context_init方法去读取文件seapp_contexts,解析其中对应seinfo拥有的type,然后将ctx中的上下文修改为对应的类型。具体代码在seapp_context_lookup中,如下:
我们再关注下setfilecon函数,如下:
将对应的路径设置问对应的安全上下文。
通过上面的分析,我们了解了应用程序在安装过程中用户数据目录的创建以及怎样设置对应的安全上下文。
2.3.1 应用程序进程的安全上下文
在SEAndroid系统中,除了要给文件关联安全上下文外,还要给进程关联安全上下文,因为只有当进程关联了安全上下文之后,SEAndroid安全策略才能发挥作用。也就是说,当一个进程想要访问一个文件时,SEAndroid会提取出进程和文件的安全上下文,根据安全策略决定是否允许访问。在Linux系统中,当一个可执行文件加载到一个进程中执行时,该进程的安全上下文设置为指定的值,即为静态设置的,这不适合Android系统,因为Android中的进程都是由zygote守护进程fork出来的,会通过exec系统调用对应的可执行文件加载起来运行,因此应用程序的安全上下文应该被动态设置。
应用程序是由ActivityManagerService请求zygote进程创建的。ActivityManagerServ-
ice请求zygote进程进行创建,在创建过程中,ActivityManagerService会传递很多参数给zygote,其中就包括seinfo,如下为ActivityManagerService.java创建新的应用程序的代码:
其中就包括了app.info.seinfo。
调用start方法后,这些参数会通过Socket传递給守护进程zygote,最后zygote进程通过ZygoteConnection类的成员函数runOnce来执行创建应用程序的操作。代码如下:
我们继续查看forkAndSpecialize方法中做了什么动作,如下:
其中调用了jni层中的nativeForkAndSpecialize方法创建新的进程并返回进程号。NativeForkAndSpecialize方法的定义在/framework/base/core/jni/com_android_internal_os_Zygote中,实现代码如下:
然后调用了ForkAndSepecializeCommon方法
在这个方法中调用了libselinux提供的函数selinux_android_setcontext方法来设置刚创建出来的应用程序进程的安全上下文,如下:
我们可以在/external/libselinux/src/android.c找到具体的定义,如下:
这个函数跟我们上节提到的应用程序安装流程类似,先检查是否SELinux可用,然后创建新的上下文,然后查找对应seinfo的type,设置type,然后调用了setcon方法设置对应进程的安全上下文。
以上流程就是去启动一个新的应用程序,然后去设置这个应用程序上下文的具体流程。
3.总结
以上章节为SEAndroid的全部概述。