冷知识:Binder如何实现跨进程权限控制

共 19282字,需浏览 39分钟

 ·

2023-10-26 18:45

读者投稿:彭小铭,原文链接: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<intopen_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名
  • 参与方式:「点击右下角”在看“并回复截图到公众号,随机抽取」

    点击就能升职、加薪水!

浏览 585
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报