SELinux策略語言--型別強制(編寫TE規則)

峻峰飛陽發表於2015-03-19

1. 簡介

     SELinux策略語言主要描述policy.conf的相關語法,其相關部分如下圖所示:


2. 型別強制概念

     SELinux策略大部分內容都是由多條型別強制規則構成的,這些規則控制被允許的使用權,大多數預設轉換標誌,稽核,以及固定部分的檢查。   

     SELinux策略大部分都是一套宣告規則一起定義的型別強制(TE:Type Enforcement)策略,一個定義良好、嚴格的TE策略可能包括上千個TE規則,TE規則數量的巨大並不令人驚奇,因為它們表達了所有由核心暴露出的允許對資源的訪問權,這就意味著每個程式對每個資源的訪問嘗試都必須至少要有一條允許的TE訪問規則,如果我們仔細思考一下現代Linux作業系統中程式和資源的數量,就明白為什麼在策略中有那麼多的TE規則了。當我們新增由TE規則控制的稽核配置和標誌時,對於具有嚴格限制的SELinux策略,常常會見到它包含有上千條規則,在“建立和編寫SELinux安全策略”中,我們將會討論如何建立和管理這些大量的規則,本文旨在理解TE規則是如何工作的。

    TE規則的絕對數量對理解SELinux策略是一個大的挑戰,但是規則本身並不複雜,它們的分類相對較少,所有的規則基本上都屬於兩類範疇:

     • 訪問向量(AV)規則

     • 型別規則

     我們使用AV規則允許或稽核兩個型別之間的訪問權,我們在某些情況下使用型別規則控制預設的標記決定。

     SELinux的一個重要概念是TE規則是將許可權程式的訪問結合在一起,而不是結合使用者。所有SELinux策略語言特性都是處理主體(正常的執行中的程式)對客體(檔案、目錄和套接字等)的訪問權的,主要集中於程式訪問控制決策,這也是SELinux的主要益處,它允許SELinux策略編寫者基於程式的功能和安全屬性,加上使用者要完成任務需要的所有訪問權做出訪問決策,可以將程式限制到功能合適,許可權最小化的程度,因此,即使它出了故障或被攻擊破壞,但整個系統的安全並不會受到威脅。

    SELinux是不會管使用者的,可以給同一個程式指定多個域型別(因此有不同的特權集),這樣就允許引入角色的概念,儘管如此,訪問控制的標準仍然是基於程式的域型別而不是使用者的特權。焦點是程式的訪問權,而不是使用者的訪問權。

3. 型別、屬性和別名

    正如你從術語型別強制猜測的那樣,型別是構成TE規則的最小單位,SELinux主要就是使用型別來確定什麼訪問是被允許的;屬性和別名是為減輕管理和使用型別的策略特性,我們使用屬性利用單個識別符號來引用一組型別。通常,策略語言允許我們在TE規則中型別的適當位置使用屬性,而別名允許我們為型別定義另一個名字,別名識別符號和型別識別符號做同等地位對待。 

3.1 型別宣告

    在使用型別前,必須使用type語句明確地宣告一個型別識別符號,SELinux沒有預定義型別,我們必須自行宣告,例如:假設我們想宣告一個型別(httpd_t),並打算將其作為Web伺服器的域型別,而另一個型別(http_user_content_t)準備應用於使用者資料檔案,即Web伺服器顯示內容的檔案,我們使用type語句進行宣告,如下:   

  1. type httpd_t;   
  2. type http_user_content_t;  

    宣告瞭型別後就可以在安全上下文、TE規則和其它策略語句中使用它們了。

    型別宣告的語法:

    •  type 型別名稱 [alias 別名集] [,屬性集];

   1) 別名集:如果指定的不止一個別名識別符號,要在一對大括號中用空格將各個別名區別開來,如:alias {aliasa_t aliasb_t}。
   2) 屬性集:是一個或多個預先宣告的屬性識別符號,如果同時指定多個屬性識別符號,屬性之間使用逗號進行分隔,如:type bin_t, file_type, exec_type;

   3) 型別宣告在整個策略中,以及基礎載入模組和非基礎載入模組中都是有效的。但在有條件的語句中無效。

3.2 型別和屬性

      可能你已經想到,一個大型的,複雜的策略可能包括上萬個代表系統上不同資源的型別,例如:Fedora Core 4(FC 4)的targeted策略相對較小,但也宣告瞭超過800個型別。由於預設的規則是拒絕所有的訪問,所以任何一個訪問都需要明確地被允許,這就導致了型別註定會很冗長,這時策略語言的屬性特性就派上用場了。屬性可以理解為:

      1) 型別的性質或屬性,或二者兼得

      2) 一組型別

      在任何一種情況下,原理都是相同的。

      假設我們想讓一個備份程式可以訪問所有的檔案,首先我們建立一個備份應用程式的域型別(backup_t),並允許它訪問與任何檔案關聯的型別:

  1. type backup_t;   
  2. allow backup_t httpd_user_content_t : file read;   
  3. allow backup_t shadow_t : file read;  

     為了完成這個例子,我們編寫了一個用於其他所有檔案型別的規則,依賴宣告的型別數量,我們需要大量的allow規則授予備份程式足夠的訪問權(每個型別都要一個)。另外,每次向策略中增加一個檔案型別時,同時要為backup_t增加一條allow規則,這是一個單調且錯誤頻出的過程,屬性使得這種"組訪問"更容易指定,通過將所有關聯的檔案型別定義一個屬性,然後授予該屬性的訪問權(而不是每個型別了),於是,我們可以使用一條規則授予backup_t必需的訪問權。
     使用attribute語句進行屬性宣告,如:

  1. attribute file_type;  

     這個語句宣告一個叫做file_type的屬性,型別和屬性共享相同的名稱空間,因此,型別和屬性的名字不能雷同,假設我們將所有適當的型別都與屬性file_type進行關聯,然後就可以使用一條規則來指定backup_t讀取這些檔案,如: 

  1. allow backup_t file_type :file read;  

     在我們使用了一條規則替代了上千條allow規則,而授予的訪問權卻是一樣的,當這個策略編譯好後,這條規則會自動擴充套件成上千條規則,分別控制不同檔案型別的訪問,更重要的是,當我們給檔案定義了一個新型別時,我們需要做的僅僅是將這個新型別與file_type屬性進行關聯,域型別backup_t將會自動獲得讀取訪問權。

    屬性宣告語法:
    • attribute 屬性名稱;

    1) 屬性和型別,別名都在同一個名稱空間,因此不能與其他型別或別名重名。

    2) 屬性宣告在整個策略,基礎載入模組和非基礎載入模組中都有效,但在有條件的語句中無效。

3.3 關聯型別和屬性

3.3.1 在type中指定屬性    

     迄今為止,我們已經討論瞭如何定義型別和屬性,下面將要講述的是如何將它們關聯起來,最常見的關聯方式是在用type語句宣告型別時就指定其屬性,例如:我們可以將宣告http_user_content_t型別的語句修改為:

  1. type http_user_content_t,file_type;  

     這個語句描述了宣告型別http_user_content_t時,同時關聯了file_type屬性,它會自動向具有file_type屬性的型別組中新增http_user_content_t型別,但從概念上將,它實質上已經改變了http_user_content_t型別的性質,因為它現在已經具有基於屬性的訪問許可了,而不只侷限於型別本身了。

     一個型別可以有多個屬性,例如:我們可以再為所有Web伺服器要用的檔案建立一個屬性httpdcontent,擁有httpdcontent屬性的型別可能是擁有file_type屬性的型別的一個子集,下面的程式碼擴充套件了我們前面的例子:

  1. type httpd_user_content_t, file_type, httpdcontent;   
  2. type shadow_t, file_type;   
  3.   
  4. allow backup_t file_type : file read;   
  5. allow httpd_t httpdcontent : file read;  

     型別具有的屬性數量沒有限制,就和型別一樣,我們可以合理定義相應的屬性。

3.3.2 使用typeattribute指定屬性

    除了使用type語句關聯型別和屬性外,還可以使用typeattribute語句,這個語句允許我們在宣告型別時,單獨關聯屬性,在策略中可能也就是一個單獨的檔案了,例如:將前面舉的示例語句:

  1. type httpd_user_content_t, file_type, httpdcontent;  

     分成兩條語句進行表述:

  1. type httpd_user_content_t;   
  2. typeattribute httpd_user_content_t file_type, httpdcontent;  

      typeattribute允許我們在一個地方定義型別,在另一個地方關聯屬性,增強了語言的靈活性,在設計策略原始檔時,可以考慮進行模組化設計了。

     typeattribute語句語法:
    • typeattribute 型別名 屬性名;
      1) 一個或多個事先宣告的屬性識別符號,如果指出多個屬性識別符號,屬性識別符號之間使用逗號分隔,如typeattribute bin_t file_type, exec_type;
      2) typeattribute語句在單個策略,基礎載入模組和非基礎載入模組中都是有效的,只有在條件語句中無效。

3.4 別名(為確保相容性而存在)

     別名是引用型別時的一個備選的名字,能夠使用型別名的地方就可以使用別名,包括TE規則,安全上下文和標記語句,別名通常用於策略改變時保證一致性,例如:一箇舊策略可能引用了型別netscape_t,更新後的策略可能將型別名改為mozilla_t了,但同時提供了一個別名netscape_t以保證與舊模組能夠正確相容。

3.4.1 在type中宣告別名

  1. type mozilla_t alias netscape_t, domain;  
     注意別名宣告是放在屬性的前面的。

3.4.2 使用typealias申明別名

  1. # 這兩條語句等同於   
  2. type mozilla_t, domain;   
  3. typealias mozilla_t alias netscape_t;   
  4.   
  5. #下面這一條語句   
  6. type mozilla_t alias netscape_t, domain;  

   typealias語句語法
      • typealias 型別名稱 alias 別名名稱

     1) 型別名稱:要新增別名的型別的名稱,型別必須使用type語句單獨宣告,而且這裡只能指定一個型別名稱。

     2) 別名名稱:如果同時指定多個別名,別名之間用空格分開,並使用大括號將所有別名括起來,如{aliasa_t aliasb_t}。
     3) typealias語句在單個策略,基礎載入模組和非基礎載入模組中都有效,只有在條件語句中無效。


4. 訪問向量規則

    AV規則就是按照對客體類別的訪問許可指定其具體含義的規則,SELinux策略語言目前支援四類AV規則:

   在程式碼中,客體類別的訪問許可集是由一些叫做訪問向量的掩碼錶現的,因此就有了術語訪問向量。

   • allow:表示允許主體對客體執行允許的操作

   • dontaudit:表示不記錄違反規則的決策資訊,且違反規則不影響執行(允許操作且不記錄)

   • auditallow:表示允許操作並記錄訪問決策資訊(允許操作且記錄)

   • neverallow: 表示不允許主體對客體執行指定的操作

   本小節剩餘部分將詳細討論這些規則的語法和語義,以及一些示例。

4.1 通用AV規則語法

   雖然這些規則的用途不一樣,但它們的基本語法是一樣的,每個規則都要包含下面五個元素:

     • 規則名稱: allow,dontaudit,auditallow和neverallow

     • 源型別:授予訪問的型別,通常是程式的域型別

     • 目標型別:客體的型別,它被授權可以訪問的型別

     • 客體類別:客體的類別

     • 許可:表示主體對客體訪問時允許的操作型別(也叫做訪問向量)。

    一個簡單的AV規則有一個源型別,目標型別,客體類別和許可,在我們前面的allow規則中可以看到許多AV規則,如:

  1. allow user_t bin_t : file execute;  

    這個allow規則的源型別為user_t,目標型別為bin_t,客體類別file,許可execute,這個規則可以解讀為"允許user_t執行型別為bin_t的檔案"

4.1.1 AV規則的金鑰(雜湊Key)

     在核心中,所有的AV規則都是通過一組【源型別+目標型別+類別】進行唯一性標識,這個三重組叫做一個金鑰,當做雜湊表使用,快取在策略資料結構中,規則是靠這個金鑰儲存和檢索的,當一個程式產生了一個訪問請求時,SELinux LSM模組被要求允許基於這個金鑰進行訪問。

     那麼,如果不止一個規則使用同一個金鑰(即相同的源型別,目標型別和許可)時會發生什麼狀況呢?如下面的規則:

  1. allow user_t bin_t : file execute;   
  2. allow user_t bin_t : file read;  

     型別為user_t的程式對型別為bin_t的檔案是可讀還是可執行?答案是兩者皆可。所有有相同金鑰的規則通過checkpolicy進行組合,編譯後的策略將只有一條規則,但它同時具有read和execute許可,它們都會被安全伺服器接受。所有的AV都按照這種方式進行累加。

4.1.2 使用AV規則中的屬性

     雖然到目前為止我們看到的AV規則都很簡單,但語法支援多種方法列出型別、客體類別和許可,使我們可以靈活地利用,並使規則語句更簡單。

     在前面的簡單樣式的規則示例中,直接引用了源型別(user_t)和目標型別(bin_t),這樣在源型別或目標型別中要引用多個型別也是很方便的,其中一個方法就是使用屬性,在AV規則中能使用型別的地方都可以使用屬性(型別組)

     例如,假設我們定義了一個屬性(exec_type),我們打算將其與所有的普通使用者程式(通過域型別user_t標記)都可以執行的檔案型別關聯,那麼我們可以將上面的例子改為引用屬性exec_type,而不用再明確地指定型別bin_t了,如:

  1. allow user_t exec_type : file execute;  

    與屬性關聯的每個型別都有一個獨立的金鑰。

    我們也可以在AV的源型別位置處使用屬性,或者乾脆在源型別和目標型別處都使用屬性,例如:假設我們建立了一個屬性(domain),並將所有的域型別(包括user_t)都與其關聯,我們想要所有的域型別都可以執行屬性為file_type的檔案型別,使用一條規則就實現這個目標:

  1. allow domain exec_type : file execute;  

    為了更好地解釋規則擴充套件的原理,假設我們的策略關聯了型別為user_t和staff_t的屬性domain,以及檔案型別為bin_t,local_bin_t和sbin_t的屬性exec_type,那麼上面那一條規則的效果就等同於下面這些規則:

  1. allow user_t bin_t : file execute;   
  2. allow user_t local_bin_t : file execute;   
  3. allow user_t sbin_t : file execute;   
  4.   
  5. allow staff_t bin_t : file execute;   
  6. allow staff_t local_bin_t : file execute;  
  7. allow staff_t sbin_t : file execute;  

4.1.3 AV規則中的多型別和屬性

   在AV規則中的源和目標區域,我們都沒有限制型別和屬性的數量,相反,可以在源和目標欄位處列出多個型別和屬性,如果有多個型別或屬性時,它們之間使用空格進行分隔,並使用大括號將它們括起來,如:

  1. allow user_t { bin_t sbin_t } : file execute;  

     在這個規則中,目標是bin_t和sbin_t,在源和目標區域有多個型別或屬性時,展開方法同單個屬性一樣,在前面的例子中,核心包括兩個金鑰,每個目標型別都有一個。

     我們還可以在源或目標區域混合型別和屬性,也可以在這兩個位置都使用混合的形式,如:

  1. allow {user_t domain} {bin_t file_type sbin_t} : file execute ;  

    如果我們明確地列出了型別以及型別具有的屬性,這是可以的,即我們實際上列出了兩次型別,核心會自動處理這個冗餘,只會處理一個例項規則。

4.1.4 特殊型別self

    策略語言保留了一個關鍵字self,它用於AV規則中的目標區域,可以當做一個型別使用,如下面這兩條規則是相等的:

  1. # 這兩條規則是相等的   
  2. allow user_t user_t : process signal;   
  3. allow user_t self : process signal;  
 
    關鍵字self說明目標型別使用的源型別自身,即目標型別等於源型別,前面的例子中,第二條規則只是用關鍵字建立了一條規則,表明源型別和目標型別都是user_t。

  1. # 這兩條規則   
  2. allow user_t user_t : process signal;  
  3. allow staff_t staff_t : process signal;   
  4.   
  5. #等於下面這一條規則   
  6. allow {user_t staff_t} self : process signal;  

    注意:你可能只會在AV規則的目標區域使用特殊型別self,特別要注意的是不能在AV規則的源區域使用self型別,另外,也不能宣告一個型別或屬性識別符號叫做self。

  1. allow domain domain : process signal; # 每個程式都能向它自己和其它程式傳送signal  
  2. allow domain self : process signal;   # 每個程式都能向它自己傳送signal  

4.1.5 "非"特殊操作符

    AV規則中最後一個型別語法是型別否定,它可以從一個型別列表中將某個型別移除,也可以用於用一個屬性中移除某個型別,通過在要移除的型別名稱前面放一個非操作符(-)實現,例如:我們想讓所有的域型別都可以訪問所有屬性為exec_type的檔案,除了sbin_t型別外,那麼編寫規則時就可以這樣:

  1. allow domain { exec_type -sbin_t } : file execute;  
  2. #等同於  
  3. allow domain { -sbin_t exec_type } : file execute;  


    這個規則在展開時就好像屬性exec_type沒有包括型別sbin_t一樣。

4.1.6 在AV規則中指定客體類別和許可

    AV規則也可以包括客體類別和許可列表,語法和型別一致,使用空格進行分隔,並用大括號括起來,如:

  1. allow user_t bin_t : { file dir } { read getattr };  
  2.   
  3. #這條規則將會產生兩個金鑰,每個客體類別一個,這條規則等同於下面這兩條規則:  
  4. allow user_t bin_t : file { read getattr };   
  5. allow user_t bin_t : dir { read getattr };  

   注意客體類別被展開了,但每條規則都有相同的許可列表,這意味著列表中的所有許可對所有客體類別都是有效的。

  1. # 無效的規則,因為search對於客體類別file是無效的   
  2. allow user_t bin_t : { file dir } { read getattr search };  
  3.   
  4. #當許可對兩個客體類別不是都有效時,需要兩條規則   
  5. allow user_t bin_t : file { read getattr };   
  6. allow user_t bin_t : dir { read getattr search } ;  

4.1.7 AV規則中的特殊許可操作符

    對於列在AV規則中的許可,我們可以使用兩個特殊的操作符,第一個是萬用字元(*),萬用字元包括了客體類別的所有許可:

  1. allow user_t bin_t : { file dir } *;  

    這條規則擴充套件後將包括file和dir的所有許可。

    萬用字元語法與列出所有的許可有點不同,使用萬用字元時,許可包括每個客體類別的許可,此時不會考慮其中一個許可是否對另一個客體類別是否有效,這樣就可以在規則中使用多個客體類別,即使這些客體類別有不同的許可,因此,上面這條規則就安全地處理了許可,不會像前面那條規則那樣,這裡只對dir客體類別有效的規則不會影響到file客體類別。

    第二個特殊操作符是求補算操作符(~),即除了列出的許可外,其它的許可都包括,如:

  1. allow user_t bin_t : file ~{ write setattr ioctl };  

   編譯時,這條規則允許所有的許可,除了write,setattr和ioctl外,與萬用字元類似,求補算操作符也擴大了客體類別的許可列表。

4.1.8 通用訪問向量規則語法

     完整的AV規則通用語法如下

     • 規則名稱 型別集 型別集:類別集 許可集;
     1) 規則名稱:訪問向量規則的名稱,有效的規則名稱是allow,auditallow,auditdeny,dontaudit和neverallow。

     2) 型別集: 一個或多個型別和(或)屬性,規則中源和目標型別有其獨立的型別集,多個型別集或屬性使用空格進行分隔,並使用大括號將它們括起來,如{bin_t sbin_t}。可以使用(-)來排除型別,如{exec_type -sbin_t}。在目標型別區域可以使用關鍵字self,但在源型別區域不能使用。neverallow規則也支援萬用字元來代表所有的型別,求補算操作符(~)也表示所有的型別,除了明確列出的之外。

     3) 類別集: 一個或多個客體類別,多個客體類別必須使用大括號括起來,如{file lnk_file}。
     4) 許可集: 一個或多個許可,所有許可對類別集列出的所有客體類別都要有效,多個許可必須用大括號括起來,如{read create}。萬用字元(*)指出所有客體類別的所有許可,求補算操作符(~)用於指出所有的許可,除了明確列出的之外
     所有AV規則在單個策略,基礎載入模組和非基礎載入模組中都有效,所有AV規則除了auditdeny,neverallow規則外,其它的在條件語句中也有效。

4.2 允許(allow)規則

     到目前為止,你已經看到了許多的allow規則,allow規則是策略中最常見的規則,它實現了SELinux策略的主要目的(即允許訪問)。
    正如前面討論的,我們使用allow規則指出了所有執行時授予的許可,它們是SELinux策略中允許許可的唯一方法,記住,預設情況下,不允許任何訪問,我們指定了兩個型別列表(源和目標型別),根據列出的客體類別的許可指定訪問權,如:

  1. allow user_t bin_t : file { read execute };  

    這個規則允許任何安全上下文中型別具有user_t的程式對任何安全上下文中具有型別為bin_t的普通檔案所有read和execute訪問權。allow規則共享了通用AV規則的的所有語法,並且也沒有增加任何額外的語法了。

4.3 稽核(audit)規則

   SELinux有大量的工具記錄日誌資訊,或稽核、訪問嘗試被策略允許或拒絕的資訊。稽核訊息通常叫做"AVC訊息",它提供了詳細了關於訪問嘗試的資訊,包括是允許還是拒絕,源和目標的安全上下文,以及其它一些訪問嘗試涉及到資源資訊。AVC訊息與其它核心訊息類似,都是儲存在/var/log目錄下的日誌檔案中,它是策略開發、系統管理和系統監視不可缺少的工具。在此,我們檢查是哪一個訪問嘗試產生了稽核訊息。

   預設情況下,SELinux不會記錄任何允許的訪問檢查,只會記錄被拒絕的訪問檢查。這並沒什麼奇怪的,在大多數系統上,每秒會允許成千上萬的訪問,只有很少的一部分會被拒絕,允許的訪問通常是在預料之中的,通常不需要稽核,被拒絕的訪問通常是(但不總是)非預期的訪問,對它們進行稽核便於管理員發現策略的bug和可能的入侵嘗試。策略語言允許我們取消這些預設的預料之中的拒絕稽核訊息,改為記錄允許的訪問嘗試稽核訊息。

    SELinux提供兩個AV規則允許我們控制稽核哪一種訪問嘗試:dontaudit和auditallow。使用這兩條規則我們就可以改變預設的稽核型別了,最常用的是dontaudit規則,它指出哪一個訪問嘗試被拒絕時不稽核,這樣就覆蓋了SELinux預設的稽核所有拒絕的訪問嘗試的行為。

  1. dontaudit httpd_t etc_t : dir search;  

    記住,稽核(audit)規則讓我們覆蓋了預設的稽核設定,allow規則指出了什麼訪問是允許的,auditallow規則不允許訪問,它只稽核允許的許可。

    注意:在許可模式和強制模式下稽核是不一樣的。執行在強制模式下時,每次允許或拒絕時都會進行稽核,應該在策略中對稽核頻率進行限制(可以使用auditctl實現)。在許可模式下時,只會記錄第一次訪問嘗試,直到下一次策略載入,或固定為強制模式,在開發時通常使用的就是許可模式,這種模式可以減少日誌檔案的大小。

4.4 neverallow規則

    最後一個AV規則是neverallow規則,我們使用這個規則來指定永遠不會被allow規則執行的訪問,你可能會疑惑,為什麼會有這個規則?因為預設情況下所有的訪問都是被拒絕的,設計這個規則的主要目的是為了幫助編寫策略時,可以明確地指出不想要的訪問許可,因此可以預防意外發生,回想一下,在一個SELinux策略中可能包含成千上萬條規則,可能不小心加入了我們本不想授予的訪問權,此時,neverallow規則就可以幫助預防這種情況發生,如: 

  1. neverallow user_t shadow_t : file write;  

     這條neverallow規則可以有效地阻止我們在策略中新增一條允許user_t對型別為shadow_t的檔案進行寫操作的規則,如果新增了這樣的規則在編譯時就會報錯,這條規則不會移除訪問權,它只是會產生編譯錯誤。我們在編寫策略時,neverallow規則往往放在allow規則前面,首先宣告哪些訪問是明確地被拒絕的,然後再宣告哪些訪問是可以接受的,這樣就可以預防我們人為出錯了。

     neverallow規則支援一些特殊的其它AV規則不支援的語法,在neverallow規則中的源和目標型別列表中可以使用萬用字元(*)和求補算操作符(~),如:

  1. neverallow * domain : dir ~{ read getattr };  

    這條規則指出沒有哪條allow可以授予任何型別對具有domain屬性的型別的目錄有任何訪問權,除了read和getattr訪問權外(即讀訪問權),這條規則的中萬用字元意味著所有的型別,在真實的策略中,類似這樣的規則很常見,它們用來阻止對/proc/目錄適當的訪問。

    我們從前面這個例子中看出,在源型別列表中需要使用萬用字元,因為我們想要指出任何型別或所有型別,包括那些還沒有建立的型別,使用萬用字元可以預防我們未來犯錯。

    另一個常見的neverallow規則是:

  1. neverallow domain ~domain : process transition;  

    這條neverallow規則增強了domain屬性,它指出了程式不能轉換到無domain屬性的型別,這就使得要為一個型別無doamin屬性的程式建立一個有效的策略是不可能的。

5. 型別規則

    型別規則在建立客體或在執行過程中重新標記指定其預設型別它僅提供一個新的預設型別標記在策略語言中定義了兩個型別規則:

     • type_transition:在域轉換過程中標記行為發生時以及建立客體時,指定其預設的型別。 

     • type_change:使用SELinux的應用程式執行標記指定其預設型別

     我們叫這些規則為"型別規則",因為它們與AV規則類似,除了規則的末尾是一個型別名而不是許可集外。

5.1 通用型別規則語法

      與AV規則一樣,每條型別規則有不同的用途和語義,但它們的語法都是通用的,每條型別規則都具有下列五項元素:
      • 規則名稱:type_transition或type_change 

      • 源型別:建立或擁有程式的型別 

      • 目標型別:包含新的或重新標記的客體的客體型別 

      • 客體類別:新建立的或重新標記的客體的類別 

      • 預設型別:新建立的或重新標記的客體的單個預設型別

      型別規則語法
      • 規則名稱 型別集 型別集:類別集  單個預設型別;

      1) 規則名稱:型別規則的名稱,有效的規則名稱有type_transition,type_change和type_member。
      2) 型別集:一個或多個型別或屬性。在規則中源和目標型別有其獨立的型別集,多個型別和屬性使用空格進行分隔,並用大括號將它們括起來,如{bin_t sbin_t},可以在型別名前放一個(-)符合將其排除,如{exec_type –sbin_t}。

      3) 類別集:一個或多個客體類別,多個客體類別必須使用大括號括起來,並用空格分開,如{file lnk_file}。
      4) 預設型別:為新建立的或重新標記的客體類別指定的單個預設型別,這裡不能使用屬性和多個型別。
      5) 所有型別規則在單個策略,基礎載入模組,非基礎載入模組和條件語句中都有效。

     型別規則語法大部分都和AV規則類似,但也有一些不同的地方,首先就是型別規則中沒有許可,不像AV規則那樣,型別規則不指定訪問權或稽核,因此就需要許可了;第二個不同點是客體類別沒有關聯目標型別,相反,客體類別指的是將要被預設型別標記的客體。

     最簡單的型別規則包括一個源預設型別,一個目標預設型別和一個客體類別,如:

  1. type_transition user_t passwd_exec_t : process passwd_t;  

     它指出了當一個型別為user_t的程式執行一個型別為passwd_exec_t的檔案時,程式型別將會嘗試轉換,除非有其它請求,預設是換到passwd_t,當宣告的客體類別是程式(process)時,隱含著目標型別要與file客體類別關聯,宣告的客體類別(process)與源和預設型別關聯,這個隱藏著的關聯很容易被忽略,即使你成為一個策略編寫專家也容易犯這個錯。

5.2 型別轉換規則type_transition

     我們使用type_transition規則指定預設型別,目前有兩種格式的type_transiton規則:

     1) 支援預設域轉換事件

     2) 支援客體轉換,它允許我們指定預設的客體標記

    這兩種形式的type_transition規則幫助增強了SELinux透明轉換到Linux使用者的安全性,預設情況下,在SELinux中,新建立的客體繼承包括它們的客體的型別(如目錄),程式會繼承父程式的型別,type_transition規則允許我們覆蓋這些預設型別,這非常有用,例如:為了確保密碼程式在/tmp/目錄下建立一個檔案時要給一個不同與普通使用者的型別。

     type_transition規則沒有allow訪問權,它僅提供一個新的預設型別標記,要成功進行型別轉換,也必須要一套相關聯的allow規則,以允許程式型別可以建立客體和標記客體。此外,預設的標記指定在type_transition規則中了,只有建立程式沒有明確地覆蓋預設標記行為它才有效。

5.2.1 預設域轉換(程式型別轉換process)

   讓我們一起來詳細地看一下這條規則中的域轉換格式,執行一個檔案時,域轉換改變了程式的型別,如下面這條規則:

  1. type_transition init_t apache_exec_t : process apache_t;  

    這條規則指出型別為init_t的程式執行一個型別為apache_exec_t的檔案時,程式型別將會轉換到apache_t。客體類別process只表示這是一個域轉換規則的格式。

   下圖顯示了一個域轉換,實際上,域轉換隻是改變了程式現有的型別,而不是新建立了一個程式,這是因為在Linux轉換建立一個新的程式首先是要呼叫fork()系統呼叫複製一份現有的程式,如果程式型別在fork上被改變了,它就會允許域在新的域中執行任意的程式碼了,通過execve()系統呼叫執行一個新的程式時,發生域轉換時就更安全些。


      正如前面談到的,只有當策略允許了有關的訪問權時才會發生型別轉換,域轉換要成功,策略必須允許下面三個訪問權:
      • execute:源型別(init_t)對目標型別(apache_exec_t)檔案有execute許可
      • transition:源域(init_t)對預設型別(apache_t)必須要有transition許可
      • entrypoint: 新的(預設)型別(apache_t)對目標型別(apache_exec_t)檔案必須要有entryponit許可

     同時,上面的域轉換規則要想成功,還必須要有下面的allow規則:

  1. # 這條域轉換規則.   
  2. type_transition init_t apache_exec_t : process apache_t;   
  3.   
  4. # 至少需要下面三條allow規則才能成功   
  5. allow init_t apache_exec_t : file execute;   
  6. allow init_t apache_t : process transition;   
  7. allow apache_t apache_exec_t : file entrypoint;  

    在實際中,除了上面這幾個最小allow規則外,我們可能還想增加一些額外的規則,例如:常見的有預設型別向源型別傳送exit訊號(即sigchld許可),繼承檔案描述符,使用管道進行通訊。

    域轉換最關鍵的概念是清楚地定義了入口點,即型別為apache_exec_t的檔案對新的預設型別apache_t有entrypoint許可,入口點檔案允許我們嚴格控制哪個程式可以在哪個域中執行(可以認為這就是型別強制的安全特性),我們知道只有程式的可執行檔案的型別對域有entrypoint許可時,這個程式才可以進入一個給定的域,因此我們可以知道並控制哪個程式有哪個特權了。

5.2.2 預設客體轉換(file)

     客體轉換規則為新建立的客體指定一個預設的型別,實際上,我們通常是在與檔案系統有關的客體(如file,dir,lnk_file等)上使用這種type_transition規則,和域轉換一樣,這些規則只會引發一個預設客體標記嘗試,也只有策略允許了有關的訪問權時,嘗試才會成功
    客體轉換規則由客體類別進行標記,如:

  1. type_transition passwd_t tmp_t : file passwd_tmp_t;  

      這條type_transition規則指出當一個型別為passwd_t的程式在一個型別為tmp_t的目錄下建立一個普通檔案(file客體類別)時,預設情況下,如果策略允許的話,新建立的檔案型別應該為passwd_tmp_t,注意客體類別目標型別不是tmp_t而是預設型別passwd_tmp_t,在這個例子中,tmp_t隱含關聯了dir客體類別,因為它是唯一能夠容納檔案的客體類別,同樣,和前面一樣,策略必須允許對預設標記的訪問,對於前面的例子,對型別為tmp_t的目錄的訪問權需要包括add_name,write和search,對型別為passwd_tmp_t的檔案要有read和write訪問權。

      這個例子很典型,它顯示了一個解決在同一個目錄下多個應用程式共享和繼承的安全問題,如在一個臨時目錄下,客體轉換規則對於那些在執行時建立的客體非常有用。

      某些情況下不能使用客體轉換規則,當程式需要在同一個客體容器中建立有多個不同型別的客體時,一條type_transition規則還不夠,例如:假設一個程式在/tmp/目錄下建立兩個UNIX域套接字,這些套接字將用於和其它域通訊,如果我們想給每個sock檔案不同的型別,客體轉換規則將不能滿足了,這時需要兩條規則,它們有相同的源型別,目標型別和客體類別,只是預設型別不同,但這樣會在編譯時產生錯誤,解決這個問題的辦法是在安裝時建立sock檔案,並明確地標記它們,將sock檔案分別放在不同目錄型別的目錄下,或讓程式在建立時明確地請求型別。

5.3 型別改變規則type_change

     我們使用type_change規則為使用SELinux特性的應用程式執行重新標記指定預設型別,和type_transition規則類似,type_change規則指定預設標記,但不允許訪問。

     與type_transition規則不同點如下

      • type_change規則的影響不會在核心中生效,而是依賴於使用者空間應用程式,如login或sshd。

      為了在策略基礎上重新標記客體,如下面的規則:

  1. type_change sysadm_t tty_device_t : chr_file sysadm_tty_device_t;  

     這條type_change規則指出以sysadm_t名義重新標記一個型別為tty_device_t的字元檔案時,應該使用sysadm_tty_device_t型別。

     這條規則是最常見的使用type_change規則的示例,它在使用者登陸時重新標記終端裝置,login程式會通過一個核心介面查詢SELinux模組中的策略,傳遞型別sysadm_t和tty_device_t,接收sysadm_tty_device_t型別作為重新標記的型別,這個機制允許在一個新的登陸會話過程中,登陸程式以使用者的名義標記tty裝置,將特殊的型別封裝到策略中,而不用硬編碼到應用程式中。

    我們可能很少使用type_change規則,因為它們通常只由核心作業系統服務使用。

6. 小結

     • 型別是SELinux中訪問控制的主要基礎。它們起著所有客體(程式,檔案,目錄,套接字等)訪問控制屬性的作用,型別使用Types語句宣告。

     • 屬性是型別組。在大多數策略中,能夠使用型別的地方就可以使用屬性。在使用屬性前,我們必須先宣告,在Types宣告語句中,我們可以將型別新增到屬性中,或使用typeattribute語句也行。

     • 別名是型別的另一個名字,主要用於重新命名型別時保持向後的相容性,可以在宣告型別時就宣告一個別名,或單獨使用typealias語句宣告。

     • 有四個AV規則,它們的語法都一樣:allow,neverallow,auditallow和dontaudit。

     • 我們使用allow規則指出訪問一個域型別時需要一個什麼客體型別,我們根據客體類別和許可指定訪問權。
預設情況下,訪問被允許時不產生稽核訊息,而是訪問被拒絕時產生。我們使用dontaudit規則指出被拒絕的訪問不產生稽核訊息,我們使用auditallow規則指出允許的訪問要產生稽核訊息。

     • AV規則(如allow)是累加的,對於一個給定的源型別目標型別客體類別的金鑰,在執行時,被允許和被稽核的訪問權是所有引用了該金鑰的規則的並集。

     • 我們使用neverallow規則指出了永遠都不會被allow規則允許的固定屬性,如果某個allow規則違背了這個原則,checkpolicy編譯器在編譯時就會產生一個錯誤。

     • 有兩個型別規則,它們的語法是一樣的:type_transition和type_change。型別規則沒有allow訪問權,相反,它們指定了客體建立和重新標記事件想要的預設標記策略

     • 我們使用type_transition規則在建立新的客體時標記它(客體轉換),或在執行一個新的應用程式時改變程式的型別(域轉換)。

     • 我們使用type_change規則為重新標記客體指定預設的型別,它們用於SELinux敏感的程式如login和sshd。

     • 策略分析工具apol在理解和分析複雜的SELinux策略時是一個非常有價值工具。


相關文章