android 8.1 安全機制 — SEAndroid & SELinux
原文鏈接:https://blog.csdn.net/qq_19923217/article/details/81240027
移植有關AT指令的應用程序時,發現很多
1. SELinux 背景知識
詳細了解 Android 8.0 SELinux,可以參閱 Google 官方文檔
1.1 DAC 與 MAC
在 SELinux 出現之前,Linux 上的安全模型叫 DAC,全稱是 Discretionary Access Control,翻譯為自主訪問控制。
DAC 的核心思想很簡單,就是:進程理論上所擁有的權限與執行它的用戶的權限相同。比如,以 root 用戶啟動 Browser,那么 Browser 就有 root 用戶的權限,在 Linux 系統上能干任何事情。
顯然,DAD 管理太過寬松,只要想辦法在 Android 系統上獲取到 root 權限就可以了。那么 SELinux 是怎么解決這個問題呢?在 DAC 之外,它設計了一種新的安全模型,叫 MAC(Mandatory Access Control),翻譯為強制訪問控制。
MAC 的理論也很簡單,任何進程想在 SELinux 系統上干任何事情,都必須在《安全策略文件》中賦予權限,凡是沒有出現在安全策略文件中的權限,就不行。
關於 DAC 和 MAC,可以總結幾個知識點:
- Linux 系統先做 DAC 檢查。如果沒有通過 DAC 權限檢查,則操作直接失敗。通過 DAC 檢查之后,再做 MAC 權限檢查
- SELinux 有自己的一套規則來編寫安全策略文件,這套規則被稱之為 SELinux Policy 語言。
1.2 SEPolicy 語言
Linux中有兩種東西,一種死的(Inactive),一種活的(Active)。死的東西就是文件(Linux哲學,萬物皆文件。注意,萬不可狹義解釋為File),而活的東西就是進程。此處的 死 和 活 是一種比喻,映射到軟件層面的意思是:進程能發起動作,例如它能打開文件並操作它。而文件只能被進程操作。
根據 SELinux 規范,完整的 Secure Context 字符串為:user:role:type[:range]
1.2.1 進程的 Secure Context
在 SELinux 中,每種東西都會被賦予一個安全屬性,官方說法叫做 Security Context,Security Context 是一個字符串,主要由三個部分組成,例如 SEAndroid 中,進程的 Security Context 可通過 ps -Z 命令查看:
rk3288:/ $ ps -AZ
u:r:hal_wifi_supplicant_default:s0 wifi 1816 1 11388 6972 0 0 S wpa_supplicant
u:r:platform_app:s0:c512,c768 u0_a14 1388 228 1612844 57396 0 0 S android.ext.services
u:r:system_app:s0 system 1531 228 1669680 119364 0 0 S com.android.gallery3d
u:r:kernel:s0 root 582 2 0 0 0 0 S [kworker/1:2]
u:r:radio:s0 radio 594 228 1634876 89296 0 0 S com.android.phone
u:r:system_app:s0 system 672 228 1686204 141716 0 0 S com.android.settings
u:r:platform_app:s0:c512,c768 u0_a18 522 223 1721656 152116 0 0 S com.android.systemui12345678
上面的最左邊的一列就是進程的 Security Context,以第一個進程 wpa_supplicant 為例
u:r:hal_wifi_supplicant_default:s01
其中
- u 為 user 的意思,SEAndroid 中定義了一個 SELinux 用戶,值為 u
- r 為 role 的意思,role 是角色之意,它是 SELinux 中一個比較高層次,更方便的權限管理思路。簡單點說,一個 u 可以屬於多個 role,不同的 role 具有不同的權限。
- hal_wifi_supplicant_default 代表該進程所屬的 Domain 為 hal_wifi_supplicant_default。MAC(Mandatory Access Control)強制訪問控制 的基礎管理思路其實是 Type Enforcement Access Control(簡稱TEAC,一般用TE表示),對進程來說,Type 就是 Domain,比如 hal_wifi_supplicant_default 需要什么權限,都需要通過 allow 語句在 te 文件中進行說明。
- s0 是 SELinux 為了滿足軍用和教育行業而設計的 Multi-Level Security(MLS)機制有關。簡單點說,MLS 將系統的進程和文件進行了分級,不同級別的資源需要對應級別的進程才能訪問
1.2.2 文件的 Secure Context
文件的 Secure Context 可以通過 ls -Z 來查看,如下
rk3288:/vendor/lib $ ls libOMX_Core.so -Z
u:object_r:vendor_file:s0 libOMX_Core.so12
- u:同樣是 user 之意,它代表創建這個文件的 SELinux user
- object_r:文件是死的東西,它沒法扮演角色,所以在 SELinux 中,死的東西都用 object_r 來表示它的 role
- vendor_file:type,和進程的 Domain 是一個意思,它表示 libOMX_Core.so 文件所屬的 Type 是 vendor_file
- s0:MLS 的等級
1.3 TE 介紹
MAC 基本管理單位是 TEAC(Type Enforcement Accesc Control),然后是高一級別的 Role Based Accesc Control。RBAC 是基於 TE 的,而 TE 也是 SELinux 中最主要的部分。上面說的 allow 語句就是 TE 的范疇。
根據 SELinux 規范,完整的 SELinux 策略規則語句格式為:
allow domains types:classes permissions;
- Domain - 一個進程或一組進程的標簽。也稱為域類型,因為它只是指進程的類型。
- Type - 一個對象(例如,文件、套接字)或一組對象的標簽。
- Class - 要訪問的對象(例如,文件、套接字)的類型。
- Permission - 要執行的操作(例如,讀取、寫入)。
= allow : 允許主體對客體進行操作
= neverallow :拒絕主體對客體進行操作
= dontaudit : 表示不記錄某條違反規則的決策信息
= auditallow :記錄某項決策信息,通常 SElinux 只記錄失敗的信息,應用這條規則后會記錄成功的決策信息。
123456789101112
使用政策規則時將遵循的結構示例:
語句:
allow appdomain app_data_file:file rw_file_perms;
這表示所有應用域都可以讀取和寫入帶有 app_data_file 標簽的文件1234
—> 相關實例
1. SEAndroid 中的安全策略文件 policy.conf
# 允許 zygote 域中的進程向 init 域中的進程(Object Class 為 process)發送 sigchld 信號
allow zygote init:process sigchld;
2. # 允許 zygote 域中的進程 search 或 getattr 類型為 appdomain 的目錄。
# 注意,多個 perm_set 可用 {} 括起來
allow zygote appdomain:dir { getattr search };
3. # perm_set 語法比較奇特,前面有一個 ~ 號。
# 它表示除了{entrypoint relabelto}之外,{chr_file #file}這兩個object_class所擁有的其他操作
allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } \
~{entrypoint relabelto};12345678910111213
Object Class 類型
文件路徑: system/sepolicy/private/security_classes
# 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 平台特有的 zygote123456789101112131415161718
Perm Set 類型
Perm Set 指得是某種 Object class 所擁有的權限。以 file 這種 Object class 而言,其擁有的 Perm Set 就包括 read、write、open、create、execute等。
和 Object Class 一樣,SELinux 或 SEAndroid 所支持的 Perm set 也需要聲明:
文件路徑: system/sepolicy/private/access_vectors1
2. SELinux 相關設置
2.1 強制執行等級
熟悉以下術語,了解如何按不同的強制執行級別實現 SELinux
- 寬容模式(permissive) - 僅記錄但不強制執行 SELinux 安全政策。
- 強制模式(enforcing) - 強制執行並記錄安全政策。如果失敗,則顯示為 EPERM 錯誤。
2.2 關閉 SELinux
臨時關閉
(1) setenforce
$ setenforce 01
setenforce 命令修改的是 /sys/fs/selinux/enforce 節點的值,是 kernel 意義上的修改 selinux 的策略。斷電之后,節點值會復位
永久關閉
(2) kernel 關閉 selinux
SECURITY_SELINUX 設置為 false,重新編譯 kernel1
(3) 設置 ro.boot.selinux=permissive 屬性,並且修改在 system/core/init/Android.mk 中設置用於 user 版本下 selinux 模式為 permissive
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=1 \
-DALLOW_PERMISSIVE_SELINUX=1 \
-DREBOOT_BOOTLOADER_ON_PANIC=1 \
-DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=0 \
-DALLOW_PERMISSIVE_SELINUX=1 \ // 修改為1,表示允許 selinux 為 permissive
-DREBOOT_BOOTLOADER_ON_PANIC=0 \
-DDUMP_ON_UMOUNT_FAILURE=0
endif
1234567891011121314
2.3 SELinux 權限不足 avc-denied 問題解決
目前所有的 SELinux 權限檢測失敗,在 Kernel Log 或者 Android Log 中都有對應的 avc-denied Log 與之對應。反過來,有 avc-denied Log,並非就會直接失敗,還需要確認當時 SELinux 的模式, 是 Enforcing 還是 Permissve。
如果是 Enforcing 模式,就要檢測對應的進程訪問權限資源是否正常?是否有必要添加? 如果有必要添加,可以找下面的方式生成需要的 sepolicy 規則並添加到對應 te 文件。
使用 audit2allow 簡化方法
\1. 從 logcat 或串口中提取相應的 avc-denied log,下面的語句為提取所有的 avc- denied log
$ adb shell "cat /proc/kmsg | grep avc" > avc_log.txt 1
- 使用 audit2allow 工具生成對應的 policy 規則
// audio2allow 使用必須先 source build/envsetup.sh,導入環境變量
$ audit2allow -i avc_log.txt -p $OUT/vendor/etc/selinux/precompiled_sepolicy
3. 將對應的policy 添加到 te 文件中
= 一般添加在 /device/<company>/common/sepolicy 或者 /device/<company>/$DEVICE/sepolicy 目錄下1234
BOARD_SEPOLICY_DIRS += device/$SoC/common/sepolicy 通過這個命令添加廠家自定義的 sepolicy 規則
3. SEAndroid 安全機制框架
SELinux 系統比起通常的 Linux 系統來,安全性能要高的多,它通過對於用戶,進程權限的最小化,即使受到攻擊,進程或者用戶權限被奪去,也不會對整個系統造成重大影響。
我們知道,Android 系統是基於 Linux 內核實現,針對 Linux 系統,NSA 開發了一套安全機制 SELinux,用來加強安全性。然而,由於 Android 系統有着獨特的用戶空間運行時,因此 SELinux 不能完全適用於 Android 系統。為此,NSA 針對 Android 系統,在 SELinux 基礎上開發了 SEAndroid。
SEAndroid 安全機制所要保護的對象是系統中的資源,這些資源分布在各個子系統中,例如我們經常接觸的文件就是分布文件子系統中的。實際上,系統中需要保護的資源非常多,除了前面說的文件之外,還有進程、socket 和 IPC 等等。對於 Android 系統來說,由於使用了與傳統 Linux 系統不一樣的用戶空間運行時,即應用程序運行時框架,因此它在用戶空間有一些特有的資源是需要特別保護的,例如系統屬性的設置等。
3.1 SEAndroid 框架流程
SEAndroid 安全機制的整體框架,可以使用下圖來概括:

以 SELinux 文件系統接口問邊界,SEAndroid 安全機制包含內核空間和用戶空間兩部分支持。
這些內核空間模塊與用戶空間模塊空間的作用及交互有:
1. 內核空間的 SELinux LSM 模塊負責內核資源的安全訪問控制
2. 用戶空間的 SEAndroid Policy 描述的是資源安全訪問策略。
系統在啟動的時候,用戶空間的 Security Server 需要將這些安全訪問策略加載內核空間的 SELinux LSM 模塊中去。
這是通過SELinux文件系統接口實現的
3. 用戶空間的 Security Context 描述的是資源安全上下文。
SEAndroid 的安全訪問策略就是在資源的安全上下文基礎上實現的
4. 用戶空間的 Security Server 一方面需要到用戶空間的 Security Context 去檢索對象的安全上下文,
另一方面也需要到內核空間去操作對象的安全上下文
5. 用戶空間的 libselinux 庫封裝了對 SELinux 文件系統接口的讀寫操作。
用戶空間的 Security Server 訪問內核空間的 SELinux LSM 模塊時,都是間接地通過 libselinux進行的。
這樣可以將對 SELinux 文件系統接口的讀寫操作封裝成更有意義的函數調用。
6. 用戶空間的 Security Server 到用戶空間的 Security Context 去檢索對象的安全上下文時,同樣也是通過 selinux 庫來進行的123456789101112
3.1.1 內核空間
- 在內核空間,存在一個 SELinux LSM(Linux Secrity Moudle)模塊,(用 MAC 強制訪問控制)負責資源的安全訪問控制。
- LSM 模塊中包含一個訪問向量緩沖(Access Vector Cache)和一個安全服務(Security Server)。Security Server 負責安全訪問控制邏輯,即由它來決定一個主體訪問一個客體是否是合法的,這個主體一般是指進程,而客體主要指資源,例如文件。
- SELinux、LSM 和內核中的子系統是如何交互的呢?首先,SELinux 會在 LSM 中注冊相應的回調函數。其次,LSM 會在相應的內核對象子系統中會加入一些 Hook 代碼。例如,我們調用系統接口 read 函數來讀取一個文件的時候,就會進入到內核的文件子系統中。在文件子系統中負責讀取文件函數 vfs_read 就會調用 LSM 加入的 Hook 代碼。這些 Hook 代碼就會調用之前 SELinux 注冊進來的回調函數,以便后者可以進行安全檢查。
- SELinux 在進行安全檢查的時候,首先是看一下自己的 Access Vector Cache 是否已經有結果。如果有的話,就直接將結果返回給相應的內核子系統就可以了。如果沒有的話,就需要到 Security Server 中去進行檢查。檢查出來的結果在返回給相應的內核子系統的同時,也會保存在自己的 Access Vector Cache 中,以便下次可以快速地得到檢查結果
上面概述的安全訪問控制流程,可以使用下圖來總結:

1. 一般性錯誤檢查,例如訪問的對象是否存在、訪問參數是否正確等
2. DAC 檢查,即基於 Linux UID/GID 的安全檢查
3. SELinux 檢查,即基於安全上下文和安全策略的安全檢查123
3.1.2 用戶空間
在用戶空間,SEAndorid 主要包含三個模塊,分別是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服務(Security Server)。
(1)安全上下文
前面已經描述過了,SEAndroid 是一種基於安全策略的 MAC 安全機制。這種安全策略又是建立在對象的安全上下文的基礎上的,SEAndroid 中的對象分為主體(Subject)和客體(Object),主體通常是指進程,而客體是指進程所要訪問的資源,如文件、系統屬性等
安全上下文實際上就是一個附加在對象上的標簽(Tag)。這個標簽實際上就是一個字符串,它由四部分內容組成,分別是 SELinux 用戶、SELinux 角色、類型、安全級別,每一個部分都通過一個冒號來分隔,格式為 “user:role:type:sensitivity”
用 ps -Z 來查看主體的安全上下文,而用 ls -Z 來查看客體的安全上下文。
安全上下文<類型>說明
通常將用來標注文件的安全上下文中的類型稱為 file_type,用來標注進程的安全上下文的類型稱為 domain。
將兩個類型相關聯可以通過 type 語句實現,例如用來描述 init 進程安全策略文件 /system/sepolicy/public/init.te 文件中,使用 type 語句將 init 與 domain 相關聯(等價)
type init domain;
// 這樣就可以表明 init 描述的類型是用來描述進程的安全上下文的12
Android 系統中對象的安全上下文定義
系統中各種類型對象(包含主體和客體)的安全上下文是在 system/sepolicy 工程中定義的,我們討論四種類型對象的安全上下文,分別是 app 進程、app 數據文件、系統文件和系統屬性。這四種類型對象的安全上下文通過四個文件來描述:mac_permissions.xml、seapp_contexts、file_contexts 和 property_contexts。
mac_permissions 文件給不同簽名的 app 分配不同的 seinfo 字符串,例如,在 AOSP 源碼環境下編譯並且使用平台簽名的 App 獲得的 seinfo 為 “platform”,使用第三方簽名安裝的 App 獲得的 seinfo 簽名為”default”。
<!-- Platform dev key in AOSP -->
<signer signature="@PLATFORM" >
<seinfo value="platform" />
</signer>
<!-- Media key in AOSP -->
<signer signature="@MEDIA" >
<seinfo value="media" />
</signer>123456789
這里的 seinfo 描述的並不是安全上下文的對象類型,它用來在 seapp_contexts 中查找對應的對象類型。在 seapp_contexts 對 seinfo 也就是平台簽名為”platform”的 app 如下定義:
user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user
也就是說明使用"platform"平台簽名的 app 所運行的進程 domain 為"platform_app",
並且他的數據文件類型為"app_data_file"。1234
接下來看一下系統文件的安全上下文是如何定義的:
###########################################
# Root
/ u:object_r:rootfs:s0
# Data files
/adb_keys u:object_r:adb_keys_file:s0
/build\.prop 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
/selinux_version u:object_r:rootfs:s0
/ueventd\..* u:object_r:rootfs:s0
/verity_key u:object_r:rootfs:s01234567891011121314
可以看到使用正則表達式描述了系統文件的安全上下文。
在 Android 系統中有一種比較特殊的權限——屬性,app 能夠讀寫他們獲取相應的系統信息以及控制系統的行為。因此不同與 SELinux,SEAndroid 中對系統屬性也進行了保護,這意味着 Android 系統的屬性也需要關聯安全上下文。這是在 property_contexts 文件中描述的
##########################
# property service keys
#
#
net.rmnet u:object_r:net_radio_prop:s0
net.gprs u:object_r:net_radio_prop:s0
net.ppp u:object_r:net_radio_prop:s0
net.qmi u:object_r:net_radio_prop:s0
net.lte u:object_r:net_radio_prop:s0
net.cdma u:object_r:net_radio_prop:s0
net.dns u:object_r:net_dns_prop:s0
sys.usb.config u:object_r:system_radio_prop:s0
ril. u:object_r:radio_prop:s0
ro.ril. u:object_r:radio_prop:s0
gsm. u:object_r:radio_prop:s0
persist.radio u:object_r:radio_prop:s012345678910111213141516
這邊的 net.rmnet 行類型為 net_radio_prop,意味着只有有權限訪問 net_radio_prop 的進程才可以訪問這個屬性。
(2)安全策略
SEAndroid 安全機制中的安全策略是在安全上下文的基礎上進行描述的,也就是說,它通過主體和客體的安全上下文,定義主體是否有權限訪問客體。
Type Enforcement
SEAndroid 安全機制主要是使用對象安全上下文中的類型來定義安全策略,這種安全策略就稱 Type Enforcement,簡稱TE。在 system/sepolicy 目錄和其他所有客制化 te 目錄(通常在 device//common,用 BOARD_SEPOLICY_DIRS 添加),所有以 .te 為后綴的文件經過編譯之后,就會生成一個 sepolicy 文件。這個 sepolicy 文件會打包在ROM中,並且保存在設備上的根目錄下。
一個 Type 所具有的權限是通過allow語句來描述的
allow unconfineddomain domain:binder { call transfer set_context_mgr };1
表明 domain 為 unconfineddomain 的進程可以與其它進程進行 binder ipc 通信(call),並且能夠向這些進程傳遞 Binder 對象(transfer),以及將自己設置為 Binder 上下文管理器(set_context_mgr)。
注意,SEAndroid 使用的是最小權限原則,也就是說,只有通過 allow 語句聲明的權限才是允許的,而其它沒有通過 allow 語句聲明的權限都是禁止,這樣就可以最大限度地保護系統中的資源
前面我們提到,SEAndroid 安全機制的安全策略經過編譯之后會得到一個 sepolicy 文件,並且最終保存在設備上的根目錄上。這個 sepolicy 文件中的安全策略是不會自動加載的到內核空間的 SELinux LSM 模塊中去的,它需要我們在系統啟動的時候進行加載。
具體的源碼分析在 4.2 節中。
(3)Security Server
Security Server 是一個比較籠統的概念,主要是用來保護用戶空間資源的,以及用來操作內核空間對象的安全上下文的,它由應用程序安裝服務 PackageManagerService、應用程序安裝守護進程 installd、應用程序進程孵化器 Zygote 進程以及 init 進程組成。其中,PackageManagerService 和 installd 負責創建 App 數據目錄的安全上下文,Zygote 進程負責創建 App 進程的安全上下文,而 init 進程負責控制系統屬性的安全訪問。
PackageManagerService & installed —— app 數據目錄的安全上下文
PackageManagerService 在啟動的時候,會找到我們前面分析的 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 函數來實現的。
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 函數來實現的。
init —— 系統屬性的安全上下文
init 進程在啟動的時候會創建一塊內存區域來維護系統中的屬性,接着還會創建一個 Property 服務系統,這個服務系統通過 socket 提供接口給其他進程訪問 android 系統中的屬性。
其他進程通過 socket 和屬性系統通信請求訪問某項系統屬性的值,屬性服務系統可以通過 libselinux 庫提供的 selabel_lookup 函數到前面我們分析的 property_contexts 中查找要訪問的屬性的安全上下文了。有了該進程的安全上下文和要訪問屬性的安全上下文之后,屬性系統就能決定是否允許一個進程訪問它所指定的服務了。
4. SEAndroid 源碼分析
4.1 SEAndroid 源碼架構
- externel/selinux:包含編譯 sepolicy 策略文件的一些實用構建工具
- external/selinux/libselinux:提供了幫助用戶進程使用 SELinux 的一些函數
- external/selinux/libsepol:提供了供安全策略文件編譯時使用的一個工具 checkcon
- system/sepolicy:包含 Android SELinux 核心安全策略(te 文件),編譯生成 sepolicy 文件
- file_contexts: 系統中所有文件的安全上下文
- property_contexts: 系統中所有屬性的安全上下文
- seapp_contexts:定義用戶、seinfo和域之間的關系,用於確定用戶進程的安全上下文
- sepolicy:二進制文件,保存系統安全策略,系統初始化時會把它設置到內核中12345678
SELinux 虛擬文件系統在 sys/fs/selinux 下,該目錄下的文件是 SELinux 內核和用戶進程進行通信的接口,libselinux 就是利用這邊的接口進行操作
4.2 init 進程 SEAndroid 啟動源碼分析
文件路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true);
// We're in the kernel domain, so re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (selinux_android_restorecon("/init", 0) == -1) {
PLOG(ERROR) << "restorecon failed";
security_failure();
}
...
}
}12345678910111213141516171819
可以看到 SEAndroid 的啟動設置在 init 進程內核態執行(first_stage)過程中,selinux_initialize 函數就是主要的初始化過程,包含加載 sepolicy 策略文件到內核的 LSM 模塊中。
文件路徑:/system/core/init/init.cpp
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
// 標識 init 進程的內核態執行和用戶態執行
if (in_kernel_domain) {
LOG(INFO) << "Loading SELinux policy";
if (!selinux_load_policy()) {
panic();
}
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
security_failure();
}
}
std::string err;
if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
LOG(ERROR) << err;
security_failure();
}
// init's first stage can't set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
} else {
selinux_init_all_handles();
}
}123456789101112131415161718192021222324252627282930313233343536373839
selinux_set_callback
selinux_set_callback 用來向 libselinux 設置 SEAndroid 日志和審計回調函數
selinux_load_policy
這個函數用來加載 sepolicy 策略文件,並通過 mmap 映射的方式將 sepolicy 的安全策略加載到 SELinux LSM 模塊中去。
下面具體分析一下流程:
文件路徑:system/core/init/init.cpp
static bool selinux_load_policy() {
return selinux_is_split_policy_device() ? selinux_load_split_policy()
: selinux_load_monolithic_policy();
}123456
這里區分了從哪里加載安全策略文件,第一個是從 /vendor/etc/selinux/precompiled_sepolicy 讀取,第二個是直接從根目錄 /sepolicy
中讀取,最終調用的方法都是 selinux_android_load_policy_from_fd
int selinux_android_load_policy_from_fd(int fd, const char *description)
{
int rc;
struct stat sb;
void *map = NULL;
static int load_successful = 0;
/*
* Since updating policy at runtime has been abolished
* we just check whether a policy has been loaded before
* and return if this is the case.
* There is no point in reloading policy.
*/
if (load_successful){
selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
return 0;
}
set_selinuxmnt(SELINUXMNT);
if (fstat(fd, &sb) < 0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
description, strerror(errno));
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",
description, strerror(errno));
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);
return -1;
}
munmap(map, sb.st_size);
selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
load_successful = 1;
return 0;
}1234567891011121314151617181920212223242526272829303132333435363738394041424344
(1)set_selinuxmnt 函數設置內核 SELinux 文件系統路徑,這里的值為 /sys/fs/selinux,SELinux 文件系統用來與內核空間 SELinux LSM 模塊空間。
(2)通過 fstat 獲取 sepolicy 文件(fd 值)的狀態信息,通過 mmap 函數將文件內容映射到內存中,起始地址為 map
(3)security_load_policy 調用另一個 security_load_policy 函數將已經映射到內存中的 SEAndroid 的安全策略加載到內核空間的 SELinux LSM 模塊中去。
int security_load_policy(void *data, size_t len)
{
char path[PATH_MAX];
int fd, ret;
if (!selinux_mnt) {
errno = ENOENT;
return -1;
}
snprintf(path, sizeof path, "%s/load", selinux_mnt);
fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0)
return -1;
ret = write(fd, data, len);
close(fd);
if (ret < 0)
return -1;
return 0;
}123456789101112131415161718192021
函數 security_load_policy 的實現很簡單,它首先打開 /sys/fs/selinux/load 文件,然后將參數 data 所描述的安全策略寫入到這個文件中去。由於 /sys/fs/selinux 是由內核空間的 SELinux LSM 模塊導出來的文件系統接口,因此當我們將安全策略寫入到位於該文件系統中的 load 文件時,就相當於是將安全策略從用戶空間加載到 SELinux LSM 模塊中去了。
以后 SELinux LSM 模塊中的 Security Server 就可以通過它來進行安全檢查
(4)加載完成,釋放 sepolicy 文件占用的內存,並且關閉 sepolicy 文件
security_setenforce
前面已經提過,selinux 有兩種工作模式:
- 寬容模式(permissive) - 僅記錄但不強制執行 SELinux 安全政策。
- 強制模式(enforcing) - 強制執行並記錄安全政策。如果失敗,則顯示為 EPERM 錯誤。
這個函數用來設置 kernel SELinux 的模式,實際上都是去操作 /sys/fs/selinux/enforce 文件, 0 表示permissive,1 表示 enforcing
selinux_init_all_handles
在 init 進程的用戶態啟動過程中會調用這個函數初始化 file_context、 property_context 相關內容 handler,根據前面的描述,init 進程給一些文件或者系統屬性進行安全上下文檢查時會使用 libselinux 的 API,查詢文件根目錄下 file_context、file_context 的安全上下文內容。所以需要先得到這些文件的 handler,以便可以用來查詢系統文件和系統屬性的安全上下文。
static void selinux_init_all_handles(void)
{
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
sehandle_prop = selinux_android_prop_context_handle();
}
