常见的AMS、PWS、WMS等都是系统服务,运行于system_server进程,并且向ServiceManager进程注册其Binder以便其他进程获取binder与对应的服务进行通信。为了新增自定义系统服务,我们可以参考AMS等原生系统 服务,如新增 TestManagerService :
- ITestManager.aidl 文件:生成Binder类,其中Stub即为Binder的服务端;
- TestManagerService:系统服务类,继承自Stub;
- TestManager:封装了AIDL接口方法的类,相当于Binder客户端(Proxy),其他进程通过此类完成与系统服务的通信。
源码中添加自定义系统服务
1、ITestManager.aidl
在frameworks/base/core/java/android/app中编写 ITestManager.aidl
1 | // ITestManager.aidl |
2、TestManager.java
在frameworks/base/core/java/android/app 下编写TestManager.java
1 | package android.app; |
3、修改Context
在frameworks/base/core/java/android/content/Context.java中加入常量:
1 | /** @hide */ |
4、TestManagerService.java
在frameworks/base/services/core/java/com/android/service/test中编写TestManagerService.java
1 | package com.android.server.test; |
5、ServiceManager注册
在frameworks/base/services/java/com/android/server/SystemServer.java中注册系统服务
1 | import com.android.server.test.TestManagerService; |
6、SystemServiceRegistry注册
在frameworks/base/core/java/android/app/SystemServiceRegistry.java注册服务获取器:
1 | import android.app.TestManager; |
7、配置SELinux权限
在system/sepolicy/prebuilts/api/31.0/private/与system/sepolicy/private/目录下,分别修改:
1 | 注意:两个目录下文件需要一致,否则报错,如: |
service_contexts
1 | activity u:object_r:activity_service:s0 |
service.te:
1 | #配置自定义服务类型 |
untrusted_app_all.te :
1 | #允许所有app使用自定义服务 |
8、更新并编译
1 | # 更新api |
编译成功后,启动模拟器或者烧写设备后命令行查看服务是否:
1 | adb shell service list| grep test |
使用自定义服务
1、双亲委托机制,在需要使用自定义服务的app中编写TestManager(包名与framework中一致),方法名空实现。app中的
TestManager仅仅只是为了编译成功编写的空壳。
2、修改app使用的sdk,可以通过make sdk 将SDK完整编译出来,在AS中使用自编译出的SDK即可,也可以将原生SDK中的android.jar替换。
安全机制
1 | SELinux: avc: denied { add } for service=cvnavi_storage pid=3933 uid=1000 scontext=u:r:system_app:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0 |
系统进程添加一个服务到系统进程调用ServiceManager.addService(name, service);系统中抛出上面的错误。
通过错误信息看出可能是没有添加到系统服务的权限,SELinux拒绝添加。然后通过命令行验证是否是SELinux安全策略导致的这个问题。
1 | setenforce 0 (1:Enforcing 0:Permissive)临时禁用掉SELinux,5.1版本默认强制开启; |
禁用掉SELinux后发现功能可以正常使用,就是这个问题导致的,直接关掉是不可能的,容易出问题,需要想办法解决。
上面的这个属于Android中SEAndroid安全机制(MAC)强制访问控制。
Android中自主访问控制是通过Linux UID/GID实现,而强制访问控制则是使用的SEAndroid。
在Android中SEAndroid安全机制(MAC)与传统的Linux UID/GID安全机制(DAC)是并存关系的,也就是说,它们同时用来约束进程的权限。
当一个进程访问一个文件的时候,首先要通过基于UID/GID的DAC安全检查,接着才有资格进入到基于SEAndroid的MAC安全检查。只要其中的一个检查不通过,那么进程访问文件的请求就会被拒绝。
DAC自主访问控制
自主访问控制,正式的英文名称为Discretionary Access Control,简称为DAC。
比如通过ls -l /system,可以查看到该目录下存在一个manifest.xml文件,其输出为
1 | -rwxr-x--- 1 root root 2544 2021-09-22 22:02 manifest.xml |
表示 manifest.xml是root用户组的root用户拥有,对于root用户来说,是rwx(可读可写可执行);而对于root用户组其他用户来说,是可读可执行;对于其他用户则没有任何权限;也就是750权限。
Android是一个基于Linux内核的系统,但是它不像传统的Linux系统,需要用户登录之后才能使用。然而,Android系统又像传统的Linux系统一样有用户的概念。只不过这些用户不需要登录,也可以使用Android系统,这是因为Android系统将每一个安装在系统的APK都映射为一个不同的Linux用户。也就是说,每一个APK都有一个对应的UID和GID。这些UID和GID是在APK安装的时候由系统安装服务PMS分配的:
1 | //framework/base/services/core/java/com/android/server/pm/PackageManagerService.java |
在完成安装并运行程序后,可以通过ps -A | grep PACKAGENAME 查看程序uid
1 | ps -A | grep xxx |
得到当前程序进程ID为7124,然后通过cat /proc/7124/status查看
1 | cat /proc/7124/status |
可以看到当前程序UID为10072,通过这种方式,就可以保证每一个APK进程都以不同的身份来运行,从而保证了相互之间不会受到干扰。这就是所谓的沙箱了,这完全是建立在Linux的UID和GID基础上的。
root的UID/GID可以通过下面方式查看:
1 | //system/core/include/private/android_filesystem_config.h |
可以看到,普通APP的UID从10000开始分配,最大到19999,而root用户的id为0。
应用权限与DAC的关系
如何才能让我们的进程能通过Linux UID/GID的拦截呢?如果是一个Android APP若让其具备网络权限,只需要在AndroidManifest.xml中配置:
1 | <uses-permission android:name="android.permission.INTERNET"/> |
APP的UID和GID是在安装时候就由PMS分配好了的。为什么这样一个配置就能够让程序通过Linux UID/GID的拦截?
这是因为PMS在安装APK时,从Manifest文件中把App信息和权限存到/data/system/packages.xml和/data/system/packages.list文件中。打开packages.list会存在下面的记录:
1 | com.baidu.homework 10051 0 /data/user/0/com.baidu.homework default:targetSdkVersion=26 3002,3003,3001 |
其中3002,3003,3001代表的就是用户组,通过/system/core/include/private/android_filesystem_config.h 查看可知
1 |
3001与3002代表了具备蓝牙相关权限的用户组,而3003则表示具备网络权限的用户组。PMS会将APK加入到相应的某个Linux用户组去,这样APK才能够具备对应的权限。
为了通过DAC的权限检查具备网络权限,以FDBus为例,有网络权限,需要将name-server指定到3003用户组。
在/framework/base/data/etc/platform.xml 中可以看到:
1 | <permission name="android.permission.NET_TUNNELING" > |
inet 就是网络权限组,即:inet就是3003。因此,我们配置init.rc内容如下:
1 | service name-server /system/bin/name-server -u tcp://104.129.181.88:60002 -n android |
在理想情况下,DAC机制是没有问题的。如果将某个系统文件的权限改为777,只有DAC机制那么任何用户都能具备对该文件的读写以及执行权限,可能造成严重的安全问题。为了解决这个问题,Android中还使用了一种更为强有力的安全机制来保证系统的安全,这种机制就是MAC。
MAC强制访问控制
Android中使用的MAC机制就是SEAndroid。SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)在Linux社区的帮助下设计的一个Linux历史上最杰出的安全系统,是一种MAC机制(Mandatory Access Control,强制访问控制)。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。由于Android系统有着独特的用户空间运行时,因此SELinux不能完全适用于Android系统。为此,NSA针对Android系统,在SELinux基础上开发了SEAndroid。
SEAndroid权限配置
SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。在/system/sepolicy目录中,所有以.te为后缀的文件均为策略配置文件。
安全上下文
SEAndroid安全机制中的安全策略是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。主体通常就是进程,而客体就是指进程所要访问的资源,例如文件、系统属性等。
由于我们的服务程序可执行为/system/bin/name-server,首先我们需要在/system/sepolicy/private/file_contexts中声明该执行文件的SEAndroid系统文件的安全上下文
1 | /system/bin/name-server u:object_r:name-server_exec:s0 |
安全上下文实际上就是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部分内容组成,
分别是SELinux用户、SELinux角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为”user:role:type:sensitivity”
在安全上下文中,只有类型(Type)才是最重要的,SELinux用户、SELinux角色和安全级别都几乎可以忽略不计的。正因为如此,SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。
用户与角色
在/system/sepolicy/private/users中声明了SELinux用户u,它可用的SELinux角色为r,它的默认安全级别为s0,可用的安全级别范围为s0 - mls_systemhigh:
1 | user u roles { r } level s0 range s0 - mls_systemhigh; |
mls_systemhigh为系统定义的最高安全级别
在/system/sepolicy/public/roles中声明了SELinux角色r与类型domain关联:
1 | role r types domain; |
在SEAndroid中,只定义了唯一一个用户u,两个角色r(适用于主题,如进程)与object_r(适用于对象,如文件),这意味着只有u、r/object_r和domain可以组合在一起形成一个合法的安全上下文,那么ps -Z查看进程应为u:r:domain:s0 ,ls -Z查看文件则为: u:object_r:domain:s0,而其它形式的安全上下文定义均是非法的。
以init进程为例,执行 adb shell ps -Z | grep zygote,可以看到输出为:
1 | u:r:zygote:s0 root 646 1 1577904 69500 poll_schedule_timeout eb7e743c S zygote |
安全上下文u:r:zygote:s0,按照上面的分析,这不是应该是一个不合法的上下文吗?原因是在/system/sepolicy/public/zygote.te 中通过type声明了类型zygote并且将domain设置为类型zygote的属性:
1 | type zygote, domain; |
因此它就可以像domain一样,可以和SELinux用户u和SELinux角色组合在一起形成合法的安全上下文。
类型
在SEAndroid中,每一个用来描述文件安全上下文的类型都将file_type设置为其属性,每一个用于进程安全上下文的类型都将domain设置为其属性。
安全策略
SEAndroid安全机制主要是使用对象安全上下文中的类型来定义安全策略,这种安全策略就称TypeEnforcement,简称TE,.te文件即为安全策略配置文件。
system/sepolicy/public:公共策略配置,将此目录视为相应平台的已导出政策 API,包括供应商特定策略。
system/sepolicy/private:系统正常运行所必需(但供应商映像政策应该不知道)的策略。
对于权限的配置不建议直接修改以上目录,应该在/device/manufacturer/device-name/sepolicy 目录进行自己设备的专用策略配置。
如Pixel5手机搭载AAOS,则应该在:/device/google_car/redfin_car/sepolicy目录下配置。同时修改或添加政策文件和上下文的描述文件后,需要修改 /device/manufacturer/device-name/BoardConfig.mk 以引用 sepolicy 子目录和每个新的政策文件。
1 | BOARD_SEPOLICY_DIRS += \ |
以FDBUS name-server为例,在/system/sepolicy/private目录下创建一个name-server.te作为该进程的策略文件。文件内容如下:
1 | # 声明name-server类型,并将domain属性关联到该类型 (进程) |
Allow规则
allow表示开放权限,当某个进程执行,如果该进程不具备对应的权限,则可以在logcat 或者在执行adb root 后执行 adb shell dmesg查看。
1 | avc: denied { bind } for pid=417 comm="name-server" scontext=u:r:name-server:s0 |
对于上面日志的分析步骤如下:
- 缺少什么权限 :denied { bind }
- 谁缺少权限:scontext=u:r:name-server:s0
- 对谁缺少权限 :tcontext=u:r:name-server:s0
- 什么类型的权限:tclass=tcp_socket
需要添加对应的权限,在TE文件中声明:
1 | #allow [谁缺少权限] [对谁缺少权限]:[什么类型的权限] [缺少什么权限] |
宏函数
在system/sepolicy/public/te_macros中定义了很多宏函数,其中init_daemon_domain表示声明 name-server是从 init 衍生而来的,并且可以与其通信。
另外还有其他宏如:net_domain,其定义为:
1 | ##################################### |
如果调用该宏:net_domain(name-server)表示将name-server赋予netdomain类型。而netdomain类型可以在
system/sepolicy/public/net.te 中看到,存在如下规则:
1 | #...... |
那么使用该宏则表示,将name-server赋予tcp_socket对应的create_stream_socket_perms权限。整体而言net_domain函数表示允许使用 net 域中的常用网络功能,如读取和写入 TCP 数据包、通过套接字进行通信,以及执行 DNS 请求等。也就是说,可以通过对应的宏函数调用完成对某些类型权限的统一allow。
name-server编译报错
out目录下error.log
1 | libsepol.report_failure: neverallow on line 959 of system/sepolicy/public/domain.te (or line 12961 of policy.conf) violated by allow name-server name-server_exec:file { entrypoint }; |
在system/sepolicy/public/domain.te中第959行
1 | full_treble_only(` |
在以上配置中,可以理解为neverallow(禁止)了coredomain类型与domain类型对于file_type类型的访问权限。为解决该问题,可以在name-server.te中,修改name-server_exec赋予system_file_type属性。
1 | type name-server_exec, system_file_type, exec_type, file_type; |
另外,在system/sepolicy/public/domain.te中第300行:
1 | #不允许coredomain类型对domain类型进行unix_stream_socket |
此时system/sepolicy/private/untrusted_app.te 中配置为:
1 | typeattribute untrusted_app coredomain; |
即第三方app类型为coredomain类型,而我们的name-server又是domain,即不允许APP通过Socket连接系统服务name-server。解决办法就是将name-server赋于其他类型(如cordomain)作为属性!
1 | typeattribute name-server coredomain; |
同时在system/sepolicy/private/mls中存在如下限制:
1 | mlsconstrain unix_stream_socket { connectto } |
必须满足3 个条件之一,才能授予 unix_stream_socket 的 connectto 权限:
- l1 == l2,l1代表untrusted_app,l2代表name-server,不满足;
- t1 == mlstrustedsubject,t1为 untrusted_app,不属于 mlstrustedsubject;
- t2 == mlstrustedsubject,t2为 name-server,不属于 mlstrustedsubject。
以上3 个条件都不满足。因此app还是无法访问name-server的socket服务。
综合上述分析,可以修改name-server.te,为name-server配置mlstrustedsubject属性即可。
1 | typeattribute name-server coredomain; |
untrusted_app与untrusted_app_all的关系:
untrusted_app表示第三方app,在untrusted_app.te中存在untrusted_app_domain(untrusted_app)配置。untrusted_app_domain 定义于:system/sepolicy/public/te_macros
1 | ##################################### |
表示为untrusted_app赋于untrusted_app_all属性,即untrusted_app就是untrusted_app_all。