你真的了解Android權限機制嗎?

2021-02-13 碼個蛋

2. 框架層

因為 Android 6.0 之前組件不能在運行時改變權限,所以系統的權限檢查執行過程是靜態的。這個情況下,組件的角色和權限的等安全屬性會被放置在元數據中,即 AndroidManifest.xml 文件中,而不是組件的本身。系統包管理器會負責記錄組件的權限,所以靜態權限檢查可以從包管理器拿到權限,由運行環境或容器來執行權限檢查,這樣子可以把業務邏輯和安全決策分離開來,但是靈活性不足。

那 Android 組件可不可以不預先聲明權限在 AndroidManifest.xml 中呢?答案是:可以的。Android 的動態權限執行,可以讓組件自身執行權限檢查,而不是運行環境。

所以接下來我們將深入了解框架層的動態和靜態權限執行的原理。

動態權限執行

動態權限執行,最典型的場景,就是 IPC。Android 的核心系統服務統一會註冊到服務管理器,任何應用,只要知道服務的註冊名稱,就可以拿到對應的 Binder引用,就可使用 Binder IPC 機制調用服務。因為 Binder 沒有內置的訪問控制機制,所以每個系統服務需要自己實現訪問控制機制。

系統服務可以直接檢查調用者的 UID,通過限定 UID 來控制訪問權限,這種方式簡單直接,但是對於非固定UID的應用,就比較棘手了。而且大部分服務,並不關心調用者的 UID,只需要檢查調用者是否被賦予特定的權限即可。所以這種方式,比較適合只允許以 root(UID:0) 或 system(UID:1000) 運行的進程訪問的服務檢查。

那換一種方式,服務怎麼拿到調用者的權限列表?我們知道,大部分 UID 都是和包一一對應的,除了共享 UID。(共享 UID 後面再詳細解釋)

使用 Binder.getCallingUid() 和 Binder.getCallingPid() 獲取調用者的 UID 和 PID,通過 UID 在包管理器中查詢到對應應用的權限。android.content.Context 類中就有 checkPermission(String permission, int pid, int uid) 方法。實質上會調用到 PMS 中的 checkUidPermission(String perName, int uid),如下:

Android 6.0 以下 PMS 中的 checkUidPermission(String perName, int uid)

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 {
HashSet<String> perms = mSystemPermissions.get(uid);
if (perms != null && perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
return PackageManager.PERMISSION_DENIED;
}

Android 6.0 以下的 checkUidPermission() 方法比較簡單,首先,基於入參 uid 獲取應用的 appId,拿到權限列表對象(也就是 packages.xml 裡的 <package> 映射),如果 GrantedPermissions 類中的 grantedPermissions 集合包含目標權限,則檢查通過。

如果沒有該 GrantedPermissions 對象,則檢查目標權限是否可以被自動授予,實際上 mSystemPermissions 就是 platform.xml 文件中的 <assing-permission> 標籤映射緩存,記錄了一些系統級應用的 uid 對應的 permission。例:

...
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
...

Android 6.0 及以上 PMS 中的 checkUidPermission(String perName, int uid)

@Override
public int checkUidPermission(String permName, int uid) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}

synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
...
final SettingBase settingBase = (SettingBase) obj;
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
BasePermission bp = mSettings.mPermissions.get(permName);
if (bp != null && bp.isInstant()) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
...
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
...
}
}
}
return PackageManager.PERMISSION_DENIED;
}

可以注意到,6.0 之後 checkPermission() 方法有所改變。多了從  mSettings.mPermissions 去查詢權限列表。關鍵就在於這個 mSettings 裡面保存的這個 SettingBase 對象,它記錄了 PermissionsState 也就是權限的授予情況。


public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);

if (mPermissions == null) {
return false;
}

PermissionData permissionData = mPermissions.get(name);
return permissionData != null && permissionData.isGranted(userId);
}

所以檢查權限的流程是本來就有的,6.0 之後差異僅在於:危險級別權限可以動態修改授權情況,也就是修改 PermissionState 的 mGranted 值,所以每次權限執行,都會查詢下 mGranted 值。

靜態權限執行

靜態權限執行的典型場景,是跨應用組件交互。我們使用隱式 Intent 來表達意圖,搜索匹配的組件,如果有多個,彈出選擇框,目標組件被選定後,會由 ActivityManagerService 執行權限檢查,檢查目標組件是否有相應的權限要求,如果有,則把權限檢查的工作交給 PMS,去檢查調用者有沒有被授權這些權限。

接下來的總體的流程和動態執行流程大致相同:Binder.getCallingUid()和Binder.getCallingPid()獲取調用者的 UID 和 PID,然後利用 UID 映射包名,再獲得相關權限集合。如果權限集合中含有所需權限即啟動,否則拋出 SecurityException 異常。靜態權限執行這裡,我們可以詳細了解下,每種組件的權限檢查時機和具體順序是怎麼樣的。

組件權限執行

思考一下,什麼時候會執行對調用者的權限檢查?那肯定是在目標組件被調用的時候,去解析目標組件聲明的權限,如果有,就執行權限檢查。

Activity 和 Service。Activity 顯而易見,會在 startActivity() 和 startActivityForResult() 裡解析到聲明權限的 Activity 時,就執行權限檢查。而 Service startService()、stopService() 和 bindService(),這 3 個方法被調用時都會進行權限檢查。

廣播。我們注意到,發送廣播除了常用的 sendBroadcast(Intent intent),還有個 sendBroadcast(Intent intent, String receiverPermission),該方法可以要求廣播接受者具備特定的權限,但是,調用 sendBroadcast 是不會進行權限檢查的,因為廣播是異步的,所以權限檢查會在 intent 傳遞到已註冊的廣播接受者時進行,如果接收者不具備特定的權限,則不會接收到該廣播,也不會收到 SecurityException 異常。

反過來,接收者可以要求廣播發送者必須具備的權限,所要求的權限在 manifest 文件中設置 <receiver> 標籤的 permission 屬性,或者動態註冊時指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),權限檢查也是在廣播傳遞時執行。

所以,收發廣播可以分開指定權限。值得一提的是,一些系統廣播被聲明為 protected,並且只能由系統進程發送,比如 PACKAGE_INSTALLED。只能由系統進程發送,這個限制會在內核層進行檢查,對調用者的 UID 進行匹配,只能是 SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果其他 UID 的進程試圖發送系統廣播,則會收到 SecurityException 異常。

想了解所有的系統廣播,可以打開/system/framework/framework-res.apk 中的 AndroidManifest.xml <protected-broadcast> 標籤詳細了解。

ContentProvider。ContentProvider 可以為讀寫分別指定不同的權限,即:調用目標 provider、query() 方法 和 insert()、update()、delete() 都會進行權限檢查。

綜上所述,Android 的權限的檢查會在各個層次上實施。

高層的組件,例如應用和系統服務,通過包管理器查詢應用程式被賦予的權限,並決定是否準予訪問。

低層的組件,通常不訪問包管理器,比如本地守護進程,依賴於進程的 UID、GID 和補充 GID 來決定賦予。

訪問系統資源時,如設備文件、UNIX 域套接字和網絡套接字,則由內核根據所有者、目標資源的訪問權限和訪問進程的進程屬性或者 packages.list 來進行控制。

最後簡單說下共享 UID,填一下前面挖的坑。雖說 Android 會為每一個應用分配唯一的 UID,但如果應用使用相同的密鑰籤發,就可以使用相同 UID 運行,也就是運行在同一個進程中。

這個特性被系統應用和核心框架服務廣泛使用,比如:Google Play 和 Google 定位服務,請求同一進程內的 Google 登錄服務,從而達到靜默自動同步用戶數據的體驗。

值得注意的是:Android 不支持將一個已安裝的應用,從非共享 UID 切換到共享狀態,因為改變了已安裝應用的 UID,會導致應用失去對自己文件的訪問權限(在一些早期 Android 版本中),所以如果使用共享 UID 必須從一開始就設計好。

相關焦點

  • Android權限機制,你真的了解嗎?
    有調查表明,惡意軟體的數量在持續的上升,Google在Android安全機制上面也做了很多工作,並且一直在持續的更新,其Android的安全模型由3個部分組成:Linux安全機制、Android本地庫及運行環境安全與Android特有的安全機制,如下圖:
  • Android 自定義權限真的安全嗎?
    今天我們聊聊 Android 的自定義權限的安全問題。如果你對 Android 的權限機制尚覺陌生,不妨先看看官方 API 指南的這一節: Android 的權限機制 (http://developer.android.com/training/articles/security-tips.html)。
  • Android權限機制與適配經驗
    一、概要Android M已經發布一段時間了,市面上很多應用都已經適配Android M。權限機制,作為Android M的一大特性,受到了很多開發者的關注。本文主要分享了以下幾個知識點的內容,1、Android權限機制關鍵知識點;2、QQ音樂對於權限的適配經驗;3、近段時間以來遇到的一些Android權限方面的問題。OK,下面進入主題。二、Android權限機制已經了解過基本知識的,建議直接跳到第三點(QQ音樂的權限適配經驗)。
  • Android消息機制,你真的了解Handler嗎?
    兇殘的程式設計師 的博客地址:http://blog.csdn.net/qian520aoAndroid 的消息機制主要是指 Handler 的運行機制,對於大家來說 Handler 已經是輕車熟路了,可是真的掌握了 Handler?
  • 【Android 】你了解嗎?
    com.android.browsercom.android.calculator2com.android.calendarcom.android.cameracom.android.certinstallercom.android.classiccom.android.contactscom.android.customlocale2
  • 一次 Android 權限刪除經歷
    2.初步定位 首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest 文件並不是app下的該文件,從 android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的 manifest文件是由多個menifest文件合併而來的
  • 一次Android權限刪除經歷
    2.初步定位首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest文件並不是app下的該文件,從android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的manifest文件是由多個menifest文件合併而來的,其合併順序如下
  • Android 面試:用廣播 BroadcastReceiver 更新 UI 界面真的好嗎?
    " | "false"]  android:icon="drawable resource"  android:label="string resource"  //繼承 BroadcastReceiver 子類的類名  android:name=".mBroadcastReceiver"  //具有相應權限的廣播發送者發送的廣播才能被此 BroadcastReceiver 所接收;  android
  • 你真的了解weight和weightSum嗎?
    本篇來自 胡蘿蔔小兔 的投稿,給大家分析了 weight 和 weightSum 定義以及使用方法,讓你了解更加細節的方面。那麼問題來了,這兩個方式有什麼區別嗎? 眼尖的童鞋立馬會說:當然有區別,一個 android:layout_width 是 0, 另一個是 wrap_content。 那麼這兩個有什麼區別嗎? 為什麼實現的效果是一樣的? 我還要問一句,真的是一樣嗎?
  • Android 12 適配你準備好了嗎?
    閃屏動畫UI 大大們會滿足於默認的的閃屏頁嗎?顯然不會!簡單介紹一下這個動畫的機制,首先動畫中的元素由 Android 清單中的 xml 資源文件定義,每個元素都有深色模式和淺色模式版本。在 Android 12 中,用戶的隱私政策變得越來越嚴格,比如,當我們在使用相機或者麥克風權限的時候,屏幕的右上角會提示一個綠色的點,提醒用戶,該 App 正在使用具體的權限。
  • Android UI 渲染機制的演進,你需要了解什麼?
    每個開發者都希望自己的應用都可以做到 60 fps 如絲般順滑,在了解 Android 的渲染之前,需要先了解下 Android 圖形系統的整體架構,以及它包含的主要組件。關於 Android 的渲染,大家肯定聽說過每秒 60 幀和 16ms 的限制問題,你是否有想過為什麼是這些數字?如果你是對於性能要求較高的開發者,這樣的技術細節是非常值得深入了解的。
  • Android 6.0以前國產手機權限處理
    Android是在6.0加入的權限機制,但是不少國產手機比如華為小米等,在6.0之前的設備已經在設置裡面有權限開關。調皮的某個用戶會關掉這個權限,然後去拍照什麼的,直接崩掉了。之前大牛郭霖在csdn做了一期權限機制的教學直播,結束後我問了他這個問題,他的回答是簡單try catch即可無需深究。那我想結合實際項目研究下這個問題。
  • Android動態權限詳解
    主要一條修改為,隱私提示與權限獲取順序。修改測試過程中,發覺部分同學對Android權限相關知識和歷史並不了解,就此疫情期間忙裡偷閒,整理些東西供參閱。首先,從一張圖開始此文。-- PHONE_STATE權限--> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!
  • Android應用權限大全
    訪問登記屬性android.permission.ACCESS_CHECKIN_PROPERTIES ,讀取或寫入登記check-in資料庫屬性表的權限獲取錯略位置android.permission.ACCESS_COARSE_LOCATION,通過WiFi或移動基站的方式獲取用戶錯略的經緯度信息
  • 你真的了解AWS標籤嗎?
    點擊上面藍色字體關注微信公眾號Δ你真的了解AWS標籤嗎?
  • 帶你了解Android窗口機制Window、PhoneWindow和DecorView之間的關係
    常用的activity、dialog、Toast等都是通過通過創建window、PhoneWindow來實現,所以其實window我們一直都見到,只是不知道那就是window。了解window的機制原理,可以更好地了解window,進而更好地了解android是怎麼管理屏幕上的view。
  • 你真的了解Handler嗎
    那麼你是否了解「IdleHandler,同步屏障,死循環」的設計原理?以及由Handler機制衍生的「IntentService,BlockCanary」?這次我們說下Android中最常見的Handler,通過解析面試點或者知識點,帶你領略Handler內部的神奇之處。
  • Android 6.0 運行時權限處理
    >又新增了運行時權限動態檢測,以下權限都需要在運行時判斷:身體傳感器日曆攝像頭通訊錄地理位置麥克風電話簡訊存儲空間運行時權限處理Android6.0系統默認為targetSdkVersion小於23的應用默認授予了所申請的所有權限,所以如果你以前的APP設置的targetSdkVersion低於23,在運行時也不會崩潰,但這也只是一個臨時的救急策略
  • 五種控制Android應用的權限的方法
    這篇文章目的在於介紹Android系統上控制權限的方法,讀者只要使用過Android,或是對智能機平臺有所了解
  • List的擴容機制,你真的明白嗎?
    講故事在前一篇大內存排查中,我們看到了Dictionary正在做擴容操作,當時這個字典的count=251w,你把字典玩的66飛起,其實都是底層為你負重前行,比如其中的擴容機制,當你遇到幾百萬甚至千萬的大集合這個擴容機制還真的需要挖一下,免的入戲太深,難以自拔。為了方便講述,我準備從List說起,因為它最簡單哈😁😁😁二:List擴容機制1.