以一个例子来记录下学习SEAndroid的笔记。
需求:很简单,一个system进程要往ServiceManager中添加服务。
没写对应的SELinux策略时遇到的错误:
02-21 10:37:25.662 484 484 E SELinux : avc: denied { add } for service=car_model_service pid=10283 uid=1000 scontext=u:r:system_app:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0
这个错误是说啥呢
denied 拒绝
{ add } 是指被拒绝的权限,或者叫被拒绝的动作
service=car_model_service 是指服务名称,这里就是要add到ServiceManager中的那个字符串"car_model_service"
pid = 10283 是指这个进程的操作被拒绝了
uid = 1000 这个进程的uid,1000就是Android中的system user
scontext=u:r:system_app:s0 这个是重点1,scontext是指源标签,就是发起操作的进程的标签,这个标签就是SELinux中用来判断权限的重要依据(SELinux是一个基于标签的系统,这个文章解释了这个标签是怎么给进程和文件打上的)
tcontext=u:object_r:default_android_service:s0 tcontext是指目的标签,object_r这个是标签中的角色部分,Android中r角色表示进程,是活的,能发起动作的角色,object_r这个角色,是死的,只能被人操作、访问,一般是文件、设备、属性等,这里是服务,更精确点说,是服务名:car_model_service,但是这个标签里是default_android_service这个类型,或者叫域,因为我们还没有在SELinux策略里明确定义car_model_service这个服务定义所属的域,所以selinux给他一个default_android_service域。
tclass=service_manager 这个是说,要操作的具体对象的类别,在这里是指要操作一个default_android_service下的service_manager对象,tclass常见的类别还有file、device等。
所以,总结以上,那条log的意思就是:uid 是system的进程10283 (SELinux标签是u:r:system_app:s0 )想要把标签是 u:object_r:default_android_service:s0 (car_model_service的标签)的对象对service_manager这个类型的对象做 { add } 操作,(就是要把car_model_service 加到 service_manager中),由于不符合现有的SELinux Policy的策略定义,被拒绝了。
注:标签u:r:system_app:s0中有4个部分分别是user:role:type:sec_level 其中SEAndroid中user都是u,role为r表示进程,role为object_r表示除进程外的其他可被进程操作的对象如file、device等,sec_level表示一个安全级别,Android中都是s0,为同一级别。所以SEAndroid大部分其实用到的只有type这部分即SELinux的子集,这篇介绍了SELinux中的三种强制执行模型,Android中主要是第一种基于类型的强制执行模型,有少部分用到了MCS(多类别安全模型)。
。
所以,我们要增加策略,那就是允许system_app域(或类型)的进程把default_android_service域中(或类型)的service add 到 service_manager中,可以试下自动将拒绝log转换成 SELinux策略的工具 audit2allow(自行搜索用法,这东西只能参考,大部分不好使)给出的策略:
#============= system_app ==============
allow system_app default_android_service:service_manager add;
输出的这个的意思是,可以把这句加到system_app.te(system_app.te是什么?)文件中 试试。
但是,如果真这么加起作用,那就坏了,因为策略中没有定义的service名,系统都给打上了u:object_r:default_android_service:s0 标签(这个标签怎么来的),上面那句真起作用,那不就很容易破坏了SELinux的保护了吗?显然SELinux没有这么脆弱,上面那句规则会违反neverallow 规则,编译是无法通过的。
编译错误提示
neverallow check failed at out/target/product/PRODUCT_NAME/obj/ETC/treble_sepolicy_tests_intermediates/built_plat_sepolicy:4273 from system/sepolicy/public/domain.te:432
(neverallow base_typeattr_9 default_android_service (service_manager (add)))
<root>
allow at out/target/product/PRODUCT_NAME/obj/ETC/treble_sepolicy_tests_intermediates/built_plat_sepolicy:11897
(allow system_app default_android_service (service_manager (add)))
Failed to generate binary
Failed to build policydb
根据错误提示可以找到对应的neverallow策略:
android/system/sepolicy/public/domain.te
# Do not allow service_manager add for default service labels.
# Instead domains should use a more specific type such as
# system_app_service rather than the generic type.
# New service_types are defined in {,hw,vnd}service.te and new mappings
# from service name to service_type are defined in {,hw,vnd}service_contexts.
neverallow * default_android_service:service_manager add;
这个策略的意思是,不允许 任何 进程将类型为default_android_service的service 对service_manager 做 add操作(service_manager.c中的add service 函数中会根据selinux策略来检查),显然上面的audit2allow输出的策略和这条neverallow策略冲突。
所以需要修改策略。
根据neverallow策略,要add的service,不能被打上u:object_r:default_android_service:s0 标签,不然天王老子(比如root进程)都不能add到ServiceManager中去,先看看这个标签怎么来的:
android/system/sepolicy/private/service_contexts
我们随便找一个可以添加到ServiceManager的服务看看是怎么定义的,例如SurfaceFlinger的标签定义:
SurfaceFlinger u:object_r:surfaceflinger_service:s0
其他所有Android系统中由ServiceManager管理的服务都在这里做了定义,其中有一条规则是这样的:
* u:object_r:default_android_service:s0
可以看到service名字为 * 被打上了u:object_r:default_android_service:s0 标签,是指没有在service_contexts定义的,都是这个标签,所以我们要自己给要添加的service写一条规则定义其标签。
上面这个文件中定义了android系统默认的service的标签,根据不同项目需要自定义的service标签最好在项目文件目录中定义:
android/device/qcom/sepolicy/private/service_contexts
Android中所有service 对应的selinux标签都由该文件定义,该文件可以有多份,编译时合并。所以我们在这个文件中定义要add的service 标签:
car_model_service u:object_r:my_car_model_service:s0
当然 my_car_model_service这个域(或类型)也得定义,不然selinux不认识啊,service相关的域,或者叫类型的定义在如下文件(android默认肯定也有一个service.te的文件,定义所有Android原生的service域):
android/device/qcom/sepolicy/common/service.te
type my_car_model_service, service_manager_type;
这句话意思是,定义一个域(或类型)my_car_model_service,并且把它关联到service_manager_type,这个service_manager_type叫attribute,其实叫域的集合更好,这样关联后,有关service_manager_type的规则,同样也适用于my_car_model_service。
照猫画虎的参考其他service,我们还发现,还需要修改system_app.te,因为我们的进程是属于system_app域的,有关system_app域的进程的访问权限都定义在此文件中(当然这个文件也可以有多份)。
android/vendor/myvendor/sepolicy/system_app.te
add_service(system_app, my_car_model_service)
这里又出现一个概念,就是宏,就是可以展开的定义,add_service就是个宏,selinux宏的定义一般都在后缀为macros的文件中(system/sepolicy/中定义了大部分android默认的policy政策以及宏和其他的定义):
android/system/sepolicy/public/te_macros
###########################################
# add_service(domain, service)
# Ability for domain to add a service to service_manager
# and find it. It also creates a neverallow preventing
# others from adding it.
define(`add_service', `
allow $1 $2:service_manager { add find };
neverallow { domain -$1 } $2:service_manager add;
')
所以上面system_app.te中的:
add_service(system_app, my_car_model_service)
编译后就变成了
allow system_app my_car_model_service:service_manager { add find };
neverallow { domain -system_app } my_car_model_service:service_manager add;
意思就是允许 system_app 域(或类型)的进程,就是打了标签 u:r:system_app:s0的进程,把my_car_model_service类型的服务对service_manager做 add 和 find 操作。就是能加进去,也能取出来用。
下面为了安全,还又加了一个neverallow策略,不允许除了system_app以外的 domain域(或类型)的进程往service_manager里加my_car_model_service。Android中所有的进程都关联到了domain上了。可以随意查看一个.te文件,开头都会定义type ***, domain; 就是说,所有对domain策略,对关联了domain的域都起作用。如果你定义一个进程的域没有关联domain域,那么所有进程相关的操作,都得自己重写。domain.te中定义了允许所有进程进行的操作的规则。毕竟selinux上任何进程对任何资源要操作,都要定义对应的策略。
稍等:
type my_car_model_service, service_manager_type;
这句有啥用呢?刚才说了type 的作用是把要定义的域关联到后面的属性(attribute)或域上,这样所有对于后面属性的策略都可以对新定义的域起作用,这句定义完成后,所有有关service_manager_type的规则,都可以对我们新的服务起作用,比如:
android/system/sepolicy/public/shell.te
allow shell { service_manager_type -gatekeeper_service -incident_service -installd_service -netd_service -virtual_touchpad_service -vr_hwc_service }:service_manager find;
这样你不用去配置,shell域的进程就可以取service_manager中去查找服务,例如dumpsys就是shell域的进程,否则,你得单独去允许my_car_model_service被shell查询。
到这里,解决这个简单问题的se策略就加完了。