1. 程式人生 > >Android M PackageManager應用程式許可權管理原始碼剖析及runtime permission實戰

Android M PackageManager應用程式許可權管理原始碼剖析及runtime permission實戰

Android應用許可權授予部分主要分為兩部分,第一部分是在PKMS啟動之後,且掃描完所有的app後,會對應用程式分配linux使用者組ID,即授予他們所申請的資源訪問許可權。

第一部分主要是對install等許可權進行無條件授予,而許多核心app的預設許可權則還沒有授予,對於使用者體驗來說相對差些,因此,許多必要的預設許可權(runtime permission)則會在PKMS.systemReady()方法中去進一步完成。

基本概念

在介紹這兩部分內容之前,我們先介紹如下概念:

使用者id與組id

/*UID和GID*/

如果你是一個Linux的使用者,那麼一定會有如下的體驗。當你的Linux的系統裡面,存在多個使用者的時候,你在登入了使用者A之後,是沒有許可權去修改使用者B的檔案內容的。

再舉個極端的例子,用普通使用者的許可權是沒有辦法進行Root使用者所特有的操作的。

UID:

Linux系統用於區別不同的使用者,使用不同的使用者名稱是一種方法。但是使用者名稱只是一種讓人方便讀的字串,對機器來講是沒有意義的。

為了方便機器的讀取,Linux採用了一個32位的整數記錄和區分不同的使用者。這個用來區分不同使用者的數字被稱為UserID,簡稱UID。

root的UID為0,普通的UID一般是從500開始。在Android上,一個使用者 UID標示一個應用程式,相當於linux中的一個使用者。應用程式在安裝時被分配使用者UID,應用程式在裝置上的存續期間內,使用者UID保持不變。對於普通的應用程式,GID即等於UID。

GID:

除了使用者的概念,Linux的系統還有使用者組的概念。同一個使用者組的使用者之間具有相似的特徵。

假如我們把某一個使用者加入到root組,那麼這個使用者就可以瀏覽root使用者組目錄的檔案。如果root使用者把某個檔案的讀寫執行許可權開放,root使用者組的所有使用者都可以修改該檔案。

Permission概念

Package的許可權資訊主要通過在AndroidManifest.xml中通過一些標籤來指定。

如 <permission> 標籤, <permission-group> 標籤, <permission-tree> 等標籤。如果package需要申請使用某個許可權,那麼需要使用<use-permission> 標籤來指定,系統的許可權定義在frameworks/base/core/res/AndroidManifest.xml(framework-res.apk)當中這些許可權會由PKMS啟動進行掃描的時候新增到對應的資料結構裡。

比如要想使用網路功能:

<uses-permissionandroid:name="android.permission.INTERNET" />

這個許可權在APK安裝掃描過程中會儲存在PackageParser.Package.requestedPermissions中,其在platform.xml中對應的item如下:

<permission name="android.permission.INTERNET" >
      <group gid="inet" />                                            
</permission>

這裡定義了上層android.permission. INTERNET許可權對應的Linux使用者組gid為inet。並且在解析xml檔案的時間也為每個permission標籤建立一個BasePermission物件,該物件中包含一個gids陣列,用來儲存與該許可權對應的Linux使用者組。

mGlobalGids陣列的值是解析platform.xml檔案得到的。

·        一個許可權主要包含三個方面的資訊:許可權的名稱,屬於的許可權組和保護級別。一個許可權組是指把許可權按照功能分成的不同的集合。每一個許可權組包含若干具體許可權,例如在storage許可權組中包含android.permission.READ_EXTERNAL_STORAGE,android.permission.WRITE_EXTERNAL_STORAGE兩個和儲存訪問相關的許可權。

Android許可權等級劃分

Android許可權等級劃分為normal,dangerous,signature,signatureOrSystem,system,development,不同的保護級別代表了程式要使用此許可權時的認證方式。

normal

在androidmanifest.xml中宣告相應的許可權,在安裝應用時,會預設獲得許可。並且使用者不能修改許可權許可。(只需要在AndroidManifest.xml中簡單宣告這些許可權就好,安裝時就授權。不需要每次使用時都檢查許可權,而且使用者不能取消以上授權。)

dangerous

許可權在安裝執行時需要使用者確認才可以使用,具體申請使用規則會在後文介紹

signature

需要與定義該許可權的apk簽名一致才能賦予許可權

signatureOrSystem

需要與定義該許可權的apk簽名一致,或者是系統級應用(放置在/system/app目錄下)才能使用該許可權。

對於上述四種許可權級別我們舉個例子:

   比如百度地圖apk的AndroidManifest.xml裡面聲明瞭一個許可權,

     (1) 許可權定義為Dangerous,那麼任何其他應用都可以使用。

     (2) 許可權定義為Signature,那麼只有使用同樣私鑰簽名的apk,例如百度網盤,可以使用這個許可權。

     (3) 許可權定義為SignatureOrSystem,那麼使用同樣私鑰簽名的apk,例如百度網盤,可以使用這個許可權。

另外在/system/priv-app下的特權應用才可以使用這個許可權,關於這一點可以在程式碼中看出來:

接下來我們具體瞭解下android的許可權管理。

PKMS許可權管理

PKMS在掃描階段會解析出每個apk的資訊,這些資訊會存在一個以pkgName為key,以pkg為value的ArrayMap<String, PackageParser.Package> mPackages的成員物件中,而pkg物件有一個mExtras成員則儲存的是PackageSettings物件,它用來描述這個pkg的安裝資訊,而PackageSetting物件繼承自SettingsBase物件,這個物件包含有一個PermissionsState物件,PermissionsState物件有個成員ArrayMap<String,PermissionData> mPermissions,可以看到它裡面儲存的是PermissionData物件,而PermissionData有如下兩個成員:

private final BasePermission mPerm;  // 描述一個基本的許可權

private SparseArray<PermissionState>mUserStates = new SparseArray<>(); // 儲存該許可權的的狀態

public static final class PermissionState {
       private final String mName;
       private boolean mGranted;
       private int mFlags;
}

許可權相關類圖如下:

有了這個關係,我們接下來就開始我們的第一部分。

updatePermissionsLPw,會進一步呼叫grantPermissionsLPw,其主要流程如下:

最終通過PermissionData的grant方法將PermissionState的狀態修改。

第一部分到此結束。

預設runtime許可權授予

前面提到第一部分主要是對install等許可權進行無條件授予,而許多核心app的預設許可權則還沒有授予,對於使用者體驗來說相對差些。

因此,許多必要的預設許可權(runtime permission)則會在PKMS.systemReady()方法中去進一步完成,我們接下來看這一部分。

這段程式碼邏輯在PKMS.systemReady中,遍歷所有的user,對於沒有進行default許可權設定的則呼叫grantDefaultPermissions,預設的許可權賦予工作是由DefaultPermissionGrantPolicy去完成的。

final DefaultPermissionGrantPolicy mDefaultPermissionPolicy =
            new DefaultPermissionGrantPolicy(this);

許可權檢查

Androidframework中提供了一些介面用來對外來的訪問(包括自己)進行許可權檢查。這些介面主要通過ContextWrapper提供,具體實現在ContextImpl中。

如果package接受到外來訪問者的操作請求,那麼可以呼叫這些介面進行許可權檢查。一般情況下可以把這些介面的檢查介面分為兩種,一種是返回錯誤,另一種是丟擲異常。

返回為錯誤型別的API如下:

方法

說明

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 許可權

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 )最後返回結果。

丟擲異常的API如下:

方法

說明

public void enforcePermission(String permission, int pid, int uid, String message)

SecurityException

public void enforceCallingPermission(String permission, String message)

SecurityException

public void enforceCallingOrSelfPermission(String permission, String message)

SecurityException

RuntimePermission 

什麼是Runtime Permission?

安卓是一個許可權分離的作業系統,在該系統中,每個應用程式都執行在一個不同的系統身份中(使用者身份和組標識)。該系統的部分也被分離成不同的身份。從而使應用程式從系統中分離。

通過“許可權”機制,執行具體的操作如限制一個特定的程序可以提供額外的安全特性以及授予每個URI許可權去臨時訪問特定的資料塊。

簡而言之,應用執行時,所申請的一些執行許可權,比如:美圖相機它要用到攝像頭,這個攝像頭使用是要有許可權的,使用者允許則可以使用,不允許就無法使用。類似的還有地圖類的應用申請使用GPS,語音類的應用申請使用麥克風(Audio)等。

Android M Runtime Permission的不同

對於棉花糖M以前的系統版本,app的許可權是在安裝時被授予的。Android 6.0以後,app將不會在安裝時被授予許可權,取而代之的是,app需要在執行時一個個的被詢問授予許可權,如下所示:

在box或者平板中的許可權申請圖:

在手機裝置中的許可權申請圖:

Android6.0之後的版本,應用要申請危險級別的許可權時,首先需要在Manifest中申請,然後需要在app內部中,如果對應的操作需要相應的許可權(比如訪問storage),則需要先向系統申請許可權,然後才能使用相應的操作,如果不處理,則會導致應用崩潰。

AndroidM+許可權申請實戰

關於Android M許可權的申請使用,這裡以使用聯絡人的許可權為例,新建app從許可權的介紹,許可權的宣告及使用詳細的進行分解,以下內容僅針對Android6.0進行探討,下文中不做特別說明。

SDK的配置細節

        1.開發工具建議使用android studio1.3以及更新的版本;

        2.SDK更新到API 23版本;

        3.新建工程時:

        注意選擇:“Phone and Tablet”;

        Minimum SDK 選擇: “API 23:Android 6.0(Marshmallow)

        4. 對於已經建立的工程要對build.gradle(Module:app)進行更新設定:

                a) compileSdkVersion 設定到23;

                b) targetSdkVersion 設定到23;

                c) compileSdkVersion設定到23;

許可權申請的一般流程如下:

許可權請求例項

1.首先需要在manifest中申請需要使用的許可權<uses-permission>標籤

2.其次在app程式碼中通過Context.checkSelfPermission(permission_name)API去檢測所要申請的許可權:

3.在回撥中處理使用者許可權申請結果

最後豁免的許可權會通過Settings持久化到runtime-permissions.xml中去

對於不在提醒的選項被勾選後,下次請求許可權,將不再彈出許可權請求的對話方塊,而是直接回調onRequestPermissionsResult方法,且回撥結果為使用者上一次的選擇。為了應對這種情況,系統提供了另一個API-shouldShowRequestPermissionRationale,這個函式的作用是幫助開發者找到需要向用戶額外解釋許可權的情況,這個函式的返回情況如下:

        1.      應用安裝後第一次訪問,直接返回false;

        2.      第一次請求許可權時,使用者拒絕了,下一次shouldShowRequestPermissionRationale()返回 true,這時候可以顯示一些為什麼需要這個許可權的說明;

        3.      第二次請求許可權時,使用者拒絕了,並選擇了“不再提醒”的選項時:shouldShowRequestPermissionRationale()返回 false;

        4.      裝置的系統設定中禁止當前應用獲取這個許可權的授權,shouldShowRequestPermissionRationale()將會返回false;

與許可權相關的PM命令

1. 讓app擁有許可權的安裝

            adb install -g <path to apk>

            例如:adb install –g xxxx.apk

很明顯用這個命令安裝apk在安裝時就直接賦予了app所有要申請的許可權,為安全著想僅建議開發除錯使用。

2. 允許/撤銷許可權

            允許某一許可權:

            adb shell pm grant <package_name><permission_name>

執行該命令後允許package_name程式的permission_name許可權,如:

            adb shell pm grant pkgNameandroid.permission.READ_EXTERNAL_STORAGE

注意:該命令僅開啟命令所要求開啟的許可權,對許可權組的許可權並不因為是許可權組的原因會連帶開起,即,當使用命令開啟了Contacts的寫許可權時,並沒有連帶將Contacts許可權組的其他的許可權也開啟了,這時候當你要呼叫讀許可權時,app由於沒有Contacts的讀許可權而crash。

撤銷某一許可權:

            adb shell pm revoke <package_name> <permission_name>

由之前許可權分析可知,我們的grant方法最終是將之前的mUserStates中的permissionState的mGranted狀態置為true,而revoke則是將其值設為false。