一 SELinux背景知識
SELinux出現之前,Linux上的安全模型叫DAC,全稱是Discretionary Access Control,翻譯為自主訪問控制。DAC的核心思想很簡單,就是:
- 進程理論上所擁有的權限與執行它的用戶的權限相同。比如,以root用戶啟動Browser,那么Browser就有root用戶的權限,在Linux系統上能干任何事情。
顯然,DAC太過寬松了,所以各路高手想方設法都要在Android系統上搞到root權限。那么SELinux如何解決這個問題呢?原來,它在DAC之外,設計了一個新的安全模型,叫MAC(Mandatory Access Control),翻譯為強制訪問控制。MAC的處世哲學非常簡單:即任何進程想在SELinux系統中干任何事情,都必須先在安全策略配置文件中賦予權限。凡是沒有出現在安全策略配置文件中的權限,進程就沒有該權限。來看一個SEAndroid中設置權限的例子:
/* 示例:
from external/sepolicy/netd.te
下面這條SELinux語句表示 允許(allow )netd域(domain)中的進程 ”寫(write)“
類型為proc的文件
注意,SELinux中安全策略文件有自己的一套語法格式,下文我們將詳細介紹它
*/
allow netd proc:file write
如果沒有在netd.te中使用上例中的權限配置allow語句,則netd就無法往/proc目錄下得任何文件中寫數據,即使netd具有root權限。
這條語句的語法為:
- 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
二、 SELinux Policy語言介紹
SELinux中,每種東西都會被賦予一個安全屬性,官方說法叫Security Context。Security Context(以后用SContext表示)是一個字符串,主要由三部分組成。例如SEAndroid中,進程的SContext可通過ps -Z命令查看,如圖1所示:
以第一個進程/init的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將系統的進程和文件進行了分級,不同級別的資源需要對應級別的進程才能訪問
再來看文件的SContext,讀者可通過ls -Z來查看,如圖2所示:
以第一行acct目錄為例,其信息為u:object_r:cgroup:s0 :
- u:同樣是user之意,它代表創建這個文件的SELinux user。
- object_r:文件是死的東西,它沒法扮演角色,所以在SELinux中,死的東西都用object_r來表示它的role。
- cgroup:死的東西的Type,和進程的Domain其實是一個意思。它表示acct目錄對應的Type是cgroup。
- s0:MLS的級別。
根據SELinux規范,完整的SContext字符串為:
user:role:type[:range]
注意,方括號中的內容表示可選項。s0屬於range中的一部分,MAC基本管理單位是TEAC(Type Enforcement Accesc Control),然后是高一級別的Role Based Accesc Control。RBAC是基於TE的,而TE也是SELinux中最主要的部分。
三、Security Labeling:
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信息。
四、SEAndroid源碼分析
本節主要看Google是如何在Android平台定制SELinux的。如前文所示,Android平台中的SELinux叫SEAndroid。
1. 編譯sepolicy
Android平台中:(注:Android7.0開始目錄為 system/sepolicy/)
- external/sepolicy:提供了Android平台中的安全策略源文件。同時,該目錄下的tools還提供了諸如m4,checkpolicy等編譯安全策略文件的工具。注意,這些工具運行於主機(即不是提供給Android系統使用的)
- external/libselinux:提供了Android平台中的libselinux,供Android系統使用。
- external/libsepol:提供了供安全策略文件編譯時使用的一個工具checkcon。
對我們而言,最重要的還是external/sepolicy。通過如下make命令查看執行情況: mmm external/sepolicy --just-print
- sepolicy的重頭工作是編譯sepolicy安全策略文件。這個文件來源於眾多的te文件,初始化相關的文件(initial_sid,initial_sid_context,users,roles,fs_context等)。
- file_context:該文件記載了不同目錄的初始化SContext,所以它和死貨打標簽有關。
- seapp_context:和Android中的應用程序打標簽有關。
- property_contexts:和Android系統中的屬性服務(property_service)有關,它為各種不同的屬性打標簽。
2. SEAndroid修改:
----------【例子1】:通過修改shell的權限,使其無法設置屬性:
先來看shell的te,如下所示:
[external/sepolicy/shell.te]
# Domain for shell processes spawned by ADB
type shell, domain;
type shell_exec, file_type;
#shell屬於unconfined_domain,unconfined即是不受限制的意思
unconfined_domain(shell)
# Run app_process.
# XXX Split into its own domain?
app_domain(shell)
unconfied_domain是一個宏,它將shell和如下兩個attribute相關聯:
[external/sepolicy/te_macros]
#####################################
# unconfined_domain(domain)
# Allow the specified domain to do anything.
#
define(`unconfined_domain', `
typeattribute $1 mlstrustedsubject; #這個和MLS有關
typeattribute $1 unconfineddomain;
')
unconfineddomain權限很多,它的allow語句定義在unconfined.te中:
[external/sepolicy/unconfined.te]
......
allow unconfineddomain property_type:property_service set;
從上面可以看出,shell所關聯的unconfineddomain有權限設置屬性。所以,我們把它改成:
allow {unconfineddomain -shell} property_type:property_service set;
通過一個“-”號,將shell的權限排除。
然后:
- 我們mmm external/sepolicy,得到sepolicy文件。
- 將其push到/data/security/current/sepolicy目錄下
- 接着調用setprop selinux.reload_policy 1,使得init重新加載sepolicy,由於/data目錄下有了sepolicy,所以它將使用這個新的。
圖示為整個測試的例子:
- 重新加載sepolicy之前,筆者可通過"setprop wlan.driver.status test_ok"設置該屬性的值為test_ok。
- 當通過 setprop selinux.reload_policy 1 的命令后,init重新加載了sepolicy。
- 筆者setprop wlan.driver.status 都不能修改該屬性的值。
圖示為dmesg輸出,可以看出,當selinux使用了data目錄下這個新的sepolicy后,shell的setprop權限就被否了!
提示:前面曾提到過audit,日志一類的事情。日志由kernel輸出,可借助諸如audit2allow等host上的工具查看哪些地方有違反權限的地方。
----------【例子2】:Nerverallow 實例分析:
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;
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 查清源頭
因為 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
4 修改對應的.te 語句
屬性是比較特殊的我們可以直接通過 set_prop 來添加權限,代碼如下:
set_prop(audioserver, audio_prop);
set_prop(hal_audio_default, audio_prop);
五、修改方法簡結:
1. 編譯、提取sepolicy相關信息
1.1 提取所有的avc LOG. 如 adb shell “cat /proc/kmsg | grep avc” > avc_log.txt
1.2 使用 audit2allow tool 直接生成policy: audit2allow -i avc_log.txt
1.3 將對應的policy 添加到selinux policy 規則中,對應MTK 廠家目錄如: device/mediatek/common/sepolicy :
allow zygote resource_cache_data_file:dir rw_dir_perms;
allow zygote resource_cache_data_file:file create_file_perms;
===> device/mediatek/common/sepolicy/zygote.te (L)
注意audit2allow 它自動機械的幫您將LOG 轉換成policy, 而無法知道你操作的真實意圖,有可能出現權限放大問題,經常出現policy 無法編譯通過的情況。
2). 按需確認方法
此方法需要工程人員對SELinux 基本原理,以及SELinux Policy Language 有了解.
2.1 確認是哪個進程訪問哪個資源,具體需要哪些訪問權限,read ? write ? exec ? create ? search ?
2.2 當前進程是否已經創建了policy 文件? 通常是process 的執行檔.te,如果沒有,並且它的父進程即source context 無須訪問對應的資源,則創建新的te 文件.
在L 版本上, Google 要求維護關鍵 security context 的唯一性, 比如嚴禁zygote, netd, installd, vold, ueventd 等關鍵process 與其它process 共享同一個security context.
2.3 創建文件后,關聯它的執行檔,在file_contexts 中, 關聯相關的執行檔.
比如 /system/bin/idmap 則是 /system/bin/idmap u:object_r:idmap_exec:s0
2.4 填寫policy 到相關的te 文件中
如果沿用原來父進程的te 文件,則直接添加.
如果是新的文件,那么首先添加:
#==============================================
# Type Declaration
#==============================================
type idmap, domain;
type idmap_exec, exec_type, file_type;
#==============================================
# Android Policy Rule
#==============================================
#permissive idmap;
domain_auto_trans(zygote, idmap_exec, idmap);
然后添加新的policy:
# new policy
allow idmap resource_cache_data_file:dir rw_dir_perms;
allow idmap resource_cache_data_file:file create_file_perms;
3). 權限放大情況處理
如果直接按照avc: denied 的LOG 轉換出SELinux Policy, 往往會產生權限放大問題. 比如因為要訪問某個device, 在這個device 沒有細化SELinux Label 的情況下, 可能出現:
<7>[11281.586780] avc: denied { read write } for pid=1217
comm="mediaserver" name="tfa9897" dev="tmpfs" ino=4385
scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0
tclass=chr_file permissive=0
如果直接按照此LOG 轉換出SELinux Policy: allow mediaserver device:chr_file {read write}; 那么就會放開mediaserver 讀寫所有device 的權限. 而Google 為了防止這樣的情況, 使用了neverallow 語句來約束, 這樣你編譯sepolicy 時就無法編譯通過.
為了規避這種權限放大情況, 我們需要細化訪問目標(Object) 的SELinux Label, 做到按需申請. 通常會由三步構成
3.1 定義相關的SELinux type.
比如上述案例, 在 device/mediatek/common/sepolicy/device.te 添加
type tfa9897_device, dev_type;
3.2 綁定文件與SELinux type.
比如上述案例, 在 device/mediatek/common/sepolicy/file_contexts 添加
/dev/tfa9897(/.*)? u:object_r:tfa9897_device:s0
3.3 添加對應process/domain 的訪問權限.
比如上述案例, 在 device/mediatek/common/sepolicy/mediaserver.te 添加
allow mediaserver tfa9897_device:chr_file { open read write };
那么哪些訪問對象通常會遇到此類呢?(以L 版本為例)
-
File 類型:
– 類型定義: external/sepolicy/file.te;
– 綁定類型: external/sepolicy/file_contexts; - device
– 類型定義: external/sepolicy/device.te;
– 類型綁定: external/sepolicy/file_contexts; -
虛擬File 類型:
– 類型定義: external/sepolicy/file.te;
– 綁定類型: external/sepolicy/genfs_contexts; -
Service 類型:
– 類型定義: external/sepolicy/service.te;
– 綁定類型: external/sepolicyservice_contexts; -
Property 類型:
– 類型定義: external/sepolicy/property.te;
– 綁定類型: external/sepolicy/property_contexts; -
通常我們強烈反對更新google default 的policy, 大家可以更新device目錄下對應OEM廠商的policy.
六、新版本遇到的問題
1.Android 8.1 以上,非系統進程設置系統域屬性問題:
權限異常的log:
[ 24.371278@2] type=1400 audit(1514764828.528:304): avc: denied { read } for pid=3126 comm="HwBinder:3126_1" name="u:object_r:default_prop:s0" dev="tmpfs" ino=12511 scontext=u:r:hal_audio_default:s0 tcontext=u:object_r:default_prop:s0 tclass=file permissive=0 duplicate messages suppressed
查找代碼源頭是設置了如下屬性:property_set("persist.audio.debug.search", "");
但是Android 8.1 及以上版本系統添加了權限限制,不允許普通進程設置系統屬性,所以按照之前的方法添加會遇到如下編譯錯誤:
22:33:00 FAILED: out/target/product/marconi/obj/ETC/precompiled_sepolicy_intermediates/precompiled_sepolicy 22:33:00 /bin/bash -c "out/host/linux-x86/bin/secilc -m -M true -G -c 30 out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil out/target/product/marconi/obj/ETC/28.0.cil_intermediates/28.0.cil out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil -o out/target/product/marconi/obj/ETC/precompiled_sepolicy_intermediates/precompiled_sepolicy -f /dev/null" 22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil:7039 22:33:00 (neverallow base_typeattr_262_28_0 base_typeattr_271_28_0 (file (ioctl read write create setattr lock relabelfrom append unlink link rename open))) 22:33:00 <root> 22:33:00 allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1514 22:33:00 (allow hal_audio_default audio_prop_28_0 (file (ioctl read getattr lock map open))) 22:33:00 22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil:7029 22:33:00 (neverallow base_typeattr_262_28_0 base_typeattr_263_28_0 (property_service (set))) 22:33:00 <root> 22:33:00 allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1513 22:33:00 (allow hal_audio_default audio_prop_28_0 (property_service (set))) 22:33:00 22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil:9866 from system/sepolicy/public/property.te:155 22:33:00 (neverallow base_typeattr_262 base_typeattr_271 (file (ioctl read write create setattr lock relabelfrom append unlink link rename open))) 22:33:00 <root> 22:33:00 allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1514 22:33:00 (allow hal_audio_default audio_prop_28_0 (file (ioctl read getattr lock map open))) 22:33:00 22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil:9824 from system/sepolicy/public/property.te:155 22:33:00 (neverallow base_typeattr_262 base_typeattr_263 (property_service (set))) 22:33:00 <root> 22:33:00 allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1513 22:33:00 (allow hal_audio_default audio_prop_28_0 (property_service (set)))
解決方案 1:
允許 hal_audio_default 進程設置 persist.audio.debug.xxx屬性
(1)property_contexts 添加:
persist.audio.debug. u:object_r:audio_debug_prop:s0
(2)hal_audio_default.te 添加:
set_prop(hal_audio_default, audio_debug_prop)
(3)property.te
添加:
type audio_debug_prop, property_type, mlstrustedsubject; #這一attribute包含了所有能越過MLS檢查的主體domain。
解決方案 2:
非系統域的屬性設置則沒有如上限制,可以將 audio 屬性修改為vender 域的屬性如: persist.vendor.audio.
2.Android9.0為系統服務添加SELinux權限:
從Android 4.4到Android 7.0的SELinux策略構建方式合並了所有sepolicy片段(平台和非平台),然后在根目錄生成單一文件,而Android 8.0開始關於selinux架構也類似於HIDL想把系統平台的selinux策略和廠商自己維護的策略剝離開來, 允許合作伙伴單獨自己的策略,構建他們的鏡像(.img)引導,這樣便可以獨立於平台更新這些.img,反之亦然(即:在不更新合作伙伴鏡像的情況下執行平台更新)。
關於8.0 selinux架構介紹官方文檔(SELinux_Treble.pdf): https://pan.baidu.com/s/161_OpZRqx7PvOmcQ4G-CwA
1、例如:xxx service權限異常有如下log:
324 E SELinux : avc: denied { add } for service=xxx pid=933 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0
則需要對selinux進行權限配置:(參考公式:allow SourceContext TargetContext:TargetClass Permission)
allow system_server default_android_service:service_manager { add };
2、以下部分是對selinux權限進行定義(實際需根據SDK的版本修改對應目錄):
(1)./system/sepolicy/prebuilts/api/26.0/nonplat_sepolicy.cil
(typeattribute xxx_service_26_0)
(roletype object_r xxx_service_26_0)
(2)./system/sepolicy/prebuilts/api/27.0/nonplat_sepolicy.cil
(typeattribute xxx_service_27_0)
(roletype object_r xxx_service_27_0)
(3)./system/sepolicy/prebuilts/api/28.0/private/compat/26.0/26.0.cil
(typeattributeset xxx_service_26_0 (xxx_service))
(4)./system/sepolicy/prebuilts/api/28.0/private/compat/27.0/27.0.cil
(typeattributeset xxx_service_27_0 (xxx_service))
(5)./system/sepolicy/prebuilts/api/28.0/private/service_contexts
xxx u:object_r:xxx_service:s0
(6)./system/sepolicy/prebuilts/api/28.0/public/service.te
type xxx_service, system_api_service, system_server_service, service_manager_type;
(7)./system/sepolicy/private/compat/26.0/26.0.cil
(typeattributeset xxx_service_26_0 (xxx_service))
(8)./system/sepolicy/private/compat/27.0/27.0.cil
(typeattributeset xxx_service_27_0 (xxx_service))
(9)./system/sepolicy/private/service_contexts
xxx u:object_r:xxx_service:s0
(10)./system/sepolicy/public/service.te
type xxx_service, system_api_service, system_server_service, service_manager_type;
3、使用修改selinux權限的系統服務:
// 1.定義aidl文件:------------------------------------ package com.xxx.aidl; interface ISecurityServer { void startLockAppSevice(); } //2.實現aidl接口:------------------------------------ package com.xxx.aidl; public class SecurityServer extends ISecurityServer.Stub{ public void startLockAppSevice() { } } //3.提供對外接口類:---------------------------------- package com.xxx.security; public class SecurityManager { private final ISecurityServer mService; public SecurityManager(ISecurityServer service) { mService = service; } public void startLockAppSevice(){ try { mService.startLockAppSevice(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //4.注冊服務:--------------------------------------- SystemServiceRegistry.java 添加 registerService("xxx", com.xxx.SecurityManager.class, new CachedServiceFetcher<com.xxx.SecurityManager>() { @Override public com.xxx.SecurityManager createService(ContextImpl ctx) { IBinder b = ServiceManager.getService("xxx"); return new com.xxx.SecurityManager(com.xxx.aidl.ISecurityServer.Stub.asInterface(b)); } }); //5. SystemServer.java 將服務添加進ServiceManager ------------- try { // com.xxx.aidl.SecurityServer Security = new com.xxx.aidl.SecurityServer(mContext); ServiceManager.addService("xxx", Security); } catch (Throwable e) { Log.e(TAG, "Failure starting olc_service_security", e); } //6. 服務調用:------------------------------------------------- SecurityManager securityManager = (SecurityManager)getSystemService("xxx");