SELinux策略語法以及示例策略

Rand_CS發表於2024-06-08
  • 首發公號:Rand_cs

SELinux策略語法以及示例策略

本文來講述 SELinux 策略常用的語法,然後解讀一下 SELinux 這個專案中給出的示例策略

安全上下文

首先來看一下安全上下文的格式:

user : role : type : level

每一個主體和客體都有一個安全上下文,通常也稱安全標籤、標籤,由 4 部分組成(最後一部分 mls 是可選的)

  • user,user 為 SELinux User,而非傳統意義上的 Linux User。使用者登入系統後,login 程式根據當前策略配置檔案,將 Linux User 對映到相應 SELinux User。
  • role,不同 SELinux User 所能扮演的 SELinux Role 不同,而不同 SELinux Role 具有不同的能力(許可權)。從而方便地給不同的 Linux User 定義不同的能力。
    • 客體的 role 都預設為 object_r,這是一個內建變數(程式碼裡面寫死的)

NOTE:上述兩部分則是 SELinux 提供的 RBAC (Role Based Access Control) 安全模型,目前我們大部分示例不會用到這兩個部分。但是根據 SELinux 語法規則,必須要定義至少一個 user 和 role。比如說 Android 運用的策略便沒有使用 user、role,在 Android 上,所有 user 部分都是 u,所有 role 部分都是 r (客體的 role 為 object_r)

  • type,SELinux 中最重要的部分,SELinux 提供的 TE(Type Enforcement) 安全模型就是基於此來實現的)。每個主體和客體都有自己的型別,但是英文中叫法有點不一樣,主體也就是程序的型別叫做 domain,客體(不止檔案)的型別就叫做 type,這裡看英文文件的時候需要稍微注意一下。
  • mls,此部分與檔案機密性有關,這是 SELinux 對 BLP 模型的實現,具體的後面再詳述

策略語法

標籤各部分定義

定義型別屬性

type a;        #定義一個型別 a
type a_t;      #定義一個型別 a_t

attribute TestFile;   #定義一個屬性 TestFile

type b_t, TestFile;   #定義一個型別 b_t,具有屬性 TestFile

typeattribute a_t TestFile;  #使a_t也具有TestFile屬性

NOTE:

  • type 和 attribute 兩者位於同一個名稱空間,也就是說如果定義了 type a,那就不能定義 attribute a
  • a_t,後面有個 t 是表示 "type" 型別,這是 PC 端 SELinux 策略常見寫法,但是 Android 不這樣用,沒有字尾
  • 對於屬性的理解,用上面的示例來說,可以理解為 a_t 和 b_t 都具有相同的屬性 TestFile,也可以理解為 TestFile 它是一個 {a_t, b_t} 的集合

定義角色

type p_t;      # 定義了一個型別 p_t
role rand;     # 定義了一個角色 rand
role rand types p_t;   #rand 這個角色與 p_t 關聯(具有 p_t 的能力)

對於 role 擁有能力的理解:拿上面的例子來說,加入 p_t 對 a_t 這個型別的檔案有讀寫許可權,那麼因為 rand 與 p_t 關聯,我們就認為 rand 這個角色擁有讀寫 a_t 型別檔案的能力

定義使用者

user Lyy roles rand;   # Lyy 這個 SELinux user 可以扮演 rand 這個角色

定義 mls

sensitivity s0;        //定義靈敏度 s0
sensitivity s1;        //定義靈敏度 s1
dominance { s0 s1 }    //表示 s0 的機密性 低於 s1

這裡只是順便提一下 sensitivity 如何定義,關於 mls 不只有 sensitivity 還有 category,這部分後面的文章再講,後續的示例策略也不會包含 mls 部分。

客體類別和許可權

class file   #定義名為 file 的客體類別。注意沒有 ';'
class file { read, write }  #針對 file 類別的客體,相關許可權有 read、write。注意還是沒有 ';'

common file { read, write }   #定義一個許可權集合,此集合名為 file
class file inherits file { create }  #上述 file 類別關聯的許可權也可以如此定義,表示 file 類別的許可權有 read write create

Acess Vector Rules

有 4 個,allow、dontaudit、auditallow、neverallow

語法格式都是一樣的

# a_t 型別的程序 對於 c_t型別的字元檔案 有read、write許可權
allow a_t c_t : chr_file { read, write };

# a_t 訪問 c_t:chr_file 失敗後,不做日誌統計
dontaudit a_t c_t : chr_file { read, write };
# a_t 即使成功訪問 c_t:chr_file,也要做日誌統計
auditallow a_t c_t : chr_file { read, write };
# 絕不允許 a_t 以 {read,write} 的形式訪問 c_t:chr
neverallow a_t c_t : chr_file { read, write };

賦予許可權的只有 allow 語句,注意 auditallow 語句也沒有賦予許可權,只是說即使訪問成功,也需要記錄此條訪問.

allow 語句使用的最為廣泛

如果某些訪問本就應該拒絕,拒絕是我們期望的,SELinux 預設只要拒絕就會有一條日誌記錄,但是我們可以使用 dontaudit 語句使其不產生日誌記錄

auditallow 用於某些重要資料訪問,因為它們很重要,即使一些程序有許可權訪問它們,但也希望有日誌記錄

neverallow 會在策略編譯期間起作用,再舉個例子

type a1, A;
type a2, A;
type b1, B;
type b2, B;
neverallow A B:file {read};
allow a1 b1:file {read};   #error

編譯的時候便會檢查到這類錯誤

型別轉換規則

type_transition a_t b_exec_t : process b_t;

a_t 型別的程序執行 b_exec_t 型別的可執行程式後,型別轉變為 b_t。通常程序要進行型別轉換都是在執行不同的 exec 之後轉換的。

此語句要生效,還需要 3 條 allow 語句:

# 允許 a_t 到 b_t 的轉換
# type_transition 只是指了一條路說你可以執行這個可執行檔案來改變自己的型別,
# 但並沒有給出實際的許可權
allow a_t b_t : process transition;

# a_t 型別的程序 需要對 b_exec_t 型別的可執行檔案有 執行、讀、獲取屬性 的許可權
allow a_t b_exec_t : file { execute read getattr };

# b_t 需要對 b_exec_t 有 entrypoint 許可權
allow b_t b_exec_t : file entrypoint;

檔案系統

SELinux 對檔案系統上面的檔案提供了幾種標記方式,標記方式就是說以何種方式決定檔案系統上檔案的標籤

fs_use_xattr ext4 system_u:object_r:fs_t;

fs_use_xattr 針對支援擴充套件屬性的檔案系統,比如說 ext4 檔案系統,其上的檔案的標籤由自身的 xattr 中名為 security.selinux 的擴充套件屬性決定。

後面的 system_u:object_r:fs_t表示檔案系統這個客體(具體到資料結構就是 超級塊)的標籤為 system_u:object_r:fs_t

fs_use_task pipefs system_u:object_r:fs_t;

fs_use_task 用於偽檔案系統,比如說 pipefs。使用 fs_use_task 修飾的檔案系統,其上的檔案的標籤都由建立它的父程序決定。同樣的對於 pipefs 檔案系統本身也就是超級塊,其標籤為 system_u:object_r:fs_t

fs_use_trans devpts system_u:object_r:devpts_t;
type_transition sysadm_t devpts_t : chr_file sysadm_devpts_t;

fs_use_trans,顧名思義,要基於 transition 規則,比如說如上定義了一條 type_transition 規則,意思是說,當 sysadm_t 型別的程序 在 devpts_t 型別的檔案系統上面 建立的檔案型別應為 sysadm_devpts_t

如果沒有 type_transition 規則,那麼其上的檔案和檔案系統的標籤是一致的,對於此例來說就是 devpts_t

genfscon proc / system_u:object_r:proc_t 
genfscon proc /kmsg system_u:object_r:proc_kmsg_t 
genfscon proc /kcore system_u:object_r:proc_kcore_t 
genfscon proc /mdstat system_u:object_r:proc_mdstat_t 

genfscon 可以很靈活的定義某個檔案系統上的檔案的標籤,從其上的例子應該就能看出,它能指定一個檔案系統下某個目錄甚至某個檔案的標籤。

示例策略

該示例策略來自:https://github.com/SELinuxProject/selinux-notebook/tree/main/src/notebook-examples/selinux-policy

按照文件所述將其編譯,得到一個 policy.conf 檔案,讓我們來簡單的過一遍,

# 這裡是定義客體類別
class security
class process
class system
....

# 這與系統剛啟動,selinux 初始化的時候有關,先跳過
sid kernel
sid security
sid unlabeled
sid fs
...

# 定義一些許可權集合,後面方便與 class 關聯
common file {ioctl read write create getattr setattr lock relabelfrom relabelto append map unlink link rename execute quotaon mounton audit_access open execmod watch watch_mount watch_sb watch_with_perm watch_reads }
common socket {ioctl read write create getattr setattr lock relabelfrom relabelto append map bind connect listen accept getopt setopt shutdown recvfrom sendto name_bind }
...

# 將 class 與 許可權關聯起來
class security { compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot read_policy validate_trans }
class process { fork transition sigchld sigkill sigstop signull signal ptrace getsched setsched getsession getpgid setpgid getcap setcap share getattr setexec setfscreate noatsecure siginh setrlimit rlimitinh dyntransition setcurrent execmem execstack execheap setkeycreate setsockcreate getrlimit }
class system { ipc_info syslog_read syslog_mod syslog_console module_request module_load halt reboot status start stop enable disable reload }


# 為了更加動態靈活的控制許可權,SELinux 推出 bool,cap 等特性,先跳過
# 如果你想嘗試在你的機器上應用此策略,務必務必務必加上 bool xserver_object_manager false;
# 不要問我為什麼有三個 務必
policycap network_peer_controls;
bool xserver_object_manager false;


# 定義一個型別 unconfined_t
type unconfined_t;
# 定義一個角色 unconfined_r
role unconfined_r;
# unconfined_r 擁有 unconfined_t 的能力
role unconfined_r types { unconfined_t };

# 接下來是一系列的 allow 語句,因為是示例策略,且只有一個型別,這裡直接使用萬用字元 * 授予所有許可權
allow unconfined_t unconfined_t:security *;
allow unconfined_t unconfined_t:process *;
....

# 定義 seuser unconfined_u,可以扮演 unconfined_r 這個角色
user unconfined_u roles { unconfined_r };
# 定義 seuser system_u,可以扮演 unconfined_r 這個角色
user system_u roles { unconfined_r };

# 系統啟動時初始化相關,先跳過
sid kernel system_u:unconfined_r:unconfined_t
sid security system_u:object_r:unconfined_t
sid unlabeled system_u:object_r:unconfined_t

# 定義檔案系統標記方式
fs_use_xattr ext2 system_u:object_r:unconfined_t;
fs_use_xattr ext3 system_u:object_r:unconfined_t;
...

fs_use_task pipefs system_u:object_r:unconfined_t;
fs_use_task sockfs system_u:object_r:unconfined_t;

fs_use_trans mqueue system_u:object_r:unconfined_t;
fs_use_trans devpts system_u:object_r:unconfined_t;
...

genfscon selinuxfs / system_u:object_r:unconfined_t
genfscon proc / system_u:object_r:unconfined_t
...

這麼一套下來,感覺策略還是挺簡單的哈,沒那麼複雜,像 Android sepolicy、refpolicy 這些策略也就是加了億點點細節而已,沒什麼大不了的。

如果想試試這個初始策略,可以按照前文,修改 /etc/selinux/config,然後重啟,,,務必記得第一次一定要設定為 permissive 模式

  • 首發公號:Rand_cs

相關文章