Seandroid 基礎


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代碼的規則,進行比較學習;

 

 

 


免責聲明!

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



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