SELinux旅程Part3-SELinux存取控制設計

先附上自製影片, 之後會再持續更新哦!


用 Ubuntu PPA 開啟 SELinux 且運行在 enforcing mode[Youtube]: https://www.youtube.com/watch?v=4fdXuIIBxes

Ubuntu PPA(Personal Package Archives)可以讓我們自行製作 Package 並透過 apt 套件管理工具下載。有鑒於社群最近修掉了一個 issue,此 PPA 未來會再進行更新,到時候就不必修改核心(Kernel)囉。

前言

上一篇講到 SELinux 從無到有的部署過程,包括核心中 SELinux 模組到使用者空間的 Library 和 SELinux-aware 應用程式,以及使用者空間程式如何透過 SELinuxfs 與 SELinux 模組溝通等等。 這篇接續來分享 SELinux 的存取控制機制到底長什麼樣子! 它會如何展示存取控制的概念呢?

讓我們從 SELinux 模式開始講起,根據運行狀態可以區分為三種模式,透過更改設定檔靜態調整或是使用 setenforce 程式來動態調整,其中 Enforcing 模式即是 SELinux 依照載入的政策(Policy)去實施限制,政策中乘載著許多的規則,這些規則語法表現出了 SELinux 設計理念,因此透過解析規則的形式我們可以了解 SELinux 如何對應到存取控制的核心概念 "Subject"、"Action"、"Object"。 


先前也提到過,SELinux 為存取主體(Subject)和資源(Object)標上標籤,可以把標籤想像成我們給予的額外名稱(綽號),而標籤的設計也表露出 SELinux 使用上的彈性,根據需求情境的不同,可以靈活變換欲使用的存取控制方法。

最後,我們會分享如何標上標籤,除了系統預設行為之外,分為在政策中使用規則的靜態方法以及運行時標記的動態方法。

SELinux 模式

依據運行狀態分為三種模式,分別為:
  • Enforcing: SELinux 環境啟動且會依照載入的政策(Policy)去實施限制。
  • Permissive: SELinux 環境啟動且政策(Policy)載入,但不會實施限制而是單純記錄存取的稽核訊息(audit message)在 log 中,一般用在測試環境。
  • Disabled: 整個 SELinux 環境沒有啟動,沒有 SELinuxfs 釋出的介面,也不會載入 Policy。
在 SELinux 世界中,環境啟動代表 SELinuxfs 已被掛載且 SELinux 設定檔 "config" 存在。

查詢 SELinux 模式

我們可以透過 sestatus 指令來查看目前運行的 SELinux 狀態(包括模式)。
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             refpolicy
Current mode:                   permissive
Mode from config file:          permissive
Policy MLS status:              disabled
Policy deny_unknown status:     denied
Memory protection checking:     requested (insecure)
Max kernel policy version:      31
  • SELinux status: "enabled" 代表 SELinuxfs 檔案系統已經被掛載且 SELinux 的設定檔 "config" 存在。
  • SELinuxfs mount: SELinuxfs 檔案系統被掛載在此位置。
  • SELinux root directory: SELinux 設定檔根目錄。
  • Loaded policy name: 在 SELinux 設定檔根目錄下,你可以把想使用的政策(Policy)名稱寫入設定檔 "config",此值 "refpolicy" 即來源於設定檔中 "SELINUXTYPE"。
  • Current mode: 目前運行的 SELinux 模式。
  • Mode from config file: 這邊的 enforcing 來自於 SELinux 設定檔 "config" 中 "SELINUX=" 的值,表示您想設定的模式。
  • Policy MLS status: 是否啟動 MLS (Multi-Level Security, 多層級安全)存取控制方法。
  • Policy deny_unknown status: 如何處理未知的資源(Object)類別/權限。
  • Memory protection checking: 呼叫 mmap 或 mprotect 系統呼叫時,在某些架構下(e.g. legacy x86 binary),如果應用程式要求讀取權限,kernel 會期望加入執行權限,這邊的設定即要依照應用程式要求的權限來檢查,或是依照 kernel 要求的權限來檢查。
    "requested" 代表依照應用程式要求的權限來檢查。
  • Max kernel policy version: 展示核心最高支援的政策(Policy)版本。
或是透過 getenforce 指令。
Permissive
此指令讀取 SELinuxfs 釋出的 enforce 介面,簡單回傳目前運行模式。

更改 SELinux 模式

我們可以透過更改設定檔 /etc/selinux/config 中的內容來調整 SELinux 模式。
# This file controls the state of SELinux on the system on boot. 
# SELINUX can take one of these three values:
#       enforcing - SELinux security policy is enforced.
#       permissive - SELinux prints warnings instead of enforcing.
#       disabled - No SELinux policy is loaded.
SELINUX=permissive

# SELINUXTYPE can take one of these four values:
#       targeted - Only targeted network daemons are protected.
#       strict   - Full SELinux protection.
#       mls      - Full SELinux protection with Multi-Level Security
#       mcs      - Full SELinux protection with Multi-Category Security 
#                  (mls, but only one sensitivity level)
SELINUXTYPE=refpolicy
更改 "SELINUX" 中的內容為 enforcing、permissive 或 disabled,然後重新開機。 "SELINUXTYPE" 代表我們挑選使用的政策名稱。
如果想要在運行期間動態改變 SELinux 模式,可以使用 setenforce 指令。
setenforce 1
可以給予 1 或 0 兩種值,1 為 enforcing 模式而 0 為 permissive 模式。

SELinux 之存取主體(Subject), 存取行為(Action), 資源(Object)

首先,關於存取控制的概念可以參考 SELinux旅程Part1-存取控制(Access Control)介紹,存取控制的核心概念為存取主體(Subject)對於資源(Object)的存取行為(Action),在 SELinux 的世界中,存取主體對應的就是系統程序(Process),而不是使用者本身,因此存取主體的標籤和程序的標籤是同一種意思,在後面我們會交替使用。

資源涵蓋了各種檔案類型,例如一般檔案、目錄、連結檔案、socket檔案、pipe檔案、character 設備檔案、block 設備檔案等等,除此之外還包括了非檔案的資源,例如程序本身、能否使用 SELinux 提供的功能、Capabillity 等等。 
SELinux 將所有資源分為各種類別(Class),詳細內容可以在 SELinux Notebook Appendix A 中或是 SELinux Project Wiki 找到,針對不同類別能夠做的存取行為(權限)也有所區別。

SELinux 會把存取主體與資源標上標籤(Label)或稱為安全性文本(Security Context),標籤比較易懂,因此後面都會使用標籤一詞,標上標籤後我們就可以用這些標籤來撰寫政策(Policy),因此政策中的內容即是這些標籤之間的關係。 當有存取行為發生,在核心中進行檢查時,核心會使用存取主體的標籤、資源的標籤和資源的類別當作索引,來查詢政策是否有給予權限存取

SELinux 安全性文本(Security Context)/標籤(Label)

User:Role:Type[:Range]
為什麼 SELinux 標籤會長成這個樣子呢? 因為 SELinux 想支援多種存取控制模型!我們可以根據需求情境的不同,靈活變換想要使用的存取控制方法
  • User 的部分代表 SELinux 使用者身份,命名習慣為 "_u" 結尾,不同的 Linux User 登入後可以對應一個 SELinux User。 用於 UBAC (User Based Access Control)方法,也就是基於 SELinux User 的存取控制方法。 常見的 SELinux User 為:
    • user_u: 用於一般使用者的程序。
    • root: (無 ”_u” )用於管理者的程序。
    • system_u: 用在系統程序或是系統資源。
  • Role 的部分代表 SELinux 角色,命名習慣為 “_r” 結尾,一個 SELinux User 可以對應不同的角色,角色之間可以轉換。 用於 RBAC (Role Based Access Control)方法,也就是基於角色的存取控制方法。 常見的 SELinux Role 為
    • user_r: 用於一般 Linux 使用者的程序。
    • sysadm_r: 用於管理員的程序。
    • system_r: 用於系統程序。
    • object_r: 用於系統資源。
  • Type 的部分代表 SELinux 類型,命名習慣為 “_t” 結尾,用在存取主體(Subject)時也稱作 Domain,一個 SELinux Role 可以對應不同的 Type,不同的 Type 之間可以轉換。 用於類型強制(Type Enforcement)方法,也就是基於類型的存取控制方法,為 SELinux 主要的存取控制方法。 這邊以政策中最常見的規則 "allow" 來說明,我們先來看看此規則的語法。
    allow source_type target_type : class perm_set;
    舉個例子,我們執行程式 A,程式 A 跑起來後我們讓他的程序標籤為 A_u:A_r:A_t,此程序會去存取一般檔案 B,我們把它標上 system_u:object_r:B_t,接下來我們可以在政策裡面寫下規則:
    allow A_t B_t:file {read write};
    此規則代表標籤中 Type 為 A_t 的程序可以對標籤中 Type 為 B_t 的一般檔案(類別為 file)行使讀取和寫入的存取行為(權限),預設上,沒有提到的權限則一律拒絕存取。
  • Range 的部分代表 SELinux 級別範圍,由 2 個 Level (低/高級別) 組成,用於 MLS (Multi-Level Security) 或 MCS (Multi-Category Security) 方法,也就是基於級別的存取控制方法。 依據是否使用 MCS/MLS 模型,假如沒使用,則標籤中我們會省略 Range。
上面提到的 UBAC 以及 RBAC 概念上很好理解,至於實作上是如何用標籤中的 User 和 Role 進行限制呢? 這邊要提到 constrain 規則。
constrain class perm_set expression;
以下我們用 constrain 來舉個例子
constrain file {read} ( u1 == u2 or r1 == r2 );
u1 和 r1 為存取主體標籤的 User 和 Role,u2 和 r2 為資源標籤的 User 和 Role,這邊的資源即為一般檔案(file),意思為存取主體標籤的 User 或 Role 要等於檔案標籤的 User 或 Role 才能讀取(read)檔案。 constrain 的詳細語法可以參考SELinux Notebook 4.12 或是 SELinux Project Wiki

而 Type Enforcement、MCS、MLS 存取控制模型概念可以參考 Daniel J Walsh 寫的這篇文章,裡面用漫畫的形式進行解說。 Type Enforcement 的概念即為上面 Type 部分的描述,存取主體和資源皆有設定好的 Type,而規則敘述 Type 之間的關聯。 MCS(Multi-Category Security)代表分組的概念,存取主體和資源在同一組才能進行存取。 MLS(Multi-Level Security)代表階級的概念,階級高的存取主體可以存取階級比它低的資源,之後會特別寫一篇講解 MCS 和 MLS

SELinux 安全性文本(Security Context)/標籤(Label) 實務

由上述內容我們了解到 SELinux 標籤的設計,接下來看看要如何查看、設定標籤吧!

Linux User 對應 SELinux User

一個 Linux 使用者可以對應到一個 SELinux 使用者,此對應關係寫在設定檔 /etc/selinux/POLICYNAME/seuser 中,"POLICYNAME" 是我們所挑選使用的政策名稱,也就是設定檔 "config" 中 "SELINUXTYPE" 的值,seuser 內容如下(以下出現的設定檔出自於我的實驗環境,內容可能會根據環境而不一樣)
bighead:unconfined_u
root:root
__default__:user_u
bighead 使用者登入後對應到 SELinux 使用者 unconfined_u,root 對應 root,其他使用者登入後對應到 SELinux 使用者 user_u。 通常在登入過程中,知道 SELinux 使用者後會去 /etc/selinux/POLICYNAME/contexts/users/ 底下找到以 SELinux 使用者為名稱的檔案,裡面存放著 SELinux 使用者後面應該接的 Role、Type 以及 Range。
system_r:local_login_t  unconfined_r:unconfined_t
system_r:remote_login_t  unconfined_r:unconfined_t
system_r:rshd_t   unconfined_r:unconfined_t
system_r:sshd_t   unconfined_r:unconfined_t
system_r:sysadm_su_t  unconfined_r:unconfined_t
system_r:unconfined_t  unconfined_r:unconfined_t
system_r:xdm_t   unconfined_r:unconfined_t
以第一行為例,如果使用者透過標有 system_r:local_login_t 的存取主體登入,例如 login 程式,則此使用者登入後的程序會被標上 unconfined_r:unconfined_t,因此登入後程序的存取主體標籤就會是 unconfined_u:unconfined_r:unconfined_t。 從上述可知,透過調整設定檔我們可以更改使用者登入後的標籤。
這邊做個小小補充,unconfined_t 是權限很高的 Type, 基本上它能做很多事,這也是為什麼在 CentOS 預設環境中我們開啟 SELinux 為 enforcing 模式後,還可以查看系統 log 以及動態改變 SELinux 行為等等。

查看所有定義在政策中的 User、Role、Type 以及資源的類別(class)

使用 seinfo 程式來查詢 SELinux User,執行 "seinfo -u"。
Users: 6
   root
   staff_u
   sysadm_u
   system_u
   unconfined_u
   user_u
"-u" 選項會回傳已定義的 SELinux User。
"-r"、"-t" 則分別回傳 Role 和 Type。
Roles: 14
   auditadm_r
   dbadm_r
   guest_r
   logadm_r
   ....
Types: 4473
   NetworkManager_etc_rw_t
   NetworkManager_etc_t
   NetworkManager_exec_t
   NetworkManager_initrc_exec_t
   NetworkManager_log_t
   ....
"-c" 回傳資源的類別(class)。
Classes: 129
   alg_socket
   appletalk_socket
   association
   atmpvc_socket
   atmsvc_socket
   ....

查看檔案以及存取主體的標籤

可以透過 "ls -Z" 來查看檔案標籤,例如 "ls -Z /bin"。
system_u:object_r:shell_exec_t bash
system_u:object_r:bin_t brltty
system_u:object_r:bin_t bunzip2
system_u:object_r:bin_t busybox
system_u:object_r:bin_t bzcat
system_u:object_r:bin_t bzcmp
執行 "ps Z" 查看存取主體標籤。
system_u:system_r:initrc_t  ... bash
unconfined_u:unconfined_r:unconfined_t ... /usr/bin/gnome-software
unconfined_u:unconfined_r:unconfined_t ... update-notifier
如果要查看目前使用者登入後所使用的標籤,可以執行 "id"。
uid=1000(bighead) gid=1000 ... context=system_u:system_r:initrc_t

為資源標上標籤

如何幫已經存在的資源標上標籤呢? 可以透過調整設定檔內容或是使用 chcon 程式來為資源上標籤,這邊以檔案資源為主,如何幫程序標上標籤會在下面提到。
我們在 Part2 中提過可以使用 setfiles,此程式會依據 SELinux 設定檔來為檔案系統上的資源標上標籤,此設定檔即為 file_contexts 和 file_contexts.homedirs 設定檔,其內容如下所示。
...
/mnt(/[^/]*)    -l      system_u:object_r:mnt_t
/mnt(/[^/]*)?   -d      system_u:object_r:mnt_t
/dev/.* system_u:object_r:device_t
/etc/.* system_u:object_r:etc_t
/opt/.* system_u:object_r:usr_t
...
基本上格式由 "檔案路徑"、"檔案類型"、"標籤" 所組成,檔案路徑可以包含正規表示式(Regular Expression)。 setfiles 運行時會將兩個檔案合併後分為 包含 以及 不包含 正規表示式的路徑,一開始會從 "不包含正規表示式的路徑" 找起,找到後會直接使用並不會繼續往後找,因此通常在除錯時得小心是否因為此原因而無法標上預期的標籤。
如果想在系統運行時改變資源的標籤,可以透過 chcon 程式來動態改變,舉個例子。
chcon -t user_t /bin/ls
利用上述指令我們即可把 /bin/ls 標籤的 Type 部分改為 user_t,此程式便於測試時快速調整標籤內容,除此之外也可以調整 User 和 Role 部分。

如何幫新建立的資源標上標籤呢? 除了預設行為之外我們可以使用 Object Transition
方法來明確規定新建立的資源在什麼樣的情況下要標上我們所指定的標籤,分為靜態和動態的 Object Transition(靜態 Object Transition 只改變 Type 部分)

預設情況下,系統上有新資源產生的時候,系統會幫新資源標上標籤,此行為是固定的,例如新建立的 file 會繼承 parent directory 的標籤,假設我們在標有 user_home_dir_t 的家目錄底下用 touch 程式建立新檔案 test,此檔案的 Type 部分即為 user_home_dir_t,User 部分繼承存取主體標籤的 User,Role 部分預設為 object_r

但如果政策中有使用靜態的 Object Transition 規則,則會去影響到預設行為中 Type 的部分讓我們來看一下 Object Transition 規則語法。
type_transition source_type target_type : class default_type object_name;
以上面的敘述來舉個例子。
type_transition user_t user_home_dir_t : file testfile_t;
當 Type 標記為 user_t 的存取主體,在 Type 標記為 user_home_dir_t 的目錄下建立一般檔案(file)時,此新建立的一般檔案 Type 部分會標記為 testfile_t,這邊以 file 為例,也可換成其他資源的類別。
當然我們使用靜態 Object Transition 的目的即是為了能更精細的去分類這些資源,為不同用途的資源標上不同標籤,而不止是像上面單純讓新建的檔案繼承目錄的標籤。

不管是預設行為或是靜態的 Object Transition,這些都是在系統建置時期已經定好的規則,如果希望在運行時期能根據程式行為不同,而為新建資源標上不同的標籤,靜態的 Object Transition 規則無法滿足我們的需求,因為不能夠有兩條靜態 Object Transition 規則的 source_type、target_type、class 一模一樣,也就是說靜態 Object Transition 規則無法在存取主體標籤和目錄標籤與之前相同的情況下給予新建資源不同的標籤

為了在運行時期更有彈性地再去分類資源,這就有了動態的 Object Transition,舉例來說程式在建立新檔案前,利用如 SELinux API "setfscreatecon" 來設定新檔案的標籤,如此一來我們就可以依據程式行為幫新檔案標上標籤。
以 OpenSSH 為例,在系統運行時期, 使用者透過 ssh 登入後,ssh 程式會幫使用者在家目錄下建立 .ssh 目錄,此目錄由 ssh 動態建立,ssh 可以透過 setfscreatecon 來幫此目錄標上標籤。

可以動態變換標籤是一種特權,因此使用 "setfscreatecon" 的 SELinux-aware 程式需要有 setfscreate 權限,且這個程式需要是可信任的(Trusted),也就是說我們假設他是絕對善良且不會出問題,但還是得未雨綢繆,我們需要嚴格限制程式的行為

為程序標上標籤

如何幫新建立的程序標上標籤呢? 除了預設行為之外可以透過 Domain Transition(Domain Transition 只改變程序標籤的 Type 部分)來明確規定新建立的程序在什麼樣的情況下要標上我們所指定的標籤,分為靜態和動態的 Domain Transition。 Domain Transition 存在的目的即是利用存取主體標籤的變化達成 Least Privilege 原則,讓可能有安全疑慮的程式運行在一個縮限的權限範圍內,如圖1,init system 會開啟系統服務,例如 ssh server 和登入服務,init system 分別去執行帶有不同標籤的程式,最後透過 Domain Transition 將執行起來的程序轉換成不同標籤
圖1. Domain Transition

預設情況下,執行程式後所建立的程序繼承 Parent 程序的標籤,如果希望程序的標籤有所變化可以使用靜態的 Domain Tranistion,也就是在政策中使用 Domain Tranistion 規則,語法如下。
type_transition source_type target_type : class default_type object_name;
你沒看錯!跟上面的 Object Transition 規則語法是一樣的,差別在於類別(class)的不同。
type_transition init_t sshd_exec_t:process sshd_t;
重複上面例子,當系統啟動,init system (標籤為 init_t)會去執行系統服務,例如啟動 ssh server,當 init system 執行 sshd 程式(標籤 Type 為 sshd_exec_t)後,sshd 程序(process)標籤中 Type 會變成 sshd_t。

系統運行時,SELinux-aware 程式可以透過 SELinux API (例如 setcon, setexeccon)動態改變程式運行時的標籤,稱為動態的 Domain Tranistion,例如 systemd 在系統啟動時會預先把政策(Policy)載入核心,此時他所使用的標籤是系統預設標籤 kernel_t,載入政策後他會利用 setcon 來將自身的標籤改成 init_t 以符合 init system 的行為規範,此行為需要 setcurrent 和 dyntransition 權限

另外一種方法利用 setexeccon + execve 來把標籤改為 init_t,setexeccon 運作原理為,將指定的標籤寫入程序的資料結構中,並在下次執行程式(execve)時,將紀錄的標籤拿出來使用,把執行後的程序標上此標籤。
這邊可看出 setcon 用來立即改變程序自身的標籤,而 setexeccon 用來改變執行後程序的標籤。

邊要再一次提醒讀者!能透過 SELinux API 進行標籤設定,表示存取主體本身需要被信任(Trusted),因為他是一個行使特權的程式,可以影響 SELinux 系統運作,雖然我們假設他是絕對善良且不會出問題,但還是得嚴格限制程式的行為。

另外,如果在測試時想用指定的標籤來運行程序,可以透過 runcon 程式動態設定,舉個例子。
runcon -t local_login_t /bin/bash
利用上述指令即可讓 /bin/bash 執行起來後標籤的 Type 部分改為 local_login_t,此程式便於測試時快速調整程序的標籤內容。

總結

我們快速整理一下 SELinux 如何展示存取控制的核心概念,存取主體(Subject)即為程序(Process),系統資源(Object)被劃分成類別(Class),針對不同類別所能進行的存取行為(權限)不同。
存取主體和資源皆會被標上標籤,標籤可視為存取主體和資源的綽號,政策中敘述著標籤之間的關聯。 設定標籤的方式,除了系統預設行為外,分為靜態和動態的方式,靜態代表於政策中使用規則語法進行描述,動態則是使用 SELinux API 或是使用程式。

下次我們將進入 audit system 的部份,分享 audit system 如何紀錄 SELinux 的行為。

留言