Binder IPC的權限控制



PS:個人理解:當進程1通過Binder調用組件2時,會將進程1的pid及uid賦給組件2,並檢測進程1的pid及uid是否有權限調用組件2.而后組件2需要調用組件3,此時組件2保存的pid及uid為進程1的,但是其實際運行在進程2中。此時調用clearCallingIdentity將組件2的保存的pid及uid轉換為進程2的pid和uid並return原來保存的進程1的pid及uid的token。此時組件2調用組件3並檢查權限。調用完成后,restoreCallingIdentity將組件2的pid及uid恢復為進程1的。

正文:

看過Android系統源代碼的朋友,一定看到過Binder.clearCallingIdentity()Binder.restoreCallingIdentity()這兩個方法,其定義在Binder.java文件:

//作用是清空遠程調用端的uid和pid,用當前本地進程的uid和pid替代; public static final native long clearCallingIdentity(); //作用是恢復遠程調用端的uid和pid信息,正好是`clearCallingIdentity`的反過程; public static final native void restoreCallingIdentity(long token);

這兩個方法涉及的uid和pid,每個線程都有自己獨一無二的IPCThreadState對象,記錄當前線程的pid和uid,可通過方法Binder.getCallingPid()Binder.getCallingUid()獲取相應的pid和uid。

clearCallingIdentity(), restoreCallingIdentity()這兩個方法使用過程都是成對使用的,這兩個方法配合使用,用於權限控制檢測功能。

二、原理

從定義這兩個方法是native方法,通過Binder的JNI調用,在android_util_Binder.cpp文件中定義了native方法所對應的jni方法。

2.1 clearCallingIdentity

[-> android_util_Binder.cpp]

static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz) { //調用IPCThreadState類的方法執行 return IPCThreadState::self()->clearCallingIdentity(); } 

2.1.1 IPC.clearCallingIdentity

[-> IPCThreadState.cpp]

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;

UID和PID是IPCThreadState的成員變量, 都是32位的int型數據,通過移位操作,將UID和PID的信息保存到token,其中高32位保存UID,低32位保存PID。然后調用clearCaller()方法將當前本地進程pid和uid分別賦值給PID和UID,最后返回token

2.2 restoreCallingIdentity

[-> android_util_Binder.cpp]

static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, jlong token) { //token記錄着uid信息,將其右移32位得到的是uid int uid = (int)(token>>32); if (uid > 0 && uid < 999) { //目前Android中不存在小於999的uid,當uid<999則拋出異常。 char buf[128]; jniThrowException(env, "java/lang/IllegalStateException", buf); return; } //調用IPCThreadState類的方法執行 IPCThreadState::self()->restoreCallingIdentity(token); } 

2.2.1 IPC.restoreCallingIdentity

[-> IPCThreadState.cpp]

void IPCThreadState::restoreCallingIdentity(int64_t token) { mCallingUid = (int)(token>>32); mCallingPid = (int)token; } 

token中解析出PID和UID,並賦值給相應的變量。該方法正好是clearCallingIdentity的反過程。

2.3 getCallingPid

[-> android_util_Binder.cpp]

static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz) { return IPCThreadState::self()->getCallingPid(); } 

2.3.1 IPC.getCallingPid

[-> IPCThreadState.cpp]

pid_t IPCThreadState::getCallingPid() const { return mCallingPid; } uid_t IPCThreadState::getCallingUid() const { return mCallingUid; } 

2.4 遠程調用

2.4.1 binder_thread_read

binder_thread_read(){
    while (1) { struct binder_work *w; switch (w->type) { case BINDER_WORK_TRANSACTION: t = container_of(w, struct binder_transaction, work); break; case :... } if (!t) continue; //只有BR_TRANSACTION,BR_REPLY才會往下執行 tr.code = t->code; tr.flags = t->flags; tr.sender_euid = t->sender_euid; //mCallingUid if (t->from) { struct task_struct *sender = t->from->proc->tsk; //當非oneway的情況下,將調用者進程的pid保存到sender_pid tr.sender_pid = task_tgid_nr_ns(sender,current->nsproxy->pid_ns); } else { //當oneway的的情況下,則該值為0 tr.sender_pid = 0; } ... } 

2.4.2 IPC.executeCommand

status_t IPCThreadState::executeCommand(int32_t cmd) { BBinder* obj; RefBase::weakref_type* refs; status_t result = NO_ERROR; switch ((uint32_t)cmd) { case BR_TRANSACTION: { const pid_t origPid = mCallingPid; const uid_t origUid = mCallingUid; mCallingPid = tr.sender_pid; //設置調用者pid mCallingUid = tr.sender_euid;//設置調用者uid ... reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags); mCallingPid = origPid; //恢復原來的pid mCallingUid = origUid; //恢復原來的uid } case :... } } 

關於mCallingPid、mCallingUid的修改過程:是在每次Binder Call的遠程進程在執行binder_thread_read()過程, 會設置pid和uid. 然后在IPCThreadState的transact收到BR_TRANSACION則會修改mCallingPid、mCallingUid。

這里需要注意的是,當oneway的的情況下mCallingPid=0,不過mCallingUid可以拿到正確值。

三、用途

3.1 場景分析

場景:首先線程A通過Binder遠程調用線程B,然后線程B通過Binder調用當前線程的另一個service或者activity之類的組件。

分析:

  1. 線程A通過Binder遠程調用線程B:則線程B的IPCThreadState中的mCallingUidmCallingPid保存的就是線程A的UID和PID。這時在線程B中調用Binder.getCallingPid()Binder.getCallingUid()方法便可獲取線程A的UID和PID,然后利用UID和PID進行權限比對,判斷線程A是否有權限調用線程B的某個方法。
  2. 線程B通過Binder調用當前線程的某個組件:此時線程B是線程B某個組件的調用端,則mCallingUidmCallingPid應該保存當前線程B的PID和UID,故需要調用clearCallingIdentity()方法完成這個功能。當線程B調用完某個組件,由於線程B仍然處於線程A的被調用端,因此mCallingUidmCallingPid需要恢復成線程A的UID和PID,這是調用restoreCallingIdentity()即可完成。

binder_clearCallingIdentity

一句話:圖中過程2(調用組件2開始之前)執行clearCallingIdentity(),過程3(調用組件2結束之后)執行restoreCallingIdentity()

3.2 類比分析

看完場景分析,估計還有不少朋友感到迷惑,為何需要這兩個方法來多此一舉,直接檢測最初調用端的權限不就行了嗎?為了更加形象明了地說明其用途,下面用一個生活中的場景來類比說明。

場景:假如你的朋友請你幫忙,給她(他)到你的公司以內部價購買公司的某個產品。

分析:這個過程分為兩個階段

binder_clearCallingIdentity_2

  • 第一階段:你的朋友請你幫忙的過程,這個過程並不一定所有朋友都會幫的,這時就需要一個權限檢測,那么在你的朋友”遠程調用”你執行任務時,你會記錄他的”Identity”信息(比如是性別),有了信息那么就可以權限檢測,不妨令權限規則是如果這個朋友是女性則答應幫忙,否則就認定權限不夠拒絕執行(可能黑客會想到先去一趟泰國,權限控制可能相應需要打補丁了),若答應幫忙則進入第二階段,否則直接返回。
  • 第二階段:你向自己所在公司的相關部門內購產品的過程,這個過程也並不是所有人都能權限能夠內購的,只有自己公司的員工才行,否則你的朋友也不會找你幫忙了。 這個過程同樣需要權限檢測,但是”Identity”保存的是性別女的信息,公司內購產品如果也以性別來判斷,那豈不是公司的所有男員工沒有權限內購,那這公司就有點太坑了,這明顯不符合實情。 clearCallingIdentity()是時候該登場了,在第二階段開始之前,先執行clearCallingIdentity()過程,也就是把”Identity”信息清空,替換為你的信息(比如員工編碼ITCode之類的),那公司相關部門通過ITCode就可以直接判斷是否允許內購某產品。當第二階段完成后,也就是你已經購買到了公司產品,這時你需要將產品交付給你的朋友,需要restoreCallingIdentity,恢復”Identity”為女的信息,這樣就嗯呢該順便交付給你的女朋友。如果不恢復信息,還是原來的ITCode,你交付的朋友可能是男的,另有其人,這樣就不科學了。

相信到此,大家應該都能明白這兩個方法的作用,缺一不可,而且要成對出現。

3.3 實例分析

上述過程主要在system_server進程的各個線程中比較常見(普通的app應用很少出現),比如system_server進程中的ActivityManagerService子線程,代碼如下:

[–>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方法則是在同一個線程中,故需要在調用該方法前清空遠程調用者的uid和pid,調用結束后恢復遠程調用者的uid和pid。

 

轉自:http://gityuan.com/2016/03/05/binder-clearCallingIdentity/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM