第一章 Android的權限機制
Android是基于Linux的系統,其權限訪問控制自然離不開Linux的權限訪問控制,而在第一章當中,將分成兩個部分來剖析Android的權限控制系統。
一. Linux權限機制
Linux的權限訪問是由進程(訪問者)和文件(被訪問者)兩部分組成的。其中相當一部分內容參考至APUE[1]。
1.1 Llinux文件權限
我們在Linux當中輸入命令
我們可以看到這樣類似如下的結果
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位字段,表示的是該文件的文件類型和其權限。下表描述了各個標志位的含義
9 |
6 - 8 |
3 - 5 |
0 - 2 |
文件類型 |
擁有者訪問權限 |
所在用戶組訪問權限 |
其它用戶訪問權限 |
p 管道文件
d 目錄文件
l 符號連接文件
- 普通文件
s socket文件
c 字符設備文件
b 塊設備文件
|
分別為讀寫執行權限,
-表示沒有該位上的權限
讀取權限: r
寫入權限: w
執行權限: x
s,S 表示設置了SUID位.
s表示該位原標志為x,
S表示該位原標志為-
|
分別為讀寫執行權限,
-表示沒有該位上的權限
讀取權限: r
寫入權限: w
執行權限: x
s,S 表示設置了GUID位.
s表示該位原標志為x,
S表示該位原標志為-
|
分別為讀寫執行權限,
-表示沒有該位上的權限
讀取權限: r
寫入權限: w
執行權限: x
s,S 表示設置了Sticky位.
s表示該位原標志為x,
S表示該位原標志為-
|
表1 Linux文件權限標識符
特殊權限SGID標志位:普通文件設置了該標志位,則表示該進程的egid變成被運行的程序的所有者的gid。沒有設置該位,則進程的egid為運行該程序的用戶的gid。
特殊權限SUID標志位:普通文件設置了該標志位,則表示該進程的euid變成被運行的程序的所有者的uid。沒有設置該位,則進程的euid為運行該程序的用戶的uid。
特殊權限Sticky標志位:舊的UNIX系統定義該位為指示操作系統在程序退出后,保留程序的代碼段到swap空間。而在linux系統當中,該位表示防刪除位,意味著該位被設置之后,只有文件的擁有者和root用戶才能刪除該文件。[1][2]
SGID和SUID的存在意義在于,當一個非特權進程可以通過執行設置了SGID和SUID標志的程序,來獲得特定權限。例如su,當它沒有設置SGID和SUID標志位的時候,實際上它是不能創建一個具有root權限的shell進程的。
以上的文件權限標識符在Linux當中實際上是使用二進制來表示的,例如rwxrw-rw-,轉成二進制形式就是111110110,但實際情況下,我們為了更方便閱讀,我們使用的是八進制進行標識,也就是766。但是文件標識符當中除了基本讀寫執行之外,再算上特殊權限,實際上Linux權限訪問控制使用的是12位二進制數字(3位特殊權限 + 9位基礎權限)來表示訪問權限。比如rwsrw-rw-,轉化成八進制表示方式,就是4766。
1.2 linux進程權限
假設,我們在系統當中運行了一個程序,然后我們通過ps命令進行查詢,得知該進程的pid為1025,之后我們在linux當中輸入命令
我們可以看到其中有一段
Name: live.androidpad
Uid:
10040
10040
10040
10040
Gid:
10040
10040
10040
10040
Groups:
1007
1015
3003
其中,Uid行有四列,它們分別為RUID,EUID,SUID,FSUID
RUID(實際用戶id:Real User ID):進程的創建用戶。
EUID(有效用戶id:Effective User ID):進程的有效用戶,用于權限訪問控制。
SUID(保存設置用戶id:Saved Set-User-ID):在程序執行(exec)之后作為EUID的副本,用于進程切換自己的EUID時使用,對用戶來說實際意義不大。參考[1]
FSUID(文件系統用戶id:File System User ID):Linux新引進的一類用戶、組,用于文件訪問控制。(推測,文件訪問上FSUID優先于EUID)
Gid行有四列,它們分別為RGID,EGID,SGID,FSGID
RGID(實際用戶id:Real User ID):進程的創建用戶組
EGID(有效用戶id:Effective User ID):進程的有效用戶組,用于權限訪問控制。
SGID(保存設置用戶id:Saved Set-User-ID):在程序執行(exec)之后作為EGID的副本,用于進程切換自己的EGID時使用,對用戶來說實際意義不大。參考[1]
FSGID(文件系統用戶id:File System User ID):Linux新引進的一類用戶、組,用于文件訪問控制。(推測,文件訪問上FSGID優先于EGID)
Groups行是組id,里面是一組使用空格分開的數字,這些數字就是是用戶組的id,它同樣用于權限訪問控制。
對于FSGID和FSUID,這個東西是Linux中引進的,很多時候它的值是直接復制EGID和EUID的。而Unix系統當中,RUID\EUID\SUID、RGID\EGID\SGID和Groups作為標配,我們這里只討論進程的這7個參數。正如我們使用命令輸出的結果一樣,除了Groups參數使用整形數組來表示之外,其余6個參數在Linux系統當中使用的都是整形來表示。而,這幾個參數都會決定進程的權限等特性,而它們是基于什么規則來賦值的呢?
雖然實際上跟文件訪問權限有關的僅僅是EUID、EGID和Groups,但是因為文章的受眾很可能是只了解Android系統的開發者,所以我這里也多講一些。Linux當中所有的進程創建都是通過fork函數創建的,當進程被fork之后,子進程會繼承父進程的RUID\EUID和RGID\EGID,而SUID和SGID會在exec之后作為EUID和EGID的副本被賦值(關于fork和exec的更多講解,請參考APUE[1]和[3])。而在進程創建之后,子進程可以通過setuid和setgid修改自身的RUID\EUID\SUID、RGID\EGID\SGID,但是這是有固定規則的。
以下是setuid的使用規則,setgid也與之類似:
1.若進程擁有超級權限,則setuid函數將RUID\EUID\SUID設置為uid。
2.若進程沒有超級權限,而uid的值等于RUID或者SUID,則setuid將會把EUID設置為uid。而不會改變RUID或者SUID的值。
3.如果上述兩個條件都不滿足,則返回失敗。
1.3 Linux的權限訪問控制
這部分很簡單,所有的系統調用最終都到內核當中,內核作為管理中樞,對所有的文件訪問調用進行了核查。而APUE描述了內核對讀寫執行權限的測試算法:
1.若進程的EUID是0,則允許訪問。
2.若進程的EUID等于所有者ID,那么:若所有者對應的訪問權限位被設置,則允許訪問,否則拒絕訪問。
3.若進程的EGID或者附加組ID之一等于文件的組ID,那么:若組對應的訪問權限位被設置,則允許訪問,否則拒絕訪問。
4.若其它用戶對應的訪問權限位被設置,則允許訪問,否則拒絕訪問。
二. Android權限機制
原本想對這部分內容進行詳細解析的,但后來發現涉及的內容包含了PKMS,AMS,應用程序安裝,應用程序啟動等內容。假若我來描寫這些內容,第一,篇幅太多,第二,自己的描述能力有限容易誤導別人。所以我就不深入說了,有興趣的朋友可以參考[4][5][6]。
在這里我們只需要知道,Android的策略是這樣的:
1.文件和設備訪問,使用Linux的權限訪問控制。部分權限聲明之后,應用程序啟動的時候,AMS會從PKMS那里獲得該應用進程的uid,gid和組id信息,然后通過Zygote來創建一個指定id的進程。獲得指定組id的進程,也會獲得部分文件的訪問權限,例如聲明android.permission.WRITE_EXTERNAL_STORAGE來訪問sdcard會被賦予sdcard_rw的組id。權限所對應的組id在frameworks/base/data/etc/platform.xml當中。
特別注意:第一章也描述了,內核檢查id的順序是EUID然后再到EGID和組ID,所以,當你聲明android.permission.WRITE_EXTERNAL_STORAGE的同時,聲明shareUserId為system,是沒有讀寫sdcard權限的。
2.Android接口調用控制,首先是root用戶和system用戶擁有所有的接口調用權限,然后對于其它用戶使用Context以下這幾個函數來實現
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開頭的,不單檢查,沒有權限的還會拋出異常。
這幾個函數最后會調用到PKMS的checkUidPermission,該函數通過對比應用權限信息來判斷該應用是否獲得權限。
3.Android權限等級劃分為normal,dangerous,signature,signatureOrSystem,system,development,其中
signature需要簽名才能賦予權限,
signatureOrSystem需要簽名或者系統級應用(放置在/system/app目錄下)才能賦予權限,
system系統級應用(放置在/system/app目錄下)才能賦予權限,系統權限的描述在frameworks/base/core/res/AndroidManifest.xml當中。
這就解答了,為什么有時候聲明一些權限沒有起作用,例如android.permission.WRITE_MEDIA_STORAGE。
如果我們想知道某個權限怎么使用,有什么制約怎么辦?
來查看系統所有權限的描述
如果我們需要在系統中增加一個權限,怎么辦?那我們照下列的步驟來做
1.確定你的權限屬于文件訪問控制,還是接口調用控制。
2.在frameworks/base/core/res/AndroidManifest.xml,中增加你的權限描述。
3.如果是文件訪問控制,那就在frameworks/base/data/etc/platform.xml為你的權限依附指定的組id。
4.如果是接口調用控制,那就在你的接口調用里面,加入上述Context檢查權限的函數。
(這段內容確實不大好寫,醞釀了好久,再醞釀就胎死腹中了,再度吐槽一下自己的描述能力。:-)第二章內容會講述一下Android root的原理。)
參考資料
[1]《Advanced Programming in the UNIX Environment》, W.Richard Stevens.
[2] Sticky標志位, http://en.wikipedia.org/wiki/Sticky_bit
[3] Linux下Fork與Exec使用, http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html
[4]《Android 內核剖析》,柯元旦.
[5]《深入理解Android》 ,鄧平凡.
[6] Android權限官方文檔 ,http://developer.android.com/intl/zh-CN/guide/topics/security/permissions.html.