淺析Android權(quán)限機(jī)制(一) —— Android的權(quán)限機(jī)制
第一章 Android的權(quán)限機(jī)制
Android是基于Linux的系統(tǒng),其權(quán)限訪問控制自然離不開Linux的權(quán)限訪問控制,而在第一章當(dāng)中,將分成兩個(gè)部分來剖析Android的權(quán)限控制系統(tǒng)。
一. Linux權(quán)限機(jī)制
Linux的權(quán)限訪問是由進(jìn)程(訪問者)和文件(被訪問者)兩部分組成的。其中相當(dāng)一部分內(nèi)容參考至APUE[1]。
1.1 Llinux文件權(quán)限
我們?cè)贚inux當(dāng)中輸入命令
$ls -l
我們可以看到這樣類似如下的結(jié)果
drwxr-xr-x 2 root root 4096 11月 2808:32 bin drwxr-xr-x 3 root root 4096 12月 1809:18 boot drwxr-xr-x 2 root root 4096 3月 182012 cdrom drwxr-xr-x 15 root root 4380 1月 419:28 dev drwxr-xr-x 176 root root 12288 1月 419:01 etc drwxr-xr-x 3 root root 4096 4月 162012 home
第一列使用的如drwxr-xr-x的10位字段,表示的是該文件的文件類型和其權(quán)限。下表描述了各個(gè)標(biāo)志位的含義
9 | 6 - 8 | 3 - 5 | 0 - 2 |
文件類型 | 擁有者訪問權(quán)限 | 所在用戶組訪問權(quán)限 | 其它用戶訪問權(quán)限 |
p 管道文件 d 目錄文件 l 符號(hào)連接文件 - 普通文件 s socket文件 c 字符設(shè)備文件 b 塊設(shè)備文件 |
分別為讀寫執(zhí)行權(quán)限, -表示沒有該位上的權(quán)限 讀取權(quán)限: r 寫入權(quán)限: w 執(zhí)行權(quán)限: x s,S 表示設(shè)置了SUID位. s表示該位原標(biāo)志為x, S表示該位原標(biāo)志為- |
分別為讀寫執(zhí)行權(quán)限, -表示沒有該位上的權(quán)限 讀取權(quán)限: r 寫入權(quán)限: w 執(zhí)行權(quán)限: x s,S 表示設(shè)置了GUID位. s表示該位原標(biāo)志為x, S表示該位原標(biāo)志為- |
分別為讀寫執(zhí)行權(quán)限, -表示沒有該位上的權(quán)限 讀取權(quán)限: r 寫入權(quán)限: w 執(zhí)行權(quán)限: x s,S 表示設(shè)置了Sticky位. s表示該位原標(biāo)志為x, S表示該位原標(biāo)志為- |
表1 Linux文件權(quán)限標(biāo)識(shí)符
特殊權(quán)限SGID標(biāo)志位:普通文件設(shè)置了該標(biāo)志位,則表示該進(jìn)程的egid變成被運(yùn)行的程序的所有者的gid。沒有設(shè)置該位,則進(jìn)程的egid為運(yùn)行該程序的用戶的gid。
特殊權(quán)限SUID標(biāo)志位:普通文件設(shè)置了該標(biāo)志位,則表示該進(jìn)程的euid變成被運(yùn)行的程序的所有者的uid。沒有設(shè)置該位,則進(jìn)程的euid為運(yùn)行該程序的用戶的uid。
特殊權(quán)限Sticky標(biāo)志位:舊的UNIX系統(tǒng)定義該位為指示操作系統(tǒng)在程序退出后,保留程序的代碼段到swap空間。而在linux系統(tǒng)當(dāng)中,該位表示防刪除位,意味著該位被設(shè)置之后,只有文件的擁有者和root用戶才能刪除該文件。[1][2]
SGID和SUID的存在意義在于,當(dāng)一個(gè)非特權(quán)進(jìn)程可以通過執(zhí)行設(shè)置了SGID和SUID標(biāo)志的程序,來獲得特定權(quán)限。例如su,當(dāng)它沒有設(shè)置SGID和SUID標(biāo)志位的時(shí)候,實(shí)際上它是不能創(chuàng)建一個(gè)具有root權(quán)限的shell進(jìn)程的。
以上的文件權(quán)限標(biāo)識(shí)符在Linux當(dāng)中實(shí)際上是使用二進(jìn)制來表示的,例如rwxrw-rw-,轉(zhuǎn)成二進(jìn)制形式就是111110110,但實(shí)際情況下,我們?yōu)榱烁奖汩喿x,我們使用的是八進(jìn)制進(jìn)行標(biāo)識(shí),也就是766。但是文件標(biāo)識(shí)符當(dāng)中除了基本讀寫執(zhí)行之外,再算上特殊權(quán)限,實(shí)際上Linux權(quán)限訪問控制使用的是12位二進(jìn)制數(shù)字(3位特殊權(quán)限 + 9位基礎(chǔ)權(quán)限)來表示訪問權(quán)限。比如rwsrw-rw-,轉(zhuǎn)化成八進(jìn)制表示方式,就是4766。
1.2 linux進(jìn)程權(quán)限
假設(shè),我們?cè)谙到y(tǒng)當(dāng)中運(yùn)行了一個(gè)程序,然后我們通過ps命令進(jìn)行查詢,得知該進(jìn)程的pid為1025,之后我們?cè)趌inux當(dāng)中輸入命令
$cat proc/1025/status
我們可以看到其中有一段
Name: live.androidpad Uid: 10040 10040 10040 10040 Gid: 10040 10040 10040 10040 Groups: 1007 1015 3003
其中,Uid行有四列,它們分別為RUID,EUID,SUID,F(xiàn)SUID
RUID(實(shí)際用戶id:Real User ID):進(jìn)程的創(chuàng)建用戶。
EUID(有效用戶id:Effective User ID):進(jìn)程的有效用戶,用于權(quán)限訪問控制。
SUID(保存設(shè)置用戶id:Saved Set-User-ID):在程序執(zhí)行(exec)之后作為EUID的副本,用于進(jìn)程切換自己的EUID時(shí)使用,對(duì)用戶來說實(shí)際意義不大。參考[1]
FSUID(文件系統(tǒng)用戶id:File System User ID):Linux新引進(jìn)的一類用戶、組,用于文件訪問控制。(推測(cè),文件訪問上FSUID優(yōu)先于EUID)
Gid行有四列,它們分別為RGID,EGID,SGID,F(xiàn)SGID
RGID(實(shí)際用戶id:Real User ID):進(jìn)程的創(chuàng)建用戶組
EGID(有效用戶id:Effective User ID):進(jìn)程的有效用戶組,用于權(quán)限訪問控制。
SGID(保存設(shè)置用戶id:Saved Set-User-ID):在程序執(zhí)行(exec)之后作為EGID的副本,用于進(jìn)程切換自己的EGID時(shí)使用,對(duì)用戶來說實(shí)際意義不大。參考[1]
FSGID(文件系統(tǒng)用戶id:File System User ID):Linux新引進(jìn)的一類用戶、組,用于文件訪問控制。(推測(cè),文件訪問上FSGID優(yōu)先于EGID)
Groups行是組id,里面是一組使用空格分開的數(shù)字,這些數(shù)字就是是用戶組的id,它同樣用于權(quán)限訪問控制。
對(duì)于FSGID和FSUID,這個(gè)東西是Linux中引進(jìn)的,很多時(shí)候它的值是直接復(fù)制EGID和EUID的。而Unix系統(tǒng)當(dāng)中,RUID\EUID\SUID、RGID\EGID\SGID和Groups作為標(biāo)配,我們這里只討論進(jìn)程的這7個(gè)參數(shù)。正如我們使用命令輸出的結(jié)果一樣,除了Groups參數(shù)使用整形數(shù)組來表示之外,其余6個(gè)參數(shù)在Linux系統(tǒng)當(dāng)中使用的都是整形來表示。而,這幾個(gè)參數(shù)都會(huì)決定進(jìn)程的權(quán)限等特性,而它們是基于什么規(guī)則來賦值的呢?
雖然實(shí)際上跟文件訪問權(quán)限有關(guān)的僅僅是EUID、EGID和Groups,但是因?yàn)槲恼碌氖鼙姾芸赡苁侵涣私釧ndroid系統(tǒng)的開發(fā)者,所以我這里也多講一些。Linux當(dāng)中所有的進(jìn)程創(chuàng)建都是通過fork函數(shù)創(chuàng)建的,當(dāng)進(jìn)程被fork之后,子進(jìn)程會(huì)繼承父進(jìn)程的RUID\EUID和RGID\EGID,而SUID和SGID會(huì)在exec之后作為EUID和EGID的副本被賦值(關(guān)于fork和exec的更多講解,請(qǐng)參考APUE[1]和[3])。而在進(jìn)程創(chuàng)建之后,子進(jìn)程可以通過setuid和setgid修改自身的RUID\EUID\SUID、RGID\EGID\SGID,但是這是有固定規(guī)則的。
以下是setuid的使用規(guī)則,setgid也與之類似:
1.若進(jìn)程擁有超級(jí)權(quán)限,則setuid函數(shù)將RUID\EUID\SUID設(shè)置為uid。
2.若進(jìn)程沒有超級(jí)權(quán)限,而uid的值等于RUID或者SUID,則setuid將會(huì)把EUID設(shè)置為uid。而不會(huì)改變RUID或者SUID的值。
3.如果上述兩個(gè)條件都不滿足,則返回失敗。
1.3 Linux的權(quán)限訪問控制
這部分很簡(jiǎn)單,所有的系統(tǒng)調(diào)用最終都到內(nèi)核當(dāng)中,內(nèi)核作為管理中樞,對(duì)所有的文件訪問調(diào)用進(jìn)行了核查。而APUE描述了內(nèi)核對(duì)讀寫執(zhí)行權(quán)限的測(cè)試算法:
1.若進(jìn)程的EUID是0,則允許訪問。
2.若進(jìn)程的EUID等于所有者ID,那么:若所有者對(duì)應(yīng)的訪問權(quán)限位被設(shè)置,則允許訪問,否則拒絕訪問。
3.若進(jìn)程的EGID或者附加組ID之一等于文件的組ID,那么:若組對(duì)應(yīng)的訪問權(quán)限位被設(shè)置,則允許訪問,否則拒絕訪問。
4.若其它用戶對(duì)應(yīng)的訪問權(quán)限位被設(shè)置,則允許訪問,否則拒絕訪問。
二. Android權(quán)限機(jī)制
原本想對(duì)這部分內(nèi)容進(jìn)行詳細(xì)解析的,但后來發(fā)現(xiàn)涉及的內(nèi)容包含了PKMS,AMS,應(yīng)用程序安裝,應(yīng)用程序啟動(dòng)等內(nèi)容。假若我來描寫這些內(nèi)容,第一,篇幅太多,第二,自己的描述能力有限容易誤導(dǎo)別人。所以我就不深入說了,有興趣的朋友可以參考[4][5][6]。
在這里我們只需要知道,Android的策略是這樣的:
1.文件和設(shè)備訪問,使用Linux的權(quán)限訪問控制。部分權(quán)限聲明之后,應(yīng)用程序啟動(dòng)的時(shí)候,AMS會(huì)從PKMS那里獲得該應(yīng)用進(jìn)程的uid,gid和組id信息,然后通過Zygote來創(chuàng)建一個(gè)指定id的進(jìn)程。獲得指定組id的進(jìn)程,也會(huì)獲得部分文件的訪問權(quán)限,例如聲明android.permission.WRITE_EXTERNAL_STORAGE來訪問sdcard會(huì)被賦予sdcard_rw的組id。權(quán)限所對(duì)應(yīng)的組id在frameworks/base/data/etc/platform.xml當(dāng)中。
特別注意:第一章也描述了,內(nèi)核檢查id的順序是EUID然后再到EGID和組ID,所以,當(dāng)你聲明android.permission.WRITE_EXTERNAL_STORAGE的同時(shí),聲明shareUserId為system,是沒有讀寫sdcard權(quán)限的。
2.Android接口調(diào)用控制,首先是root用戶和system用戶擁有所有的接口調(diào)用權(quán)限,然后對(duì)于其它用戶使用Context以下這幾個(gè)函數(shù)來實(shí)現(xiàn)
Context.checkCallingOrSelfPermission(String);
Context.checkCallingOrSelfUriPermission(Uri,
int
);
Context.checkCallingPermission(Permission);
Context.checkCallingUriPermission(Uri,
int
);
Context.checkPermission(String,
int
,
int
);
Context.checkUriPermission(Uri,
int
,
int
,
int
);
Context.checkUriPermission(Uri,String,String,
int
,
int
,
int
);
Context.enforceCallingOrSelfPermission(String,String);
Context.enforceCallingOrSelfUriPermission(Uri,
int
,String);
Context.enforceCallingPermission(String,String);
Context.enforceCallingUriPermission(String,String);
Context.enforcePermission(String,
int
,
int
,String);
Context.enforceUriPermission(Uri,
int
,
int
,
int
,String);
Context.enforceUriPermission(Uri,String,String,
int
,
int
,
int
,String);
|
其中check開頭的,只做檢查。enforce開頭的,不單檢查,沒有權(quán)限的還會(huì)拋出異常。
這幾個(gè)函數(shù)最后會(huì)調(diào)用到PKMS的checkUidPermission,該函數(shù)通過對(duì)比應(yīng)用權(quán)限信息來判斷該應(yīng)用是否獲得權(quán)限。
3.Android權(quán)限等級(jí)劃分為normal,dangerous,signature,signatureOrSystem,system,development,其中
signature需要簽名才能賦予權(quán)限,
signatureOrSystem需要簽名或者系統(tǒng)級(jí)應(yīng)用(放置在/system/app目錄下)才能賦予權(quán)限,
system系統(tǒng)級(jí)應(yīng)用(放置在/system/app目錄下)才能賦予權(quán)限,系統(tǒng)權(quán)限的描述在frameworks/base/core/res/AndroidManifest.xml當(dāng)中。
這就解答了,為什么有時(shí)候聲明一些權(quán)限沒有起作用,例如android.permission.WRITE_MEDIA_STORAGE。
如果我們想知道某個(gè)權(quán)限怎么使用,有什么制約怎么辦?
pm list permissions -f
|
來查看系統(tǒng)所有權(quán)限的描述
如果我們需要在系統(tǒng)中增加一個(gè)權(quán)限,怎么辦?那我們照下列的步驟來做
1.確定你的權(quán)限屬于文件訪問控制,還是接口調(diào)用控制。
2.在frameworks/base/core/res/AndroidManifest.xml,中增加你的權(quán)限描述。
3.如果是文件訪問控制,那就在frameworks/base/data/etc/platform.xml為你的權(quán)限依附指定的組id。
4.如果是接口調(diào)用控制,那就在你的接口調(diào)用里面,加入上述Context檢查權(quán)限的函數(shù)。
(這段內(nèi)容確實(shí)不大好寫,醞釀了好久,再醞釀就胎死腹中了,再度吐槽一下自己的描述能力。:-)第二章內(nèi)容會(huì)講述一下Android root的原理。)
參考資料
[1]《Advanced Programming in the UNIX Environment》, W.Richard Stevens.
[2] Sticky標(biāo)志位, http://en.wikipedia.org/wiki/Sticky_bit
[3] Linux下Fork與Exec使用, http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html
[4]《Android 內(nèi)核剖析》,柯元旦.
[5]《深入理解Android》 ,鄧平凡.
[6] Android權(quán)限官方文檔 ,http://developer.android.com/intl/zh-CN/guide/topics/security/permissions.html.
posted on 2017-06-02 21:20 聽風(fēng) 閱讀(203) 評(píng)論(0) 編輯 收藏 所屬分類: 嵌入式