Android : SELinux 簡析&修改


一 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");

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM