1. 程式人生 > >ClipboardService(CBS)中的許可權管理

ClipboardService(CBS)中的許可權管理

ClipboardService(CBS)是Android系統中的元老級服務了,自Android 1.0起就支援剪貼功能。在Android 4.0中再遇見它時,此功能已有了長足改進。本節將集中討論CBS中的許可權管理。先來回顧一下CBS中和許可權管理相關的函式呼叫。

//copy方設定ClipData在CBS的setPrimaryClip函式中進行:
checkDataOwnerLocked(clip, Binder.getCallingUid());
clearActiveOwnersLocked();
//paste方獲取ClipData在CBS的getPrimaryClip函式中進行:
addActiveOwnerLocked(Binder.getCallingUid(), pkg);

在分析這3個函式之前,先介紹一下Android系統中的URI許可權管理。

1. URI許可權管理介紹Android系統的許可權管理中有一類是專門針對URI的,先來看一個示例,該例來自package/providers/ContactsProvider,在它的AndroidManifest.xml中有如下宣告:

[-->AndroidManifest.xml]

<provider android:name="ContactsProvider2"
         ......
          android:readPermission="android.permission.READ_CONTACTS"
          android:writePermission="android.permission.WRITE_CONTACTS">
          ......
          <grant-uri-permission android:pathPattern=".*" />
</provider>

這裡聲明瞭一個名為ContactsProvider2的ContentProvider,並定義了幾個許可權宣告,下面對其進行解釋。

  • readPermission:要求呼叫query函式的客戶端必須宣告一個use-permission為READ_CONTACTS的許可權。
  • writePermission:要求呼叫update或insert函式的客戶端必須宣告一個use-permission為WRITE_CONTACTS的許可權。
  • grant-uri-permission:和授權有關。

初識grant-uri-permission時,會覺得它比較難理解,下面通過舉例分析幫助讀者加深認識。

  • Contacts和ContactProvider這兩個APP都是由系統提供的程式,而且二者關係緊密,所以Contacts一定會宣告use_Permission為READ_CONTACTS和WRITE_CONTACT的許可權。如此,Contacts就可以毫無阻礙地通過ContactsProvider來查詢或更新資料庫了。
  • 假設Contacts新增一個功能,將ContactsProvider中的某條資料複製到剪下板。根據前面已介紹過的知識可以知道,Contacts會向剪下板中複製一個URI型別的資料。
  • 另外一個程式從剪下板中貼上(paste)這條資料,由於是URI型別的,所以此程式會通過ContentResolver來查詢該uri所指向的資料。但是這個程式卻並未宣告READ_CONTACTS的許可權,所以它查詢資料時必然會失敗。

或許有人會問,為什麼第三個程式不宣告相應許可權呢?原因很簡單,第三個程式不知道自己該宣告怎樣的許可權(除非這兩個程式的開發者能互通訊息)。本例ContactsProvider設定的讀許可權是READ_CONTACTS,以後可能換成READ_CONTACTS_EXTEND,第三個程式不太可能知道其中的變化。為了解決類似問題,Android提供了一套專門針對uri的許可權管理機制。以這套機制解決示例中許可權宣告問題的方法是這樣的:當第三個程式從剪下板中貼上資料時,系統會判斷是否需要為這個程式授權。當然,系統不會隨意授權,而是需要考慮ContactsProvider的情況。因為ContactsProvider聲明瞭grant-uri-permission,所以只要第三個程式所貼上的URI匹配其中的pathPattern,授權就能成功。倘若ContactsProvider沒有宣告grant-uri-permission,或者uri不匹配指定的pathPattern,則授權失敗。

有了前面介紹的許可權管理機制,相信下面CBS中的許可權管理理解起來就比較簡單了。

提示 感興趣的讀者可閱讀SDK安裝目錄下/docs/guide/topics/security/security.html中關於uri Permission的說明部分。

2. checkDataOwnerLocked函式分析

checkDataOwnerLocked函式的程式碼如下:

[-->ClipboardService.java::checkDataDwnerLocked]

private final void checkDataOwnerLocked(ClipData data, int uid) {
      //第二個引數uid為copy方程序的uid
      final int N = data.getItemCount();
      for (int i=0; i<N; i++) {
          //為每一個item呼叫checkItemOwnerLocked
          checkItemOwnerLocked(data.getItemAt(i), uid);
      }
}
// checkItemOwnerLocked函式分析
private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
      if (item.getUri() != null) {//檢查uri
          checkUriOwnerLocked(item.getUri(), uid);
      }
      Intent intent = item.getIntent();
      //getData函式返回的也是一個uri,因此這裡實際上檢查的也是uri
      if (intent != null && intent.getData() != null) {
          checkUriOwnerLocked(intent.getData(), uid);
      }
}

許可權檢查就是針對uri的,因為uri所指向的資料可能是系統內部使用或私密的。例如Setting資料中的Secure表,這裡的資料不能隨意訪問。雖然直接使用ContentResolver訪問這些資料時系統會進行許可權檢查,但是由於目前的剪下板服務也支援URI資料型別,所以這裡也需要做檢查,否則惡意程式就能輕鬆讀取私密資訊。
下邊來分析checkUriOwnerLocked函式,其程式碼如下:

[-->ClipboardService.java::checkUriOwnerLocked]

private final void checkUriOwnerLocked(Uri uri, int uid) {
      ......
      long ident = Binder.clearCallingIdentity();
      boolean allowed = false;
      try {
          /*
            呼叫ActivityManagerService的checkGrantUriPermission函式,
             該函式內部將檢查copy方是否能被賦予URI_READ許可權。如果不允許,
             該函式會拋SecurityException異常
          */
          mAm.checkGrantUriPermission(uid, null, uri, 
                                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
      } catch (RemoteException e) {
      } finally {
          Binder.restoreCallingIdentity(ident);
      }
  }

根據前面的知識,這裡先要檢查copy方是否有讀取uri的許可權。下面來分析paste方的許可權管理。

3. clearActiveOwnersLocked函式分析

clearActiveOwnersLocked函式的程式碼如下:

[-->ClipboardService.java::clearActiveOwnersLocked]

private final void addActiveOwnerLocked(int uid, String pkg) {
      PackageInfo pi;
      try {
         /*
         呼叫PackageManagerService的getPackageInfo函式得到相關資訊
          然後做一次安全檢查,如果PacakgeInfo的uid資訊和當前呼叫的uid不一致,
          則丟擲SecurityException。這個很好理解,因為paste方可以傳遞虛假的
          packagename,但uid是沒法造假的
          */
          pi = mPm.getPackageInfo(pkg, 0); 
          if (pi.applicationInfo.uid != uid) {
              throw new SecurityException("Calling uid " + uid
                      + " does not own package " + pkg);
          }
       } ......
      }
      //mActivePermissionOwners用來儲存已經通過安全檢查的package
      if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) {
          //針對ClipData中的每一個Item,都需要呼叫grantItemLocked來檢查許可權
          final int N = mPrimaryClip.getItemCount();
          for (int i=0; i<N; i++) {
              grantItemLocked(mPrimaryClip.getItemAt(i), pkg);
          }//儲存package資訊到mActivePermissionOwners
          mActivePermissionOwners.add(pkg);
      }
}
//grantItemLocked分析
private final void grantItemLocked(ClipData.Item item, String pkg) {
      if (item.getUri() != null) {
          grantUriLocked(item.getUri(), pkg);
      } //和copy方一樣,這裡僅檢查uri的情況
      Intent intent = item.getIntent();
      if (intent != null && intent.getData() != null) {
          grantUriLocked(intent.getData(), pkg);
      }
}

再來看grantUriLocked的程式碼:

[-->ClipboardService.java::grantUriLocked]

private final void grantUriLocked(Uri uri, String pkg) {
      long ident = Binder.clearCallingIdentity();
      try {
         /*
             呼叫ActivityManagerService的grantUriPermissionFromOwner函式,
             注意第二個引數傳遞的是CBS所在程序的uid。該函式內部也會檢查許可權。
              該函式呼叫成功後,paste方就被授予了對應uri的讀許可權
          */
          mAm.grantUriPermissionFromOwner(mPermissionOwner, 
                                  Process.myUid(), pkg, uri,
                                  Intent.FLAG_GRANT_READ_URI_PERMISSION);
      } catch (RemoteException e) {
      } finally {
          Binder.restoreCallingIdentity(ident);
      }
}

既然有授權,那麼客戶端使用完畢後就需要撤銷授權,這個工作是在setPrimaryClip函式的clearActiveOwnersLocked中完成的。當為剪下板設定新的ClipData時,自然需要將與舊ClipData相關的許可權撤銷。讀者可自行分析clearActiveOwnersLocked函式。
------------------------------

本文節選自《深入理解Android:卷II》第3章“深入理解SystemServer”第3.7.3節:CBS中的許可權管理。

【內容簡介】

本書是“深入理解Android”系列的第二本,第一本書上市後得到了廣大讀者的高度評價,在Android開發者社群內口口相傳。本書不僅繼承了第一本的優點、改正了其在細微處存在的一些不足,而且還在寫作的總體思想上進行了創新,更強調從系統設計者的角度去分析Android系統中各個模組內部的實現原理和工作機制。從具體內容上講,本書的重點是Android Framework的Java層,對Java層涉及的核心模組和服務進行了深入而細緻的分析。通過本書,讀者不僅能對Android系統本身有更深入的理解,而且還能掌握分析大型複雜原始碼的能力。

全書共8章:第1章介紹了閱讀本書所需要做的準備工作,包括Android 4.0原始碼的下載和編譯、Eclipse環境的搭建,以及Android系統程序(system_process)的除錯等;第2章對Java Binder和MessageQueue的實現進行了深入分析;第3章詳細剖析了SystemServer的工作原理,這些服務包括EntropyService、DropboxManagerService、DiskStatsService、DeviceStorageMonitorService、SamplingProfilerService和ClipboardService;第4章對系統中負責Package資訊查詢和APK安裝、解除安裝、更新等工作的服務PackageManagerService進行了詳細分析;第5章則對Android系統中負責電源管理的核心服務 PowerManagerService的原理進行了一番深入的分析;第6章以ActivityManagerService為分析重點,對它的啟動、Activity的建立和啟動、BroadcastReceiver的工作原理、Android中的程序管理等內容展開了較為深入的研究;第7章對ContentProvider的建立和啟動、SQLite、Cursor query和close的實現等進行了深入分析;第8章以ContentService和AccountManagerService為分析物件,介紹了資料更新通知機制的實現,以及賬戶管理和資料同步等相關知識。

【作者簡介】

鄧凡平,資深Android開發工程師和系統工程師,熱衷於Android原始碼的研究,對Android的架構設計和實現原理有非常深刻的認識和理解,應用開發經驗也十分豐富。《深入理解Android:卷I》的作者,同時也是“深入理解Andriod”系列圖書的總策劃。目前就職於國內領先的Android企業中科創達(ThunderSoft),負責Android Framework的開發和維護。喜歡鑽研,樂於分享,活躍於CSDN、51CTO和開源中國等專業技術社群,撰寫的Android Framework原始碼分析的系列文章深受讀者歡迎。此外,他對Linux核心、C/C++/Python相關的技術,以及高效能網路伺服器和多核並行開發等也有一定的研究。

【關於本書的各種網址】