對Android 平臺下SElinux的理解及遇到過的相關問題解決方法總結

筆者在工作中多次遇到和SELinux相關的問題,初次遇到時一頭霧水,走了很多彎路,也耗費了很多時間精力。後來看了不少資料和博客,也研究了相關代碼,對SELinux有了些認識。所以用本文來做個總結,加深理解。

本文將從下面五個方面來逐步認識和理解Android 下SELinux。

  • 什麼是SELinux
  • 爲什麼需要SELinux
  • SElinux 工作原理
  • android 上的實現
  • 曾經遇到過的問題及解決辦法

一、什麼是SELinux
     
SELinux(Security-Enhanced Linux) 是美國國家安全局(NSA)對於強制訪問控制的實現,是 Linux歷史上最傑出的新安全子系統。NSA是在Linux社區的幫助下開發了一種訪問控制體系,在這種訪問控制體系的限制下,進程只能訪問那些在他的任務中所需要文件。SELinux 默認安裝在 Fedora 和 Red Hat Enterprise Linux 上,也可以作爲其他發行版上容易安裝的包得到。
SELinux 是 2.6 版本的 Linux 內核中提供的強制訪問控制(MAC)系統。對於目前可用的 Linux安全模塊來說,SELinux 是功能最全面,而且測試最充分的,它是在 20 年的 MAC 研究基礎上建立的。SELinux 在類型強制服務器中合併了多級安全性或一種可選的多類策略,並採用了基於角色的訪問控制概念。SELinux是一種基於 域-類型 模型(domain-type)的強制訪問控制(MAC)安全系統,它由NSA編寫並設計成內核模塊包含到內核中,相應的某些安全相關的應用也被打了SELinux的補丁,最後還有一個相應的安全策略。任何程序對其資源享有完全的控制權。
以上來之百度百科,也就是說SELinux 是一個安全系統,這個系統是要強制控制進程對系統資源的訪問,並提供基於角色和多層級的安全訪問控制策略。它要控制的不僅僅是用戶,而是進程級別的。
二、 爲什麼需要SELinux
      一般的系統提供的訪問控制策略(DAC)都是針對用戶級別的,比如說一個文件或者目錄設定的訪問權限針對者都是用戶,用戶包括文件的創建者owner,和創建者同在的組group用戶,組外用戶other,對於這三者我們都可以設定讀寫執行權限。可見,對於一個文件,如果owner有讀寫執行權限,以owner運行的所有程序都對這個文件有讀寫執行權限。這裏就會有問題了?如果owner 不小心運行了一個攻擊程序,這個程序就有可能破壞這個文件,這個是不安全的。再者比如用戶以root身份登錄,就有可能不小心刪除掉系統文件,造成不可預料的後果。
   針對以上場景,SELinux就能派上用場了。使用SELinux,我們可以指定只有滿足某種條件的進程才能訪問相關的系統資源,這樣即使用戶運行了攻擊程序或者以root身份運行,它也訪問不了SElinux 不授權的文件。

三、SELinux 的工作原理
        1. 把系統實體分爲主體和客體。比如進程爲主體,文件、目錄、文件描述符,網絡套接字等爲客體,進程也可以作爲客體看待。
        2. 對主體定義域上下文屬性,對文件定義類型上下文屬性,其實就是打個標籤。
       3. 在用戶空間的策略文件裏配置域所具有的訪問某種類型屬性以及域切換規則等
       4. 策略的判斷和執行由內核實現。 在系統運行過程中,當某個進程要訪問一個文件時,有系統內核根據用戶空間的配置的策略文件來判斷進程的域上下文是否有訪問文件的權限。同時在需要是完成域切換等操作。
下面看下什麼是域上下文和類型上下文
        用命令 ps -Z  可列出當前系統正在運行進程的域上下文:
        
      用命令 ls -Z 可列出文件和目錄的類型上下文屬性:
     
    可以看到域和類型的定義格式是一樣的,其實可認爲表達的是同一個意思,就是標籤。
    格式爲:用戶:角色:域名(或者類型):等級
    用戶: 標識哪個用戶,android裏面都是u
   域名(類型):用來標識一個安全上下文,不管是域還是類型,在SELinux看來都是一個屬性,劃分成不同的類型和域,其實是爲了在策略那裏可以達到統一控制。
      下面是定了2個類型集合(組)和2個域集合(組)。
# All types used for devices.
attribute dev_type;
# All types used for processes.
attribute domain;
# All types used for filesystems.
attribute fs_type;
# All domains used for apps.
attribute appdomain;
   下面的定義的logd_debugsystem_file類型都屬於file_type類型組。
    type logd_debug, file_type;
     type system_file, file_type;
    當然一個類型可以同時屬於多個類型組。
    下面是init 和init_shell 進程的安全上下文域,它們都屬於domain域組。
    type init, domain;
    type init_shell, domain;
    那麼就可以只通過下面一條策略來控制所有屬於domain的進程的權限,而不需要單獨針對每個進程域去做處理。
   allow domainfile_type:dir getattr; // 允許所有屬於domain 域組的進程對所有屬於fs_type類型組的目錄客體有讀取屬性的權限。
   當然也可以針對單個域做控制:
   allow init_shell system_file:file execute_no_trans;  //允許所有屬於init_shell 域的進程對所有屬於system_file類型的文件具有執行權限。
    角色:android 裏只有兩個,主體是r,客體是object_r。SELinux是同時支持基於角色的訪問控制的,比如針對公司可以定義總經理角色,部門經理角色和員工角色的三個角色,在允許同樣程序情況下,對有的資源,只允許總經理角色訪問,有的只允許部門經理訪問。可以通過角色來控制。
   級別:android 裏面都是s0 。SELinux同時也支持多層次安全級別管理,在什麼場景下可能會用到呢?比如說政府部門,可能設定高級別的領導是可以任意讀取低級別職員資料的,但不能給低級別寫資料,防止泄密;同樣低級別職員只能給領導寫東西,但是不能讀取領導的信息。對應到系統裏,就是級別高的進程可以讀取比它低級別的客體,低級別的進程不能讀取高級別的客體;低級別的進程可以往高級別的客體寫東西,高級別進程不能往低級別客體寫東西。這點使用SElinux是可以實現的。

 更多的策略配置實現在下節對照android上的實現來分析。

四、android 上的實現
    1.  在init.c main函數裏面初始化
int main(int argc, char **argv){
..........
union selinux_callback cb;
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
selinux_initialize();
.......
}
selinux_initialize()這個函數裏面先判斷SELinux 是否啓用,啓用則載入編譯出來的策略文件,並在用戶空間用mmap方式與內核共享策略文件,這樣內核之後就按照拿到的策略來控制訪問,然後設置SELinux啓動模式。(./external/libselinux/src)
   SELinux 啓動模式有兩種enforcing 和permissive。 enforcing 是強制安全檢查,不符合安全策略時不允許執行;permissive 模式也進行安全檢測,但是遇到不符合安全策略時僅僅打印警告信息,依然運行程序執行。
   可以在編譯文件裏面kernel cmdline 裏面加入啓動模式如androidboot.selinux=permissive。 一般eng版本使用的是permissive,user和user-debug版本啓動的是enforcing。當然如果對安全性要求不高,完全可以不啓用SELinux。在android5.1 之前默認並沒有真正意義上的使用。
 2. 策略文件在哪配置
    android模式的策略配置文件放在 external/sepolicy目錄下,一般不提倡直接修改這下面的文件,而是針對平臺或者項目在device或者vendor目錄下添加。比如:
  自己策略文件所在目錄:
    BOARD_SEPOLICY_DIRS += \
                    device/xxx/sepolicy

  所添加的策略文件
BOARD_SEPOLICY_UNION := \
        bluetooth.te \
        file.te 
  這樣添加的策略就參與編譯了。
3. 編譯出來的策略文件在哪?
    編譯出來的策略文件在out\...\root目錄下
   root\file_contexts
   root\sepolicy
   root\property_contexts
   root\seapp_contexts
   root\service_contexts
   所以修改完只需要編譯bootimage,但是燒錄boot 就可以驗證了。
4. 如何配置策略
   需要先了解系統針對客體和主體提供了哪些設置屬性和操作。
   external/sepolicy/security_classes 定義了客體的基本類型,比如下面的
 # for userspace object managers
class security
class process
class system
class capability
# file-related classes
class filesystem
class file
class dir
class fd
class lnk_file
class chr_file
class blk_file
class sock_file

     external/sepolicy/access_vectors 文件列出來各種客體類型所擁有的屬性,比如下面的file類型和dir 類型,類型間可以有集成關係。
common file
{
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
unlink
link
rename
execute
swapon
quotaon
mounton
}
class dir
inherits file
{
add_name
remove_name
reparent
search
rmdir
open
audit_access
execmod
}
external/sepolicy/global_macros全局宏變量定義文件,也就是用一個宏變量同時表示幾個屬性。如下例子
define(`x_file_perms', `{ getattr execute execute_no_trans }')
define(`r_file_perms', `{ getattr open read ioctl lock }')
define(`w_file_perms', `{ open append write }')
define(`rx_file_perms', `{ r_file_perms x_file_perms }')
define(`ra_file_perms', `{ r_file_perms append }')
define(`rw_file_perms', `{ r_file_perms w_file_perms }')
define(`rwx_file_perms', `{ rw_file_perms x_file_perms }')
define(`link_file_perms', `{ getattr link unlink rename }')
define(`create_file_perms', `{ create setattr rw_file_perms link_file_perms }')
external/sepolicy/te_macros 宏規則變量定義文件,也就是用一個變量來表示連續執行幾條規則。如下例子
define(`domain_trans', `
# Old domain may exec the file and transition to the new domain.
allow $1 $2:file { getattr open read execute };
allow $1 $3:process transition;
# New domain is entered by executing the file.
allow $3 $2:file { entrypoint open read execute getattr };
# New domain can send SIGCHLD to its caller.
allow $3 $1:process sigchld;
# Enable AT_SECURE, i.e. libc secure mode.
dontaudit $1 $3:process noatsecure;
# XXX dontaudit candidate but requires further study.
allow $1 $3:process { siginh rlimitinh };
')
external/sepolicy/file_contexts  文件安全上下文定義文件。就是說對於一個文件或者目錄,它的安全上下文應該在這裏定義,如果沒有定義,那麼默認和父目錄的安全上下文一樣。
比如說我們在data目錄下建了一個log目錄存放系統運行時的log,那麼我們可能希望這個log目錄的的安全上下文是u:object_r:data_log_file:s0
那麼就應該在file_contexts文件裏做如下定義:/data/log(/.*)?u:object_r:data_log_file:s0

external/sepolicy/property_contexts 屬性安全上下文定義文件。比如說新加了一個persist.log. * 開頭的屬性,那麼就需要給它添加對應的安全上下文,如果不添加或者添加的上下文不對,那麼在的某個進程裏寫入時就寫不進去。
比如做了如下定義,那麼是具有system_prop域安全上下文,只能在system進程裏修改值,在shell裏是不能訪問的。
persist.log.        u:object_r:system_prop:s0
如果希望在shell裏能修改值,就應該這麼寫persist.log.        u:object_r:shell_prop:s0 ,當然如果通過修改相關*.te 文件也能實現,但是不是很規範。

external/sepolicy/property.te 文件允許我們定義自己的prop 類型域,還有seapp_contexts、service_contexts 文件等,都是類似的,都可以根據需要修改。

下面幾個是常用到的規則操作
allow:賦予某項權限。
allowaudit:audit含義就是記錄某項操作。默認情況下是SELinux只記錄那些權限檢查失敗的操作。allowaudit則使得權限檢查成功的操作也被記錄。注意,allowaudit只是允許記錄,它和賦予權限沒關係。賦予權限必須且只能使用allow語句。
dontaudit:對那些權限檢查失敗的操作不做記錄。
neverallow:前面講過,用來檢查安全策略文件中是否有違反該項規則的allow語句
規則命令格式:規則名 主體域 客體域:類型權限屬性 ;       或者    規則名 主體域 客體域:類型 {權限屬性集合} ;
比如:
allow domain system_data_file:dir { search getattr };
allow domain system_file:file r_file_perms;

規則都定義在以te爲後綴的文件裏面,比如見到的init.te  installd.te system_app.te platform_app.te shell.te 等等。init.te 是針對init進程定義的規則,system_app.te 針對的是app進程是system的應用定義的規則,shell.te 針對的是shell進程定義的規則,以此類推。如果我們對某種類型的進程定義了新的規則,那麼我們應該把新規則添加到它對應的te文件裏面。比如我們在data目錄下加了一個log目錄,並且它的DAC權限爲777,安全上下文是u:object_r:data_log_file:s0,我們希望系統運行過程通過shell命令查看log,那麼我們就可以往shell.te 文件添加如下策略:
allow shelldata_log_file:dir r_dir_perms;
allow shell data_log_file:file r_file_perms;
如果我們新啓了一個進程,並且希望它運行在自己定義的安全上下文裏面,那麼我們應該給它添加對應的te規則文件。

五、 曾經遇到過的問題和解決辦法

   1. 在init.rc 文件裏啓動一個log服務,一直屬於restarting 狀態,無法啓動。系統輸出如下warning log。

W/init    ( 9795): type=1400 audit(0.0:284): avc: denied { execute_no_trans } for path="/system/bin/start_log.sh" dev="dm-0" ino=430 scontext=u:r:init:s0 tcontext=u:object_r:logsvc_exec:s0 tclass=file permissive=0

    主體是:init進程,對應域是u:r:init:s0 

    客體是:system/bin/start_log.sh 對應域是u:object_r:logsvc_exec:s0

    這個log的意思就是init進程在執行system/bin/start_log.sh 時沒有 execute_no_trans權限。那我們就給它init加權限。

    找到init.te 文件添加如下到末尾:

    allow init logsvc_exec:file execute_no_trans;


   2. 在開發一個自動壓力測試程序,其中有一項是測試休眠喚醒相關的,會通過讀取/d/suspend_stats 設備節點來判斷是否有休眠失敗情況。但是什麼也讀取不到這個值。打log發現如下warning。

    09-07 12:22:52.480 W/AutoStressTools(32594): type=1400 audit(0.0:11): avc: denied { read } for name="suspend_stats" dev="debugfs" ino=10246 scontext=u:r:system_app:s0 tcontext=u:object_r:debugfs:s0 tclass=file permissive=0

    主體是:AutoStressTools應用,屬於system app進程,對應域是u:r:system_app:s0

    客體是:/d/suspend_stats 對應域是u:object_r:debugfs:s0

    這個log的意思就是system讀取 /d/suspend_stats 時沒有 read權限。那我們就給它system app加權限。

    找到system_app.te 文件添加如下到末尾:

    allow system_app debugfs:file r_file_perms;


   3. CTS認證相關的,在init.rc 添加一個服務後,CTS 測試failed掉。

    

-- testInitDomain
junit.framework.AssertionFailedError: Expected 1 process in SELinux domain "u:r:init:s0" Found "[pid: "1" proctitle: "/init" label: "u:r:init:s0" vsize: 4161536, pid: "211" proctitle: "/system/bin/memsicp" label: "u:r:init:s0" vsize: 3977216]" expected:<1> but was:<2> at junit.framework.Assert.fail(Assert.java:50)
  也就是說使用了init域,這個在系統看來是不安全的,init域權限比較大。

  那麼我們就另外給這個服務添加域就可以,並且添加對應的規則te文件。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章