1. 程式人生 > >Android許可權機制,你真的瞭解嗎?

Android許可權機制,你真的瞭解嗎?

一、Android的許可權機制

Android是目前最流行的智慧手機軟體平臺之一,在智慧移動終端如火如荼發展的同時,其安全態勢也日益嚴峻。有調查表明,惡意軟體的數量在持續的上升,Google在Android安全機制上面也做了很多工作,並且一直在持續的更新,其Android的安全模型由3個部分組成:Linux安全機制、Android本地庫及執行環境安全與Android特有的安全機制,如下圖:

  

本文只涉及到其中的許可權機制介紹,其他的部分如果有感興趣的,我們可以後續一起探討。

Android的許可權管理遵循的是“最小特權原則”,即所有的Android應用程式都被賦予了最小許可權。一個Android應用程式如果沒有宣告任何許可權,就沒有任何特權。因此,應用程式如果想訪問其他檔案、資料和資源就必須在AndroidManifest.xml檔案中進行宣告,以所宣告的許可權去訪問這些資源。否則,如果缺少必要的許可權,由於沙箱的保護,這些應用程式將不能夠正常提供所期望的功能與服務。

所有應用程式對許可權的申請和宣告都被強制標識於AndroidManifest.xml檔案之中,通過,,等標籤指定。如果需要申請某個許可權,可以通過指定。應用程式申請的許可權在安裝時提示給使用者,使用者可以根據自身需求和隱私保護決定是否允許對該應用程式授權。

二、許可權基本知識

2.1 許可權的類別

由於基於Linux核心,Android系統中的許可權分為以下3類。

(1)Android手機所有者許可權

這個和廠商相關,可以理解為系統許可權。

(2)Android ROOT許可權

類似於Linux,這是Android系統中的最高許可權。如果擁有該許可權,就可以對Android系統中的任何檔案、資料、資源進行任意操作。所謂“越獄”,就是令使用者獲得最高的ROOT許可權。

(3)Android應用程式許可權

該許可權在AndroidManifest檔案中由程式開發者宣告,在程式安裝時由使用者授權,共有下述4類不同的許可權保護級別(Protection Level)。

2.2 Protection level

我們經常在AndroidManifest中使用許可權,如果我們想讓應用程式可以發簡訊,那麼應該這樣寫:

其許可權的定義是在frameworks/base/core/res/AndroidManifest.xml中,如下:

android:permissionGroup="android.permission-group.COST_MONEY" 

android:protectionLevel="dangerous" 

android:label="@string/permlab_sendSms" 

android:description="@string/permdesc_sendSms" /> 

這個XML可以認為是系統APK使用的AndroidManifest.xml,該APK使用系統的私鑰進行簽名。

下面分別簡單介紹下各個標籤的含義:

android:name:許可權的名字,uses-permisson使用的。

android:permissionGroup:許可權的分類,在提示使用者安裝時會把某些功能差不多的許可權放到一類。

android:protectionLeve:分為Normal、Dangerous、Signature、SignatureOrSystem。

android:label:提示給使用者的許可權名。

android:description:提示給使用者的許可權描述。

其中android:protectionLevel各個屬性說明如下:

(1)Normal

風險較低的許可權,任何應用都可以申請,在安裝應用時,不會直接提示給使用者,點選全部才會展示。

(2)Dangerous

風險較高的許可權,任何應用都可以申請,安裝時需要使用者確認才能使用。

(3)Signature

僅當申請該許可權的應用程式與宣告該許可權的程式使用相同的簽名時,才賦予該許可權。

(4)SignatureOrSystem

僅當申請該許可權的應用程式位於相同的Android系統映象中,或申請該許可權的應用程式與宣告該許可權的程式使用相同的簽名時,才賦予該許可權。

可以這樣理解:

1)和該APK(定義了這個許可權的APK)用相同的私鑰簽名的應用。

2)在/system/app目錄下的應用。

2.3 程序的許可權表現

Android是一個多程序系統,在這個系統中,應用程式會在自己的程序中執行,系統和應用之間的安全性是通過Linux程序級別來強制實現的,會給應用程式分配userID和GroupID。

比如我們檢視qqdownload這個程序,adb shell後檢視下其程序id(紅色部分):

根據id,執行下檢視狀態,如下:

 

我們關注如下三行:

Uid:10301   10301   10301   10301

Gid:10301   10301   10301   10301

Groups:1006 1013 1015 1028 3001 3002 3003 9997 50301

這裡我們便看到了系統程序的許可權配置資訊,這裡的數字具體代表意義,可以在Android

\system\core\include\private\android_filesystem_config.h裡面看到,其部分內容如下:

#define AID_ROOT             0  /* traditional unix root user */

#define AID_SYSTEM        1000  /* system server */

#define AID_RADIO         1001  /* telephony subsystem, RIL */

#define AID_BLUETOOTH     1002  /* bluetooth subsystem */

#define AID_GRAPHICS      1003  /* graphics devices */

#define AID_INPUT         1004  /* input devices */

#define AID_AUDIO         1005  /* audio devices */

#define AID_CAMERA        1006  /* camera devices */

#define AID_LOG           1007  /* log devices */

#define AID_COMPASS       1008  /* compass device */

#define AID_MOUNT         1009  /* mountd socket */

#define AID_WIFI          1010  /* wifi subsystem */

#define AID_ADB           1011  /* android debug bridge (adbd) */

#define AID_INSTALL       1012  /* group for installing packages */

#define AID_MEDIA         1013  /* mediaserver process */

...

其中qqdownload對應的Groups描述如下:

1006 camera devices

1013 mediaserver process

1015 external storage write access

1028 external storage read access

3001 bluetooth: create any socket

3002 bluetooth: create sco,rfcomm or l2cap sockets

3003 can create AF_INET and AF_INET6 sockets

9997 shared between all apps in the same profile

50301 start of gids for apps in each user to share

不同的手機這個資訊顯示的不一樣,這個是vivo手機的資訊,在華為手機上只有3001 3002 3003 9997 50689。

我們拿其中一個寫SD卡的許可權來簡單說明一下:

寫SD卡許可權是

我們看一個重要的檔案,frameworks\base\data\etc\Platform.xml裡的內容:

在看下圖:

對於申請了WRITE_EXTERNAL_STORAGE特權的應用,該應用的程序的gids就包含了sdcard_rw,就可以對sd卡中的檔案進行操作了。

再看Android_filesystem_config.h的原始碼:

#define AID_SDCARD_RW 1015 /* external storage write access */ 

我們看到qqdownload程序的Groups裡面有gid為1015,1015就是對應sdcard_rw。

以上介紹了程序的許可權表現,實際工作中,我們可能不需要關注這些。

2.4 Android 系統對應用程式許可權申請的處理方式分析

Android系統對應用程式授權申請的處理流程:

(1)進入處理應用程式授權申請的入口函式;

(2)系統從被安裝應用程式的AndroidManifest.xml檔案中獲取該應用正常執行需申請的許可權列表;

(3)顯示對話方塊,請求使用者確認是否滿足這些許可權需求;

若同意,則應用程式正常安裝,並被賦予相應的許可權;若否定,則應用程式不被安裝。系統僅提供給使用者選擇“是”或者“否”的權利,沒有選擇其中某些許可權進行授權的權利。

具體如下圖:

2.5 Android原生許可權管理:AppOps

2.5.1 AppOps 簡介

AppOps全稱是Application Operations,類似我們平時常說的應用程式的操作許可權管理,AppOps是Google原生Android包含的功能,但是Google在每次版本更新時都會隱藏掉AppOps的入口,Google高管Hiroshi Lockheimer的原話:“App ops釋出的時機不太對頭,我們需要全面解決問題,而不是單獨地釋出Appops”。

在今年的GoogleIO大會上,Google透露AndroidM(Android 6.0)會加入Application Permission Manage的功能,該功能應該就是基於AppOps實現的。

注意:AppOps雖然涵蓋了App的許可權管理,但是Google原生的設計並不僅僅是對“許可權”的管理,而是對App的“動作”的管理。我們平時講的許可權管理多是針對具體的許可權(App開發者在Manifest裡申請的許可權),而AppOps所管理的是所有可能涉及使用者隱私和安全的操作,包括access notification,keep weak lock,activate vpn,display toast等等,有些操作是不需要Manifest裡申請許可權的。

2.5.2 功能效果

Setting UI:

AppOps的許可權設定是在系統的Settings App裡,Settings->Security->AppOps。

點選某一app,可以檢視該app的許可權管理詳情,也可以設定顯示。

使用效果:

AppOps預設給使用者提供了兩個設定選項:

允許該項許可權/禁止該項許可權

而其實程式碼邏輯裡,有三種可選項:

允許/禁止/提示

使用者選擇“提示”選項,則該app在執行這一操作時,系統會給使用者相應的提示,待使用者選擇後app繼續執行。

2.5.3 AppOps總體概覽

核心服務:AppOpsService

系統服務,系統啟動時該服務會啟動執行。

參考以下ActivityManagerService.java,ActivityManagerService啟動過程中:

配置檔案:appops.xml、appops_policy.xml

Appops.xml位於/data/system/目錄下,儲存各個app的許可權設定和操作資訊。

Appops_policy.xml位於/system/etc/目錄下,該檔案只在appops strict mode enable時才會存在和使用。

API介面:AppOpsManager

AppOpsService實現了大部分的核心功能邏輯,但它不能被其他模組直接呼叫訪問,而是通過AppOpsManager提供訪問介面。

UI層:AppOpsSummary,AppOpsCategory等

上傳UI顯示以及基本邏輯處理。

2.5.4 結構圖

AppOps整體的工作框架基本如下:

 

Setting UI通過AppOpsManager與AppOpsService互動,給使用者提供入口管理各個app的操作。

AppOpsService具體處理使用者的各項設定,使用者的設定項儲存在/data/system/appops.xml檔案中。

AppOpsService也會被注入到各個相關的系統服務中,進行許可權操作的檢驗。

各個許可權操作對應的系統服務(比如定位相關的Location Service,Audio相關的Audio Service等)中注入AppOpsService的判斷。如果使用者做了相應的設定,那麼這些系統服務就要做出相應的處理。

(比如,LocationManagerSerivce的定位相關介面在實現時,會有判斷呼叫該介面的app是否被使用者設定成禁止該操作,如果有該設定,就不會繼續進行定位。)

2.5.5 相關API介面

儘管在Android SDK裡能夠看到部分AppOps的API介面,但是Google對此解釋的很清楚:

This API is not generally intended for third party application developers; most features are only available to system applications。Obtain an instance of it throughContext.getSystemService withContext.APP_OPS_SERVICE。

即是說,這些API不是讓第三方app使用的,而是供系統應用呼叫的。

使用Android SDK開發應用,如果要呼叫這些API的話,也會編譯不通過。

但是想使用的話,可以嘗試把Android原始碼裡AppOpsManager.java打包一下,把jar包匯入自己的工程,就可以使用了。

部分重要的API介面如下:

void setMode(int code,int uid,String packageName,int mode)

這個是我們最需要的方法,改變app的許可權設定,但偏偏被google隱藏了。

code代表具體的操作許可權,mode代表要更改成的型別(允許/禁止/提示)

正常情況下(如果OEM廠商沒有做特殊處理),把AppOpsManager.java打包,引入jar包到工程內,是可以使用上述API介面的,

也即是可以自行設計UI,提供入口來改變app許可權。 

具體許可權對應的code,可以檢視AppOpsManager.java原始碼裡的描述。

三、許可權變化趨勢

Android M之前,應用的許可權請求是在安裝時提示,確認後許可權就會擁有。

但Android M出來後,將這個許可權在執行時做了進一步的檢查,使用者隨時可拒絕許可權。

● 從平臺角度看:Android許可權集不斷擴充套件,但不是以提供更細粒度的許可權為目標,而是為訪問新的硬體功能提供安全保障。特別地,Dangerous許可權集的數量也在不斷增多;

● 從第三方應用和預裝應用角度看:大量應用並未遵守最小特權原則,而是存在大量過多申請許可權的情形。值得注意的是:許多預裝應用使用大量高級別的許可權,帶來很大的安全隱患。

使用者只有通過不斷學習,充分理解新加入的許可權說明,才能在安裝軟體時從Android許可權警告中獲取足夠的資訊,從而做出正確的決定。鑑於Android系統每隔數月就有較大的版權更新,並引入較多新的許可權,這為使用者提出了很高要求。

四、Android M變化以及帶來的影響

從Android6.0(API LEVEL23)開始,使用者對應用許可權進行授權是發生在應用執行時,而不是在安裝時。這樣可以讓使用者在安裝時節省時間,而且可以更方便的控制應用的許可權(至少許可權管理不需要ROOT了)。使用者可以按照對應用的需求來控制應用的許可權,比如百度地圖的聯絡人許可權。同時使用者也可以在應用程式設定中撤銷對應用的許可權授權。

Android系統中的許可權被劃分為兩類:普通許可權和敏感許可權(更多普通許可權、敏感許可權及許可權組資訊:

普通許可權不會涉及到使用者隱私,如果應用在manifest檔案中直接聲明瞭普通許可權,系統會自動授予許可權給應用。比如:網路INTERNET、藍芽BLUETOOTH、震動VIBRATE等許可權。

敏感許可權則要獲取到一些使用者私密的資訊。如果你的應用需要獲取敏感許可權,首先需要獲取使用者的授權。比如:相機CAMERA、聯絡人CONTACTS、儲存裝置STORAGE。

https://developer.android.com/reference/android/Manifest.permission.html

詳情見上面這個連結,每個許可權裡面可能會有Protection level,標記著是dangerous還是normal

在Android的各個版本中,不論是普通許可權還是敏感許可權,都需要在manifest檔案中宣告,例如許可權宣告。然而,在不同版本的作業系統或不同的target SDK level中的結果是不同的。

如果裝置執行Android5.1或者更低版本的作業系統,或者你的目標SDK版本號小於或等於22,當你在manifest檔案中請求了一些許可權,使用者必須在安裝過程時授予全部許可權,否則應用不能正常安裝。

如果裝置執行在Android6.0或者更高版本,並且目標SDK版本號大於或等於23,應用程式必須要在manifest檔案中宣告需要的許可權,當程式執行時,它必須要向用戶請求授權每個所需的敏感許可權。使用者可以允許或拒絕每個許可權,並且程式可以依賴使用者已經授權的許可權繼續執行。(這裡可能比較繞,舉個例子:假設你的APP需要聯絡人和拍照許可權,在請求許可權時使用者只授予了聯絡人許可權,那麼當前程式可以正常執行並獲取聯絡人資訊,但是無法進行拍照)

注:本篇文章講解如何在API level 23或更高版本並且裝置版本為Android6.0或者更高。如果APP的targetSDKVersion為22 或者更低,系統會在安裝或者更新程式時提示使用者授權所有敏感許可權。

這裡介紹下幾個常量:

targetSdkVersion:是在程式執行的時候起作用,用於提高指定版本的裝置上程式執行體驗。

minSdkVersion和maxSdkVersion:是在程式安裝的時候起作用,用於指定哪些版本的裝置可以安裝此應用。

targetAPIleve:是在編譯的時候起作用,用於指定使用哪個API版本(SDK版本)進行編譯。

4.1 PROTECTION_NORMAL類許可權

當用戶安裝或更新應用時,系統將授予應用所請求的屬於PROTECTION_NORMAL的所有許可權(安裝時授權的一類基本許可權),這類許可權包括:

只需要在AndroidManifest.xml中簡單宣告這些許可權就好,安裝時就授權。不需要每次使用時都檢查許可權,而且使用者不能取消以上授權。

4.2 許可權組

許可權被分組了,如下表:

同一組的任何一個許可權被授權了,其他許可權也自動被授權。例如,一旦WRITE_CONTACTS被授權了,APP也有READ_CONTACTS和GET_ACCOUNTSG許可權了。

4.3 檢查許可權

如果你的程式需要敏感許可權,那麼你必須在每次呼叫需要該許可權的方法時都需要檢查許可權。因為使用者隨時都可能會對你程式的某些許可權取消授權,所以即使你的應用昨天使用過相機,你也無法確定今天是否還有這個許可權。

你可以通過ContextCompat.checkSelfPermission()方法來驗證你的應用是否擁有某個許可權。比如,下面的程式碼段是檢查是否有擁有寫日曆許可權:

如果該應用已經獲取到該許可權,該方法返回PackageManager.PERMISSION_GRANTED並且程式可以繼續執行。如果該應用未被授予該許可權,這個方法會返回PREMISSION_DENIED,同時應用需要明確提示使用者該應用所需要的許可權。

4.4 請求許可權

如果你的應用需要敏感許可權並且這些敏感許可權已經在manifestm檔案中宣告,一定要詢問使用者獲取許可權。Android系統提供了幾種請求許可權的方法。呼叫這些方法後,系統會彈出一些Dialog(無需使用者自定義)。

4.5 解釋需要許可權的原因

在一些應用場景下,你可能想要讓使用者知道需要獲取某個許可權的原因。例如,如果使用者使用相簿應用,使用者可能會理解這個應用會需要相機許可權,但是使用者可能不會理解為什麼相簿應用還需要獲取位置或者聯絡人。在你請求獲取許可權之前,你應該考慮提示使用者。切記不要使用大量解釋;如果你解釋的內容過多,使用者可能會覺得你的應用比較煩人,可能會解除安裝你的應用…(這段翻譯可能有點問題…)

如果你需要的許可權已經被使用者拒絕過一次許可權請求,當用戶再次使用需要獲取許可權的功能時,應用程式最好向使用者解釋需要對應許可權的原因。因為如果使用者一直嘗試使用需要許可權的功能,卻一直沒給為該功能對應的許可權,說明使用者還沒有明白為什麼應用程式需要這個許可權來實現這個功能。在這種情況下可能需要提示使用者需要許可權的原因。

Android 系統提供了shouldShowRequestPermissionRationale()方法來幫助開發者判斷是否需要向用戶解釋需要許可權的原因。當某條許可權之前已經請求過,並且使用者已經拒絕了該許可權時,shouldShowRequestPermissionRationale ()方法返回的是true。

注意:如果使用者拒絕某條許可權,並且在提示授權的視窗中勾選了不再提示選項時,shouldShowRequestPermissionRationale ()的返回值為false。當某些裝置禁止應用程式獲取某些許可權時,shouldShowRequestPermissionRationale ()也會返回false。

4.6 向用戶請求獲取應用程式需要的許可權

如果你的應用程式沒有獲取到它需要的許可權,那麼應用程式需要呼叫該許可權對應的requestPermissions()方法,呼叫requestPermissions()方法時需要傳入一個請求碼(requestCode),這時系統會彈出一個對話方塊讓使用者選擇是否授權,使用者選擇後,在回撥方法onRequestPermissionsResult()中返回對應的請求碼(requestCode)和授權結果。

下面這段程式碼檢查應用程式是否有讀聯絡人許可權,在未獲取讀聯絡人授權時請求獲取該許可權(完整示例見Android_M_Permission):

 

注意:當應用程式呼叫requestPermissions()方法時,系統會彈出一個對話方塊給使用者。應用程式不能設定或更改該對話方塊,如果應用程式需要提供一些資訊或者向用戶解釋,需要在呼叫requestPermissions()方法之前。

4.7 處理請求許可權的結果

當應用程式請求獲取許可權時,系統會彈出一個對話方塊給使用者。當用戶點選某個選項時,系統會呼叫onRequestPermissionResult()方法來傳遞使用者的選擇結果。應用程式需要重寫onRequestPermissionsResult()方法來判斷使用者是否對相應許可權授權。。這個回撥方法會傳遞一個與requestPermission()方法相同的requestCode。例如,應用程式請求READ_CONTACTS方法,它將會有如下的回撥方法:

授權的對話方塊顯示的是系統描述的許可權組(permission group),它沒有顯示列出詳細的許可權列表。比如,如果你請求READ_CONTACTS許可權,系統對話方塊只會提示使用者應用程式需要獲取聯絡人許可權,使用者只需要給每個許可權組授權一次。如果應用程式請求獲取一個許可權組的其他許可權(在manifest檔案中宣告的許可權),系統會自動授予該許可權。當你請求這個許可權時,系統會呼叫onRequestPermissionResult(),回撥方法並且傳遞PERMISSION_GRANTED,這跟使用者在彈窗中點選授予許可權的按鈕的流程是相同的。

注意:應用程式還是需要明確的請求它所需要的每個許可權,即使使用者已經授予了跟這個許可權在同一個permission group的其他許可權。除此之外,對某個許可權組的授權可能會改變。程式的程式碼不能依賴於使用者已經對某個許可權組授權的假設。

例如,應用程式在manifest 檔案用聲明瞭READ_CONTACTS和WRITE_CONTACTS許可權,如果應用程式請求了READ_CONTACTS許可權並且使用者授予了該許可權,那麼當應用程式請求WRITE_CONTACTS許可權時,系統會自動授予應用程式該許可權。

譯者注:READ_CONTACTS和WRITE_CONTACTS都屬於CONTACTS許可權組。更多關於許可權組資訊可以訪問permission group或直接看我的截圖:許可權和許可權組

如果使用者拒絕了一個應用許可權請求,那麼應用程式應該進行適當的操作。例如:應用程式可以彈出一個對話方塊來解釋為什麼使用者不能執行需要該許可權的操作。

當系統提示使用者給應用程式授權許可權時,會給使用者提供一個不再提示的選項來通知系統不再針對該許可權進行詢問。使用者勾選該選項後,當應用程式請求獲取對應許可權時,系統會立即拒絕授權。系統會呼叫onRequestPermissionResult()回撥方法並且傳遞PERMISSION_DENIED引數,就像使用者拒絕授權一樣。這意味著,當你呼叫requestPermissions()方法時,你不能假定應用程式會跟使用者直接互動。