Binder是如何做到跨进程权限控制的

每日一问 | Binder是如何做到跨进程权限控制的?

原问题出处:每日一问 | Binder是如何做到跨进程权限控制的?:https://wanandroid.com/wenda/show/26624

// 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进程

 

 

每个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了。不管看多少书,更重要的是自己思考,动手重复的实践!也许这个过程很耗时间,但是,这个不断以代码去验证自己的某些猜想的过程。

Linus

作者:彭小铭
链接:https://juejin.cn/post/7250008208159309861
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值