一個權限主要包含三個方面的信息:權限的名稱;屬於的權限組;保護級別。一個權限組是指把權限按照功能分紅的不一樣的集合。每個權限組包含若干具體權限,例如在 COST_MONEY 組中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和費用相關的權限。java
每一個權限經過 protectionLevel 來標識保護級別: normal , dangerous , signature , signatureorsystem 。不一樣的保護級別表明了程序要使用此權限時的認證方式。 normal 的權限只要申請了就可使用; dangerous 的權限在安裝時須要用戶確認纔可使用; signature 和 signatureorsystem 的權限須要使用者的 app 和系統使用同一個數字證書。linux
Package 的權限信息主要 經過在 AndroidManifest.xml 中經過一些標籤來指定。如 <permission> 標籤, <permission-group> 標籤 <permission-tree> 等標籤。若是 package 須要申請使用某個權限,那麼須要使用 <use-permission> 標籤來指android
permission 的初始化,是指 permission 的向系統申請,系統進行檢測並受權,並創建相應的數據結構。絕大多數的狀況下 permission 都是從一個 package 中掃描所得,而這發生在 package 安裝和升級的時候。通常有以下幾種 安裝入口:算法
n packageInstaller , package 被下載安裝時會觸發使用。 packageInstaller 會經過 AppSecurityPermissions 來檢查 dangerous 的權限,並對用戶給出提示。數組
n pm 命令 。數據結構
n adb install 。最終仍是 調用 pm install 來安裝 apk 包。app
n 拷貝即安裝。 PackageManagerService 中使用 AppDirObserver 對 /data/app/ 進行監視 ,若是有拷貝即觸發安裝。ide
這些安裝方式 最終都會經過調用 PackageManagerService 中的函數來完成程序的安裝。函數
第一步,從 AndroidManifest.xml 中提取 permission 信息。主要提取以下信息:工具
² shared uid
指定與其它 package 共享同一個 uid 。
² permission
提取 permissions 標籤指定屬性。它使用 permissionInfo 來描述一個權限的基本信息。須要指定 protectedLevel 信息,並指定所屬 group 信息。它將被添加到這個 package 的 permissions 這個 list 結構中。
² permission-tree
提取 permissions-tree 標籤屬性。 permissions-tree 也經過 permissionInfo 來描述,並被添加到 package 的 permissions 這個 list 結構中。 permission-tree 只是一個名字空間,用來向其中動態添加一些所謂 Dynamic 的 permission ,這些 permission 能夠動態修改。這些 permission 名稱要以 permission-tree 的名稱開頭。它自己不是一種權限,沒有 protectedLevel 和所屬 group 。只是保存了所屬的 packge 和權限名(帶有 package 前綴的)。
² permission-group
定義 permission 組信息,用 PermissionGroup 表示。自己不表明一個權限,會添加進入 package 的 permissionGroups 這個 list 中。
² uses-permission
定義了 package 須要申請的權限名。將權限名添加到 package 的 requestedPermissions 這個 list 中。
² adopt-permissions
將該標籤指定的 name 存入 package 的 mAdoptPermissions 這個 list 中。 Name 指定了這個 package 須要從 name 指定的 package 進行權限領養。在 system package 進行升級時使用。
第二步。獲取 Package 中的證書,驗證,並將簽名信息保存在 Package 結構中。
1. 若是該 package 來自 system img (系統 app ),那麼只須要從該 Package 的 AndroidManifest.xml 中獲取簽名信息,而無需驗證其完整性。可是若是這個 package 與其它 package 共享一個 uid ,那麼這個共享 uid 對應的 sharedUser 中保存的簽名與之不一致,那麼簽名驗證失敗。
2. 若是是普通的 package ,那麼須要提取證書和簽名信息,並對文件的完成性進行驗證。
第三步。若是是普通的 package ,那麼清除 package 的 mAdoptPermissions 字段信息(系統 package 升級才使用)。
第四步。若是在 AndroidManifest.xml 中指定了 shared user ,那麼先查看全局 list 中( mSharedUsers )是否該 uid 對應的 SharedUserSetting 數據結構,若沒有則新分配一個 uid ,建立 SharedUserSetting 並保存到全局全局 list ( mSharedUsers )中。
mUserIds 保存了系統中已經分配的 uid 對應的 SharedUserSetting 結構。每次分配時老是從第一個開始輪詢,找到第一個空閒的位置 i ,而後加上 FIRST_APPLICATION_UID 便可。
第五步。建立 PackageSettings 數據結構。並將 PackageSettings 與 SharedUserSetting 進行綁定。其中 PackageSettings 保存了 SharedUserSetting 結構;而 SharedUserSetting 中會使用 PackageSettings 中的簽名信息填充本身內部的簽名信息,並將 PackageSettings 添加到一個隊列中,表示 PackageSettings 爲其中的共享者之一。
在建立時,首先會以 packageName 去全局數據結構 mPackages 中查詢是否已經有對應的 PackageSettings 數據結構存在。若是已經存在 PackageSettings 數據結構(好比這個 package 已經被 uninstall ,可是尚未刪除數據,此時 package 結構已經被釋放)。那麼比較該 package 中的簽名信息(從 AndroidManifest 中掃描獲得)與 PackageSettings 中的簽名信息是否匹配。若是不匹配可是爲 system package ,那麼信任此 package ,並將 package 中的簽名信息更新到已有的 PackageSettings 中去,同時若是這個 package 與其它 package 共享了 uid ,並且 shared uid 中保存的簽名信息與當前 package 不符,那麼簽名也驗證失敗。
第六步。若是 mAdoptPermissions 字段不爲空,那麼處理 permission 的領養(從指定的 package 對應的 PackageSettings 中,將權限的擁有者修改成當前 package ,通常在 system app 升級的時候才發生,在此以前須要驗證當被領養的 package 已經被卸載,即檢查 package 數據結構是否存在)。
第七步。添加自定義權限。將 package 中定義的 permissionGroup 添加到全局的列表 mPermissionGroups 中去;將 package 中定義的 permissions 添加到全局的列表中去(若是是 permission-tree 類型,那麼添加到 mSettings.mPermissionTrees ,若是是通常的 permission 添加到 mSettings.mPermissions 中)。
第八步。清除不一致的 permission 信息。
1. 清除不一致的 permission-tree 信息。若是該 permission-tree 的 packageSettings 字段爲空,說明還未對該 package 進行過解析(若代碼執行到此處時 packageSettings 確定已經被建立過),將其 remove 掉。若是 packageSettings 不爲空,可是對應的 package 數據結構爲空(說明該 package 已經被卸載,但數據還有保留),或者 package 數據結構中根本不含有這個 permission-tree ,那麼將這個 permission-tree 清除。
2. 清除不一致的 permission 信息。若是 packageSettings 或者 package 結構爲空(未解析該 package 或者被卸載,但數據有保留),或者 package 中根本沒有定義該 permission ,那麼將該 permission 清除。
第九步。對每個 package 進行輪詢,並進行 permission 受權。
1. 對申請的權限進行檢查,並更新 grantedPermissions 列表
2. 若是其沒有設置 shared user id ,那麼將其 gids 初始化爲 mGlobalGids ,它從 permission.xml 中讀取。
3. 遍歷全部申請的權限,進行以下檢查
1 )若是是該權限是 normal 或者 dangerous 的。經過檢查。
2 )若是權限須要簽名驗證。若是簽名驗證經過。還須要進行以下檢查
* 若是程序升級,並且是 system package 。那麼是否授予該權限要看原來的 package 是否被授予了該權限。若是被授予了,那麼經過檢查,不然不經過。
* 若是是新安裝的。那麼檢查經過。
4. 若是 3 中檢查經過,那麼將這個 permission 添加到 package 的 grantedPermissions 列表中,表示這個 permission 申請成功( granted )。申請成功的同時會將這個申請到的 permission 的 gids 添加到這個 package 的 gids 中去。
5. 將 permissionsFixed 字段標準爲 ture ,表示這個 packge 的 permission 進行過修正。後續將禁止對非 system 的 app 的權限進行再次修正。
PackageManagerService 提供了 addPermission/ removePermission 接口用來動態添加和刪除一些權限。可是這些權限必須是所謂的動態權限( BasePermission.TYPE_DYNAMIC )。
一個 Package 若是要添加 Dynamic permissions ,首先必需要在 manifest 中申明 <permission-tree> 標籤,它其實是一個權限的名字空間(例如,「 com.foo.far 」這個權限就是 permission-tree 「com.foo 」的成員),自己不是一個權限。一個 Package 只能爲本身的 permission-tree 或者擁有相同的 uid 的 package 添加或者刪除權限。
Package 不可以經過這種接口去修改在 manifest 中靜態申請的權限,不然拋出異常。
首先查找這個 permission 在全局 permission 列表 mSettings.mPermissions 中是否存在。若是存在,並且類型爲 BasePermission.TYPE_DYNAMIC 那麼根據傳入的權限信息修改全局表中的權限信息,並觸發 permissions.xml 的持久化。
若是在全局的 permission 列表 mSettings.mPermissions 中沒有找到,先找到這個 permission 所在 permissionTree ,而後添加到全局 permission 列表 mSettings.mPermissions 中去,並觸發 permissions.xml 的持久化。
下面兩個 接口 主要用於 Uri permission 的管理 (其實如今 ActivityManagerService 中)。
// 爲指定的 uid 和 targetPkg 添加對某個 content Uri 的讀或者寫權限。
public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException;
// 清除全部經過 grantUriPermission 對某個 Uri 授予的權限。
public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException;
grantUriPermission 主要的實現過程分析。
grantUriPermission 分析:
1. 驗證 caller 的 ProcessRecord 和 targetPkg 不爲空。不然檢測不經過。
2. 驗證所請求的 mode 爲 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者爲 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,不然不經過。
3. 確保參數 Uri 是一個 content Uri 。不然,則檢測不經過。
4. 經過 Uri 獲得目標 ContentProvider ,若是不存在,則檢測不經過。
5. 從 PackageManagerService 中得到 targetPkg 對應的 uid 。
6. 檢查 target uid 所對應的 package 是否真正須要這個權限?
先判斷要申請的是讀仍是寫權限,而後查看對應的 ContentProvider 中對應的 readPermission writePermission 字段是否保存了權限名稱。 若是該字段不爲空,則以 target uid 和該權限名去PackageManagerService 中去查找該 uid 是否被 granted 了該權限。若是已經得到了該權限,那麼無需再去爲這個 Activity 去申請這個 Uri 權限了,返回。否者繼續執行以下操做。
7. 檢查這個 ContentProvider 的 grantUriPermissions 開關變量,是否容許對其它 package 進行權限的 grant 操做。若是禁止,那麼拋出異常。
8. 檢查這個 ContentProvider 是否設置了 Uri 的過濾類型 uriPermissionPatterns ,若是設置了過濾類型,則將須要申請權限的 Uri 與之匹配。匹配不一樣過,則拋出異常。
9. 檢查調用者本身是否有權限訪問這個 Uri 。若是沒有,拋出異常。
10. 從 mGrantedUriPermissions 中取得 target uid 對應的 HashMap<Uri, UriPermission> 數據結構。用 target uid 和 Uri 生成 UriPermission 並保存在 mGrantedUriPermissions 中。
revokeUriPermission 實現分析。
找到該 Uri 對應的 ContentProvider ,而後刪除 mGrantedUriPermissions 中與 Uri 對應的全部權限。
這裏的動態檢查是指是 package 在程序運行過程當中進行某些操做或者數據訪問時才進行的 check ,與之對應的是應用程序安裝或者升級時 PackageManagerService 經過掃描包中的靜態權限信息相對應。
系統與權限 檢查 相關的機制的實現主要集中在 PackageManagerService 和 ActivityManagerService 中。 ActivityManagerService 主要負責的是底層的 uid 層次的身份檢查; PackageManagerService 則維護了 uid 到本身擁有的和被授予的權限的一張表。在經過 ActivityManagerService 的身份檢查後, PackageManagerService 根據請求者的 uid 來查看這張表,判斷其是否具備相應的權限。
除此以外, per-URI permission 機制的實現也須要一張表,它維護在 ActivityManagerService 中,它創建了從 content URI 到被受權訪問這個 URI 的 component 之間的映射。可是它也須要藉助 PackageManagerService 的機制來輔助實現。
Android framework 中提供了一些接口用來對外來的訪問(包括本身)進行權限檢查 。 這些接口 主要經過 ContextWrapper 提供,具體實如今 ContextImpl 中 。若是 package 接受到外來訪問者的操做請求,那麼能夠調用這些接口進行權限檢查。通常狀況下能夠把這些接口的檢查接口分爲兩種,一種是返回錯誤,另外一種是拋出異常。
主要包含以下幾組:
n permission 和 uid 檢查 API
下面這一組接口主要用來檢查某個調用(或者是其它 package 或者是本身)是否擁有訪問某個 permission 的權限。參數中 pid 和 uid 能夠指定,若是沒有指定,那麼 framework 會經過 Binder 來獲取調用者的 uid 和 pid 信息,加以填充。返回值爲 PackageManager.PERMISSION_GRANTED 或者 PackageManager.PERMISSION_DENIED 。
public int checkPermission(String permission, int pid, int uid) // 檢查某個 uid 和 pid 是否有 permission 權限
public int checkCallingPermission(String permission) // 檢查調用者是否有 permission 權限,若是調用者是本身那麼返回 PackageManager.PERMISSION_DENIED
public int checkCallingOrSelfPermission(String permission) // 檢查本身或者其它調用者是否有 permission 權限
下面這一組和上面相似,若是遇到檢查不經過時,會拋出異常,打印消息 。
public void enforcePermission(String permission, int pid, int uid, String message)
public void enforceCallingPermission(String permission, String message)
public void enforceCallingOrSelfPermission(String permission, String message)
n per-URI 檢查 API
爲某個 package 添加訪問 content Uri 的讀或者寫權限。
public void grantUriPermission(String toPackage, Uri uri, int modeFlags)
public void revokeUriPermission(Uri uri, int modeFlags)
檢查某個 pid 和 uid 的 package 是否擁有 uri 的讀寫權限,返回值表示是否被 granted 。
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags)
public int checkCallingUriPermission(Uri uri, int modeFlags)
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags)
public int checkUriPermission(Uri uri, String readPermission,String writePermission, int pid, int uid, int modeFlags)
檢查某個 pid 和 uid 的 package 是否擁有 uri 的讀寫權限,若是失敗則拋出異常,打印消息 。
public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)
public void enforceCallingUriPermission(Uri uri, int modeFlags, String message)
public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)
public void enforceUriPermission(Uri uri, String readPermission, String writePermission,int pid, int uid, int modeFlags, String message)
ContextImpl.java 中提供的 API ,其實都是由 ActivityManagerService 中的以下幾個接口進行的封裝。
public int checkPermission(String permission, int pid, int uid) throws RemoteException; // 主要用於通常的 permission 檢查
public int checkUriPermission(Uri uri, int pid, int uid, int mode) throws RemoteException; // 主要用於 Content Uri 的 permission 檢查
n checkPermission 的實現分析
1. 若是傳入的 permission 名稱爲 null ,那麼返回 PackageManager.PERMISSION_DENIED 。
2. 判斷調用者 uid 是否符合要求 。
1 ) 若是 uid 爲 0 ,說明是 root 權限的進程,對權限不做控制。
2 ) 若是 uid 爲 system server 進程的 uid ,說明是 system server ,對權限不做控制。
3 ) 若是是 ActivityManager 進程自己,對權限不做控制。
4 )若是調用者 uid 與參數傳入的 req uid 不一致,那麼返回 PackageManager.PERMISSION_DENIED 。
3. 若是經過 2 的檢查後,再 調用 PackageManagerService.checkUidPermission ,判斷 這個 uid 是否擁有相應的權限,分析以下 。
1 ) 首先它經過調用 getUserIdLP ,去 PackageManagerService.Setting.mUserIds 數組中,根據 uid 查找 uid (也就是 package )的權限列表。一旦找到,就表示有相應的權限。
2 ) 若是沒有找到,那麼再去 PackageManagerService.mSystemPermissions 中找。這些信息是啓動時,從 /system/etc/permissions/platform.xml 中讀取的。這裏記錄了一些系統級的應用的 uid 對應的 permission 。
3 )返回結果 。
n 一樣 checkUriPermission 的實現 主要在 ActivityManagerService 中,分析以下:
1. 若是 uid 爲 0 ,說明是 root 用戶,那麼不控制權限。
2. 不然,在 ActivityManagerService 維護的 mGrantedUriPermissions 這個表中查找這個 uid 是否含有這個權限,若是有再檢查其請求的是讀仍是寫權限。
關於簽名機制,其實分兩個階段。
包掃描階段須要進行完整性和證書的驗證。普通 package 的簽名和證書是必需要先通過驗證的。具體作法是對 manifest 下面的幾個文件進行完整性檢查。完整性檢查包括這個 jar 包中的全部文件。若是是系統 package 的話,只須要使用 AndroidMenifest.xml 這個文件去提取簽名和驗證信息就能夠了。
在權限建立階段。若是該 package 來自 system img (系統 app ),那麼 trust it ,並且使用新的簽名信息去替換就的信息。前提是若是這個 package 與其它 package 共享一個 uid ,那麼這個共享 uid 對應的 sharedUser 中保存的簽名與之不一致,那麼簽名驗證失敗。有些時候系卸載一個 app ,可是不刪除數據,那麼其 PackageSettings 信息會保留,其中會保存簽名信息。這樣再安裝是就會出現不一致。
android 中系統和 app 都是須要簽名的。能夠本身經過 development/tools/make_key 來生成公鑰和私鑰。
android 源代碼中提供了工具 ./out/host/linux-x86/framework/signapk.jar 來進行手動簽名。簽名的主要做用在於限制對於程序的修改僅限於同一來源。系統中主要有兩個地方會檢查。若是是程序升級的安裝,則要檢查新舊程序的簽名證書是否一致,若是不一致則會安裝失敗;對於申請權限的 protectedlevel 爲 signature 或者 signatureorsystem 的,會檢查權限申請者和權限聲明者的證書是不是一致的。簽名相關文件能夠從 apk 包中的 META-INF 目錄下找到。
signapk.jar 的源代碼在 build/tools/signapk ,簽名主要有如下幾步:
l 將除去 CERT.RSA , CERT.SF , MANIFEST.MF 的全部文件生成 SHA1 簽名
首先將除了 CERT.RSA , CERT.SF , MANIFEST.MF 以外的全部非目錄文件分別用 SHA-1 計算摘要信息,而後使用 base64 進行編碼,存入 MANIFEST.MF 中。 若是 MANIFEST.MF 不存在,則須要建立。存放格式是 entry name 以及對應的摘要
l 根據 以前計算的 SHA1 摘要信息,以及 私鑰生成 一系列的 signature 並寫入 CERT.SF
對 整個 MANIFEST.MF 進行 SHA1 計算,並將摘要信息存入 CERT.SF 中 。而後對以前計算的全部摘要信息使用 SHA1 再次計算數字簽名,並寫入 CERT.SF 中。
l 把公鑰和簽名信息寫入 CERT.RST
把以前整個的簽名輸出文件 使用私有密鑰計算簽名。同時將簽名結果,以及以前聲稱的公鑰信息寫入 CERT.RSA 中保存。
安裝時對一個 package 的簽名驗證的主要邏輯在 JarVerifier.java 文件的 verifyCertificate 函數中實現。 其主要的思路是經過提取 cert.rsa 中的證書和簽名信息,獲取簽名算法等信息,而後按照以前對 apk 簽名的方法進行計算,比較獲得的簽名和摘要信息與 apk 中保存的匹配。
第一步。提取證書信息,並對 cert.sf 進行完整性驗證。
1. 先找到是否有 DSA 和 RSA 文件 ,若是找到則對其進行 decode ,而後讀取其中的全部的證書列表(這些證書會被保存在 Package 信息中,供後續使用)。
2. 讀取這個文件中的簽名數據信息塊列表,只取第一個簽名數據塊。讀取其中的發佈者和證書序列號。
3. 根據證書序列號,去匹配以前獲得的全部證書,找到與之匹配的證書。
4. 從以前獲得的簽名數據塊中讀取簽名算法和編碼方式等信息
5. 讀取 cert.sf 文件,並計算整個的簽名,與數據塊中的簽名(編碼格式的)進行比較,若是相同則完整性校驗成功。
第二步。使用 cert.sf 中的摘要信息,驗證 MANIFEST.MF 的完整性。
在 cert.sf 中提取 SHA1-Digest-Manifest 或者 SHA1-Digest 開頭的簽名 數據塊 ( -Digest-Manifest 這個是整個 MANIFEST.MF 的摘要 信息,其它的是 jar 包中其它文件的摘要信息 ), 並逐個對這些數據塊 進行驗證。驗證的方法是,現將 cert.sf 看作是不少的 entries ,每一個 entries 包含了一些基本信息,如這個 entry 中使用的摘要算法( SHA1 等),對 jar 包中的哪一個文件計算了摘要,摘要結果是什麼。 處理時先找到每一個摘要數據開中的文件信息,而後從 jar 包中讀取,而後使用 -Digest 以前的摘要算法進行計算,若是計算結果與摘要數據塊中保存的信息的相匹配,那麼就完成驗證。