深入理解SELinux SEAndroid(第一部分)
按哥的習慣,應該是全部洗剪吹完後再發,不過今年是馬年,什麼都強調 馬上。所以 現在就先奉獻 馬上有第一部分 祝各位同仁,朋友 馬年快樂。
深入理解SELinux SEAndroid
SEAndroid是Google在Android 4.4上正式推出的一套以SELinux為基礎於核心的系統安全機制。而SELinux則是由美國NSA(國安局)和一些公司(RedHat、Tresys)設計的一個針對Linux的安全加強系統。
NSA最初設計的安全模型叫FLASK,全稱為Flux Advanced Security Kernel(由Uta大學和美國國防部開發,後來由NSA將其開源),當時這套模型針對DTOS
Linux Kernel中,SELinux通過Linux Security Modules實現。在2.6之前,SElinux通過Patch方式釋出。從2.6開始,SELinux正式入駐核心,成為標配。
思考:
1 同樣是政府部門,差別咋這麼大?
2 同樣涉及作業系統和安全相關,NSA為何敢用Linux,為什麼想方設法要開源?
由於Linux有多種發行版本,所以各家的SELinux表現形式也略有區別。具體到Android平臺,Google對其進行了一定得修改,從而得到SEAndroid
本文將先對SELinux相關知識進行介紹,然後看看Android是如何實現SELinux的(咱們只看使用者空間)。
目標:學完本文,讀者應該可以輕鬆修改相關安全策略檔案,以進一步在安全方面定製自己的Android系統。
一 SELinux背景知識
1. DAC和MAC
SELinux出現之前,Linux上的安全模型叫DAC,全稱是Discretionary Access Control,翻譯為自主訪問控制。DAC的核心思想很簡單,就是:
- 程序理論上所擁有的許可權與執行它的使用者的許可權相同。比如,以root使用者啟動Browser,那麼Browser就有root使用者的許可權,在Linux系統上能幹任何事情。
顯然,DAC太過寬鬆了,所以各路高手想方設法都要在Android系統上搞到root許可權。那麼SELinux如何解決這個問題呢?原來,它在DAC之外,設計了一個新的安全模型,叫MAC(Mandatory Access Control),翻譯為強制訪問控制。MAC的處世哲學非常簡單:即任何程序想在SELinux系統中幹任何事情,都必須先在安全策略配置檔案中賦予許可權。凡是沒有出現在安全策略配置檔案中的許可權,程序就沒有該許可權。來看一個SEAndroid中設定許可權的例子:
[例子1]
/*
from external/sepolicy/netd.te
下面這條SELinux語句表示 允許(allow )netd域(domain)中的程序 ”寫(write)“
型別為proc的檔案
注意,SELinux中安全策略檔案有自己的一套語法格式,下文我們將詳細介紹它
*/
allow netd proc:file write
如果沒有在netd.te中使用上例中的許可權配置allow語句,則netd就無法往/proc目錄下得任何檔案中寫資料,即使netd具有root許可權。
顯然,MAC比DAC在許可權管理這一塊要複雜,要嚴格,要細緻得多。
那麼,關於DAC和MAC,此處筆者總結了幾個知識點:
- Linux系統先做DAC檢查。如果沒有通過DAC許可權檢查,則操作直接失敗。通過DAC檢查之後,再做MAC許可權檢查。
- SELinux中也有使用者的概念,但它和Linux中原有的user概念不是同一個東西。什麼意思呢?比如,Linux中的超級使用者root在SELinux中可能就是一個沒許可權,沒地位,打打醬油的”路人甲“。當然,這一切都由SELinux安全策略的制定者來決定。
通過上述內容,讀者應該能感覺到,在SELinux中,安全策略檔案是最重要的。確實如此。事實上,對本文的讀者而言,學習SELinux的終極目標應該是:
- 看懂現有的安全策略檔案。
- 編寫符合特定需求的安全策略檔案。
前面也曾提到,SELinux有自己的一套規則來編寫安全策略檔案,這套規則被稱之為SELinux Policy語言。它是掌握SELinux的重點。
2. SELinux Policy語言介紹
Linux中有兩種東西,一種死的(Inactive),一種活的(Active)。死的東西就是檔案(Linux哲學,萬物皆檔案。注意,萬不可狹義解釋為File),而活的東西就是程序。此處的“死”和“活”是一種比喻,對映到軟體層面的意思是:程序能發起動作,例如它能開啟檔案並操作它。而檔案只能被程序操作。
SELinux中,每種東西都會被賦予一個安全屬性,官方說法叫Security Context。Security Context(以後用SContext表示)是一個字串,主要由三部分組成。例如SEAndroid中,程序的SContext可通過ps -Z命令檢視,如圖1所示:
圖1 Nexus 7 ps -Z結果圖
圖1中最左邊的那一列是程序的SContext,以第一個程序/system/bin/logwrapper的SContext為例,其值為u:r:init:s0,其中:
- u為user的意思。SEAndroid中定義了一個SELinux使用者,值為u。
- r為role的意思。role是角色之意,它是SELinux中一種比較高層次,更方便的許可權管理思路,即Role Based Access Control(基於角色的訪問控制,簡稱為RBAC)。簡單點說,一個u可以屬於多個role,不同的role具有不同的許可權。RBAC我們到最後再討論。
- init,代表該程序所屬的Domain為init。MAC的基礎管理思路其實不是針對上面的RBAC,而是所謂的Type Enforcement Accesc Control(簡稱TEAC,一般用TE表示)。對程序來說,Type就是Domain。比如init這個Domain有什麼許可權,都需要通過[例子1]中allow語句來說明。
- S0和SELinux為了滿足軍用和教育行業而設計的Multi-Level Security(MLS)機制有關。簡單點說,MLS將系統的程序和檔案進行了分級,不同級別的資源需要對應級別的程序才能訪問。後文還將詳細介紹MLS。
再來看檔案的SContext,讀者可通過ls -Z來檢視,如圖2所示:
圖2 Nexus 7 ls -Z結果圖
圖2中,倒數第二列所示為Nexus 7根目錄下幾個檔案和目錄的SContext資訊,以第一行root目錄為例,其資訊為u:object_r:rootfs:s0:
- u:同樣是user之意,它代表建立這個檔案的SELinux user。
- object_r:檔案是死的東西,它沒法扮演角色,所以在SELinux中,死的東西都用object_r來表示它的role。
- rootfs:死的東西的Type,和程序的Domain其實是一個意思。它表示root目錄對應的Type是rootfs。
- s0:MLS的級別。
根據SELinux規範,完整的SContext字串為:
user:role:type[:range]
注意,方括號中的內容表示可選項。s0屬於range中的一部分。下文再詳細介紹range所代表的Security Level相關的知識。
看,SContext的核心其實是前三個部分:user:role:type。
剛才說了,MAC基本管理單位是TEAC(Type Enforcement Accesc Control),然後是高一級別的Role Based Accesc Control。RBAC是基於TE的,而TE也是SELinux中最主要的部分。
下面來看看TE。
2.1 TE介紹
在例子1中,大家已經見過TE的allow語句了,再來細緻研究下它:
[例子2]
allow netd proc:file write
這條語句的語法為:
- allow:TE的allow語句,表示授權。除了allow之外,還有allowaudit、dontaudit、neverallow等。
- netd:source type。也叫subject,domain。
- proc:target type。它代表其後的file所對應的Type。
- file:代表Object Class。它代表能夠給subject操作的一類東西。例如File、Dir、socket等。在Android系統中,有一個其他Linux系統沒有的Object Class,那就是Binder。
- write:在該類Object Class中所定義的操作。
根據SELinux規範,完整的allow相關的語句格式為:
rule_name source_type target_type : class perm_set
我們直接來看幾個例項:
[例子3]
//SEAndroid中的安全策略檔案policy.conf
#允許zygote域中的程序向init type的程序(Object Class為process)傳送sigchld訊號
allow zygote init:process sigchld;
#允許zygote域中的程序search或getattr型別為appdomain的目錄。注意,多個perm_set
#可用{}括起來
allow zygote appdomain:dir { getattr search };
#來個複雜點的:
#source_type為unconfineddomain target_type為一組type,由
#{ fs_type dev_type file_type }構成。object_class也包含兩個,為{ chr_file file }
#perm_set語法比較奇特,前面有一個~號。它表示除了{entrypoint relabelto}之外,{chr_file #file}這兩個object_class所擁有的其他操作
allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } \
~{entrypoint relabelto};
#特殊符號除了~外,還有-號和*號,其中:
# 1):-號表示去除某項內容。
# 2):*號表示所有內容。
#下面這條語句中,source_type為屬於appdomain,但不屬於unconfinedomain的程序。
#而 *表示所有和capability2相關的許可權
#neverallow:表示絕不允許。
neverallow { appdomain -unconfineddomain } self:capability2 *;
特別注意,前面曾提到說許可權必須顯示宣告,沒有宣告的話預設就沒有許可權。那neverallow語句就沒必要存在了。因為”無許可權“是不需要宣告的。確實如此,neverallow語句的作用只是在生成安全策略檔案時進行檢查,判斷是否有違反neverallow語句的allow語句。例如,筆者修改shell.te中一個語句後,生成安全策略檔案時就檢測到了衝突,如圖3所示:
圖3 neverallow的作用
如圖3所示,筆者修改shell.te後,意外導致了一條allow語句與neverallow語句衝突,從而生成安全策略檔案失敗。
下面我們來看上述allow語句中所涉及到的object class和perm set。
(1) Object class和Perm Set
Object class很難用語言說清楚它到底是怎麼定義的,所以筆者也不廢話,直接告訴大家常見的Object class有哪些。見下面的SEPolicy示例:
[external/sepolicy/security_classes示例]
.......
#此檔案定義了Android平臺中支援的Object class
#根據SELinux規範,Object Class型別由class關鍵字申明
# file-related classes
class filesystem
class file #代表普通檔案
class dir #代表目錄
class fd #代表檔案描述符
class lnk_file #代表連結檔案
class chr_file #代表字元裝置檔案
......
# network-related classes
class socket #socket
class tcp_socket
class udp_socket
......
class binder #Android平臺特有的binder
class zygote #Android平臺特有的zygote
#Android平臺特有的屬性服務。注意其後的userspace這個詞
class property_service # userspace和使用者空間中的SELinux許可權檢查有關,下文再解釋
上述示例展示了SEAndroid中Object Class的定義,其中:
- Object Class需要通過class語句申明。這些申明一般放在一個叫security_class的檔案中。
- 另外,這些class和kernel中相關模組緊密結合。
據說:在kernel編譯時會根據security_class檔案生成對應的標頭檔案。從這裡可以看出,SELinux需要根據發行平臺來做相應修改。同時可以看出,該檔案一般也不需要我們去修改。
再來看Perm set。Perm set指得是某種Object class所擁有的操作。以file這種Object class而言,其擁有的Perm set就包括read,write,open,create,execute等。
和Object class一樣,SELinux或SEAndroid所支援的Perm set也需要宣告,來看下面的例子:
[external/sepolicy/access_vectors]
#SELinux規範中,定義perm set有兩種方式,一種是使用下面的common命令
#其格式為:common common_name { permission_name ... } common定義的perm set能
#被另外一種perm set命令class所繼承
#以下是Android平臺中,file對應的許可權(perm set)。其大部分許可權讀者能猜出是幹什麼的。
#有一些許可權需要結合文後的參考文獻來學習
common file {
ioctl read write create getattr setattr lock relabelfrom relabelto
append unlink link rename execute swapon quotaon mounton }
#除了common外,還有一種class命令也可定義perm set,如下面的例子:
#class命令的完整格式是:
#class class_name [ inherits common_name ] { permission_name ... }
#inherits表示繼承了某個common定義的許可權 注意,class命令定義的許可權其實針對得就是
#某個object class。它不能被其他class繼承
class dir inherits file {
add_name remove_name reparent search rmdir open audit_access execmod
}
#來看SEAndroid中的binder和property_service這兩個Object class定義了哪些操作許可權
class binder {
impersonate call set_context_mgr transfer }
class property_service { set }
提示:Object class和Perm set的具體內容(SELinux中其實叫Access Vector)都和Linux系統/Android系統密切相關。所以,從知識鏈的角度來看,Linux程式設計基礎很重要。
(2) type,attribute和allow等
現在再來看type的定義,和type相關的命令主要有三個,如下面的例子所示:
[external/sepolicy相關檔案]
#type命令的完整格式為:type type_id [alias alias_id,] [attribute_id]
#其中,方括號中的內容為可選。alias指定了type的別名,可以指定多個別名。
#下面這個例子定義了一個名為shell的type,它和一個名為domain的屬性(attribute)關聯
type shell, domain; #本例來自shell.te,注意,可以關聯多個attribute
#屬性由attribute關鍵字定義,如attributes檔案中定義的SEAndroid使用的屬性有:
attribute domain
attribute file_type
#可以在定義type的時候,直接將其和某個attribute關聯,也可以單獨通過
#typeattribue將某個type和某個或多個attribute關聯起來,如下面這個例子
#將前面定義的system型別和mlstrustedsubject屬性關聯了起來
typeattribute system mlstrustedsubject
特別注意:對初學者而言,attribute和type的關係最難理解,因為“attribute”這個關鍵詞實在是沒取好名字,很容易產生誤解:
- 實際上,type和attribute位於同一個名稱空間,即不能用type命令和attribute命令定義相同名字的東西。
- 其實,attribute真正的意思應該是類似type(或domain) group這樣的概念。比如,將type A和attribute B關聯起來,就是說type A屬於group B中的一員。
使用attribute有什麼好處呢?一般而言,系統會定義數十或數百個Type,每個Type都需要通過allow語句來設定相應的許可權,這樣我們的安全策略檔案編起來就會非常麻煩。有了attribute之後呢,我們可以將這些Type與某個attribute關聯起來,然後用一個allow語句,直接將source_type設定為這個attribute就可以了:
- 這也正是type和attribute位於同一名稱空間的原因。
- 這種做法實際上只是減輕了TE檔案編寫者的煩惱,安全策略檔案在編譯時會將attribute拓展為其包含的type。如例子4所示:
[例子4]
#定義兩個type,分別是A_t和B_t,它們都管理到attribute_test
type A_t attribute_test;
type B_t attribute_test;
#寫一個allow語句,直接針對attribute_test
allow attribute_test C_t:file {read write};
#上面這個allow語句在編譯後的安全策略檔案中會被如下兩條語句替代:
allow A_t C_t:file {read write};
allow B_t C_t:file {read write};
前面講過,TE的完整格式為:
rule_name source_type target_type : class perm_set
所以,attribute可以出現在source_type中,也可以出現在target_type中。
提示:一般而言,定義type的時候,都會在名字後新增一個_t字尾,例如type system_t。而定義attribute的時候不會新增任何字尾。但是Android平臺沒使用這個約定俗成的做法。不過沒關係,SEAndroid中定義的attribute都在external/sepolicy/attribute這個檔案中,如果分不清是type還是attribute,則可以檢視這個檔案中定義了哪些attribute。
最後我們來看看TE中的rule_name,一共有四種:
- allow:賦予某項許可權。
[例子5]
#來自external/sepolicy/netd.te檔案
#永遠不允許netd域中的程序 讀寫 dev_type型別的 塊裝置檔案(Object class為blk_file)
neverallow netd dev_type:blk_file { read write }
(3) RBAC和constrain
絕大多數情況下,SELinux的安全配置策略需要我們編寫各種各樣的xx.te檔案。由前文可知,.te檔案內部應該包含包含了各種allow,type等語句了。這些都是TEAC,屬於SELinux MAC中的核心組成部分。
在TEAC之上,SELiunx還有一種基於Role的安全策略,也就是RBAC。RBAC到底是如何實施相關的許可權控制呢?我們先來看SEAndroid中Role和User的定義。
[external/sepolicy/roles]
#Android中只定義了一個role,名字就是r
role r;
#將上面定義的r和attribute domain關聯起來
role r types domain;
再來看user的定義。
[external/sepolicy/users]
#支援MLS的user定義格式為:
#user seuser_id roles role_id level mls_level range mls_range;
#不支援MLS user定義格式為:
#user seuser_id roles role_id;
#SEAndroid使用了支援MLS的格式。下面定義的這個user u,將和role r關聯。
#注意,一個user可以和多個role關聯。
#level之後的是該user具有的安全級別。s0為最低階,也就是預設的級別,mls_systemHigh
#為u所能獲得的最高安全級別(security level)。此處暫且不表MLS
user u roles { r } level s0 range s0 - mls_systemhigh;
那麼,Roles和User中有什麼樣的許可權控制呢?
1)首先,我們應該允許從一個role切換(SELinux用Transition表達切換之意)到另外一個role,例如:
#注意,關鍵字也是allow,但它和前面TE中的allow實際上不是一種東西
#下面這個allow允許from_role_id切換到to_role_id
allow from_role_id to_role_id;
2) 角色之間的關係。SELinux中,Role和Role之間的關係和公司中的管理人員的層級關係類似,例如:
#dominance語句屬於deprecated語句,MLS中有新的定義層級相關的語句。不過此處要介紹的是
#selinux中的層級關係
#下面這句話表示super_r dominate(統治,關鍵詞dom) sysadm_r和secadm_r這兩個角色
#反過來說,sysadm_r和secadm_r dominate by (被統治,關鍵詞 domby) super_r
#從type的角度來看,super_r將自動繼承sysadm_r和secadm_r所關聯的type(或attribute)
dominance { role super_r {role sysadm_r; role secadm_r; }
3)其他內容,由於SEAndroid沒有使用,此處不表。讀者可閱讀後面的參考文獻。
話說回來,怎麼實現基於Role或User的許可權控制呢?SELinux提供了一個新的關鍵詞,叫constrain,來看下面這個例子:
[例子6]
#constrain標準格式為:
# constrain object_class_set perm_set expression ;
#下面這句話表示只有source和target的user相同,並且role也相同,才允許
#write object_class為file的東東
constrain file write (u1 == u2 and r1 == r2) ;
前面已經介紹過object_class和perm_set了,此處就不再贅述。constrain中最關鍵的是experssion,它包含如下關鍵詞:
- u1,r1,t1:代表source的user,role和type。
- u2,r2,t2:代表target的user,role和type。
- ==和!=:==表示相等或屬於,!=表示不等或不屬於。對於u,r來說,==和!=表示相等或不等,而當諸如t1“==或!=”某個attribute時,表示源type屬於或不屬於這個attribute。
- dom,domby,incomp,eq:僅針對role,表示統治,被統治,沒關係和相同(和==一樣)
關於constrain,再補充幾個知識點:
- SEAndroid中沒有使用constrain,而是用了MLS中的mlsconstrain。所以下文將詳細介紹它。
- constrain是對TEAC的加強。因為TEAC僅針對Type或Domain,沒有針對user和role的,所以constrain在TEAC的基礎上,進一步加強了許可權控制。在實際使用過程中,SELinux進行許可權檢查時,先檢查TE是否滿足條件,然後再檢查constrain是否也滿足條件。二者都通過了,許可權才能滿足。
關於RBAC和constrain,我們就介紹到此。
提示:筆者花了很長時間來理解RBAC和constrain到底是想要幹什麼。其實這玩意很簡單。因為TE是Type Enforcement,沒user和role毛事,而RBAC則可通過constrain語句來在user和role上再加一些限制。當然,constrain也可以對type進行限制。如此而已!
2.2 Labeling介紹
前面陸陸續續講了些SELinux中最常見的東西。不過細心的人可能會問這樣一個問題:這些SContext最開始是怎麼賦給這些死的和活的東西的?Good Question!
提示:SELinux中,設定或分配SContext給程序或檔案的工作叫Security Labeling,土語叫打標籤。
(1) sid和sid_context
這個問題的回答嘛,其實也蠻簡單。Android系統啟動後(其他Linux發行版類似),init程序會將一個編譯完的安全策略檔案傳遞給kernel以初始化kernel中的SELinux相關模組(姑且用Linux Security Module:LSM來表示它把),然後LSM可根據其中的資訊給相關Object打標籤。
提示:上述說法略有不準,先且表述如此。
LSM初始化時所需要的資訊以及SContext資訊儲存在兩個特殊的檔案中,以Android為例,它們分別是:
- initial_sids:定義了LSM初始化時相關的資訊。SID是SELinux中一個概念,全稱是Security Identifier。SID其實類似SContext的key值。因為在實際執行時,如果老是去比較字串(還記得嗎,SContext是字串)會嚴重影響效率。所以SELinux會用SID來匹配某個SContext。
- initial_sid_context:為這些SID設定最初的SContext資訊。
來看這兩個檔案的內容:
[external/sepolicy/initial_sids和initial_sid_context]
#先看initial_sids
sid kernel #sid是關鍵詞,用於定義一個sid
sid security
sid unlabeled
sid fs
sid file
sid file_labels
sid init
......
#再來看initial_sid_context
sid