冷知识:Binder如何实现跨进程权限控制
❝读者投稿:彭小铭,原文链接:https://juejin.cn/post/7250008208159309861
❞
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
public class PackageManagerServiceUtils {
...
/**
* Check if the Binder caller is system UID, root's UID, or shell's UID.
*/
public static boolean isSystemOrRootOrShell() {
final int uid = Binder.getCallingUid();
return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID || uid == Process.SHELL_UID;
}
...
}
Binder.getCallingUid()字面理解是获取调用方的uid,但是这个代码是目标进程调用的,如何通过一个静态方法调用,就拿到调用方的uid呢?
这里看看Binder.getCallingUid()的代码:
// frameworks/base/core/java/android/os/Binder.java
public class Binder implements IBinder {
...
/**
* Return the Linux UID assigned to the process that sent you the
* current transaction that is being processed. This UID can be used with
* higher-level system services to determine its identity and check
* permissions. If the current thread is not currently executing an
* incoming transaction, then its own UID is returned.
*/
@CriticalNative
public static final native int getCallingUid();
...
}
发现getCallingUid()方法是Native函数,那么它是动态注册到那个cpp类的方法呢?其实在Android系统启动过程中,Zygote进程就已经动态注册的该方法,Zygote进程是由Init进程通过init.zygote.rc文件而创建的,zygote所对应的可执行程序app_process,所对应的源文件frameworks/base/cmds/app_process/app_main.cpp。上图的第4步startReg()方法就是Zygote注册Native的方法入口:
// frameworks/base/core/jni/AndroidRuntime.cpp
// 4.extern关键词修饰此函数表示:在别处定义的,要在此处引用。
extern int register_android_os_Binder(JNIEnv* env);
// RegJNIRec数组
static const RegJNIRec gRegJNI[] = {
...
// 3
REG_JNI(register_android_os_Binder),
...
}
//宏定义REG_JNI
//REG_JNI(register_android_os_Binder).mProc(env)等价于执行了register_android_os_Binder(env)方法
#ifdef NDEBUG
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
/*
* Register android native functions with the VM.
* 注册Android的Native函数
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
...
// 1.注册gRegJNI数组的Native方法
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
...
}
...
}
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
// 遍历gRegJNI数组,其含有REG_JNI(register_android_os_Binder)对象
for (size_t i = 0; i < count; i++) {
// 2.REG_JNI(register_android_os_Binder).mProc(env)
// 等价于执行了register_android_os_Binder(env)方法
if (array[i].mProc(env) < 0) {
...
}
}
return 0;
}
综上所述,流程最终会调用register_android_os_Binder(JNIEnv* env)方法,这个函数定义在frameworks/base/core/jni/android_util_Binder.cpp里:
// frameworks/base/core/jni/android_util_Binder.cpp
// JNI函数数组
static const JNINativeMethod gBinderMethods[] = {
/* name, signature, funcPtr */
...
// 3.Native方法getCallingUid()映射android_os_Binder_getCallingUid()方法
// @CriticalNative
{ "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
...
}
static jint android_os_Binder_getCallingUid()
{ // 4.调用IPCThreadState.getCallingUid()
return IPCThreadState::self()->getCallingUid();
}
static int int_register_android_os_Binder(JNIEnv* env)
{ ...
// 2.通过注册Native方法的辅助函数动态注册gBinderMethods数组的JNIMethod
return RegisterMethodsOrDie(
env, kBinderPathName,
gBinderMethods, NELEM(gBinderMethods));
}
int register_android_os_Binder(JNIEnv* env)
{
// 1.调用int_register_android_os_Binder()
if (int_register_android_os_Binder(env) < 0)
...
}
综上所述,兜兜转转Binder.getCallingUid()从Java层最终会调用IPCThreadState.getCallingUid()的Native层:
// frameworks/native/libs/binder/IPCThreadState.cpp
uid_t IPCThreadState::getCallingUid() const
{
...
return mCallingUid;
}
正如代码所展示的,IPCThreadState.getCallingUid()直接返回mCallingUid字段,写入mCallingUid引用多个方法里,发现在executeCommand()方法:
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::executeCommand(int32_t cmd)
{
...
switch ((uint32_t)cmd) {
...
case BR_TRANSACTION:
{
binder_transaction_data_secctx tr_secctx;
binder_transaction_data& tr = tr_secctx.transaction_data;
// 1.从mIn的Parcel对象读取binder_transaction_data
if (cmd == (int) BR_TRANSACTION_SEC_CTX) {
result = mIn.read(&tr_secctx, sizeof(tr_secctx));
} else {
result = mIn.read(&tr, sizeof(tr));
tr_secctx.secctx = 0;
}
...
// 2.mCallingUid赋值为binder_transaction_data的sender_euid
mCallingUid = tr.sender_euid;
}
}
...
}
由上面代码可知,在IPCThreadState.executeCommand()执行解析Binder驱动BR_TRANSACTION的ioctl系统调用命令返回的结果时,mCallingUid赋值为binder_transaction_data的sender_euid,根据名字应该是调用方的uid,下面是Binder非oneway和oneway调用的简单流程图:从上面2张图可知,Binder非oneway和oneway调用都会执行Binder驱动BR_TRANSACTION的ioctl系统调用,这里看看下面完整的Binder调用的简单流程图,以startService()的Binder调用为例:Binder复杂的程度远远不能就用一张图来说得清楚,这里只是让大家知道个大概的流程,从图中得知,在Kernel层的Binder驱动里的binder_thread_read()方法处理BR_TRANSACTION的ioctl系统调用命令:
// 注意这是Kernel代码,AOSP把Kernel分离出来了
// common/drivers/android/binder.c
static int binder_thread_read(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed, int non_block)
{
...
while (1) {
uint32_t cmd;
struct binder_transaction_data_secctx tr;
// 当BR_TRANSACTION调用的时候,trd就是要发送给Service端的数据内容
struct binder_transaction_data *trd = &tr.transaction_data;
struct binder_work *w = NULL;
// 当BR_TRANSACTION调用的时候,t就是Client端发送过来的数据内容
struct binder_transaction *t = NULL;
...
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
...
t = container_of(w, struct binder_transaction, work);
} break;
}
//只有BR_TRANSACTION,BR_REPLY才会往下执行
if (!t)
continue;
...
trd->code = t->code;
trd->flags = t->flags;
// 设置binder_transaction_data的sender_euid
// 为Client端发送过来的数据内容里uid
trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);
}
}
由上面的流程图可知,binder_thread_read()是从binder_transaction()执行进来的,在它里sender_euid字段被赋值:
// common/drivers/android/binder.c
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply,
binder_size_t extra_buffers_size)
{
...
// 调用task_euid()方法传入binder_pro的task_struct的tsk获取euid
t->sender_euid = task_euid(proc->tsk);
...
}
// common/drivers/android/binder_internal.h
struct binder_proc {
...
struct task_struct *tsk;
...
}
如果熟悉Linux kernel进程的管理和调度机制的话,应该就非常熟悉task_struct结构体就是进程描述符了,它包含了一个进程所需的所有信息。因为在Client端发送数据到Service端的场景下,上面代码binder_transaction()传入的参数*proc就是Client端的binder_proc对象,binder驱动层的每一个binder_proc结构体都与用户空间的一个用于binder通信的进程一一对应。
每个App在启动前必须先创建一个进程,该进程是由Zygote进程fork出来,进程具有独立的资源空间,用于承载app上运行的各种Activity/Service等组件,进程在创建时候打开Binder驱动,然后才能进行Binder IPC通讯,如startActivity()调用流程AMS先检测目标进程是否启动,若没则创建目标进程。这里看一下上图的关键方法onZygoteInit()会进入Native层调用AndroidRuntime.cpp注册关联的方法:
// frameworks/base/core/jni/AndroidRuntime.cpp
// 全局静态AndroidRuntime对象指针,在构造函数里赋值并保证进程单例
static AndroidRuntime* gCurRuntime = NULL;
...
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
//1.调用AndroidRuntime的纯虚函数onZygoteInit()
gCurRuntime->onZygoteInit();
}
...
在AndroidRuntime的头文件中的onZygoteInit()定义为纯虚函数,其子类AppRuntime实现在frameworks/base/cmds/app_process/app_main.cpp里定义:
// frameworks/base/cmds/app_process/app_main.cpp
...
class AppRuntime : public AndroidRuntime
{
public:
AppRuntime(char* argBlockStart, const size_t argBlockLength)
: AndroidRuntime(argBlockStart, argBlockLength)
, mClass(NULL){}
...
virtual void onZygoteInit()
{
//1.创建sp智能指针引用的ProcessState对象
// 在其作用域结束后将自动释放
sp<ProcessState> proc = ProcessState::self();
...
}
}
// frameworks/native/libs/binder/ProcessState.cpp
...
const char* kDefaultDriver = "/dev/binder";
...
sp<ProcessState> ProcessState::self()
{
return init(kDefaultDriver, false /*requireDefault*/);
}
// sp智能指针引用的ProcessState进程单例
static sp<ProcessState> gProcess;
...
sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault)
{ ...
// 2.创建sp智能指针引用的ProcessState对象
gProcess = sp<ProcessState>::make(driver);
...
}
...
ProcessState::ProcessState(const char* driver)
: mDriverName(String8(driver)),
... {
// 3.构造方法里调用open_driver执行打开Binder驱动逻辑
base::Result<int> opened = open_driver(driver);
...
}
...
static base::Result<int> open_driver(const char* driver) {
// 4.open系统调用打开/dev/binder驱动设备
int fd = open(driver, O_RDWR | O_CLOEXEC);
}
这里看一下打开Binder驱动的代码:
// 注意这是Kernel代码,AOSP把Kernel分离出来了
// common/drivers/android/binder.c
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc, *itr;
...
// 这里的current表示的是当前的进程,
proc->tsk = current->group_leader;
}
这样Client进程打开Binder驱动时调用binder_open(),就将当前进程的task_struct进程描述符绑定到了对应的binder_proc的tsk字段里。也就能获取Client进程的uid了。
总结
有同学看到这些觉得难,其实,这并不奇怪,一般写应用界面根本就不会接触到,因为这需要了解Framework基本的知识才行,如Android系统启动的流程、进程的创建、四大组件与AMS的流程。我认为学习Framework层需要具备以下基础:
-
1.c/c++基础 -
2.Linux内存基础(大抵理解kernel进程的管理和调度机制,内存管理和内存寻址,I/O驱动设备(字符设备、块设备、网络设备)和调度机制等)
如果没有以上这2个必备的基础,你在看Binder源码很可能停留在表面,当然前面所说的也只不过是整个Binder的冰山一角而已。学习新东西往往有共性所在,正如你会Jetpack Compose,那么你也会70%的Flutter了。不管看多少书,更重要的是自己思考,动手重复的实践!也许这个过程很耗时间,但是,这个不断以代码去验证自己的某些猜想的过程。
「点击关注,Carson每天带你学习一个Android知识点。」
最后福利:学习资料赠送
-
福利:本人亲自整理的「Android学习资料」 -
数量:10名 -
参与方式:「点击右下角”在看“并回复截图到公众号,随机抽取」
点击就能升职、加薪水!