1 Selinux 基礎
1.1 什么是Selinux
SEAndroid 是 Google 在 Android 4.4 上正式推出的一套以SELinux 為基礎與核心的系統安全機制。而 SELinux 則是由美國 NSA(國安局)和一些公司(RedHat、Tresys)設計的一個針對 Linux的安全加強系統。
SELinux 是一種訪問控制系統,其核心就是對系統中的各種操作進行控制。為了實現這一目的,需要做兩個修改:
1、在各種操作實際進行之前添加鈎子,以判斷權限並控制后續操作;
2、對不同的操作進行標記,並制定相應的控制策略;
對於第一點,是由 SELinux 實現框架來做到的,對於初學者不必深入研究。第二點才是我們要重點研究的內容。
1.2 如何將系統中的操作進行標記
1.2.1 什么是操作
之前也介紹過,Selinux 是一種訪問控制系統,說的直白點,就是對訪問權限進行控制,允許(不允許)某種主體對某種類型的客體做什么樣的動作,如果沒有定義權限,那么默認不允許;
1.2.2 主體、客體及動作
對於任何一個操作,都包含主體、客體、動作這三個部分:
1.主體通常就是進程或者內核的線程(后面以進程說明)
2.客體是系統中所有可以被操作的資源,包括文件、進程、設備等等
3.在操作系統的正常運行中會有大量的動作,因此 SELinux的設計者把這些動作根據客體類別(object class)進行分類,並且把每個類別所可以進行的動作都定義好。例如:file 類別定義了read、write、open 等權限,dir 類別定義了 add_name、remove_name、search 等權限
除了標准 SELinux 定義的類別,SEAndroid 還定義了特有的類別,包括 binder、zygote、property_service、service_manager等。
1.3 什么是 Scontext?
在 Selinux 中,主體和客體都被賦予了一個屬性,叫做Security Context(安全上下文),簡稱 Scontext。這個屬性就是主體和客體的唯一標識。
在 SeAndroid 中,使用命令 ps -z 即可查看所有進程的Scontext,使用 ls -Z 可以查看當前目錄文件的 Scontext,
如圖:
比如 Vendor 文件的 Scontext 就是 u:object_r:vendor_file:s0.
據 SELinux 規范,SContext 的格式為:
user:role:type:[range]
user:表示對象的所屬用戶。在 SEAndroid 中,目前僅定義一個 user(也就是 u)。
role:表示活動對象的角色。在 SEAndroid 中,目前僅定義一個 role(也就是 r)。非活動對象此處均為 object_r。
type:表示對象的類型。
range:表示對象的分級。如不同級別的資源需要對應級別的進程才能訪問,一般都是 s0。
其中 type 是需要重點關注的,其它幾個暫時無需關注。
1.4 什么是 type?
在 SELinux 體系中,所有的東西都要有 type。為了做到精確控制,必須對不同的東西定義不同的 type。這樣就會引入大量 type,而規則又都是使用 type 來進行控制的,有些 type 是有共性的(比如,vfat 和 fuse都屬於 sd 卡的文件系統),為了同一目的,可能需要需要編寫多條規則。因此 SELinux 的設計者引入了attribute,在定義 type 時可以指定 attribute。同一 type 可以指定多個attribute,不同 type 也可以指定同一個 attribute。在 SEAndroid 中,對於進程的 type 都會指定 domain 屬性。對於文件的 type 都會指定 file_type屬性,而可執行文件的 type 都會指定 exec_type 屬性。
例如:
type vold, domain;
type vold_exec, exec_type, file_type;
vold type 就是 vold 進程的 type,所以擁有 domain 屬性;而vold_exec 是 vold 可執行文件的 type,所以同時擁有 exec_type和 file_type 屬性。
type 定義命令的完整格式為:
type type_id [alias alias_id] , [attribute_id];
如果在 type 定義之后再指定屬性可以用 typeattribute。
例如:
typeattribute vold mlstrustedsubject;
特別注意:
當遇到neverallow問題時,但是在文件中卻找不到沖突的語句,可能就是有的type不但使用type type_id [alias alias_id] , [attribute_id]這種格式定義,還有其他地方使用的typeattribute來增
加定義,此點很重要,算是解決selinux問題的一個關鍵點吧。
1.5 如何進行規則定義?
SELinux 控制各種操作的策略是通過 TE 語句來完成的。最普遍的就是 allow 語句,例如:
allow vold device:dir write;
這句的意思是:允許 type 為 vold 的主體 對 type 為 device的客體 擁有 客體類別為 dir 的write 權限。根據 SELinux 規范,規則定義的格式為:
rule_name source_type target_type : class perm_set;
可以看到規則中只用到了主體和客體的 type,而動作是以操作類別與具體操作的組合體現的。
所有 rule_name 如下:
allow 表示允許主體對客體執行允許的操作;
auditallow 表示即便允許操作也要記錄訪問決策信息(仍然需要有 allow 規則才允許);
dontaudit 表示違反規則的決策信息也不記錄(便於定位問題,已知此操作會被拒絕但不會引起真正問題);
neverallow 表示不允許主體對客體執行指定的操作;
2 實例分析
2.1 章節背景
此章節的目的為以一個在系統中創建服務為例子,幫助同學理解在實際項目中,會遇到的一些實際問題。
2.2 創建服務
想要創建服務,首先需要修改代碼中的 init.rc,代碼如下:
+service closesourcecode /system/bin/closesourcecode
+ class main
+ user root
+ disabled
+ onshot
+ seclabel u:r:closesourcecode:s0
+
+on property:sys.move.vendor=1
+ start closesourcecode
以上即創建了一個名字為 closesourcecode 的服務,當屬性sys.move.vendor 為 1 時,即調用此服務,服務執行的程序為/system/bin/closesourcecode
Seclabel 是用於在服務在運行之前修改其安全上下文,主要用在位於 rootfs 分區的服務,比如ueventd, adbd。而位於 system分區的服務,則可以通過 transitions 規則進行修改,如果沒有指定transition 規則,那么默認就是 init context。
由之前的基礎知識可知 seclabel u:r:closesourcecode:s0中的 u:r:closesourcecode:s0 指的是closesourcecode 這個服務的 Scontext(安全上下文),seclabel u:r:closesourcecode:s0中的
closesourcecode 指的是服務的類型,即 type,所以接下來我們為這個服務以及服務對應的可執行文件定義 type;
2.3 為服務以及服務對應的可執行文件定義 type
根據 1.4 章節的內容可知,為 closesourcecode 以及服務對應的可執行文件定義 type 的代碼
如下:
+type closesourcecode, domain, coredomain;
+type closesourcecode_exec, exec_type, file_type;
解析一下:
定義一個名字為 closesourcecode 的 type,這個 type 擁有domain(進程)屬性,且是一個coredomain(核心進程)。
定義一下名字為 closesourcecode_exec 的 type,這個 type擁有 file_type(文件)屬性,且是一個 exec_type(可執行文件)。
由於 closesourcecode 這個 type 在創建服務的時候已經指定了 Scontext(安全上下文),所以不需要定義 Scontext(安全上下文),但是 closesourcecode_exec 這個 type 沒有對應的 Scontext(安全上下文),所以我們需要自己創建。
2.4 創建 Scontext(打標記)
在 Linux 系統中,大概可以分成兩種東西,一種是死的(比如文件、端口、系統屬性等),一種是活的(就是進程)。此處的“死”和“活”是一種比喻,映射到軟件層面的意思是:進程能發起動作,例如它能打開文件並操作它。而文件只能被進程操作。
給死東西打標記很容易,我們先來看一下這個。
在 SEAndroid 系統中 external/sepolicy 下有這么幾個文件是
用來給死東西打標記的
file_contexts 給文件打標記
property_contexts 給屬性打標記
seapp_contexts 給應用打標記
service_contexts 給服務打標記
由於我們的服務 closesourcecode 對應的是一個可執行文件,所以我們只需要在 file_contexts 文件修改即可,改動代碼如下:
+/system/bin/closesourcecode u:object_r:closesourcecode_exec:s0
以上,我們就完成了基礎工作,接下來就會遇到一些具體的解析 log,調試功能的工作。
2.5 如何抓取完整的 Selinux log
編譯完版本后(需要編譯 boot 以及 vendor),抓取 kernel以及 logcat,可以發現會出現 selinux log,但是此時千萬不要根據一個 log 修改一次代碼,再編譯版本,出現 log,再次解析出 allow 語句,再編譯版本...無限循環,這是非常錯誤的做法。
Selinux 啟用后有兩種運行模式:
1.強制模式(Enforcing) 正常運行模式,遇到權限檢查無法通過會拒絕操作並打出日志;
2.寬容模式(Permissive) 規則檢查不變,遇到權限檢查無法通過會打出日志,但不會拒絕;
實際發布時都會使用強制模式,但是運行在寬容模式可以方便調試。因為只有這樣才能收集到所有的權限拒絕信息,保證一次性添加所有規則。
2.6 關閉 selinux
關閉 Selinux 的方法有兩種,一種是在代碼中修改,還有一種是使用命令進行修改,當服務是在開機時即需要調用,那么需要使用在代碼中關閉 selinux 的方法,如果服務可以通過屬性控制並不需要在開機時即調用,那么可以使用命令關閉 selinux。
2.6.1 代碼中關閉 selinux 的方法
在 高 通 平 台 , 只 需 要 修 改 device/qcom/ 對 應 的 平 台/BoardConfig.mk 文 件 添 加 如 下 代 碼 ( 在 最 后一 個BOARD_KERNEL_CMDLINE 接着添加):
BOARD_KERNEL_CMDLINE += androidboot.selinux=permissive
2.6.2 使用 shell 命令關閉 selinux
Selinux 提供了命令用來查看或者設置運行模式:
#getenforce ——用來查看運行模式,1 為強制模式,0 為寬容模式
#setenforce 1 ——設置運行模式為強制模式即 Enforcing
#setenforce 0 — — 設 置 運 行 模 式 為 寬 容 模 式 即Permissive
2.7 解析 selinux log
抓取完整 Selinux log 之后,我們就需要解析 log,log 示例如下:
[ 70.343292] type=1400 audit(257168.459:53): avc: denied { open } for pid=1340 comm="cp" path="/system/vendor/bin/btconfig" dev="mmcblk0p6" ino=1905 scontext=u:r:closesourcecode:s0 tcontext=u:object_r:vendor_file:s0 tclass=file permissive=1
其中關鍵的信息有這么幾個:
avc denied { open } 拒絕了 open 操作
scontext u:r:closesourcecode:s0 主體 SContext,其
type 為 closesourcecode
tcontext u:r:vendor_file:s0 客體 SContext,其 type
為 vendor_file
tclass file 操作類別為 file
如果要解決這個問題,只要將這幾項信息組合成前面說的allow 規則然后添加策略文件中就可以了。規則如下:
allow closesourcecode vendor_file:file open;
任何selinux權限拒絕錯誤都會包含這幾項信息,一般情況下,都可以通過添加 allow 規則來解決這種問題。
2.8 Neverallow 問題解決方法
有時,當我們添加完對應的.te 規則后,在編譯時會報nerverallow 錯誤,導致不能編譯通過,針對這種問題,其實就是我們自己添加的規則和系統源生的 nerverallow 語句沖突導致的。這里提供一下解決此類問題的思路:
1.首先我們需要分析編譯 log,在 log 中一定會提示我們添加的 allow 語句與系統中的哪個文件中的哪一個語句有沖突,我們要
根據沖突,找到自己添加的 allow 語句(當然如果您只添加了一個
語句就不用查了)。
2.假如說 allow closesourcecode vendor_file:file open;這個 allow 語句與系統中的 nerverallow 沖突,那么您就需要查清楚主體 closesourcecode 到底是對哪個 vendor_file 類型的文件操作,並查一下系統是否有定義 closesourcecode 允許對某種類型的文件進行 open 操作,如果有那么直接把 closesourcecode 操作的文件的 type 修改為 closesourcecode 允許的類型即可。當然思路也可以轉換一下,可以到系統中查看是否有 allow 某種主體對vendor_file 這種客體的文件有 open 權限,修改主體的 type 亦可。如果以上兩種方式均沒有解決,那么直接把closesourcecode 具體操作的文件的標記改為自己定義的就可以啦。
3.某些同學可能自作聰明,認為直接把沖突的語句刪掉或者修改一下不就可以了嘛,答案肯定是不行的,這樣會影響后期的 cts認證。
4.由於理論看起來比較復雜,所以后續第 3 章會有具體的實例分析。
2.9 代碼規范建議
1.在沒有產品線的前提下,所有的 selinux 修改目錄為device/qcom/sepolicy/對應平台/
2.針對每一個主體的權限修改都要有一個主體名字對應的.te 文件,如果沒有則 touch 一個
3 Selinux工作中的問題總結
3.1 編譯版本刷到手機后 selinux 沒生效原因總結
3.1.1 編譯問題
一定不要偷懶只編譯 boot,當修改 device/qcom/sepolicy/對應平台/目錄下的文件時,是需要編譯 boot 以及 vendor 的,且都要燒寫。
3.1.2 代碼問題
代碼如下:
+type closesourcecode, coredomain;
+type closesourcecode_exec, exec_type, file_type;
如果代碼按照如上模式寫的話即不能生效,coredomain,並不代碼它修飾的 type 就是 domain 類型,文件也一樣,如果形容為可執行文件,
系統並不知道它是一個文件,道理如此,此問題很難查到,希望大家能引起重視。
3.2 工具簡介
在某些情況下,可能會出現大量的 selinux log,那么此時就需要小工具進行輔助,即 audit2allow。
使用環境:Linux 操作系統
安裝方法:sudo apt-get install policycoreutils
使用方法:audit2allow -i *.log(此工具還有很多使用方法,具體其他用法請百度),這樣即可根據 Log 自動生成 allow 語句。
3.3 Nerverallow 實例分析
3.3.1 問題背景介紹
項目中想要添加與 audio 相關的功能,添加完后,編譯版本,在關閉 selinux 的情況下,功能正常,打開 selinux 即有問題,且
在 log 中有 selinux 相關的 log,所以判斷此問題為 selinux 問題,根據關閉 selinux 的 log 我們解析出兩句 allow 語句:
allow hal_audio_default default_prop:property_service set;
allow audioserver default_prop:property_service set;
3.3.2 編譯問題
根據上文生成的.te 語句,我們在代碼中創建對應的.te 文件(沒有則創建),編譯代碼,發現代碼編譯不過,log 如下:
libsepol.report_failure: neverallow on line 447 of system/sepolicy/public/domain.te (or line 8935 of policy.conf) violated by allow hal_audio_default default_prop:property_service { set };
libsepol.report_failure: neverallow on line 447 of system/sepolicy/public/domain.te (or line 8935 of policy.conf) violated by allow audioserver default_prop:property_service { set }; libsepol.check_assertions: 2 neverallow failures occurred
分析可知:我 們 添 加 的 代 碼 與system/sepolicy/public/domain.te 中的如下代碼有沖突:
Nerverallow { domain -init } default_prop:property_service set;
我們來對比一下:
allow hal_audio_default default_prop:property_service set;
allow audioserver default_prop:property_service set;
結果很顯然,是因為我們的主體對 default_prop 這個 type 進行了 set 操作,所以我們需要繞過去。
3.3.3 查清源頭
因為 default_prop 是一個很宏觀的概念,所以我們需要查清我們的主體到底是對什么屬性進行了 set 操作,所以需要查代碼,看代碼中具體做了什么操作,代碼如下:
property_set("pmc.audio.spkoff_w_bths_status", "true");
property_set("pmc.audio.spkoff_w_bths_status", "false");
由此便得知,我們的主體是對 pmc.audio.這個屬性進行了 set操作,所以我們需要給 pmc.audio.重新打一個標記,給屬性打標記需要在 property_contexts 中添加,代碼如下:
pmc.audio. u:object_r:audio_prop:s0
3.3.4 修改對應的.te 語句
屬性是比較特殊的我們可以直接通過 set_prop 來添加權限,代碼如下:
set_prop(audioserver, audio_prop);
set_prop(hal_audio_default, audio_prop);
3.3.5 驗證功能
編譯代碼,驗證功能。
Ps:源碼是最好的老師,感興趣的同學可以下載一套android的代碼,查看selinux代碼的規則,進行比較學習;