「Android 安全架構(gòu)」你真的了解權(quán)限機(jī)制嗎国章?

前言

Android將安全設(shè)計(jì)貫穿系統(tǒng)架構(gòu)的各個層面,覆蓋系統(tǒng)內(nèi)核豆村、虛擬機(jī)液兽、應(yīng)用程序框架層以及應(yīng)用層各個環(huán)節(jié),力求在開放的同時掌动,也最大程度地保護(hù)用戶的數(shù)據(jù)四啰、應(yīng)用程序和設(shè)備的安全。Android安全模型主要提供以下幾種安全機(jī)制:

從技術(shù)架構(gòu)角度來看粗恢,Android安全模型基于Linux操作系統(tǒng)內(nèi)核安全性柑晒,有基本的用戶和文件訪問隔離,在這個基礎(chǔ)上輔以內(nèi)存管理技術(shù)和進(jìn)程間通信機(jī)制眷射,來適應(yīng)移動端處理器性能與內(nèi)存容量的限制匙赞。在應(yīng)用層面,使用顯式定義且經(jīng)用戶授權(quán)的權(quán)限控制機(jī)制妖碉,系統(tǒng)化地規(guī)范并強(qiáng)制各類應(yīng)用程序的行為準(zhǔn)則與權(quán)限許可涌庭,可以看到權(quán)限機(jī)制在安全模型中所處的位置。

那么為什么有權(quán)限機(jī)制嗅绸?

我們知道 Android 應(yīng)用程序是沙箱隔離的脾猛,每個應(yīng)用都有一個只有自己具有讀寫權(quán)限的專用數(shù)據(jù)目錄,應(yīng)用只能訪問自己的文件和一些設(shè)備上全局可訪問的資源鱼鸠。

那如果我需要訪問系統(tǒng)服務(wù)呢猛拴?這就有了 Android 的權(quán)限機(jī)制。所以根本原因一是沙箱隔離蚀狰,二是服務(wù)支持的需求愉昆。

在本文開始之前,先拋出幾個問題思考麻蹋,什么是權(quán)限跛溉?權(quán)限是怎么進(jìn)行賦予的?怎么判斷一個組件是否擁有特定的權(quán)限?權(quán)限維護(hù)在哪芳室?Android 6.0 運(yùn)行時權(quán)限是什么原理专肪?權(quán)限賦予后還能再更改嗎?

帶著問題和結(jié)構(gòu)圖堪侯,我們一一解開疑惑嚎尤。

權(quán)限的本質(zhì)

什么是權(quán)限?

在 Android 中伍宦,一個權(quán)限芽死,本質(zhì)上是一個字符串,一個可以表示執(zhí)行特定操作的能力的字符串次洼。比如說:訪問 SD 卡的能力关贵,訪問通訊錄的能力,啟動或訪問一個第三方應(yīng)用中的組件的能力卖毁。

使用 pm list permissions -f 命令可以詳細(xì)查看 Android 所有預(yù)定義的權(quán)限揖曾。我們挑一個權(quán)限出來:

+ permission:android.permission.DELETE_PACKAGES
  package:android
  label:null
  description:null
  protectionLevel:signature|privileged

可以看到,一個權(quán)限的信息包括:定義的包名势篡、標(biāo)簽翩肌、描述和保護(hù)級別,保護(hù)級別禁悠,嗯念祭,這個我們需要詳細(xì)講講。因?yàn)椴皇菓?yīng)用聲明了權(quán)限碍侦,就一定會全部被自動賦予的粱坤,保護(hù)級別決定了包管理器是否應(yīng)該賦予組件所申請的權(quán)限。

權(quán)限的級別

  • normal 級別
    權(quán)限保護(hù)級別的默認(rèn)值瓷产,無須用戶確認(rèn)站玄,只要聲明了,就自動默默授權(quán)濒旦。如:ACCESS_NETWORK_STATE株旷。

  • dangerous 級別
    賦予權(quán)限前,會彈出對話框尔邓,顯式請求權(quán)限晾剖。如:READ_SMS。因?yàn)?Android 需要在安裝時賦予權(quán)限梯嗽,所以安裝的確認(rèn)對話框齿尽,也會顯示列出權(quán)限清單。

  • signature 級別
    signature 級別的權(quán)限是最嚴(yán)格的權(quán)限灯节,只會賦予與聲明權(quán)限使用相同證書的應(yīng)用程序循头。

    以系統(tǒng)內(nèi)置 signature 級別權(quán)限為例绵估,Android 系統(tǒng)應(yīng)用的簽名由平臺密鑰簽發(fā),默認(rèn)情況下源碼樹里有 4 個不同的密鑰文件:platform卡骂、shared国裳、media 和 testkey。所有核心平臺的包(如:設(shè)置偿警、電話躏救、藍(lán)牙)均使用 platform 密鑰簽發(fā);搜索和通訊錄相關(guān)的包使用 shared 簽發(fā)螟蒸;圖庫和媒體相關(guān)的包使用 media 密鑰簽發(fā);其他的應(yīng)用使用 testkey 簽發(fā)崩掘。定義系統(tǒng)內(nèi)置權(quán)限的 framework-res.apk 文件是使用平臺密鑰簽發(fā)的七嫌,因此任何試圖請求 signature 級別內(nèi)置權(quán)限的應(yīng)用程序,需要使用與框架資源包相同的密鑰進(jìn)行簽名苞慢。

  • signatureOrSystem 級別
    可以看做是一種折中的級別诵原,可被賦予與聲明權(quán)限具有相同簽名證書密鑰的應(yīng)用程序(同 signature 級別)或者系統(tǒng)鏡像的部分應(yīng)用,也就是說這允許廠商無須共享簽名密鑰挽放。Android 4.3 之前绍赛,安裝在 system 分區(qū)下的應(yīng)用會被自動賦予該保護(hù)級別的權(quán)限,而 Android 4.4 之后辑畦,只允許安裝在 system/priv-app/ 目錄下的應(yīng)用才能被主動賦予吗蚌。

權(quán)限管理

那么,系統(tǒng)內(nèi)置權(quán)限纯出,自定義權(quán)限蚯妇,是怎么維護(hù)和管理的?

在每個應(yīng)用安裝時暂筝,權(quán)限就已經(jīng)賦予了箩言,系統(tǒng)使用包管理服務(wù)來管理權(quán)限。打開我們系統(tǒng)目錄下的 /data/system/packages.xml焕襟,可以看到文件包含了所有已定義的權(quán)限列表和所有 apk 的包信息陨收,這可以看做是包管理服務(wù)維護(hù)的一個已安裝程序的核心數(shù)據(jù)庫,這個數(shù)據(jù)庫鸵赖,隨著每次應(yīng)用安裝务漩、升級或卸載而進(jìn)行更新。

主要屬性如圖:

<permissions> 標(biāo)簽內(nèi)卫漫,定義了目前系統(tǒng)中的所有權(quán)限菲饼,分為系統(tǒng)內(nèi)置的(package 屬性為 android 的)和 apk 自定義的(package 屬性為 apk 的包名)。根元素 <package> 含有每個 apk 的核心屬性列赎,以一個應(yīng)用程序的條目為例:

  • Android 6.0 以下 packages.xml

    <package 
        name="com.feelschaotic.demo" 
        codePath="/data/app/com.feelschaotic.demo-1" 
        nativeLibraryPath="/data/app/com.feelschaotic.demo-1/lib" 
        primaryCpuAbi="x86" 
        flags="5783366" 
        ft="16349bcc4d0" 
        it="16349bcc752" 
        ut="16349bcc752" 
        version="8220" 
        userId="10097">
        <sigs count="1">
            <cert index="7" />
        </sigs>
        <perms>
            <item name="android.permission.READ_SMS" />
            <item name="android.permission.ACCESS_FINE_LOCATION" />
            <item name="android.permission.CHANGE_NETWORK_STATE" />
            <item name="android.permission.INTERNET" />
            <item name="android.permission.READ_EXTERNAL_STORAGE" />
            <item name="android.permission.ACCESS_COARSE_LOCATION" />
            <item name="android.permission.READ_PHONE_STATE" />
            <item name="android.permission.CALL_PHONE" />
            <item name="android.permission.CHANGE_WIFI_STATE" />
            <item name="android.permission.ACCESS_NETWORK_STATE" />
            <item name="android.permission.CAMERA" />
            <item name="android.permission.WRITE_EXTERNAL_STORAGE" />
            <item name="android.permission.READ_CONTACTS" />
        </perms>
        <proper-signing-keyset identifier="1686" />
        <signing-keyset identifier="1686" />
    </package>
  • Android 6.0 及以上 packages.xml

 <package 
    name="com.feelschaotic.demo" 
    codePath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ==" 
    nativeLibraryPath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ==/lib" 
    primaryCpuAbi="x86" 
    publicFlags="945307462" 
    privateFlags="0" 
    ft="16348dc3870" 
    it="16343f1d6aa" 
    ut="16348dc4c4d" 
    version="8220" 
    userId="10102">
        <sigs count="1">
            <cert index="20" key="..." />
        </sigs>
        <perms>
          <!-- 此處普通權(quán)限的 granted 全都默認(rèn)是 true宏悦,且不可改變 granted 值-->
            <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.INTERNET" granted="true" flags="0" />
            <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="48" />
    </package>

可以發(fā)現(xiàn)镐确,其他信息沒有太大差異,但是權(quán)限列表中饼煞,部分在 Android 6.0 被標(biāo)記為高危的權(quán)限都不在 <perms> 里了源葫,如:READ_SMSREAD_EXTERNAL_STORAGE砖瞧、CALL_PHONE 等息堂。這是怎么一回事呢?

Android 6.0 之前块促,權(quán)限都是在安裝時自動賦予的荣堰,不卸載應(yīng)用的情況下,不能更改或撤銷(實(shí)際上有些廠商打開了appOps竭翠,使得 6.0 以下也能更改權(quán)限振坚,這種特殊情況我們不討論)。而 Android 6.0 版本對 permission 的管理做了部分改動斋扰,針對 dangerous 級別渡八,不再安裝的時候賦予權(quán)限,而是在運(yùn)行時動態(tài)申請传货。

我們大膽地推斷下屎鳍,packages.xml 里保留的是不會再變更的權(quán)限,運(yùn)行時權(quán)限一定是另外單獨(dú)地維護(hù)问裕。我們在 data/system 目錄下來了個全盤搜索逮壁,找到了 /data/system/users/0/runtime-permissions.xml

<pkg name="com.feelschaotic.demo">
   <!-- 該demo我們故意拒絕了定位權(quán)限僻澎,可以看到:ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 的 granted 為 false -->
    <item name="android.permission.ACCESS_FINE_LOCATION" granted="false" flags="1" />
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.ACCESS_COARSE_LOCATION" granted="false" flags="1" />
    <item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
    ...
</pkg>

沒錯了貌踏,就是這個文件。里面就記錄著運(yùn)行時權(quán)限的授予和拒絕狀態(tài)窟勃。申請時祖乳,申請的結(jié)果會動態(tài)修改 granted 值。

管理權(quán)限的仍然是 PMS秉氧,只不過 Android 6.0 之后新增了 runtime-permissions.xml 數(shù)據(jù)庫眷昆。

權(quán)限賦予

我們知道,Android 應(yīng)用安裝時汁咏,會被分配一個唯一的 UID亚斋,應(yīng)用啟動時,包管理器會設(shè)置新建進(jìn)程的 UID 和 GID 為應(yīng)用程序的 UID攘滩。如果應(yīng)用已經(jīng)被賦予了額外的權(quán)限帅刊,就把這些權(quán)限映射成一組 GID,作為補(bǔ)充 GID 分配給進(jìn)程漂问。低層就可以依賴于進(jìn)程的 UID赖瞒、GID 和補(bǔ)充 GID 來決定是否賦予權(quán)限了女揭。

那么,上面流程的重點(diǎn):

  1. 權(quán)限是如何映射到 OS 層的 UID栏饮、GID 上的呢吧兔?
  2. 映射完是怎么分配給進(jìn)程的?
  3. 低層是怎么判斷是否賦予權(quán)限的袍嬉?

我們一個個來看境蔼,首先第一個,權(quán)限是如何映射的伺通?內(nèi)置權(quán)限到 GID 的映射是定義在 /etc/permission/platform.xml 中箍土。

<permissions>
    ···
    <permission name="android.permission.READ_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
    </permission>

    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
        <group gid="sdcard_rw" />
    </permission>
    
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    ···
</permissions>

值得注意的是:READ_EXTERNAL_STORAGE 這種運(yùn)行時權(quán)限,在 Android 6.0 之后已經(jīng)不會映射到 gid 了罐监。動態(tài)賦予涮帘,動態(tài)申請,也就不需要映射了笑诅。

    <!-- These are permissions that were mapped to gids but we need
         to keep them here until an upgrade from L to the current
         version is to be supported. These permissions are built-in
         and in L were not stored in packages.xml as a result if they
         are not defined here while parsing packages.xml we would
         ignore these permissions being granted to apps and not
         propagate the granted state. From N we are storing the
         built-in permissions in packages.xml as the saved storage
         is negligible (one tag with the permission) compared to
         the fragility as one can remove a built-in permission which
         no longer needs to be mapped to gids and break grant propagation. -->
         
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />

包管理器在啟動時讀取 platform.xml,并維護(hù)「權(quán)限-GID」對應(yīng)的列表疮鲫。當(dāng)它給安裝中的包授權(quán)時吆你,會把權(quán)限對應(yīng)的 GID 加入到該應(yīng)用進(jìn)程的補(bǔ)充 GID 中。

我們舉個例子俊犯,看下進(jìn)程的屬性:adb shell ps 拿到想要查找的進(jìn)程的 PID妇多。

USER    PID  PPID     VSZ    RSS WCHAN            ADDR S NAME
...
u0_a88  2149  1624 1929916 112340 SyS_epoll_wait      0 S com.feelschaotic.demo

接著 adb shell -> cd proc -> cd 2149 (2149 為進(jìn)程 PID,不固定) -> cat status

我們關(guān)注如下兩行:

Gid: 10050 10050 10050 10050
Groups: 1006 1015 1028 3002 3003 50050

這里我們便看到了系統(tǒng)進(jìn)程的權(quán)限配置信息燕侠,這里的數(shù)字具體代表意義者祖,可以在Android
\system\core\include\private\android_filesystem_config.h 里面看到,其部分內(nèi)容如下:

#define AID_CAMERA 1006  /* camera devices */  
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_SDCARD_R 1028 /* external storage read access */
···
static const struct android_id_info android_ids[] = {
{ "camera",    AID_CAMERA, },
{ "sdcard_r", AID_SDCARD_R, },
{ "sdcard_rw", AID_SDCARD_RW, },
    ···
};

有沒有發(fā)現(xiàn)什么绢彤?前文所述 sdcard_randroid.permission.READ_EXTERNAL_STORAGE 的映射已經(jīng)定義在 /etc/permission/platform.xml 中了七问,此處 sdcard_r 映射 AID_SDCARD_R 對應(yīng) gid = 1015,這就是權(quán)限映射到 gid 的整個關(guān)系茫舶。

總共分為三層:

那么第二個問題械巡,當(dāng)我們安裝應(yīng)用完成,啟動應(yīng)用饶氏,應(yīng)用的進(jìn)程是如何啟動并被賦予進(jìn)程屬性的呢讥耗?

每個應(yīng)用都會運(yùn)行在自己的 Dalvik 虛擬機(jī)進(jìn)程中,但是為了提高啟動效率疹启,Android 不會為每個應(yīng)用都新建一個 Dalvik 進(jìn)程古程,而是采用 fork 的形式。每個進(jìn)程都 fork form zygote 進(jìn)程喊崖。

那么 fork 比起 new 挣磨,效率提高在哪里雇逞?

因?yàn)?zygote 進(jìn)程已經(jīng)預(yù)加載了大部分核心和 Java 應(yīng)用框架庫,fork 的子進(jìn)程會繼承 zygote 的進(jìn)程空間趋急,也就是說喝峦,fork 的子進(jìn)程,可以共享這些預(yù)加載的副本(記住呜达,是副本谣蠢,不是直接共享。fork 時查近,會 copy-on-write 預(yù)加載的內(nèi)容)眉踱,減少了重新加載核心庫的時間。

當(dāng) zygote 收到啟動新進(jìn)程的請求時霜威,它會 fork 自身出一個子進(jìn)程谈喳,并對該子進(jìn)程做特殊化處理。其源代碼位于 dalvik/vm/native/dalvik_system_Zygote.c 中戈泼。forkAndSpecializeCommon() 的主要代碼如下:

static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer)  
{  
 ...  
 pid = fork();  //創(chuàng)建新進(jìn)程  
 if (pid == 0)  //判斷是否是root婿禽,有沒有權(quán)限修改自己的進(jìn)程屬性
 {  
  setgroupsIntarray(gids);  //設(shè)置進(jìn)程的所有組  
  setrlimitsFromArray(rlimits);  
  setgid(gid);    //設(shè)置進(jìn)程的組ID  
  setuid(uid);    //設(shè)置進(jìn)程的用戶ID  
     }  
  ...  
} 

如上所示:這里設(shè)置進(jìn)程的組 ID 和用戶 ID,通過 fork 創(chuàng)建的子進(jìn)程調(diào)用 setgroups Intarray 設(shè)置該進(jìn)程所屬的組大猛,這樣應(yīng)用程序就擁有了該組的權(quán)限扭倾,并且可以通過 setgid()setuid() 確定應(yīng)用程序的 GID 及 UID 值。剛剛開始 fork 時挽绩,子進(jìn)程是以 root 執(zhí)行的膛壹,所以它可以更改自己的進(jìn)程屬性,當(dāng)屬性都設(shè)置完成唉堪,子進(jìn)程就以分配的 GID 和 UID 執(zhí)行模聋,此時,子進(jìn)程無法再更改自己的進(jìn)程屬性了唠亚,因?yàn)橛脩?ID 已經(jīng)不是 root 即 ! = 0 了链方,沒有修改自己進(jìn)程屬性的權(quán)限了。

adb shell ps趾撵,看下進(jìn)程列表:

USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME

root             1     0   10456   2352 SyS_epoll_wait      0 S init
...
root          1620     1 1614572  20312 poll_schedule_timeout 0 S zygote
...
u0_a88        3468  1620 1929916 112340 SyS_epoll_wait      0 S com.feelschaotic.demo
u0_a90        3574  1620 1696012  24176 SyS_epoll_wait      0 S com.demo.pushdemo
u0_a90        3607  1620 1708844  26748 SyS_epoll_wait      0 S com.demo.pushdemo:mult
u0_i0         3741  1879 1557800  11720 SyS_epoll_wait      0 S com.android.chrome:sandboxed
u0_a15        3774  1620 1690604  22056 SyS_epoll_wait      0 S com.google.android.ext.services
system        3805  1620 1687840  19688 SyS_epoll_wait      0 S com.android.keychain
u0_a42        3831  1620 1834408  45660 SyS_epoll_wait      0 S com.android.chrome
...

PID 表示應(yīng)用的進(jìn)程 ID侄柔,PPID 表示父進(jìn)程 ID,NAME 表示進(jìn)程名稱(一般情況下 NAME 是應(yīng)用包名)占调≡萏猓可以看到 zygote 進(jìn)程是由 init 進(jìn)程啟動,所有的應(yīng)用進(jìn)程的父進(jìn)程都是 zygote究珊。USER 表示的是進(jìn)程的專有用戶薪者,這個我們下次再詳細(xì)講講 Android 的用戶管理機(jī)制。

好了剿涮,既然如此言津,每個應(yīng)用進(jìn)程都分配好自己的 GID攻人、UID和補(bǔ)充 GID,系統(tǒng)內(nèi)核和守護(hù)進(jìn)程就可以用這些標(biāo)識來決定悬槽,是否要賦予進(jìn)程權(quán)限怀吻。

權(quán)限檢查(權(quán)限執(zhí)行)

1. 系統(tǒng)內(nèi)核層權(quán)限檢查

思考一下:如果我們的應(yīng)用沒有在 AndroidManifest.xml 中申請 android.permission.INTERNET 權(quán)限就進(jìn)行網(wǎng)絡(luò)請求,是不是會報 Permission denied 錯誤初婆。這個權(quán)限蓬坡,是誰來檢查?其他進(jìn)程來檢查嗎磅叛?明顯不是屑咳,網(wǎng)絡(luò)訪問權(quán)限是由低層來進(jìn)行控制的。

Android 的訪問控制弊琴,和 Linux 是一樣的兆龙,但 Android 增加了個特有的網(wǎng)絡(luò)訪問安全控制機(jī)制,也就是說敲董,創(chuàng)建網(wǎng)絡(luò)套接字的進(jìn)程紫皇,必須屬于 inet 組。

如上內(nèi)核代碼腋寨,current_has_network(void) 方法檢查了進(jìn)程的所在組坝橡。如果不在 inet 組,則直接返回錯誤精置。所以為了使我們的應(yīng)用具有訪問網(wǎng)絡(luò)的能力,我們需要在 AndroidManifest.xml 中申請 INTERNET 權(quán)限锣杂,經(jīng)過解析脂倦,逐步映射到內(nèi)核層的組 ID 和用戶 ID,最終才能通過內(nèi)核層的檢查元莫。

你可能會有疑問赖阻,那非內(nèi)核層的其他 C/C++ 層,要怎么拿到進(jìn)程的所在組信息呢踱蠢?

在 PMS 初始化所有包信息之后,就會調(diào)用 mSettings.writeLPr()火欧。

這段代碼的任務(wù)就是將mPackages 中保存的所有包的信息保存到 /data/system/packages.list。所以茎截,packages.list中保存了所有應(yīng)用申請的權(quán)限苇侵,C代碼只要讀這個文件就能判斷某個應(yīng)用是否申請了我們要求的權(quán)限。

2. 框架層

因?yàn)?Android 6.0 之前組件不能在運(yùn)行時改變權(quán)限企锌,所以系統(tǒng)的權(quán)限檢查執(zhí)行過程是靜態(tài)的榆浓。這個情況下,組件的角色和權(quán)限的等安全屬性會被放置在元數(shù)據(jù)中撕攒,即 AndroidManifest.xml 文件中陡鹃,而不是組件的本身烘浦。系統(tǒng)包管理器會負(fù)責(zé)記錄組件的權(quán)限,所以靜態(tài)權(quán)限檢查可以從包管理器拿到權(quán)限萍鲸,由運(yùn)行環(huán)境或容器來執(zhí)行權(quán)限檢查闷叉,這樣子可以把業(yè)務(wù)邏輯和安全決策分離開來,但是靈活性不足脊阴。

那 Android 組件可不可以不預(yù)先聲明權(quán)限在 AndroidManifest.xml 中呢握侧?答案是:可以的。Android 的動態(tài)權(quán)限執(zhí)行蹬叭,可以讓組件自身執(zhí)行權(quán)限檢查藕咏,而不是運(yùn)行環(huán)境。

所以接下來我們將深入了解框架層的動態(tài)和靜態(tài)權(quán)限執(zhí)行的原理秽五。

動態(tài)權(quán)限執(zhí)行

動態(tài)權(quán)限執(zhí)行孽查,最典型的場景,就是 IPC坦喘。Android 的核心系統(tǒng)服務(wù)統(tǒng)一會注冊到服務(wù)管理器盲再,任何應(yīng)用,只要知道服務(wù)的注冊名稱瓣铣,就可以拿到對應(yīng)的 Binder引用答朋,就可使用 Binder IPC 機(jī)制調(diào)用服務(wù)。因?yàn)?Binder 沒有內(nèi)置的訪問控制機(jī)制棠笑,所以每個系統(tǒng)服務(wù)需要自己實(shí)現(xiàn)訪問控制機(jī)制梦碗。

系統(tǒng)服務(wù)可以直接檢查調(diào)用者的 UID,通過限定 UID 來控制訪問權(quán)限蓖救,這種方式簡單直接洪规,但是對于非固定UID的應(yīng)用,就比較棘手了循捺。而且大部分服務(wù)斩例,并不關(guān)心調(diào)用者的 UID,只需要檢查調(diào)用者是否被賦予特定的權(quán)限即可从橘。所以這種方式念赶,比較適合只允許以 root(UID:0) 或 system(UID:1000) 運(yùn)行的進(jìn)程訪問的服務(wù)檢查。

那換一種方式恰力,服務(wù)怎么拿到調(diào)用者的權(quán)限列表叉谜?我們知道,大部分 UID 都是和包一一對應(yīng)的踩萎,除了共享 UID正罢。(共享 UID 后面再詳細(xì)解釋)

使用 Binder.getCallingUid()Binder.getCallingPid() 獲取調(diào)用者的 UID 和 PID,通過 UID 在包管理器中查詢到對應(yīng)應(yīng)用的權(quán)限。android.content.Context 類中就有 checkPermission(String permission, int pid, int uid) 方法翻具。實(shí)質(zhì)上會調(diào)用到 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() 方法比較簡單,首先裆泳,基于入?yún)?uid 獲取應(yīng)用的 appId叹洲,拿到權(quán)限列表對象(也就是 packages.xml 里的 <package> 映射),如果 GrantedPermissions 類中的 grantedPermissions 集合包含目標(biāo)權(quán)限工禾,則檢查通過运提。

如果沒有該 GrantedPermissions 對象,則檢查目標(biāo)權(quán)限是否可以被自動授予闻葵,實(shí)際上 mSystemPermissions 就是 platform.xml 文件中的 <assing-permission> 標(biāo)簽映射緩存民泵,記錄了一些系統(tǒng)級應(yīng)用的 uid 對應(yīng)的 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 去查詢權(quán)限列表。

關(guān)鍵就在于這個 mSettings 里面保存的這個 SettingBase 對象厢钧,它記錄了 PermissionsState 也就是權(quán)限的授予情況鳞尔。

// PermissionsState.java
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);
}

所以檢查權(quán)限的流程是本來就有的,6.0 之后差異僅在于:危險級別權(quán)限可以動態(tài)修改授權(quán)情況早直,也就是修改 PermissionStatemGranted 值寥假,所以每次權(quán)限執(zhí)行,都會查詢下 mGranted 值霞扬。

靜態(tài)權(quán)限執(zhí)行

靜態(tài)權(quán)限執(zhí)行的典型場景糕韧,是跨應(yīng)用組件交互。

我們使用隱式 Intent 來表達(dá)意圖喻圃,搜索匹配的組件兔沃,如果有多個,彈出選擇框级及,目標(biāo)組件被選定后,會由 ActivityManagerService 執(zhí)行權(quán)限檢查额衙,檢查目標(biāo)組件是否有相應(yīng)的權(quán)限要求饮焦,如果有,則把權(quán)限檢查的工作交給 PMS窍侧,去檢查調(diào)用者有沒有被授權(quán)這些權(quán)限县踢。

接下來的總體的流程和動態(tài)執(zhí)行流程大致相同:Binder.getCallingUid()Binder.getCallingPid()獲取調(diào)用者的 UID 和 PID,然后利用 UID 映射包名伟件,再獲得相關(guān)權(quán)限集合硼啤。如果權(quán)限集合中含有所需權(quán)限即啟動,否則拋出 SecurityException 異常斧账。

靜態(tài)權(quán)限執(zhí)行這里谴返,我們可以詳細(xì)了解下煞肾,每種組件的權(quán)限檢查時機(jī)和具體順序是怎么樣的。

組件權(quán)限執(zhí)行

思考一下嗓袱,什么時候會執(zhí)行對調(diào)用者的權(quán)限檢查籍救?那肯定是在目標(biāo)組件被調(diào)用的時候,去解析目標(biāo)組件聲明的權(quán)限渠抹,如果有蝙昙,就執(zhí)行權(quán)限檢查。

  • Activity 和 Service

Activity 顯而易見梧却,會在 startActivity()startActivityForResult() 里解析到聲明權(quán)限的 Activity 時奇颠,就執(zhí)行權(quán)限檢查。

而 Service startService()放航、stopService()bindService()烈拒,這 3 個方法被調(diào)用時都會進(jìn)行權(quán)限檢查。

  • 廣播

我們注意到三椿,發(fā)送廣播除了常用的 sendBroadcast(Intent intent)缺菌,還有個 sendBroadcast(Intent intent, String receiverPermission),該方法可以要求廣播接受者具備特定的權(quán)限搜锰,但是伴郁,調(diào)用 sendBroadcast 是不會進(jìn)行權(quán)限檢查的,因?yàn)閺V播是異步的蛋叼,所以權(quán)限檢查會在 intent 傳遞到已注冊的廣播接受者時進(jìn)行焊傅,如果接收者不具備特定的權(quán)限,則不會接收到該廣播狈涮,也不會收到 SecurityException 異常狐胎。

反過來,接收者可以要求廣播發(fā)送者必須具備的權(quán)限歌馍,所要求的權(quán)限在 manifest 文件中設(shè)置 <receiver> 標(biāo)簽的 permission 屬性握巢,或者動態(tài)注冊時指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),權(quán)限檢查也是在廣播傳遞時執(zhí)行松却。

所以暴浦,收發(fā)廣播可以分開指定權(quán)限。值得一提的是晓锻,一些系統(tǒng)廣播被聲明為 protected歌焦,并且只能由系統(tǒng)進(jìn)程發(fā)送,比如 PACKAGE_INSTALLED砚哆。只能由系統(tǒng)進(jìn)程發(fā)送独撇,這個限制會在內(nèi)核層進(jìn)行檢查,對調(diào)用者的 UID 進(jìn)行匹配,只能是 SYSTEM_UID纷铣、PHONE_UID卵史、SHELL_UID、BLUETOOTH_UID 或 root关炼。如果其他 UID 的進(jìn)程試圖發(fā)送系統(tǒng)廣播程腹,則會收到 SecurityException 異常。

想了解所有的系統(tǒng)廣播儒拂,可以打開/system/framework/framework-res.apk 中的 AndroidManifest.xml <protected-broadcast> 標(biāo)簽詳細(xì)了解寸潦。

  • ContentProvider

ContentProvider 可以為讀寫分別指定不同的權(quán)限,即:調(diào)用目標(biāo) provider社痛、query() 方法 和 insert()见转、update()delete() 都會進(jìn)行權(quán)限檢查蒜哀。

3. 總結(jié)

綜上所述斩箫,Android 的權(quán)限的檢查會在各個層次上實(shí)施。

高層的組件撵儿,例如應(yīng)用和系統(tǒng)服務(wù)乘客,通過包管理器查詢應(yīng)用程序被賦予的權(quán)限,并決定是否準(zhǔn)予訪問淀歇。

低層的組件易核,通常不訪問包管理器,比如本地守護(hù)進(jìn)程浪默,依賴于進(jìn)程的 UID牡直、GID 和補(bǔ)充 GID 來決定賦予。

訪問系統(tǒng)資源時纳决,如設(shè)備文件碰逸、UNIX 域套接字和網(wǎng)絡(luò)套接字,則由內(nèi)核根據(jù)所有者阔加、目標(biāo)資源的訪問權(quán)限和訪問進(jìn)程的進(jìn)程屬性或者 packages.list 來進(jìn)行控制饵史。

共享 UID

最后簡單說下共享 UID,填一下前面挖的坑胜榔。雖說 Android 會為每一個應(yīng)用分配唯一的 UID胳喷,但如果應(yīng)用使用相同的密鑰簽發(fā),就可以使用相同 UID 運(yùn)行苗分,也就是運(yùn)行在同一個進(jìn)程中。

這個特性被系統(tǒng)應(yīng)用和核心框架服務(wù)廣泛使用牵辣,比如:Google Play 和 Google 定位服務(wù)摔癣,請求同一進(jìn)程內(nèi)的 Google 登錄服務(wù),從而達(dá)到靜默自動同步用戶數(shù)據(jù)的體驗(yàn)。

值得注意的是:Android 不支持將一個已安裝的應(yīng)用择浊,從非共享 UID 切換到共享狀態(tài)戴卜,因?yàn)楦淖兞艘寻惭b應(yīng)用的 UID,會導(dǎo)致應(yīng)用失去對自己文件的訪問權(quán)限(在一些早期 Android 版本中)琢岩,所以如果使用共享 UID 必須從一開始就設(shè)計(jì)好投剥。


我是 FeelsChaotic,一個寫得了代碼 p 得了圖担孔,剪得了視頻畫得了畫的程序媛江锨,致力于追求代碼優(yōu)雅、架構(gòu)設(shè)計(jì)和 T 型成長糕篇。

歡迎關(guān)注 FeelsChaotic 的簡書掘金啄育,如果我的文章對你哪怕有一點(diǎn)點(diǎn)幫助,歡迎 ??拌消!你的鼓勵是我寫作的最大動力挑豌!

最最重要的,請給出你的建議或意見墩崩,有錯誤請多多指正氓英!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呵燕,老刑警劉巖昔汉,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莱找,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門啊鸭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匿值,你說我怎么就攤上這事赠制。” “怎么了挟憔?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵钟些,是天一觀的道長。 經(jīng)常有香客問我绊谭,道長政恍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任达传,我火速辦了婚禮篙耗,結(jié)果婚禮上迫筑,老公的妹妹穿的比我還像新娘。我一直安慰自己宗弯,他們只是感情好脯燃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蒙保,像睡著了一般辕棚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邓厕,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天逝嚎,我揣著相機(jī)與錄音,去河邊找鬼邑狸。 笑死懈糯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的单雾。 我是一名探鬼主播赚哗,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硅堆!你這毒婦竟也來了屿储?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤渐逃,失蹤者是張志新(化名)和其女友劉穎够掠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茄菊,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疯潭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了面殖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竖哩。...
    茶點(diǎn)故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脊僚,靈堂內(nèi)的尸體忽然破棺而出相叁,到底是詐尸還是另有隱情,我是刑警寧澤辽幌,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布增淹,位于F島的核電站,受9級特大地震影響乌企,放射性物質(zhì)發(fā)生泄漏虑润。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一加酵、第九天 我趴在偏房一處隱蔽的房頂上張望拳喻。 院中可真熱鬧梁剔,春花似錦、人聲如沸舞蔽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渗柿。三九已至,卻和暖如春脖岛,著一層夾襖步出監(jiān)牢的瞬間朵栖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工柴梆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陨溅,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓绍在,卻偏偏與公主長得像门扇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子偿渡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容