0%

Android源码的Binder权限是如何控制?

转自:https://www.zhihu.com/question/41003297

一、源码分析

  1. clearCallingIdentity 方法,最终调用如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int64_t IPCThreadState::clearCallingIdentity()
    {
    int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
    clearCaller();
    return token;
    }

    void IPCThreadState::clearCaller()
    {
    mCallingPid = getpid(); //当前进程pid赋值给mCallingPid
    mCallingUid = getuid(); //当前进程uid赋值给mCallingUid
    }
    • mCallingUid(记为UID),保存 Binder IPC 通信的调用方进程的 Uid
    • mCallingPid(记为PID),保存 Binder IPC 通信的调用方进程的 Pid

    UIDPIDIPCThreadState 的成员变量, 都是 32 位的 int 型数据,通过移位操作,将 UIDPID 的信息保存到 token,其中高 32 位保存 UID,低 32 位保存 PID。然后调用 clearCaller() 方法将当前本地进程 piduid 分别赋值给 PIDUID,最后返回 token

    一句话总结:
    clearCallingIdentity 作用是清空远程调用端的 uidpid,用当前本地进程的 uidpid 替代;

  2. restoreCallingIdentity 方法,最终调用如下:

    1
    2
    3
    4
    5
    void IPCThreadState::restoreCallingIdentity(int64_t token)
    {
    mCallingUid = (int)(token>>32);
    mCallingPid = (int)token;
    }

    token 中解析出 PIDUID,并赋值给相应的变量。该方法正好是clearCallingIdentity的反过程。

    一句话总结:
    restoreCallingIdentity 作用是恢复远程调用端的 uidpid 信息,正好是clearCallingIdentity 的反过程;到此,应该明白了从代码角度是如何实现的。

  3. 源码示例上述过程主要在 system_server 进程的各个线程中比较常见(普通的app应用很少出现),比如 system_server 进程中的 ActivityManagerService 子进程,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ActivityManagerService.java
@Override
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
//获取远程Binder调用端的pid
int callingPid = Binder.getCallingPid();
//清除远程Binder调用端uid和pid信息,并保存到origId变量
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid);
//通过origId变量,还原远程Binder调用端的uid和pid信息
Binder.restoreCallingIdentity(origId);
}
}

代码中 startService 中有讲到 attachApplication() 的调用。该方法一般是 system_server 进程的子线程调用远程进程时使用,而 attachApplicationLocked 方法则是在同一个线程中,故需要在调用该方法前清空远程调用者的 uidpid,调用结束后恢复远程调用者的 uidpid

二、场景分析

场景:
首先 进程A 通过 Binder 远程调用 进程B,然后 进程B 通过 Binder 调用当前进程的另一个 service 或者 activity 之类的组件。

分析:

  1. 进程A 通过 Binder 远程调用 进程B:则 进程BIPCThreadState 中的 mCallingUidmCallingPid 保存的就是 进程AUIDPID。这时在 进程B 中调用 Binder.getCallingPid()Binder.getCallingUid() 方法便可获取 进程AUIDPID,然后利用 UIDPID 进行权限比对,判断 进程A 是否有权限调用 进程B 的某个方法。
  2. 进程B 通过 Binder 调用 当前进程 的某个组件:此时 进程B进程B 某个组件的调用端,则 mCallingUidmCallingPid 应该保存 当前进程BPIDUID,故需要调用 clearCallingIdentity() 方法完成这个功能。当 进程B 调用完某个组件,由于 进程B 仍然处于 进程A 的被调用端,因此 mCallingUidmCallingPid 需要恢复成 进程AUIDPID,这是调用 restoreCallingIdentity() 即可完成。

场景分析.jpg

一句话:
图中过程2(调用组件2开始之前)执行 clearCallingIdentity(),过程3(调用组件2结束之后)执行 restoreCallingIdentity()

三、类比分析

看完场景分析,估计还有不少朋友感到迷惑,为何需要这两个方法来多此一举,直接检测最初调用端的权限不就行了吗?为了更加形象明了地说明其用途,下面用一个生活中的场景来类比说明。

场景:
假如你的朋友请你帮忙,给她(他)到你的公司以内部价购买公司的某个产品。

分析:
这个过程分为两个阶段: 类比分析.jpg

  • 第一阶段:你的朋友请你帮忙的过程,这个过程并不一定所有朋友都会帮的,这时就需要一个权限检测,那么在你的朋友”远程调用”你执行任务时,你会记录他的 Identity 信息(比如是性别),有了信息那么就可以权限检测,不妨令权限规则是如果这个朋友是女性则答应帮忙,否则就认定权限不够拒绝执行(可能黑客会想到先去一趟泰国,权限控制可能相应需要打补丁了),若答应帮忙则进入第二阶段,否则直接返回。
  • 第二阶段:你向自己所在公司的相关部门内购产品的过程,这个过程也并不是所有人都能权限能够内购的,只有自己公司的员工才行,否则你的朋友也不会找你帮忙了。 这个过程同样需要权限检测,但是 Identity 保存的是性别女的信息,公司内购产品如果也以性别来判断,那岂不是公司的所有男员工没有权限内购,那这公司就有点太坑了,这明显不符合实情。 clearCallingIdentity() 是时候该登场了,在第二阶段开始之前,先执行 clearCallingIdentity() 过程,也就是把 Identity 信息清空,替换为你的信息(比如员工编码 ITCode 之类的),那公司相关部门通过 ITCode 就可以直接判断是否允许内购某产品。当第二阶段完成后,也就是你已经购买到了公司产品,这时你需要将产品交付给你的朋友,需要 restoreCallingIdentity,恢复 Identity 为女的信息,这样就该顺便交付给你的女朋友。如果不恢复信息,还是原来的 ITCode,你交付的朋友可能是男的,另有其人,这样就不科学了。

相信到此,大都能明白这两个方法的作用,缺一不可,而且要成对出现。

  • 本文作者: 6x
  • 本文链接: https://6xyun.cn/article/152
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!