Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> SEAndroid安全機制框架分析

SEAndroid安全機制框架分析

編輯:關於Android編程

我們知道,Android系統基於Linux實現。針對傳統Linux系統,NSA開發了一套安全機制SELinux,用來加強安全性。然而,由於Android系統有著獨特的用戶空間運行時,因此SELinux不能完全適用於Android系統。為此,NSA針對Android系統,在SELinux基礎上開發了SEAndroid。本文就對SEAndroid安全機制框架進行分析,以便後面可以更好地分析其實現細節。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

SEAndroid安全機制所要保護的對象是系統中的資源,這些資源分布在各個子系統中,例如我們經常接觸的文件就是分布文件子系統中的。實際上,系統中需要保護的資源非常多,除了前面說的文件之外,還有進程、socket和ipc等等。對於Android系統來說,由於使用了與傳統Linux系統不一樣的用戶空間運行時,即應用程序運行時框架,因此它在用戶空間有一些特有的資源是需要特別保護的,例如系統屬性的設置。

接下來,我們就通過圖1來觀察SEAndroid安全機制的整體框架,如下所示:

\

圖1 SEAndroid安全機制框架

從圖1可以看到,以SELinux文件系統接口為邊界,SEAndroid安全機制包含有內核空間和用戶空間兩部分支持。在內核空間,主要涉及到一個稱為SELinux LSM的模塊。而在用戶空間中,涉到Security Context、Security Server和SEAndroid Policy等模塊。這些內核空間模塊和用戶空間模塊的作用以及交互如下所示:

1. 內核空間的SELinux LSM模塊負責內核資源的安全訪問控制。

2. 用戶空間的SEAndroid Policy描述的是資源安全訪問策略。系統在啟動的時候,用戶空間的Security Server需要將這些安全訪問策略加載內核空間的SELinux LSM模塊中去。這是通過SELinux文件系統接口實現的。

3. 用戶空間的Security Context描述的是資源安全上下文。SEAndroid的安全訪問策略就是在資源的安全上下文基礎上實現的。

4. 用戶空間的Security Server一方面需要到用戶空間的Security Context去檢索對象的安全上下文,另一方面也需要到內核空間去操作對象的安全上下文。

5. 用戶空間的selinux庫封裝了對SELinux文件系統接口的讀寫操作。用戶空間的Security Server訪問內核空間的SELinux LSM模塊時,都是間接地通過selinux進行的。這樣可以將對SELinux文件系統接口的讀寫操作封裝成更有意義的函數調用。

6. 用戶空間的Security Server到用戶空間的Security Context去檢索對象的安全上下文時,同樣也是通過selinux庫來進行的。

接下來,我們就從內核空間和用戶空間兩個角度來分析SEAndroid安全機制框架。

一. 內核空間

在內核空間中,存在一個SELinux LSM模塊,這個模塊包含有一個訪問向量緩沖(Access Vector Cache)和一個安全服務(Security Server)。Security Server負責安全訪問控制邏輯,即由它來決定一個主體訪問一個客體是否是合法的。這裡說的主體一般就是指進程,而客體就是主體要訪問的資源,例如文件。

與SELinux Security Server相關的一個內核子模塊是LSM,全稱是Linux Security Model。LSM可以說是為了SELinux而設計的,但是它是一個通用的安全模塊,SELinux可以使用,其它的模塊也同樣可以使用。這體現了Linux內核模塊的一個重要設計思想,只提供機制實現而不提供策略實現。在我們這個例子中,LSM實現的就是機制,而SELinux就是在這套機制下的一個策略實現。也就是說,你也可以通過LSM來實現自己的一套MAC安全機制。

SELinux、LSM和內核中的子系統是如何交互的呢?首先,SELinux會在LSM中注冊相應的回調函數。其次,LSM會在相應的內核對象子系統中會加入一些Hook代碼。例如,我們調用系統接口read函數來讀取一個文件的時候,就會進入到內核的文件子系統中。在文件子系統中負責讀取文件函數vfs_read就會調用LSM加入的Hook代碼。這些Hook代碼就會調用之前SELinux注冊進來的回調函數,以便後者可以進行安全檢查。

SELinux在進行安全檢查的時候,首先是看一下自己的Access Vector Cache是否已經有結果。如果有的話,就直接將結果返回給相應的內核子系統就可以了。如果沒有的話,就需要到Security Server中去進行檢查。檢查出來的結果在返回給相應的內核子系統的同時,也會保存在自己的Access Vector Cache中,以便下次可以快速地得到檢查結果。

上面描述的安全訪問控制流程可以通過圖2來總結,如下所示:

\

圖2 SELinux安全訪問控制流程

從圖2可以看到,內核中的資源在訪問的過程中,一般需要獲得三次檢查通過:

1. 一般性錯誤檢查,例如訪問的對象是否存在、訪問參數是否正確等。

2. DAC檢查,即基於Linux UID/GID的安全檢查。

3. SELinux檢查,即基於安全上下文和安全策略的安全檢查。

二. 用戶空間

在用戶空間中,SEAndroid包含有三個主要的模塊,分別是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服務(Security Server)。接下來我們就分別對它們進行描述。

1. 安全上下文

SEAndroid是一種基於安全策略的MAC安全機制。這種安全策略又是建立在對象的安全上下文的基礎上的。這裡所說的對象分為兩種類型,一種稱主體(Subject),一種稱為客體(Object)。主體通常就是指進程,而客觀就是指進程所要訪問的資源,例如文件、系統屬性等。

安全上下文實際上就是一個附加在對象上的標簽(Tag)。這個標簽實際上就是一個字符串,它由四部分內容組成,分別是SELinux用戶、SELinux角色、類型、安全級別,每一個部分都通過一個冒號來分隔,格式為“user:role:type:sensitivity”。

例如,在開啟了SEAndroid安全機制的設備上執行帶-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安全機制的設備上執行帶-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用戶設置為創建它的進程的SELinux用戶。

在SEAndroid中,只定義了一個SELinux用戶u,因此我們通過ps -Z和ls -Z命令看到的所有的進程和文件的安全上下文中的SELinux用戶都為u。同時,SEAndroid也只定義了一個SELinux角色r,因此,我們通過ps -Z命令看到的所有進程的安全上下文中的SELinux角色都為r。

通過external/sepolicy/users和external/sepolicy/roles文件的內容,我們就可以看到SEAndroid所定義的SELinux用戶和SELinux角色。

文件external/sepolicy/users的內容如下所示:

user u roles { r } level s0 range s0 - mls_systemhigh;
上述語句聲明了一個SELinux用戶u,它可用的SELinux角色為r,它的默認安全級別為s0,可用的安全級別范圍為s0~mls_systemhigh,其中,mls_systemhigh為系統定義的最高安全級別。

文件external/sepolicy/roles的內容如下所示:

role r;
role r types domain;
第一個語句聲明了一個SELinux角色r;第二個語句允許SELinux角色r與類型domain關聯。

上面提到,對於進程來說,SELinux用戶和SELinux角色只是用來限制進程可以標注的類型,這是如何體現的呢?以前面列出的external/sepolicy/users和external/sepolicy/roles文件內容來例,如果沒有出現其它的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)都關聯有一個安全級別。其中,安全級別較高的主體可以讀取安全級別較低的客體,而安全級別較低的主體可以寫入安全級別較高的客體。前者稱為“read down”,而後者稱為“write up”。通過這種規則,可以允許數據從安全級別較低的主體流向安全級別較高的主體,而限制數據從安全級別較高的主體流向安全級別較低的主體,從而有效地保護了數據。注意,如果主體和客體的安全級別是相同的,那麼主體是可以對客體進行讀和寫的。

通過圖3可以看到基於安全級別的MAC安全機制的數據流向控制,如下所示:

\

圖3 基於安全級別的MAC安全機制數據流

在圖3中,我們定義了兩個安全級別:PUBLIC和SECRET,其中,SECRET的安全級別高於PUBLIC。

在實際使用中,安全級別是由敏感性(Sensitivity)和類別(Category)兩部分內容組成的,格式為“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語句實現。例如,我們前面提到的用來描述進程init的安全策略的文件external/sepolicy/init.te,就使用以下的type語句來將類型 domain設置類型init的屬性:

type init domain;
這樣就可以表明init描述的類型是用來描述進程的安全上下文的。

同樣,如果我們查看另外一個文件external/sepolicy/file.te,可以看到App數據文件的類型聲明:

type app_data_file, file_type, data_file_type;
上述語句表明類型app_data_file具有屬笥file_type,即它是用來描述文件的安全上下文的。

了解了SEAndroid安全機制的安全上下文之後,我們就可以繼續Android系統中的對象的安全上下文是如何定義的了。這裡我們只討論四種類型的對象的安全上下文,分別是App進程、App數據文件、系統文件和系統屬性。這四種類型對象的安全上下文通過四個文件來描述:mac_permissions.xml、seapp_contexts、file_contexts和property_contexts,它們均位於external/sepolicy目錄中。

文件external/sepolicy/mac_permissions.xml的內容如下所示:




    
    
      
    

    
    
      
    

    
    
      
    

    
    
      
    

    
    
      
    

文件mac_permissions.xml給不同簽名的App分配不同的seinfo字符串,例如,在AOSP源碼環境下編譯並且使用平台簽名的App獲得的seinfo為“platform”,使用第三方簽名安裝的App獲得的seinfo簽名為"default"。

這個seinfo描述的是其實並不是安全上下文中的Type,它是用來在另外一個文件external/sepolicy/seapp_contexts中查找對應的Type的。文件external/sepolicy/seapp_contexts的內容如下所示:

# 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”,並且它的數據文件的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”,並且它的數據文件的file_type為“app_data_file”。

接下來我們再來看系統文件的安全上下文是如何定義的。通過查看external/sepolicy/file_contexts文件,我們就可以看到系統文件的安全上下文描述,如下所示:

###########################################
# 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通過正則表達式來描述系統文件的安全上下文。例如,在上面列出的內容的最後三行中,倒數第三行的正則表達式表示在/system目錄下的所有文件的安全上下文均為“u:object_r:system_file:s0”,最後兩行的正則表達式則表示文件/system/bin/ash和/system/bin/mksh的安全上下文應為“u:object_r:shell_exec:s0”。雖然倒數第三行的正則表達式描述的文件涵蓋後面兩個正則表達示描述的文件,但是後面兩個正則表達式描述的方式更加具體,因此/system/bin/ash和/system/bin/mksh兩個文件的最終安全上下文都被設置為“u:object_r:shell_exec:s0”。

在Android系統中,有一種特殊的資源——屬性,App通過讀寫它們能夠獲得相應的信息,以及控制系統的行為,因此,SEAndroid也需要對它們進行保護。這意味著Android系統的屬性也需要關聯有安全上下文。這是通過文件external/sepolicy/property_contexts來描述的,它的內容如下所示:

##########################
# 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。從上面列出的內容可以看出,以net.開頭的幾個屬性,以及所有以gsm.開頭的屬性、persist.radio和sys.usb.config屬性的安全上下文均被設置為”u:object_r:radio_prop:s0“。這意味著只有有權限訪問Type為radio_prop的資源的進程才可以訪問這些屬性。

2. 安全策略

上面我們分析了SEAndroid安全機制中的對象安全上下文,接下來我們就繼續分析SEAndroid安全機制中的安全策略。SEAndroid安全機制中的安全策略是在安全上下文的基礎上進行描述的,也就是說,它通過主體和客體的安全上下文,定義主體是否有權限訪問客體。

前面提到,SEAndroid安全機制主要是使用對象安全上下文中的類型來定義安全策略,這種安全策略就稱Type Enforcement,簡稱TE。在external/sepolicy目錄中,所有以.te為後綴的文件經過編譯之後,就會生成一個sepolicy文件。這個sepolicy文件會打包在ROM中,並且保存在設備上的根目錄下,即它在設備上的路徑為/sepolicy。

接下來,我們就通過app.te文件的內容來分析SEAndroid安全機制為使使用平台簽名的App所定義的安全策略,相關的內容如下所示:

#
# 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"。從上面列出的內容可以看出,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的參數,即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語句聲明的權限都是禁止,這樣就可以最大限度地保護系統中的資源。

如果我們繼續分析app.te的內容,會發現使用第三方簽名的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檢查,也就是要通過傳統的Linux UID/GID安全檢查。由於使用平台簽名和第三方簽名的App在安裝的時候分配到的Linux UID/GID是不一樣的,因此就決定了它們所具有權限是不一樣的。

同時,這裡使用平台簽名和第三方簽名的App之所以會同時被賦予unconfineddomain這個domain的權限,是因為前面我們分析的app.te文件是來自於Android 4.3的。在Android 4.3中,SEAndroid安全機制是試驗性質的,並且啟用的是Permissive模式,也就是即使主體違反了安全策略,也只是會發出警告,而不會真的拒絕執行。如果我們分析的是Android 4.4的app.te文件,就會發現,使用第三方簽名的App不再具有大部分unconfineddomain這個domain的權限,因為Android 4.4的SEAndroid安全機制不再是試驗性質的,並且啟用的Enforcing模式。

以上描述的就是基於TE的安全策略,它的核心思想就是最小權限原則,即主體對客體擁有的權限必須要通過allow語句定義才允許,否則的話,一切都是禁止的。

前面我們還提到,SEAndroid安全機制的安全策略經過編譯後會得到一個sepolicy文件,並且最終保存在設備上的根據目錄下。注意,如果我們什麼也不做,那麼保存在這個sepolicy文件中的安全策略是不會自動加載到內核空間的SELinux LSM模塊去的。它需要我們在系統啟動的過程中進行加載。

系統中第一個啟動的進程是init進程。我們知道,Init進程在啟動的過程中,執行了很多的系統初始化工作,其中就包括加載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相關的函數: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的函數來打開前面分析file_contexts和property_contexts文件,以便可以用來查詢系統文件和系統屬性的安全上下文。

函數selinux_set_callback用來向libselinux設置SEAndroid日志和審計回調函數,而函數selinux_android_load_policy則是用來加載安全策略到內核空間的SELinux LSM模塊中去。我們重點關注函數selinux_android_load_policy的實現。

函數selinux_android_load_policy定義在文件external/libselinux/src/android.c,它的實現如下所示:

int 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"
回到函數selinux_android_load_policy中,我們不難發現它的實現邏輯如下所示:

A. 以/sys/fs/selinux為安裝點,安裝一個類型為selinuxfs的文件系統,也就是SELinux文件系統,用來與內核空間的SELinux LSM模塊通信。

B. 如果不能在/sys/fs/selinux這個安裝點安裝SELinux文件系統,那麼再以/selinux為安裝點,安裝SELinux文件系統。

C. 成功安裝SELinux文件系統之後,接下來就調用另外一個函數selinux_android_reload_policy來將SEAndroid安全策略加載到內核空間的SELinux LSM模塊中去。

在較舊版本的Linux系統中,SELinux文件系統是以/selinux為安裝點的,不過後面較新的版本都是以/sys/fs/selinux為安裝點的,Android系統使用的是後者。

函數selinux_android_reload_policy也是定義在文件external/libselinux/src/android.c中,它的實現如下所示:

static 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;
}
函數selinux_android_reload_policy的執行過程如下所示:

A. 依次從/data/security/current和根目錄尋找sepolicy文件,找到之後就打開,獲得一個文件描述符fd。

B. 通過文件描述符fd將前面打開的sepolicy文件的內容映射到內存中來,並且得到它的起始地址為map。

C. 調用另外一個函數security_load_policy將已經映射到內存中的sepolicy文件內容,即SEAndroid安全策略,加載到內核空間的SELinux LSM模塊中去。

D. 加載完成後,釋放sepolicy文件占用的內存,並且關閉sepolicy文件。

函數security_load_policy定義在文件external/libselinux/src/load_policy.c中,它的實現如下所示:

int 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文件系統的安裝點。在我們這個情景中,它的值就等於/sys/fs/selinux。

函數security_load_policy的實現很簡單,它首先打/sys/fs/selinux/load文件,然後將參數data所描述的安全策略寫入到這個文件中去。由於/sys/fs/selinux是由內核空間的SELinux LSM模塊導出來的文件系統接口,因此當我們將安全策略寫入到位於該文件系統中的load文件時,就相當於是將安全策略從用戶空間加載到SELinux LSM模塊中去了。以後SELinux LSM模塊中的Security Server就可以通過它來進行安全檢查。

3. Security Server

用戶空間的Security Server主要是用來保護用戶空間資源的,以及用來操作內核空間對象的安全上下文的,它由應用程序安裝服務PackageManagerService、應用程序安裝守護進程installd、應用程序進程孵化器Zygote進程以及init進程組成。其中,PackageManagerService和installd負責創建App數據目錄的安全上下文,Zygote進程負責創建App進程的安全上下文,而init進程負責控制系統屬性的安全訪問。

應用程序安裝服務PackageManagerService在啟動的時候,會在/etc/security目錄中找到我們前面分析的mac_permissions.xml文件,然後對它進行解析,得到App簽名或者包名與seinfo的對應關系。當PackageManagerService安裝App的時候,它就會根據其簽名或者包名查找到對應的seinfo,並且將這個seinfo傳遞給另外一個守護進程installed。

守護進程installd負責創建App數據目錄。在創建App數據目錄的時候,需要給它設置安全上下文,使得SEAndroid安全機制可以對它進行安全訪問控制。Installd根據PackageManagerService傳遞過來的seinfo,並且調用libselinux庫提供的selabel_lookup函數到前面我們分析的seapp_contexts文件中查找到對應的Type。有了這個Type之後,installd就可以給正在安裝的App的數據目錄設置安全上下文了,這是通過調用libselinux庫提供的lsetfilecon函數來實現的。

從前面Android應用程序進程啟動過程的源代碼分析和Android系統進程Zygote啟動過程的源代碼分析這兩篇文章可以知道,在Android系統中,Zygote進程負責創建應用程序進程。應用程序進程是SEAndroid安全機制中的主體,因此它們也需要設置安全上下文,這是由Zygote進程來設置的。組件管理服務ActivityManagerService在請求Zygote進程創建應用程序進程之前,會到PackageManagerService中去查詢對應的seinfo,並且將這個seinfo傳遞到Zygote進程。於是,Zygote進程在fork一個應用程序進程之後,就會使用ActivityManagerService傳遞過來的seinfo,並且調用libselinux庫提供的selabel_lookup函數到前面我們分析的seapp_contexts文件中查找到對應的Domain。有了這個Domain之後,Zygote進程就可以給剛才創建的應用程序進程設置安全上下文了,這是通過調用libselinux庫提供的lsetcon函數來實現的。

前面提到,在Android系統中,屬性也是一項需要保護的資源。Init進程在啟動的時候,會創建一塊內存區域來維護系統中的屬性,接著還會創建一個Property服務。這個Property服務通過socket提供接口給其它進程訪問Android系統中的屬性。其它進程通過socket來和Property服務通信時,Property服務可以獲得它的安全上下文。有了這個安全上下文之後,Property服務就可以通過libselinux庫提供的selabel_lookup函數到前面我們分析的property_contexts去查找要訪問的屬性的安全上下文了。有了這兩個安全上下文之後,Property服務就可以決定是否允許一個進程訪問它所指定的屬性了。

至此,我們就分析完成SEAndroid安全機制的整體框架了。有了這些基礎知識之後,接下來我們就可以更加深入地去分析一些具體的使用情景了。例如,我們前面介紹的用戶空間的Security Server是如何一步一步地設置應用程序數據目錄和應用程序進程的安全上下文的,以及Init進程是如何控制系統中的屬性訪問的,敬請關注!更多信息可以關注老羅的新浪微博:http://weibo.com/shengyangluo。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved