SEAndroid安全機制框架分析
SEAndroid安全機制所要保護的對象是系統(tǒng)中的資源,這些資源分布在各個子系統(tǒng)中,例如我們經(jīng)常接觸的文件就是分布文件子系統(tǒng)中的。實際上,系統(tǒng)中需要保護的資源非常多,除了前面說的文件之外,還有進程、socket和ipc等等。對于Android系統(tǒng)來說,由于使用了與傳統(tǒng)Linux系統(tǒng)不一樣的用戶空間運行時,即應用程序運行時框架,因此它在用戶空間有一些特有的資源是需要特別保護的,例如系統(tǒng)屬性的設置。
接下來,我們就通過圖1來觀察SEAndroid安全機制的整體框架,如下所示:
圖1 SEAndroid安全機制框架
從圖1可以看到,以SELinux文件系統(tǒng)接口為邊界,SEAndroid安全機制包含有內(nèi)核空間和用戶空間兩部分支持。在內(nèi)核空間,主要涉及到一個稱為SELinux LSM的模塊。而在用戶空間中,涉到Security Context、Security Server和SEAndroid Policy等模塊。這些內(nèi)核空間模塊和用戶空間模塊的作用以及交互如下所示:
1. 內(nèi)核空間的SELinux LSM模塊負責內(nèi)核資源的安全訪問控制。
2. 用戶空間的SEAndroid Policy描述的是資源安全訪問策略。系統(tǒng)在啟動的時候,用戶空間的Security Server需要將這些安全訪問策略加載內(nèi)核空間的SELinux LSM模塊中去。這是通過SELinux文件系統(tǒng)接口實現(xiàn)的。
3. 用戶空間的Security Context描述的是資源安全上下文。SEAndroid的安全訪問策略就是在資源的安全上下文基礎上實現(xiàn)的。
4. 用戶空間的Security Server一方面需要到用戶空間的Security Context去檢索對象的安全上下文,另一方面也需要到內(nèi)核空間去操作對象的安全上下文。
5. 用戶空間的selinux庫封裝了對SELinux文件系統(tǒng)接口的讀寫操作。用戶空間的Security Server訪問內(nèi)核空間的SELinux LSM模塊時,都是間接地通過selinux進行的。這樣可以將對SELinux文件系統(tǒng)接口的讀寫操作封裝成更有意義的函數(shù)調用。
6. 用戶空間的Security Server到用戶空間的Security Context去檢索對象的安全上下文時,同樣也是通過selinux庫來進行的。
接下來,我們就從內(nèi)核空間和用戶空間兩個角度來分析SEAndroid安全機制框架。
一. 內(nèi)核空間
在內(nèi)核空間中,存在一個SELinux LSM模塊,這個模塊包含有一個訪問向量緩沖(Access Vector Cache)和一個安全服務(Security Server)。Security Server負責安全訪問控制邏輯,即由它來決定一個主體訪問一個客體是否是合法的。這里說的主體一般就是指進程,而客體就是主體要訪問的資源,例如文件。
與SELinux Security Server相關的一個內(nèi)核子模塊是LSM,全稱是Linux Security Model。LSM可以說是為了SELinux而設計的,但是它是一個通用的安全模塊,SELinux可以使用,其它的模塊也同樣可以使用。這體現(xiàn)了Linux內(nèi)核模塊的一個重要設計思想,只提供機制實現(xiàn)而不提供策略實現(xiàn)。在我們這個例子中,LSM實現(xiàn)的就是機制,而SELinux就是在這套機制下的一個策略實現(xiàn)。也就是說,你也可以通過LSM來實現(xiàn)自己的一套MAC安全機制。
SELinux、LSM和內(nèi)核中的子系統(tǒng)是如何交互的呢?首先,SELinux會在LSM中注冊相應的回調函數(shù)。其次,LSM會在相應的內(nèi)核對象子系統(tǒng)中會加入一些Hook代碼。例如,我們調用系統(tǒng)接口read函數(shù)來讀取一個文件的時候,就會進入到內(nèi)核的文件子系統(tǒng)中。在文件子系統(tǒng)中負責讀取文件函數(shù)vfs_read就會調用LSM加入的Hook代碼。這些Hook代碼就會調用之前SELinux注冊進來的回調函數(shù),以便后者可以進行安全檢查。
SELinux在進行安全檢查的時候,首先是看一下自己的Access Vector Cache是否已經(jīng)有結果。如果有的話,就直接將結果返回給相應的內(nèi)核子系統(tǒng)就可以了。如果沒有的話,就需要到Security Server中去進行檢查。檢查出來的結果在返回給相應的內(nèi)核子系統(tǒng)的同時,也會保存在自己的Access Vector Cache中,以便下次可以快速地得到檢查結果。
上面描述的安全訪問控制流程可以通過圖2來總結,如下所示:
圖2 SELinux安全訪問控制流程
從圖2可以看到,內(nèi)核中的資源在訪問的過程中,一般需要獲得三次檢查通過:
1. 一般性錯誤檢查,例如訪問的對象是否存在、訪問參數(shù)是否正確等。
2. DAC檢查,即基于Linux UID/GID的安全檢查。
3. SELinux檢查,即基于安全上下文和安全策略的安全檢查。
二. 用戶空間
在用戶空間中,SEAndroid包含有三個主要的模塊,分別是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服務(Security Server)。接下來我們就分別對它們進行描述。
1. 安全上下文
SEAndroid是一種基于安全策略的MAC安全機制。這種安全策略又是建立在對象的安全上下文的基礎上的。這里所說的對象分為兩種類型,一種稱主體(Subject),一種稱為客體(Object)。主體通常就是指進程,而客觀就是指進程所要訪問的資源,例如文件、系統(tǒng)屬性等。
安全上下文實際上就是一個附加在對象上的標簽(Tag)。這個標簽實際上就是一個字符串,它由四部分內(nèi)容組成,分別是SELinux用戶、SELinux角色、類型、安全級別,每一個部分都通過一個冒號來分隔,格式為“user:role:type:sensitivity”。
例如,在開啟了SEAndroid安全機制的設備上執(zhí)行帶-Z選項的ls命令,就可以看到一個文件的安全上下文:
- $ ls -Z /init.rc
- -rwxr-x--- root root u:object_r:rootfs:s0 init.rc
上面的命令列出文件/init.rc的安全上下文為“u:object_r:rootfs:s0”,這表明文件/init.rc的SELinux用戶、SELinux角色、類型和安全級別分別為u、object_r、rootfs和s0。
又如,在開啟了SEAndroid安全機制的設備上執(zhí)行帶-Z選項的ps命令,就可以看到一個進程的安全上下文:
- $ ps -Z
- LABEL USER PID PPID NAME
- u:r:init:s0 root 1 0 /init
- ......
上面的命令列出進程init的安全上下文為“u:r:init:s0”,這表明進程init的SELinux用戶、SELinux角色、類型和安全級別分別為u、r、init和s0。
在安全上下文中,只有類型(Type)才是最重要的,SELinux用戶、SELinux角色和安全級別都幾乎可以忽略不計的。正因為如 此,SEAndroid安全機制又稱為是基于TE(Tyoe Enforcement)策略的安全機制。不過為了方便理解安全上下文,接下來我們還是簡單地對SELinux用戶、SELinux角色和安全級別的作用 進行介紹。
對于進程來,SELinux用戶和SELinux角色只是用來限制進程可以標注的類型。而對于文件來說,SELinux用戶和SELinux角色就可以完 全忽略不計。為了完整地描述一個文件的安全上下文,通常將它的SELinux角色固定為object_r,而將它的SELinux用戶設置為創(chuàng)建它的進程 的SELinux用戶。
在SEAndroid中,只定義了一個SELinux用戶u,因此我們通過ps -Z和ls -Z命令看到的所有的進程和文件的安全上下文中的SELinux用戶都為u。同時,SEAndroid也只定義了一個SELinux角色r,因此,我們通 過ps -Z命令看到的所有進程的安全上下文中的SELinux角色都為r。
通過external/sepolicy/users和external/sepolicy/roles文件的內(nèi)容,我們就可以看到SEAndroid所定義的SELinux用戶和SELinux角色。
文件external/sepolicy/users的內(nèi)容如下所示:
- user u roles { r } level s0 range s0 - mls_systemhigh;
述語句聲明了一個SELinux用戶u,它可用的SELinux角色為r,它的默認安全級別為s0,可用的安全級別范圍為s0~mls_systemhigh,其中,mls_systemhigh為系統(tǒng)定義的最高安全級別。
文件external/sepolicy/roles的內(nèi)容如下所示:
- role r;
- role r types domain;
第一個語句聲明了一個SELinux角色r;第二個語句允許SELinux角色r與類型domain關聯(lián)。
上面提到,對于進程來說,SELinux用戶和SELinux角色只是用來限制進程可以標注的類型,這是如何體現(xiàn)的呢?以前面列出的 external/sepolicy/users和external/sepolicy/roles文件內(nèi)容來例,如果沒有出現(xiàn)其它的user或者 role聲明,那么就意味著只有u、r和domain可以組合在一起形成一個合法的安全上下文,而其它形式的安全上下文定義均是非法的。
讀者可能注意到,前面我們通過ps -Z命令看到進程init的安全上下文為“u:r:init:s0”,按照上面的分析,這是不是一個非法的安全上下文呢?答案是否定的,因為在另外一個文 件external/sepolicy/init.te中,通過type語句聲明了類型init,并且將domain設置為類型init的屬性,如下所 示:
- type init, domain;
由于init具有屬性domain,因此它就可以像domain一樣,可以和SELinux用戶u和SELinux角色組合在一起形成合法的安全上下文。
關于SELinux用戶和SELinux角色,我們就介紹到這里,接下來我們再介紹安全級別。安全級別實際上也是一個MAC安全機制,它是建立在TE的基礎之上的。在SELinux中,安全級別是可選的,也就是說,可以選擇啟用或者不啟用。
安全級別最開始的目的是用來對政府分類文件進行訪問控制的。在基于安全級別的MAC安全機制中,主體(subject)和客體(object)都關聯(lián)有一個安全級別。其中,安全級別較高的主體可以讀取安全級別較低的客體,而安全級別較低的主體可以寫入安全級別較高的客體。前者稱為“read down”,而后者稱為“write up”。通過這種規(guī)則,可以允許數(shù)據(jù)從安全級別較低的主體流向安全級別較高的主體,而限制數(shù)據(jù)從安全級別較高的主體流向安全級別較低的主體,從而有效地保護了數(shù)據(jù)。注意,如果主體和客體的安全級別是相同的,那么主體是可以對客體進行讀和寫的。
通過圖3可以看到基于安全級別的MAC安全機制的數(shù)據(jù)流向控制,如下所示:
圖3 基于安全級別的MAC安全機制數(shù)據(jù)流
在圖3中,我們定義了兩個安全級別:PUBLIC和SECRET,其中,SECRET的安全級別高于PUBLIC。
在實際使用中,安全級別是由敏感性(Sensitivity)和類別(Category)兩部分內(nèi)容組成的,格式為“sensitivity[:category_set]”,其中,category_set是可選的。例如,假設我們定義有s0、s1兩個Sensitivity,以c0、c1、c2三個Category,那么“s0:c0,c1”表示的就是Sensitivity為s0、Category為c0和c1的一個安全級別。
介紹完成SELinux用戶、SELinux角色和安全級別之后,最后我們就介紹類型。在SEAndroid中,我們通常將用來標注文件的安全上下文中的類型稱為file_type,而用來標注進程的安全上下文的類型稱為domain,并且每一個用來描述文件安全上下文的類型都將file_type設置為其屬性,每一個用來進程安全上下文的類型都將domain設置為其屬性。
將一個類型設置為另一個類型的屬性可以通過type語句實現(xiàn)。例如,我們前面提到的用來描述進程init的安全策略的文件external/sepolicy/init.te,就使用以下的type語句來將類型 domain設置類型init的屬性:
- type init domain;
這樣就可以表明init描述的類型是用來描述進程的安全上下文的。
同樣,如果我們查看另外一個文件external/sepolicy/file.te,可以看到App數(shù)據(jù)文件的類型聲明:
- type app_data_file, file_type, data_file_type;
上述語句表明類型app_data_file具有屬笥file_type,即它是用來描述文件的安全上下文的。
了解了SEAndroid安全機制的安全上下文之后,我們就可以繼續(xù)Android系統(tǒng)中的對象的安全上下文是如何定義的了。這里我們只討論四種類型的對 象的安全上下文,分別是App進程、App數(shù)據(jù)文件、系統(tǒng)文件和系統(tǒng)屬性。這四種類型對象的安全上下文通過四個文件來描 述:mac_permissions.xml、seapp_contexts、file_contexts和property_contexts,它們均 位于external/sepolicy目錄中。
文件external/sepolicy/mac_permissions.xml的內(nèi)容如下所示:
- ?xml version="1.0" encoding="utf-8"?>
- <policy>
- <!-- Platform dev key in AOSP -->
- <signer signature="@PLATFORM" >
- <seinfo value="platform" />
- </signer>
- <!-- Media dev key in AOSP -->
- <signer signature="@MEDIA" >
- <seinfo value="media" />
- </signer>
- <!-- shared dev key in AOSP -->
- <signer signature="@SHARED" >
- <seinfo value="shared" />
- </signer>
- <!-- release dev key in AOSP -->
- <signer signature="@RELEASE" >
- <seinfo value="release" />
- </signer>
- <!-- All other keys -->
- <default>
- <seinfo value="default" />
- </default>
- </policy>
文件mac_permissions.xml給不同簽名的App分配不同的seinfo字符串,例如,在AOSP源碼環(huán)境下編譯并且使用平臺簽名的App獲得的seinfo為“platform”,使用第三方簽名安裝的App獲得的seinfo簽名為"default"。
這個seinfo描述的是其實并不是安全上下文中的Type,它是用來在另外一個文件external/sepolicy/seapp_contexts 中查找對應的Type的。文件external/sepolicy/seapp_contexts的內(nèi)容如下所示:
- # Input selectors:
- # isSystemServer (boolean)
- # user (string)
- # seinfo (string)
- # name (string)
- # sebool (string)
- # isSystemServer=true can only be used once.
- # An unspecified isSystemServer defaults to false.
- # An unspecified string selector will match any value.
- # A user string selector that ends in * will perform a prefix match.
- # user=_app will match any regular app UID.
- # user=_isolated will match any isolated service UID.
- # All specified input selectors in an entry must match (i.e. logical AND).
- # Matching is case-insensitive.
- # Precedence rules:
- # (1) isSystemServer=true before isSystemServer=false.
- # (2) Specified user= string before unspecified user= string.
- # (3) Fixed user= string before user= prefix (i.e. ending in *).
- # (4) Longer user= prefix before shorter user= prefix.
- # (5) Specified seinfo= string before unspecified seinfo= string.
- # (6) Specified name= string before unspecified name= string.
- # (7) Specified sebool= string before unspecified sebool= string.
- #
- # Outputs:
- # domain (string)
- # type (string)
- # levelFrom (string; one of none, all, app, or user)
- # level (string)
- # Only entries that specify domain= will be used for app process labeling.
- # Only entries that specify type= will be used for app directory labeling.
- # levelFrom=user is only supported for _app or _isolated UIDs.
- # levelFrom=app or levelFrom=all is only supported for _app UIDs.
- # level may be used to specify a fixed level for any UID.
- #
- isSystemServer=true domain=system
- user=system domain=system_app type=system_data_file
- user=bluetooth domain=bluetooth type=bluetooth_data_file
- user=nfc domain=nfc type=nfc_data_file
- user=radio domain=radio type=radio_data_file
- user=_app domain=untrusted_app type=app_data_file levelFrom=none
- user=_app seinfo=platform domain=platform_app type=platform_app_data_file
- user=_app seinfo=shared domain=shared_app type=platform_app_data_file
- user=_app seinfo=media domain=media_app type=platform_app_data_file
- user=_app seinfo=release domain=release_app type=platform_app_data_file
- user=_isolated domain=isolated_app
文件中的注釋解釋了如何在文件seapp_contexts查找對象的Type,這里不再累述,只是舉兩個例子來說明。
從前面的分析可知,對于使用平臺簽名的App來說,它的seinfo為“platform”。用戶空間的Security Server在為它查找對應的Type時,使用的user輸入為"_app"。這樣在seapp_contexts文件中,與它匹配的一行即為:
- user=_app seinfo=platform domain=platform_app type=platform_app_data_file
這樣我們就可以知道,使用平臺簽名的App所運行在的進程domain為“platform_app”,并且它的數(shù)據(jù)文件的file_type為“platform_app_data_file”。
又如,使用第三方簽名的App的seinfo為“default”。用戶空間的Security Server在為它查找對應的Type時,使用的user輸入也為"_app"。我們注意到,在seapp_contexts文件中,沒有一行對應的 user和seinfo分別為“_app”和“default”。但是有一行是最匹配的,即:
- user=_app domain=untrusted_app type=app_data_file levelFrom=none
這樣我們就可以知道,使用第三方簽名的App所運行在的進程domain為“unstrusted_app”,并且它的數(shù)據(jù)文件的file_type為“app_data_file”。
接下來我們再來看系統(tǒng)文件的安全上下文是如何定義的。通過查看external/sepolicy/file_contexts文件,我們就可以看到系統(tǒng)文件的安全上下文描述,如下所示:
- ###########################################
- # Root
- / u:object_r:rootfs:s0
- # Data files
- /adb_keys u:object_r:rootfs:s0
- /default.prop u:object_r:rootfs:s0
- /fstab\..* u:object_r:rootfs:s0
- /init\..* u:object_r:rootfs:s0
- /res(/.*)? u:object_r:rootfs:s0
- /ueventd\..* u:object_r:rootfs:s0
- # Executables
- /charger u:object_r:rootfs:s0
- /init u:object_r:rootfs:s0
- /sbin(/.*)? u:object_r:rootfs:s0
- ......
- #############################
- # System files
- #
- /system(/.*)? u:object_r:system_file:s0
- /system/bin/ash u:object_r:shell_exec:s0
- /system/bin/mksh u:object_r:shell_exec:s0
- ......
文件file_contexts通過正則表達式來描述系統(tǒng)文件的安全上下文。例如,在上面列出的內(nèi)容的最后三行中,倒數(shù)第三行的正則表達式表示在 /system目錄下的所有文件的安全上下文均為“u:object_r:system_file:s0”,最后兩行的正則表達式則表示文件 /system/bin/ash和/system/bin/mksh的安全上下文應為“u:object_r:shell_exec:s0”。雖然倒數(shù)第 三行的正則表達式描述的文件涵蓋后面兩個正則表達示描述的文件,但是后面兩個正則表達式描述的方式更加具體,因此/system/bin/ash和 /system/bin/mksh兩個文件的最終安全上下文都被設置為“u:object_r:shell_exec:s0”。
在Android系統(tǒng)中,有一種特殊的資源——屬性,App通過讀寫它們能夠獲得相應的信息,以及控制系統(tǒng)的行為,因此,SEAndroid也需要對它們 進行保護。這意味著Android系統(tǒng)的屬性也需要關聯(lián)有安全上下文。這是通過文件external/sepolicy /property_contexts來描述的,它的內(nèi)容如下所示:
- ##########################
- # property service keys
- #
- #
- net.rmnet0 u:object_r:radio_prop:s0
- net.gprs u:object_r:radio_prop:s0
- net.ppp u:object_r:radio_prop:s0
- net.qmi u:object_r:radio_prop:s0
- net.lte u:object_r:radio_prop:s0
- net.cdma u:object_r:radio_prop:s0
- gsm. u:object_r:radio_prop:s0
- persist.radio u:object_r:radio_prop:s0
- net.dns u:object_r:radio_prop:s0
- sys.usb.config u:object_r:radio_prop:s0
- ......
屬性的安全上下文與文件的安全上下文是類似的,它們的SELinux用戶、SELinux角色和安全級別均定義為u、object_r和s0。從上面列出 的內(nèi)容可以看出,以net.開頭的幾個屬性,以及所有以gsm.開頭的屬性、persist.radio和sys.usb.config屬性的安全上下文 均被設置為”u:object_r:radio_prop:s0“。這意味著只有有權限訪問Type為radio_prop的資源的進程才可以訪問這些屬 性。
#p#
2. 安全策略
上面我們分析了SEAndroid安全機制中的對象安全上下文,接下來我們就繼續(xù)分析SEAndroid安全機制中的安全策略。SEAndroid安全機 制中的安全策略是在安全上下文的基礎上進行描述的,也就是說,它通過主體和客體的安全上下文,定義主體是否有權限訪問客體。
前面提到,SEAndroid安全機制主要是使用對象安全上下文中的類型來定義安全策略,這種安全策略就稱Type Enforcement,簡稱TE。在external/sepolicy目錄中,所有以.te為后綴的文件經(jīng)過編譯之后,就會生成一個sepolicy 文件。這個sepolicy文件會打包在ROM中,并且保存在設備上的根目錄下,即它在設備上的路徑為/sepolicy。
接下來,我們就通過app.te文件的內(nèi)容來分析SEAndroid安全機制為使使用平臺簽名的App所定義的安全策略,相關的內(nèi)容如下所示:
- #
- # Apps signed with the platform key.
- #
- type platform_app, domain;
- permissive platform_app;
- app_domain(platform_app)
- platform_app_domain(platform_app)
- # Access the network.
- net_domain(platform_app)
- # Access bluetooth.
- bluetooth_domain(platform_app)
- unconfined_domain(platform_app)
- ......
前面在分析seapp_contexts文件的時候,我們提到,使用平臺簽名的App所運行在的進程的domain指定為"platform_app"。 從上面列出的內(nèi)容可以看出,platform_app接下來會通過app_domain、platform_app_domain、 net_domain、bluetooth_domain和unconfined_domain宏分別加入到其它的domain中去,以便可以獲得相應的 權限。接下來我們就以unconfined_domain宏為例,分析platform_app獲得了哪些權限。
宏unconfined_domain定義在文件te_macros文件中,如下所示:
- ......
- #####################################
- # unconfined_domain(domain)
- # Allow the specified domain to do anything.
- #
- define(`unconfined_domain', `
- typeattribute $1 mlstrustedsubject;
- typeattribute $1 unconfineddomain;
- ')
- ......
$1引用的就是unconfined_domain的參數(shù),即platform_app。通過接下來的兩個typeattribute語句,為 platform_app設置了mlstrustedsubject和unconfineddomain兩個屬性。也就是 說,mlstrustedsubject和unconfineddomain這兩個Type具有權限,platform_app這個Type也具有。接下 來我們主要分析unconfineddomain這個Type具有哪些權限。
文件unconfined.te定義了unconfineddomain這個Type所具有的權限,如下所示:
- allow unconfineddomain self:capability_class_set *;
- allow unconfineddomain kernel:security *;
- allow unconfineddomain kernel:system *;
- allow unconfineddomain self:memprotect *;
- allow unconfineddomain domain:process *;
- allow unconfineddomain domain:fd *;
- allow unconfineddomain domain:dir r_dir_perms;
- allow unconfineddomain domain:lnk_file r_file_perms;
- allow unconfineddomain domain:{ fifo_file file } rw_file_perms;
- allow unconfineddomain domain:socket_class_set *;
- allow unconfineddomain domain:ipc_class_set *;
- allow unconfineddomain domain:key *;
- allow unconfineddomain fs_type:filesystem *;
- allow unconfineddomain {fs_type dev_type file_type}:{ dir blk_file lnk_file sock_file fifo_file } *;
- allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } ~entrypoint;
- allow unconfineddomain node_type:node *;
- allow unconfineddomain node_type:{ tcp_socket udp_socket rawip_socket } node_bind;
- allow unconfineddomain netif_type:netif *;
- allow unconfineddomain port_type:socket_class_set name_bind;
- allow unconfineddomain port_type:{ tcp_socket dccp_socket } name_connect;
- allow unconfineddomain domain:peer recv;
- allow unconfineddomain domain:binder { call transfer set_context_mgr };
- allow unconfineddomain property_type:property_service set;
一個Type所具有的權限是通過allow語句來描述的,以下這個allow語句:
- allow unconfineddomain domain:binder { call transfer set_context_mgr };
表明domain為unconfineddomain的進程可以與其它進程進行binder ipc通信(call),并且能夠向這些進程傳遞Binder對象(transfer),以及將自己設置為Binder上下文管理器(set_context_mgr)。
注意,SEAndroid使用的是最小權限原則,也就是說,只有通過allow語句聲明的權限才是允許的,而其它沒有通過allow語句聲明的權限都是禁止,這樣就可以最大限度地保護系統(tǒng)中的資源。
如果我們繼續(xù)分析app.te的內(nèi)容,會發(fā)現(xiàn)使用第三方簽名的App所運行在的進程同樣是加入到unconfineddomain這個domain的,如下所示:
- #
- # Untrusted apps.
- #
- type untrusted_app, domain;
- permissive untrusted_app;
- app_domain(untrusted_app)
- net_domain(untrusted_app)
- bluetooth_domain(untrusted_app)
- unconfined_domain(untrusted_app)
這是不是意味著使用平臺簽名和第三方簽名的App所具有的權限都是一樣的呢?答案是否定的。雖然使用平臺簽名和第三方簽名的App在SEAndroid安 全框架的約束下都具有unconfineddomain這個domain所賦予的權限,但是別忘記,在進行SEAndroid安全檢查之前,使用平臺簽名 和第三方簽名的App首先要通過DAC檢查,也就是要通過傳統(tǒng)的Linux UID/GID安全檢查。由于使用平臺簽名和第三方簽名的App在安裝的時候分配到的Linux UID/GID是不一樣的,因此就決定了它們所具有權限是不一樣的。
同時,這里使用平臺簽名和第三方簽名的App之所以會同時被賦予unconfineddomain這個domain的權限,是因為前面我們分析的 app.te文件是來自于Android 4.3的。在Android 4.3中,SEAndroid安全機制是試驗性質的,并且啟用的是Permissive模式,也就是即使主體違反了安全策略,也只是會發(fā)出警告,而不會真 的拒絕執(zhí)行。如果我們分析的是Android 4.4的app.te文件,就會發(fā)現(xiàn),使用第三方簽名的App不再具有大部分unconfineddomain這個domain的權限,因為 Android 4.4的SEAndroid安全機制不再是試驗性質的,并且啟用的Enforcing模式。
以上描述的就是基于TE的安全策略,它的核心思想就是最小權限原則,即主體對客體擁有的權限必須要通過allow語句定義才允許,否則的話,一切都是禁止的。
前面我們還提到,SEAndroid安全機制的安全策略經(jīng)過編譯后會得到一個sepolicy文件,并且最終保存在設備上的根據(jù)目錄下。注意,如果我們什 么也不做,那么保存在這個sepolicy文件中的安全策略是不會自動加載到內(nèi)核空間的SELinux LSM模塊去的。它需要我們在系統(tǒng)啟動的過程中進行加載。
系統(tǒng)中第一個啟動的進程是init進程。我們知道,Init進程在啟動的過程中,執(zhí)行了很多的系統(tǒng)初始化工作,其中就包括加載SEAndroid安全策略的工作,如下所示:
- int main(int argc, char **argv)
- {
- ......
- union selinux_callback cb;
- cb.func_log = klog_write;
- selinux_set_callback(SELINUX_CB_LOG, cb);
- cb.func_audit = audit_callback;
- selinux_set_callback(SELINUX_CB_AUDIT, cb);
- INFO("loading selinux policy\n");
- if (selinux_enabled) {
- if (selinux_android_load_policy() < 0) {
- selinux_enabled = 0;
- INFO("SELinux: Disabled due to failed policy load\n");
- } else {
- selinux_init_all_handles();
- }
- } else {
- INFO("SELinux: Disabled by command line option\n");
- }
- ......
- }
上述代碼定義在文件system/core/init/init.c中。
這里調用到了三個與SEAndroid相關的函數(shù):selinux_set_callback、selinux_android_load_policy 和selinux_init_all_handles,其中,selinux_set_callback和 selinux_android_load_policy來自于libselinux,而selinux_init_all_handles也是定義在文 件system/core/init/init.c中,并且它最終也是通過調用libselinux的函數(shù)來打開前面分析file_contexts和 property_contexts文件,以便可以用來查詢系統(tǒng)文件和系統(tǒng)屬性的安全上下文。
函數(shù)selinux_set_callback用來向libselinux設置SEAndroid日志和審計回調函數(shù),而函數(shù) selinux_android_load_policy則是用來加載安全策略到內(nèi)核空間的SELinux LSM模塊中去。我們重點關注函數(shù)selinux_android_load_policy的實現(xiàn)。
函數(shù)selinux_android_load_policy定義在文件external/libselinux/src/android.c,它的實現(xiàn)如下所示:
- nt selinux_android_load_policy(void)
- {
- char *mnt = SELINUXMNT;
- int rc;
- rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
- if (rc < 0) {
- if (errno == ENODEV) {
- /* SELinux not enabled in kernel */
- return -1;
- }
- if (errno == ENOENT) {
- /* Fall back to legacy mountpoint. */
- mnt = OLDSELINUXMNT;
- rc = mkdir(mnt, 0755);
- if (rc == -1 && errno != EEXIST) {
- selinux_log(SELINUX_ERROR,"SELinux: Could not mkdir: %s\n",
- strerror(errno));
- return -1;
- }
- rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
- }
- }
- if (rc < 0) {
- selinux_log(SELINUX_ERROR,"SELinux: Could not mount selinuxfs: %s\n",
- strerror(errno));
- return -1;
- }
- set_selinuxmnt(mnt);
- return selinux_android_reload_policy();
- }
SELINUXMNT、OLDSELINUXMNT和SELINUXFS是三個宏,它們定義在文件external/libselinux/src/policy.h文件中,如下所示:
- /* Preferred selinuxfs mount point directory paths. */
- #define SELINUXMNT "/sys/fs/selinux"
- #define OLDSELINUXMNT "/selinux"
- /* selinuxfs filesystem type string. */
- #define SELINUXFS "selinuxfs"
回到函數(shù)selinux_android_load_policy中,我們不難發(fā)現(xiàn)它的實現(xiàn)邏輯如下所示:
A. 以/sys/fs/selinux為安裝點,安裝一個類型為selinuxfs的文件系統(tǒng),也就是SELinux文件系統(tǒng),用來與內(nèi)核空間的SELinux LSM模塊通信。
B. 如果不能在/sys/fs/selinux這個安裝點安裝SELinux文件系統(tǒng),那么再以/selinux為安裝點,安裝SELinux文件系統(tǒng)。
C. 成功安裝SELinux文件系統(tǒng)之后,接下來就調用另外一個函數(shù)selinux_android_reload_policy來將SEAndroid安全策略加載到內(nèi)核空間的SELinux LSM模塊中去。
在較舊版本的Linux系統(tǒng)中,SELinux文件系統(tǒng)是以/selinux為安裝點的,不過后面較新的版本都是以/sys/fs/selinux為安裝點的,Android系統(tǒng)使用的是后者。
函數(shù)selinux_android_reload_policy也是定義在文件external/libselinux/src/android.c中,它的實現(xiàn)如下所示:
- tatic const char *const sepolicy_file[] = {
- "/data/security/current/sepolicy",
- "/sepolicy",
- 0 };
- ......
- int selinux_android_reload_policy(void)
- {
- int fd = -1, rc;
- struct stat sb;
- void *map = NULL;
- int i = 0;
- while (fd < 0 && sepolicy_file[i]) {
- fd = open(sepolicy_file[i], O_RDONLY | O_NOFOLLOW);
- i++;
- }
- if (fd < 0) {
- selinux_log(SELINUX_ERROR, "SELinux: Could not open sepolicy: %s\n",
- strerror(errno));
- return -1;
- }
- if (fstat(fd, &sb) < 0) {
- selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
- sepolicy_file[i], strerror(errno));
- close(fd);
- return -1;
- }
- map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (map == MAP_FAILED) {
- selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n",
- sepolicy_file[i], strerror(errno));
- close(fd);
- return -1;
- }
- rc = security_load_policy(map, sb.st_size);
- if (rc < 0) {
- selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n",
- strerror(errno));
- munmap(map, sb.st_size);
- close(fd);
- return -1;
- }
- munmap(map, sb.st_size);
- close(fd);
- selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[i]);
- return 0;
- }
函數(shù)selinux_android_reload_policy的執(zhí)行過程如下所示:
A. 依次從/data/security/current和根目錄尋找sepolicy文件,找到之后就打開,獲得一個文件描述符fd。
B. 通過文件描述符fd將前面打開的sepolicy文件的內(nèi)容映射到內(nèi)存中來,并且得到它的起始地址為map。
C. 調用另外一個函數(shù)security_load_policy將已經(jīng)映射到內(nèi)存中的sepolicy文件內(nèi)容,即SEAndroid安全策略,加載到內(nèi)核空間的SELinux LSM模塊中去。
D. 加載完成后,釋放sepolicy文件占用的內(nèi)存,并且關閉sepolicy文件。
函數(shù)security_load_policy定義在文件external/libselinux/src/load_policy.c中,它的實現(xiàn)如下所
- nt security_load_policy(void *data, size_t len)
- {
- char path[PATH_MAX];
- int fd, ret;
- if (!selinux_mnt) {N
- errno = ENOENT;
- return -1;
- }
- snprintf(path, sizeof path, "%s/load", selinux_mnt);
- fd = open(path, O_RDWR);
- if (fd < 0)
- return -1;
- ret = write(fd, data, len);
- close(fd);
- if (ret < 0)
- return -1;
- return 0;
- }
selinux_mnt是一個全局變量,它描述的是SELinux文件系統(tǒng)的安裝點。在我們這個情景中,它的值就等于/sys/fs/selinux。
函數(shù)security_load_policy的實現(xiàn)很簡單,它首先打/sys/fs/selinux/load文件,然后將參數(shù)data所描述的安全策 略寫入到這個文件中去。由于/sys/fs/selinux是由內(nèi)核空間的SELinux LSM模塊導出來的文件系統(tǒng)接口,因此當我們將安全策略寫入到位于該文件系統(tǒng)中的load文件時,就相當于是將安全策略從用戶空間加載到SELinux LSM模塊中去了。以后SELinux LSM模塊中的Security Server就可以通過它來進行安全檢查。
#p#
3. Security Server
用戶空間的Security Server主要是用來保護用戶空間資源的,以及用來操作內(nèi)核空間對象的安全上下文的,它由應用程序安裝服務 PackageManagerService、應用程序安裝守護進程installd、應用程序進程孵化器Zygote進程以及init進程組成。其 中,PackageManagerService和installd負責創(chuàng)建App數(shù)據(jù)目錄的安全上下文,Zygote進程負責創(chuàng)建App進程的安全上下 文,而init進程負責控制系統(tǒng)屬性的安全訪問。
應用程序安裝服務PackageManagerService在啟動的時候,會在/etc/security目錄中找到我們前面分析的 mac_permissions.xml文件,然后對它進行解析,得到App簽名或者包名與seinfo的對應關系。當 PackageManagerService安裝App的時候,它就會根據(jù)其簽名或者包名查找到對應的seinfo,并且將這個seinfo傳遞給另外一 個守護進程installed。
守護進程installd負責創(chuàng)建App數(shù)據(jù)目錄。在創(chuàng)建App數(shù)據(jù)目錄的時候,需要給它設置安全上下文,使得SEAndroid安全機制可以對它進行安 全訪問控制。Installd根據(jù)PackageManagerService傳遞過來的seinfo,并且調用libselinux庫提供的 selabel_lookup函數(shù)到前面我們分析的seapp_contexts文件中查找到對應的Type。有了這個Type之后,installd就 可以給正在安裝的App的數(shù)據(jù)目錄設置安全上下文了,這是通過調用libselinux庫提供的lsetfilecon函數(shù)來實現(xiàn)的。
從前面Android應用程序進程啟動過程的源代碼分析和Android系統(tǒng)進程Zygote啟動過程的源代碼分析這 兩篇文章可以知道,在Android系統(tǒng)中,Zygote進程負責創(chuàng)建應用程序進程。應用程序進程是SEAndroid安全機制中的主體,因此它們也需要 設置安全上下文,這是由Zygote進程來設置的。組件管理服務ActivityManagerService在請求Zygote進程創(chuàng)建應用程序進程之 前,會到PackageManagerService中去查詢對應的seinfo,并且將這個seinfo傳遞到Zygote進程。于是,Zygote進 程在fork一個應用程序進程之后,就會使用ActivityManagerService傳遞過來的seinfo,并且調用libselinux庫提供 的selabel_lookup函數(shù)到前面我們分析的seapp_contexts文件中查找到對應的Domain。有了這個Domain之 后,Zygote進程就可以給剛才創(chuàng)建的應用程序進程設置安全上下文了,這是通過調用libselinux庫提供的lsetcon函數(shù)來實現(xiàn)的。
前面提到,在Android系統(tǒng)中,屬性也是一項需要保護的資源。Init進程在啟動的時候,會創(chuàng)建一塊內(nèi)存區(qū)域來維護系統(tǒng)中的屬性,接著還會創(chuàng)建一個 Property服務。這個Property服務通過socket提供接口給其它進程訪問Android系統(tǒng)中的屬性。其它進程通過socket來和 Property服務通信時,Property服務可以獲得它的安全上下文。有了這個安全上下文之后,Property服務就可以通過 libselinux庫提供的selabel_lookup函數(shù)到前面我們分析的property_contexts去查找要訪問的屬性的安全上下文了。 有了這兩個安全上下文之后,Property服務就可以決定是否允許一個進程訪問它所指定的屬性了。
本文鏈接:http://blog.csdn.net/luoshengyang/article/details/37613135