在运行了SELinux的Linux系统中,一般通过为C/C++编写的可执行文件指定特殊的安全上下文,然后用type_transition语句设定这些文件被执行后,产生对应的C/C++进程的安全上下文,通过这种方法,可以为系统的所有C/C++进程设定我们需要的安全上下文。
在Android系统中,除了C/C++进程外,还有大量的java应用,上述的通过type_transition指定安全上下文的方法对于java应用不再适用,其设定安全上下文的方法要复杂一些,关系到apk本身的签名证书,本文将讲述如何设定Android O中java应用的安全上下文。
Android上的apk都是经过签名的,原生代码中提供了4种key用于build阶段为应用进行签名:
*Media
*Platform
*Shared
*Testkey
在Android源码的build/target/product/security目录下,存放着这四种类型对应的私钥和x509格式的证书:
在每个apk的Android.mk文件中,有一个LOCAL_CERTIFICATE字段,由它指定用哪个key签名,如未通过这个字段进行指定,则默认用testkey。
在Android源码的system/sepolicy/private/mac_permissions.xml文件中,可以根据apk的签名类型,来指定apk的seinfo的值。以下图为例,当apk的签名是Platform类型时,其对应的seinfo的值是platform。如果没有指定签名类型对应的seinfo的值,则seinfo的值默认使用default。
这个seinfo的值对apk的安全上下文的值十分重要,下面会详细描述,我们先记住是在mac_permissions.xml这个文件通过signature的类型设定apk的seinfo值。
确定了apk的seinfo的值后,就可以在源码的的system/sepolicy/private/seapp_contexts中指定app的安全上下文了。
以下图第一行为例,当应用的用户是system,且seinfo的值为platform,那么这个app的安全上下文就是domain字段(Android系统一般将进程的安全上下文称为domain)的值system_app,而这个app对应的数据文件类型为app_data_file,levelForm为user(和多级安全相关,本文不做叙述。)
不难发现,在上面的例子中,满足user为system和seinfo为platform的应用肯定不止一个,如果想将“精确定位”,只为某个app指定安全上下文,可以在user和seinfo两个条件以外,制定应用的名字,以图中最后一条为例,当user为_app,seinfo为media,且应用名字是android.process.media时,该应用的安全上下文为mediaprovider。
从最后一条规则可以看出,如果app的各项信息和system/sepolicy/private/seapp_contexts的定义都无法完全符合,则默认使用untrusted_app_25作为类型。
在system/sepolicy/private/seapp_contexts文件中,user字段表示应用属于哪个user,Android系统实施了沙箱机制,每个应用都会分配给某个user,应用的uid即表示应用属于哪个user,不同uid的应用默认无法访问对方的数据。
Android系统保留了数字较小的用户ID(1000~9999)专供系统使用,目前只有一部分被使用到了,定义在android_filesystem_config.h文件。
对于第三方的应用,如不适合使用系统预定义的uid,则会通过特定的算法,计算得到应用的uid,如下图,通过ps可以看到应用的uid,格式为ux_xxx,对于这类uid,在system/sepolicy/private/seapp_contexts文件中,需要将其user字段的值统一写为_app,否则规则将无法生效。
这类uid生成具体的算法参考下面的文章:
https://blog.csdn.net/keheinash/article/details/101100563
https://blog.csdn.net/keheinash/article/details/101100494
注意,上述的user并不是指登录到系统的用户,不要将这两个概念混淆
有人可能会问,如果仅仅通过user和name两个字段不就可以“锁定”唯一的app吗?为什么还需要加上seinfo来确认?
这种方法确实是可以确认唯一的应用,并为该应用指定安全上下文,但是并不是对所有的应用有效。只有当user为前面提到系统预留的uid时,才可以忽略seinfo,如下图,navigation是一个专供系统使用的保留uid,所以此规则可以通过编译并且生效。
当user=_app时,SEAndroid的原生规则明确规定,seinfo字段不能为空,这条规则在system/sepolicy/private/seapp_contexts文件中:
如下图,如果user为_app,去掉seinfo字段,当编译SELinux的策略时,会直接报错:
如果第三方提供了一个用自己的密钥签过名的apk给我们,要如何指定其安全上下文呢?找一个符合这样要求的apk,查看其Android.mk文件,发现LOCAL_CERTIFICATE的值为:
LOCAL_CERTIFICATE := PRESIGNED
根据前面的方法,我们在自己开发维护的SELinux策略目录中,增加mac_permissions.xml文件,在文件中指定,当signature值为PRESIGNED时,seinfo的值为presigned。
然后在自己的seapp_contexts文件指定app的安全上下文。
注意,上述的修改都是在自己的SELinux策略目录中新增加的文件修改,不要修改/system/sepolicy下的内容,否则影响CTS认证。
完成上述修改后,编译SELinux的策略,生成sepolicy、nonplat_mac_permissions.xml、nonplat_seapp_contexts等文件,都替换到机器上。但是通过ps -Z命令查看,发现应用并没有按照我们的设想变成test_context类型,仍然是untrusted_app_25类型。
首先怀疑我们的修改出了差错,将进行了上述修改后编译生成的nonplat_mac_permissions.xml、nonplat_seapp_contexts文件分别和原生代码编译生成的plat_mac_permissions.xml、plat_seapp_contexts文件进行,发现nonplat_mac_permissions.xml和plat_mac_permissions.xml文件的内容格式存在很大的差别,可以看到,plat_mac_permissions.xml文件存在大量的x509格式证书的内容:
于是重新查看system/sepolicy/private/mac_permissions.xml,发现注释说明里有这么一段描述,大概的意思是每个signer的tag都需要有一个对应的X509格式的证书,联想到前面四种系统签名都有对应x509.pem文件,于是先无脑猜测这个证书就是用于验证apk签名的证书
紧接着在源码里搜索包含keys.conf这个关键字的文件,确实存在这个文件
观察文件的内容,发现对每个signature,都指定了存放对应x509格式证书的路径,而且这个文件还能指定编译不同版本(如USER或者USERDEBUG)分别使用不同的证书。
根据上面的分析,可以这样猜测:在编译SELinux的策略时,会去读取源码mac_permissions.xml的signature tag的值,然后在keys.conf查找对应的x509证书,将证书的内容写入到编译生成的plat_mac_permissions.xml和nonplat_mac_permissions.xml,只有证书可以成功验证apk的签名时,才允许将seinfo设置成你想要定义的值。
为了验证上面的猜测,我们用android.process.acore进行实验。
android.process.acore使用shared类型进行签名,于是我们在自己的mac_permissions.xml加上:
另外创建一个keys.conf文件,避免修改原生策略里的文件,在keys.conf指定share类型证书的路径:
最后在自己的seapp_contexts文件,将seinfo的值为test_input_contexts,且app名字为android.process.acore的应用的安全上下文指定为input_t,当然input_t类型需要在策略里预先定义,否则会编译出错
编译策略,将需要替换的文件都替换到机器上,系统启动后,通过ps -Z命令查看android.process.acore的安全上下文,已经转换为我们需要指定的安全上下文了:
根据上面的分析和验证,我们知道如果要修改已经签名的app的安全上下文,需要指定其证书的路径,同时在mac_permissions.xml文件指定seinfo的值,在seapp_contexts文件指定app的类型。
对于第三方提供的应用,如果是未经过签名,可以根据我们的需要使用系统定义的四种类型的签名,也可以使用我们自己的私钥进行签名,只要保证编译策略的时候可以找得到signature类型对应的证书即可。
如果应用经过了第三方签名,可以要求提供证书,或者将keystore文件转换为对应的pem证书。