Android系統權限是建立在框架層上的一套權限解析分配和鑑權流程,其主要數據結構和校驗流程主要在pms(包管理服務)中實現。
簡單理解系統權限機制主要分爲權限解析、權限分配、鑑權這三個主要內容。
Sdk版本大於等於23後,新增了動態權限管理,讓Android系統權限管理更加靈活和自主。
下面來分析下Android 5.1上面的權限管理和鑑權過程。
權限的數據結構
涉及到系統權限的數據結構如圖7所示,分別來分析每個數據結構的內容和作用。
Ø BasePermission
系統權限的基本表示單元是BasePermission,Settings中維護了一個總的權限映射表mPermissions,所有的權限都會添加到mPermissions列表中,其中key是權限的名字,value是具體的BasePermission實例。
[/frameworks/base/services/core/java/com/android/server/pm/Settings.java]
// Mapping from permission names to info about them.
final ArrayMap<String, BasePermission> mPermissions = new ArrayMap<String, BasePermission>();
我們都知道絕大部分權限已經在系統中定義好,如READ_EXTERNAL_STORAGE讀寫外部存儲權限、READ_PHONE_STATE讀取手機狀態信息權限等,這些權限的定義都在framework-res.apk中,在掃描framework-res.apk過程中解析並添加到mPermissions映射表中。系統權限的定義:
[/frameworks/base/core/res/AndroidManifest.xml]
<!-- Allows an application to send SMS messages. -->
<permission android:name="android.permission.SEND_SMS"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="dangerous"
android:permissionFlags="costsMoney"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms" />
系統中絕大部分的權限定義都在frameworks/base/services/core/res/AndroidManifest.xml中,最終會打包進framework-res.apk中並經過解析保存到Settings的mPermissions映射表中。
Ø PackageParser.Permission
PackageParser.Permission在上面分析PackageParser解析apk過程中有提及過,解析apk的AndroidManifest.xml文件中的<permission>標籤後得到的權限表示。
PackageParser.Permission中包含一個對應的PermissionInfo。
Ø PermissionInfo
權限信息的表示,其中包含權限等級的定義(NORMAL, DANGER, SIGNERATURE),另外實現了序列化,用戶於進程間通信。每個BasePermission實例中包含一個PermissionInfo的實例。
以上三個類結構是系統權限部分的定義,下面四個類結構和pkg強相關,表示pkg的權限狀態。
Ø GrantedPermissions
表示一個pkg已經賦予的權限,類裏面定義了一個字符串列表grantedPermissions保存pkg已經被賦予的所有權限。
[/frameworks/base/services/core/java/com/android/server/pm/GrantedPermissions.java]
class GrantedPermissions {
int pkgFlags;
ArraySet<String> grantedPermissions = new ArraySet<String>();
Ø PackageSettingBase
PackageSettingBase繼承了GrantedPermissions類並添加更多和 pkg相關的信息,是一個pkg信息的基本表示類。PackageSettingBase保存爲了如pkg的codePath, resourcePath, signature等信息,同時PackageSettingBase是GrantedPermissions的子類,因爲也包含了pkg被賦予的權限列表。
Ø PackageSetting
PackageSetting繼承了PackageSettingBase類,並新增如PackageParser.Package和SharedUserSetting。那麼以後只要獲得一個pkg的PackageSetting實例,就可以獲得對應的SharedUserSetting實例,通過這個SharedUserSetting實例來獲得與其sharedUser的其他pkg信息。同時PackageSetting是GrantedPermissions的子類,那麼也就意味着只要拿到pkg的PackageSetting實例就可以知道pkg已經被賦予了哪些權限。
Ø SharedUserSetting
SharedUserSetting繼承了GrantedPermissions,類裏面保存了一個packages的列表,表示這個列表中的所有應用共享該uid。同時共享這個uid所賦予的所有權限。
權限管理
我們知道在pms構造方法中掃描apk後,創建對應的PackageSetting實例並賦予給pkg的mExtras,以後我們就可以通過pkg.mExtras獲得相應的PackageSetting實例。一開始掃描過程中,PackageSetting實例中保存的grantedPermissions列表爲空,權限的賦予會在pms構造方法的結束部分,通過調用updatePermissionsLPw方法遍歷所有已經安裝的pkg,一個一個的根據其請求的權限來確定是否賦予。
首先來看看updatePermissionsLPw方法:
[/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java]
private void updatePermissionsLPw(String changingPkg,PackageParser.Package pkgInfo, int flags) {
// Make sure there are no dangling permission trees.
// 更新permission trees列表,去除無效的permission tree
// Make sure all dynamic permissions have been assigned to a package,
// and make sure there are no dangling permissions.
// 更新permission列表,去除無效無主的permission定義
if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
for (PackageParser.Package pkg : mPackages.values()) {
if (pkg != pkgInfo) {
grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0,
changingPkg);
}
}
}
if (pkgInfo != null) {
grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0, changingPkg);
}
}
updatePermissionsLPw方法中主要做兩個事情,檢查無效的permissiontree和permission定義,另外一個就是根據傳入的flags是否帶有UPDATE_PERMISSIONS_ALL來遍歷mPackages包列表來更新所有安裝包的權限信息。重點分析grantPermissionsLPw方法。
[/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java]
private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, String packageOfInterest) {
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
ArraySet<String> origPermissions = gp.grantedPermissions;
boolean changedPermission = false;
/* 獲得解析pkg中請求的權限數量,循環一個一個去決定是否分配給該應用或者sharedUserId */
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i);
final boolean required = pkg.requestedPermissionsRequired.get(i);
final BasePermission bp = mSettings.mPermissions.get(name);
/* 檢查應用申請的這個權限是否存在有效,如果無效,忽略 */
if (bp == null || bp.packageSetting == null) {
if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
Slog.w(TAG, "Unknown permission " + name + " in package " + pkg.packageName);
}
continue;
}
final String perm = bp.name;
boolean allowed;
boolean allowedSig = false;
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
if (level == PermissionInfo.PROTECTION_NORMAL || level == PermissionInfo.PROTECTION_DANGEROUS) {
/* 權限等級爲NORMAL和DANGER的權限,只要申請就分配 */
allowed = (required || origPermissions.contains(perm)
|| (isSystemApp(ps) && !isUpdatedSystemApp(ps)));
} else if (bp.packageSetting == null) {
/* 無頭的權限,直接拒絕 */
allowed = false;
} else if (level == PermissionInfo.PROTECTION_SIGNATURE) {
/* 權限等級要求籤名的,檢查該pkg簽名是否和platform apk簽名一致,一致則返回 */
allowed = grantSignaturePermission(perm, pkg, bp, origPermissions);
if (allowed) { allowedSig = true; }
} else {
allowed = false;
}
if (allowed) {
if (allowed) {
if (!gp.grantedPermissions.contains(perm)) {
/* 權限允許賦予後,如果pkg已獲得權限列表中沒有,則添加 */
changedPermission = true;
gp.grantedPermissions.add(perm);
gp.gids = appendInts(gp.gids, bp.gids);
}
}
} else {
/* 權限不允許 */
if (gp.grantedPermissions.remove(perm)) {
/* 如果已獲取權限列表中存在去除的該權限,說明apk版本有變化 */
changedPermission = true;
gp.gids = removeInts(gp.gids, bp.gids);
}
}
}
}
grantPermissionsLPw方法中實現了5.1權限賦予的流程。主要流程首先獲得pkg的PackageSettings實例,上面我們知道pkg的PackageSettings實例中包含了應用已經獲得的權限列表grantedPermissions。第一次開機掃描應用的過程中,這個列表爲空。通過遍歷包解析後得到的requestPermissions列表的權限的等級來判斷是否授權,如果請求的權限授權成功後,就會保存進入grantedPermissions列表中。
grantedPermissions列表會根據應用的更新而更新。
權限管理的基本流程如上。
鑑權過程
一般客戶端調用checkPermission方法鑑權的流程如下:
①Context.java--->public int checkPermission(String permission, int pid, int uid)
②ActivityManagerService.java--->public int checkPermission(String permission, int pid, int uid)
③ActivityManagerService.java---> checkComponentPermission
④ActivityManager.java--->checkComponentPermission
Root用戶(uid=0)和System用戶(uid=1000)直接鑑權通過。
⑤PackageManagerService.java--->checkUidPermission
查看pms的checkUidPermission方法:
[/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java]
@Override
public int checkUidPermission(String permName, int uid) {
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
GrantedPermissions gp = (GrantedPermissions)obj;
if (gp.grantedPermissions.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null && perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
return PackageManager.PERMISSION_DENIED;
}
發現checkUidPermission方法中調用Settings的getUserIdLPr獲得一個Object實例,這個Object實例就是對應uid的pkgSettings實例,裏面包含grantedPermissions信息。最後查詢grantedPermissions列表中是否包含所要檢查的權限即可判斷該uid是否具有該權限。
繼續分析Settings的getUserIdLPr方法:
[/frameworks/base/services/core/java/com/android/server/pm/Settings.java]
public Object getUserIdLPr(int uid) {
if (uid >= Process.FIRST_APPLICATION_UID) {
final int N = mUserIds.size();
final int index = uid - Process.FIRST_APPLICATION_UID;
return index < N ? mUserIds.get(index) : null;
} else {
return mOtherUserIds.get(uid);
}
}
這個方法首先判斷檢查的uid是否是應用的uid(10000以上),如果是,通過uid來查詢mUserIds表來獲得對應uid的pkgSettings實例(mUserIds就是uid和pkgSettings的映射表)。通過進一步分析,mUserIds列表是在手機開機後讀取packages.xml緩存生成的。
鑑權流程2:
① 根據PMS解析的申請權限數據,對應用添加gid
② 啓動應用進程時,指定所有對應的gid
舉例:android.permission.WRITE_EXTERNAL_STORAGE
① 應用在AndroidManifest.xml中申請android.permission.WRITE_EXTERNAL_STORAGE
② 安裝後,PMS解析該應用,併爲該應用添加sdcard_rw的組ID。
③ 啓動該應用進程時,使用上面解析的gid。
下一章我們來將證書的校驗流程