1. 程式人生 > >android中SELINUX規則分析和語法簡介

android中SELINUX規則分析和語法簡介

目錄:

SELINUX簡介

SELINUX是可以理解為一種android上面的安全機制,是有美國國家安全域性和一些公司設計的一個針對linux的安全加強系統,我們可以通過配置SELINUX的相關policy,來定製自己的手機的一些許可權,比如,我們可以完全讓root使用者沒有任何的許可權和user一樣

檢視SELINUX許可權

在android裡面,有兩個型別:

  1. 程序
  2. 檔案

針對這兩種型別,我們可以先來看看他們的不同。

1. 檢視程序許可權

在android上面,adb shell之後進入手機,ps -Z可以檢視當前程序所擁有的selinux的許可權。

$ ps -Z
LABEL                          USER     PID   PPID  NAME
u:r:init:s0                    root      1     0     /init
u:r:kernel:s0                  root      2     0     kthreadd
..
. u:r:kernel:s0 root 258 2 irq/322-HPH_R O u:r:logd:s0 logd 259 1 /system/bin/logd u:r:healthd:s0 root 260 1 /sbin/healthd u:r:lmkd:s0 root 261 1 /system/bin/lmkd u:r:servicemanager:s0 system 262 1 /system/bin/servicemanager u:r:vold:s0 root 263 1 /system/bin/vold u:r:surfaceflinger:s0 system 264 1 /system/bin/surfaceflinger u:r:tctd:s0 root 265 1 /system/bin/tctd u:r:rfs_access:s0 system 268 1 /system/bin/rfs_access u:r:tee:s0 system 271 1 /system/bin/qseecomd u:r:kernel:s0 root 280 2 kworker/3:1H u:r:kernel:s0 root 290 2 kauditd u:r:rmt_storage:s0 nobody 291 1 /system/bin/rmt_storage u:r:shell:s0 shell 292 1 /system/bin/sh u:r:netd:s0 root 295 1 /system/bin/netd u:r:debuggerd:s0 root 296 1 /system/bin/debuggerd u:r:tee:s0 system 297 271 /system/bin/qseecomd

在這個例子中,我們可以進行分析。

  • u:在android中,只定義了一個user即為u
  • r:程序統一定義成r , 檔案統一定義成 object_r
  • init:程序所屬的域 , 不唯一。
  • s0:一種安全等級

2. 檢視檔案許可權

另外就是檔案,檔案想要檢視相關SELINUX許可權的話,需要去執行ls -Z

$ ls -Z
drwxr-x--x root     sdcard_r          u:object_r:rootfs:s0 storage
drwx--x--x root     root              u:object_r:tmpfs:s0 synthesis
dr-xr-xr-x root     root              u:object_r:sysfs:s0 sys
drwxr-xr-x root     root              u:object_r:system_file:s0 system
drwxrwxr-x system   tctpersist          u:object_r:tct_persist_file:s0 tctpersist
lrwxrwxrwx root     root              u:object_r:rootfs:s0 tombstones -> /data/tombstones
-rw-r--r-- root     root              u:object_r:rootfs:s0 ueventd.qcom.rc
-rw-r--r-- root     root              u:object_r:rootfs:s0 ueventd.rc
  • u:在android中,只定義了一個user即為u
  • object_r:代表是一個檔案
  • rootfs:這種代表檔案所屬的域,看有的解釋說可以理解成type
  • s0:一種安全等級

如何配置selinux

1.基本語法

很多te檔案集中在external\sepolicy資料夾下,廠商也是有自己的配置規則在devic目錄下,例如RK專案中
BOARD_SEPOLICY_DIRS ?= device/rockchip/common/sepolicyBOARD_SEPOLICY_DIRS在系統編譯時會被使用,非本文重點,有興趣的話請自行查閱相關資料。

A. 上下文描述檔案

可以在上下文的描述檔案中為您的物件指定標籤。

檔名 歸類
mac_permissions.xml App程序
seapp_contexts App資料檔案
file_contexts 系統檔案
property_contexts 系統屬性
  • file_contexts 用於為檔案分配標籤,並且可供多種使用者空間元件使用。在建立新政策時,請建立或更新該檔案,以便為檔案分配新標籤。
  • genfs_contexts 用於為不支援擴充套件屬性的檔案系統(例如,procvfat)分配標籤。
  • property_contexts 用於為 Android 系統屬性分配標籤,以便控制哪些程序可以設定這些屬性。
  • service_contexts 用於為 Android Binder 服務分配標籤,以便控制哪些程序可以為相應服務新增(註冊)和查詢(查詢)Binder 引用。
  • seapp_contexts 用於為app程序和 /data/data 目錄分配標籤。
  • mac_permissions.xml 用於根據應用簽名和應用軟體包名稱(後者可選)為應用分配 seinfo 標記。隨後,分配的 seinfo 標記可在 seapp_contexts 檔案中用作金鑰,以便為帶有該 seinfo 標記的所有應用分配特定標籤。

B. 策略檔案te

以te結尾的檔案是SELinux中的策略檔案,它定義了作用域和標籤。
來看一個te檔案:

allow factory ttyMT_device:chr_file { read write open ioctl};
allow factory ttyGS_device:chr_file { read write open ioctl};
allow factory irtx_device:chr_file { read write ioctl open };

上面這幾行就是最基本的te語句了,相似的te語句的會被歸類在一個的te檔案下面。如上面的語句都是作用於factory,則會在factory.te檔案裡。external\sepolicy中集中了很多系統定義的te檔案

例如第一條

allow factory ttyMT_device:chr_file { read write open ioctl};

翻譯一下就是

允許`factory`域裡的程序或服務
對型別為`ttyMT_device`的類別為檔案(`chr_file`)
執行`read`,`write`,`open`,`ioctl`許可權的操作

te檔案的基本語法:

# rule_name source_type target_type:class perm_set
  • rule_name:規則名,分別有allow,dontaudit,auditallow,neverallow等
  • source_type:源型別,主要作用是用來填寫一個域(domain)
  • target_type:目標的型別
  • class:類別,目標(客體)是哪種類別,主要有File,Dir,Socket,SEAndroid還有Binder等,在這些基礎上又細分出裝置字元型別(chr_file),連結檔案(lnk_file)等。可以通過ls -l檢視檔案型別
  • perm_set:動作集

我們從上到下按順序介紹一下:
B1. rule_name

規則名稱 匹配是否允許 不匹配是否允許 匹配是否記錄 不匹配是否記錄
allow Yes No No Yes
dontaudit No No No No
auditallow No No Yes Yes
neverallow - - - -
  • allow:允許某個程序執行某個動作
  • auditallow:audit含義就是記錄某項操作。預設SELinux只記錄那些許可權檢查失敗的操作。 auditallow則使得許可權檢查成功的操作也被記錄。注意,allowaudit只是允許記錄,它和賦予許可權沒關係。賦予許可權必須且只能使用allow語句。
  • dontaudit:對那些許可權檢查失敗的操作不做記錄。
  • neverallow:沒有被allow到的動作預設就不允許執行的。neverallow只是顯式地寫出某個動作不被允許,如果添加了該動作的allow,則會編譯錯誤

B2. source_type
指定一個“域”(domain),一般用於描述程序,該域內的的程序受該條TE語句的限制。用type關鍵字,把一個自定義的域與原有的域相關聯

type shell, domain;

上面這句話的意思是,賦予shelldomain屬性,同時,shell域屬於domain這個集合裡。如果有一個allow domain xxxxx的語句,同樣地也給了shell xxxxx的屬性

B3. target_type
指定程序需要操作的客體(檔案,資料夾等)型別,同樣是用type與一些已有的型別,屬性相關聯
以上面的ttyMT_device為例:

# 定義一個型別,屬於dev_type屬性
type ttyMT_device, dev_type; 

屬性dev_typeexternal/sepolicyattributes的定義如下

attribute dev_type;

attribute關鍵字定義一個屬性,type可以與一個或多個屬性關聯,例如:

type usb_device, dev_type, mlstrustedobject;

另外,還有一個關鍵字typeattribute,type有兩個作用:

  1. 定義(宣告)
  2. 關聯某個屬性。

可以把這兩個作用分開,type定義,typeattribute進行關聯

# 定義httpd_user_content_t,並關聯兩個屬性
type httpd_user_content_t, file_type, httpdcontent;

分成兩條語句進行表述:

#定義httpd_user_content_t
type httpd_user_content_t;
#關聯屬性
typeattribute httpd_user_content_t file_type, httpdcontent;

external/sepolicy/attributes裡定義了很多屬性,下面截取了一些常見的定義:

# All types used for devices.
attribute dev_type;
# All types used for processes.
attribute domain;
# All types used for filesystems.
attribute fs_type;
# All types used for files that can exist on a labeled fs.
# Do not use for pseudo file types.
attribute file_type;
# All types used for domain entry points.
attribute exec_type;
# All types used for property service
attribute property_type;
# All service_manager types created by system_server
attribute system_server_service;
# All domains that can override MLS restrictions.
# i.e. processes that can read up and write down.
attribute mlstrustedsubject;
# All types that can override MLS restrictions.
# i.e. files that can be read by lower and written by higher
attribute mlstrustedobject;
# All domains used for apps.
attribute appdomain;
# All domains used for apps with network access.
attribute netdomain;
# All domains used for binder service domains.
attribute binderservicedomain;

B4. class
客體的具體類別。用class來定義一個客體類別,來看一下external/sepolicy/security_classes檔案

# file-related classes
class filesystem
class file #代表普通檔案
class dir #代表目錄
class fd #代表檔案描述符
class lnk_file #代表連結檔案
class chr_file #代表字元裝置檔案
......
# network-related classes
class socket #socket
class tcp_socket
class udp_socket
......
class binder #Android平臺特有的binder
class zygote #Android平臺特有的zygote

B5. perm_set
具體的操作,系統的定義在external/sepolicy/access_vectors中。有兩種定義方法。
1.用common命令定義:
格式為:

common common_name { permission_name ... }

common定義的perm_set能被另外一種perm_set命令class所繼承,而class定義的perm_set則不能被繼承
如:

common file {
	ioctl read write create getattr setattr lock relabelfrom relabelto
	append unlink link rename execute swapon quotaon mounton
}

class命令定義:

# class class_name [ inherits common_name ] { permission_name ... }

例如class dir繼承了file

class dir
inherits file
{
	add_name
	remove_name
	reparent
	search
	rmdir
	open
	audit_access
	execmod
}

最後是一些特殊的配置檔案:

  • external/sepolicy/attributes -> 所有定義的attributes都在這個檔案
  • external/sepolicy/access_vectors -> 對應了每一個class可以被允許執行的命令
  • external/sepolicy/roles -> Android中只定義了一個role,名字就是r,將r和attribute domain關聯起來
  • external/sepolicy/users -> 其實是將user與roles進行了關聯,設定了user的安全級別,s0為最低階是預設的級別,mls_systemHigh是最高的級別
  • external/sepolicy/security_classes -> 指的是上文命令中的class,個人認為這個class的內容是指在android執行過程中,程式或者系統可能用到的操作的模組
  • external/sepolicy/te_macros -> 系統定義的巨集在te_macros檔案
  • external/sepolicy/***.te -> 一些配置的檔案,包含了各種執行的規則

2.幾個例子

# 將init關聯到domain,即將domain設定為init型別的屬性
type init, domain;

# 允許init型別對unlabeled型別的filesystem進行mount的操作
allow init unlabeled:filesystem mount;

# 允許init型別對fotad型別的unix_stream_socket 進行bind和create的操作
allow init fotad:unix_stream_socket { bind create };

# appdomain是定義在te_macros裡面的一個巨集,很多的app規則會使用類似app_domain(shell)的命令將其新增進去
# 允許app去對anr_data_file型別的目錄進行查詢的操作
allow appdomain anr_data_file:dir search;
# 允許app對anr_data_file型別的file進行開啟和新增操作
allow appdomain anr_data_file:file { open append };

#絕不允許app(除了有unconfineddomain屬性的app)對kmem_device型別的字元裝置進行讀寫的操作
neverallow { appdomain -unconfineddomain } kmem_device:chr_file { read write };

# 絕不允許除了unconfineddomain以外的app對self型別的capability2進行任何的操作
neverallow { appdomain -unconfineddomain } self:capability2 *;

# 宣告一個httpd_user_content_t的型別,具有file_type和httpdcontent的屬性
type httpd_user_content_t, file_type, httpdcontent;

# 宣告一個httpd_user_content_t的型別
type httpd_user_content_t;
# 定義httpd_user_content_t具有file_type, httpdcontent的屬性
typeattribute httpd_user_content_t file_type, httpdcontent;

# 允許所有具有app屬性的內容可以去對self屬性的rawip_socket進行create的操作
allow appdomain self:rawip_socket create_socket_perms;

# 允許user_t和domain屬性的類對bin_t, file_type, sbin_t型別的file進行可執行的操作
allow {user_t domain} {bin_t file_type sbin_t}:file execute ;

# 這兩條語句的表述其實是一致的,其實self指的是目標的型別和發起人的型別是一致的
allow user_t user_t:process signal;
allow user_t self:process signal;

# 允許user_t對bin_t型別的file進行除了write setattr ioctl相關的操作
allow user_t bin_t:file ~{ write setattr ioctl };

生成規則檔案的方法

下面介紹一下最簡單的安全策略(se-policy)新增方法,大家碰到SELinux導致的訪問禁止問題,可以參考用這種方法確認和解決。

1.安裝pc上的工具,用於自動生成安全策略

$ sudo apt-get install policycoreutils

2.刷userdebug/eng軟體,先將SELinux設定成Permissive模式,只輸出警告不阻止操作
使用getenforce命令檢視當前模式:

$ adb shell getenforce
Enforcing

在Enforcing模式下,除安全策略允許外的操作都會被阻止;使用setenforce命令更改當前模式(root許可權需要):

$ adb root
restarting adbd as root

$ adb shell setenforce 0

$ adb shell getenforce
Permissive

開發如果碰到懷疑是SELinux 可以通過這種方法關閉SELiunx( setenforce 0),以確認是不是SELinux引起的

3.按照流程完成整個操作,抓取log,過濾出警告資訊
如果log較多,可以先用grep工具過濾一下:

$ grep "avc: *denied" log.txt > denied.txt
$ cat denied.txt
<14>[  389.521062] avc:  denied  { set } for property=audio.ftm.rcv_reverse scontext=u:r:system_app:s0 tcontext=u:object_r:default_prop:s0 tclass=property_servic

4.使用pc工具audit2allow生成安全策略
命令audit2allow用來一次性生成所有安全策略,輸入為前面抓取的 log

$ audit2allow -i denied.txt
#============= system_app ==============
allow system_app default_prop:property_servic set;

使用audit2allow遇到的問題

本地執行audit2allow時發現一個很奇怪的問題,執行audit2allow -i 123.txt的時候首行log會失效,解析出來的結果是從第二行開始的,然後跟了下原始碼,發現呼叫到python2.7/dist-packages/sepolgen/audit.py__parse_line方法時因捕獲到ValueError異常unable to open (null): Bad address,因而msg被設定為DaemonStartMessage型別,導致未能完成之後的解析。
可是奇怪的是哪怕第二行和第一行寫的一模一樣,audit2allow卻不會報錯。

$ cat 123.txt
<14>[  389.521062] avc:  denied  { set } for property=audio.ftm.rcv_reverse scontext=u:r:system_app:s0 tcontext=u:object_r:default_prop:s0 tclass=property_servic

$ audit2allow -i 123.txt
木有輸出。。。

$ cat 456.txt
<14>[  389.521062] avc:  denied  { set } for property=audio.ftm.rcv_reverse scontext=u:r:system_app:s0 tcontext=u:object_r:default_prop:s0 tclass=property_servic
<14>[  389.521062] avc:  denied  { set } for property=audio.ftm.rcv_reverse scontext=u:r:system_app:s0 tcontext=u:object_r:default_prop:s0 tclass=property_servic

$ audit2allow -i 456.txt
#============= system_app ==============
allow system_app default_prop:property_servic set;
這次正常輸出了。。。

python2.7/dist-packages/sepolgen/audit.py

def __parse_line(self, line):
   rec = line.split()
   for i in rec:
       found = False
       if i == "avc:" or i == "message=avc:" or i == "msg='avc:":
           msg = AVCMessage(line)
           found = True
       elif i == "security_compute_sid:":
           msg = ComputeSidMessage(line)
           found = True
       elif i == "type=MAC_POLICY_LOAD" or i == "type=1403":
           msg = PolicyLoadMessage(line)
           found = True
       elif i == "type=AVC_PATH":
           msg = PathMessage(line)
           found = True
       elif i == "type=DAEMON_START":
           msg = DaemonStartMessage(list)
           found = True
           
       if found:
           self.check_input_file = True
           try:
               msg.from_split_string(rec)
           except ValueError:
               msg = InvalidMessage(line)
           return msg
   return None

因為未跟蹤到具體raise資訊為unable to open (null): Bad addressValueError位置,未能繼續跟進,所以在捕獲到異常時又重新呼叫了一次msg.from_split_string

commit 227845eb421a425568a3bc2a0e92e8ad20a720b3
Author: liuxiuquan <[email protected].com>
Date:   Tue Dec 11 16:38:49 2018 +0800

    [liuxq]add for audit2allow parse first line error

diff --git a/audit.py b/audit.py
index 56919be..53cd61c 100644
--- a/audit.py
+++ b/audit.py
@@ -370,11 +370,17 @@ class AuditParser:
     #      and valid audit message.
     def __parse_line(self, line):
         rec = line.split()
+
+        # 20181211 liuxq add for audit2allow parse first line error begin
+        isAvc = False
+        # 20181211 liuxq add for audit2allow parse first line error end
+
         for i in rec:
             found = False
             if i == "avc:" or i == "message=avc:" or i == "msg='avc:":
                 msg = AVCMessage(line)
                 found = True
+                isAvc = True
             elif i == "security_compute_sid:":
                 msg = ComputeSidMessage(line)
                 found = True
@@ -387,14 +393,33 @@ class AuditParser:
             elif i == "type=DAEMON_START":
                 msg = DaemonStartMessage(list)
                 found = True
-                
+
+
+            # 20181211 liuxq add for audit2allow parse first line error begin
+            # if found:
+            #     self.check_input_file = True
+            #     try:
+            #         msg.from_split_string(rec)
+            #     except ValueError:
+            #         msg = InvalidMessage(line)
+            #     return msg
+
             if found:
                 self.check_input_file = True
                 try:
                     msg.from_split_string(rec)
-                except ValueError:
+                except ValueError,err: #liuxq add
                     msg = InvalidMessage(line)
+                    #print "liuxq first get ValueError",err.message
+                    if isAvc and 'Bad' in err.message:
+                        msg = AVCMessage(line)
+                        try:
+                            msg.from_split_string(rec)
+                        except ValueError,err:
+                            #print "liuxq second get ValueError",err.message
+                            msg = InvalidMessage(line)
                 return msg
+            # 20181211 liuxq add for audit2allow parse first line error end
         return None
 
     # Higher-level parse function - take a line, parse it into an

可以看到修改audit.py後可以正常解析第一行log了

$ audit2allow -i 123.txt 
#============= system_app ==============
allow system_app default_prop:property_servic set;

本地環境:
Ubuntu 14.04.5 LTS
Python 2.7.6

參考文章:
https://source.android.com/security/selinux/implement
https://blog.csdn.net/chaoy1116/article/details/45063629
https://blog.csdn.net/chaoy1116/article/details/45063629
https://blog.csdn.net/jaczen/article/details/73028302