本文主要講述 FreeBSD 5.0 操作系統中新增的重要安全機制,即強制訪問控制機制( MAC )的使用與源代碼分析,主要包括強制訪問控制框架及多級安全( MLS )策略兩部分內容。這一部分講述要將 MAC 框架與 MLS 策略用起來,應該做的一些工作,以及如何有效使用它們的問題。
?
強制訪問控制(英文縮寫 MAC )是實現操作系統安全的一個重要的方法,現在幾乎所有的安全操作系統都采用強制訪問控制作為其核心安全機制之一。強制訪問控制是對操作系統的各種客體(如文件、 socket 、系統 FIFO 、 SCD 、 IPC 等)進行細粒度的訪問控制,即當用戶或用戶程序訪問系統的某個客體時,強制訪問控制機制對這種訪問的安全性進行檢查。與自主訪問控制不同,強制訪問控制對用戶及用戶程序的行為進行限制,從而達到更高的安全級別。
?
強制訪問控制是一種機制,它對用戶與用戶程序對客體的訪問進行檢查,但什么樣的訪問是安全的呢?這就需要引入安全策略的概念。安全策略可以認為是一組檢查條件,它為每次訪問的主體(用戶或用戶程序)和被訪問的客體(如文件等)定義一個安全標記,再根據主體和客體的安全標記來決定這次訪問是否安全。目前已經開發出多種安全策略,其中 MLS 用得最多。 MLS 是多級安全的意思,它最早用于軍事領域。它的基本思想是定義一些安全級,如從低到高分普通、機密、絕密等安全級,要求高安全級別的信息不能泄露給低安全級別的用戶,這樣就要求低安全級的主體不能讀高安全級的客體,同時高安全級的主體不能寫低安全級的客體。詳情請參見有關資料。
?
與 Linux 一樣, FreeBSD 系統是開放源代碼的操作系統,而且 FreeBSD 的結構清晰,安全性好,所以使用也很廣泛。從 5.0RC2 版開始, FreeBSD 內核開始引入強制訪問控制機制。它在內核中實現了一個靈活通用 MAC 框架,這個框架對 FreeBSD 內核中幾乎所有的核心對象進行了訪問控制,并且這個框架設計合理、接口簡潔,使得我們可以很方便地開發各種安全策略模塊并將之掛接到系統,從而按我們自己的策略對系統進行安全控制。另外,系統還提供了包括 MLS 策略模塊在內的多個策略模塊供我們選用。
?
本文從使用與源代碼的分析這兩方面詳細講述 FreeBSD 系統中的強制訪問控制機制,內容主要包括 MAC 框架和 MLS 策略,相信會對對此部分內容感興趣的朋友有所啟發。
1 FreeBSD 5.0中強制訪問控制機制的使用
FreeBSD 5.0 RC2 版本發布時,內核源代碼中已經包含了 MAC 框架和一些 MAC 策略模塊(如 MLS 策略模塊、用于開發及實驗的 MAC_NONE 策略模塊、完整性模塊 MAC_BIBA 等),但是這個版本的內核并沒有正式對 MAC 提供支持,所以缺省情況下,在編譯內核的時候沒有把實現 MAC 框架的代碼編譯進去。另外,我們將以 MLS 策略為例,說明怎樣利用 MAC 框架加載策略模塊。為了讓 MLS 策略真正實用,還需要配置系統的擴展文件屬性等,我們將對之進行一定的介紹。最后我們將介紹如通過控制臺命令以及在程序中如何使用系統調用接口對文件或進程的 MAC 標記進行操作。
1.1 重新編譯內核
如上所述,由于 FreeBSD 5.0 RC2 版中的 MAC 框架還處于開發階段,所以缺省情況下,內核二進制代碼中并沒有包括對 MAC 框架的支持。要使內核支持 MAC 框架,必須配置并重新編譯內核,具體方法如下。
?
到 /usr/src/sys/i386/conf 下,把 GENERIC 文件復制成另一個文件如 yxd_kernel (不要改動 GENERIC 文件),然后在 yxd_kernel 文件中找到 makeoptions 這個部分,仿照已有的格式,添加一行(詳細信息請參見 "FreeBSD Developers' Handbook" 中的 "TrustedBSD MAC FrameWork" 章節):
?
options MAC
?
這個選項將打開 MAC 編譯開關,這樣編譯時將把相應的 MAC 框架代碼編譯進內核。
?
另外,為了支持文件及目錄的 MAC 標記的存儲,必須讓內核支持擴展文件系統(缺省情況下也不支持)。要達到這個目的,必須在 yxd_kernel 中加入如下兩個編譯選項:
?
mac_mls_load = "YES"
?
值得注意的一點是,當我們修改并重新編譯了 mac_mls.ko 這個模塊后,必須把它從 /usr/src/sys/modules/mac_mls 目錄拷貝到 /boot/kernel 目錄,因為系統啟動的時候是從 /boot/kernel 目錄尋找模塊的。
1.3 配置擴展文件系統
?
當我們做完 1.1 和 1.2 兩步后, MLS 模塊就已經在系統中起作用了,我們可以使用 "getfmac 文件 / 目錄名 " 來得到文件或目錄的 MAC 標記(形如 " 文件 / 目錄名 : mls/low" ,詳細的介紹請參閱 1.4 節),但此時的標記是一種偽標記,也就是說,這個標記是 MLS 策略給每個文件的缺省標記,且這個標記在關閉系統的時候并沒有被存儲。但是按照我們的需要,我們希望每個文件或目錄的 MAC 標記應該有 " 非易逝性 " 的標記,也就是說我們希望這個標記與文件一樣被存儲在磁盤上。這就需要引入擴展文件系統,使得每個文件或目錄的 MAC 標記存儲在對應文件或目錄的擴展屬性中。為了便于理解,下面對 FreeBSD 中的擴展文件系統進行一點簡要的介紹。
?
簡單地說,擴展文件系統是對現有文件系統的一種擴充。擴展屬性( EA , Extended Attribute )是相對于傳統文件系統的 inode 節點中已存儲的文件或目錄的標準屬性而言的,它可以為 inode 節點中存儲的文件或目錄增加額外的信息,這些信息再由系統根據 inode 結點訪問到。每個文件系統的擴展屬性( EA )是一組( name , value )對。一個 inode 節點(文件或目錄)可能定義了某種屬性,也可能沒有定義。如果 inode 節點定義了某個屬性,則它可能包含 0 個,也可能包含多個字節的這種屬性的數據。這與一般 shell 的環境變量類似。這樣,擴展屬性機制就為 TrustedBSD 的各種安全特征( ACL 、 MAC 和能力等),提供了存儲訪問安全信息的簡單方法。一些非安全應用也可以根據需要創建擴展屬性,例如文件的校驗和、密鑰等。擴展屬性可以動態地增加新的擴展屬性而不必改變文件系統在磁盤上的存儲格式。
?
如果使用的是 MLS 策略,那么返回的 MAC 標記為 mls/[single_mac]([mac_range]) ,其中 mls 為策略名,后面用 "/" 與標記數據隔開。 [single_mac] 為單一的 MAC 標記,可能為 "low" 或 "high" 或 "equal" 。 [mac_range] 為一個 MAC 標記范圍,往往主體(進程)有一個范圍。一個 MAC 標記的例子為: mls/low(low-high) 。
?
在應用程序中,可以使用的系統調用請參見 /usr/src/sys/sys/mac.h 文件中 #ifndef _KERNEL 這個塊中提供的函數原型,值得注意的是, man page 中的有些接口參數及返回值類型有誤,請以 mac.h 文件中的類型為準。
?
下面是幾個最常用的函數的使用方法:
-
mac_get_proc , mac_get_file 的用法:
struct mac myMac;
char ss[60] = "mls";
myMac.m_string = ss;
myMac.m_buflen = 60;
mac_get_proc(&myMac);
mac_get_file("/tmp/test",&myMac);
-
mac_set_proc , mac_set_file 的用法:
struct mac myMac;
myMac.m_string = "mls/high";
myMac.m_buflen = strlen(myMac.m_string);
mac_set_proc(&myMac);
mac_set_file("/tmp/test",&myMac);
1.5 小結
由于 FreeBSD 5.0RC2 版才剛剛開始非正式地支持 MAC ,所以針對普通用戶而言,要將其真正用起來,要做的工作還比較多比較煩瑣的。對于 MLS 策略而言,還有很多值得完善的地方,比如現有的 MLS 策略不支持對用戶設置 MAC 標記,主體對客體訪問的時候限制過多,還有 MLS 不支持可信進程等等,都是下一個版本需要完善的。
?
在使用上,從我個人的使用經驗來看,比較不好用。當然,這個問題不能全怪系統, MLS 策略導致易用性降低已經是不爭的事實,相信經過一段時間的開發,最終會給我們一個滿意的答案。
?
但是瑕不掩玉,撇開 MLS ,單看 MAC 框架,應該說是設計得很完美的:良好的結構、清晰的邏輯、非常簡潔的接口,這些優點應該來說可以給每一個對它有興趣的人一個大大的驚喜。即使是 MLS 策略,由于其作為一個 KLD 模塊來實現,所以十分便于我們對它進行修改和擴充,相信經過一段時間, MLS 模塊亦會成為我們的驚喜。如果大家有興趣了解 MAC 框架及 MLS 策略的實現,請接著往下閱讀。
?
本文主要講述 FreeBSD 5.0 操作系統中新增的重要安全機制,即強制訪問控制機制( MAC )的使用與源代碼分析,主要包括強制訪問控制框架及多級安全( MLS )策略兩部分內容。這一部分較系統地對 MAC 框架及 MLS 策略的源代碼進行分析。
2 MAC框架與MLS策略源代碼分析
與本文相關的源代碼文件主要有兩個,即 /usr/src/sys/kern/kern_mac.c 和 /usr/src/sys/security/mac_mls/mac_mls.c 。另外還有一些頭文件如 mac.h 、 mac_policy.h 等。
2.1 MAC框架整體結構
?
下面是 MAC 框架的示意性結構圖,當用戶控制臺或用戶程序通過系統調用對內核對象進行訪問的時候,由于內核代碼中相應的位置插入了 MAC 框架的檢查函數,于是內核就會調用 MAC 框架的相應檢查函數來做安全性檢查。 MAC 框架會依次調用每個掛接在 MAC 框架上的安全策略,以決定訪問是否安全。另外,其它可能涉及到安全問題的系統事件,如初始化各種安全標記、初始化各種內核對象等,也會通知 MAC 框架,由它做出相應的處理。
?
從圖中我們也可以看到,安全策略作為一個獨立的 KLD 模塊,可以獨立于內核進行編譯,再在使用的時候掛接到 MAC 框架上。要判斷一次訪問是否安全, MAC 框架會調用所有的安全策略,只有當所有的安全策略均表示同意, MAC 框架才會授權這次訪問。
2.2 安全標記
?
安全標記是由 MAC 框架和各個安全策略定義的一組數據,用于描述主體或客體的安全信息,安全標記與內核描述主客體的其它數據一起存儲在內核中。要實現強制訪問控制,首先必須為主客體定義安全標記。不同的策略由于判斷的依據不一樣,可能定義的標記也不相同。作為 MAC 框架,當安全策略向它注冊的時候,它必須把該策略使用的安全標記附加到各個內核對象上去,這樣當需要調用該策略做安全性檢查的時候,才能為策略提供它們自己定義和理解的安全標記。我們先給出 MAC 框架與 MLS 策略定義的安全標記,再對之作進一步的解釋。
?
MAC 框架中安全標記的定義是這樣的:
?
struct label {
int l_flags;
union {
void *l_ptr;
long l_long;
} l_perpolicy[MAC_MAX_POLICIES];
};
?
其中 l_flags 是一個標志,被 MAC 框架用來判斷是否初始化了整個標記數據結構。 l_perpolicy 數組為每個策略定義了一個聯合,這樣當策略向 MAC 框架注冊時,它們既可以用一個 long 類的整數作為它們自己的安全標記,也可以使用聯合中的指針指向一個它們自己定義的標記數據結構。 MLS 策略選擇了后者。
?
MLS 策略定義的安全標記是這們的:
?
struct mac_mls_element {
u_short mme_type;
u_short mme_level;
u_char mme_compartments[MAC_MLS_MAX_COMPARTMENTS >> 3];
};
struct mac_mls {
int mm_flags;
struct mac_mls_element mm_single;
struct mac_mls_element mm_rangelow, mm_rangehigh;
};
?
在 mac_mls 結構中,定義了一個單一標記( mm_single )和一個標記范圍( mm_rangelow , mm_rangehigh ),主客體既可以使用單一標記來標識單一安全級,也可以使用標記范圍來標識一個安全級范圍,還可以同時使用二者。如第 1 章中我們使用 getfmac 得到的輸出 "mls/high" 表明該文件使用的是值為 "high" 的單一安全標記。再比如我們使用 getpmac 得到的輸出 "mls/low(low-high)" 表明進程同時使用了單一標記和標記范圍。至于究竟使用的是哪種標記,由 mm_flags 標識。
?
mac_mls_element 定義一個標記,它定義的標記功能很強大,既支持安全類型( mme_type 變量,值為 LOW 、 HIGH 、 EQUAL 和 UNDEFINE ),也支持多達 256 個級別的安全級(當 mme_type 的值為 LEVEL 時, mme_level 變量有效,由它定義安全級),同時還使用 mme_compartments 數組支持域( field ),后續章節將具體講述 MLS 策略是怎樣使用它所定義的標記的。
?
當用戶試圖打開一個文件時,內核就會試圖打開一個 vnode ,在打開之前會調用這個函數,用于檢查主體是否有權限打開這個 vnode 。 MAC_CHECK 宏定義如下:
?
#define MAC_CHECK(check, args...) do {
struct mac_policy_conf *mpc;
error = 0;
MAC_POLICY_LIST_BUSY();
LIST_FOREACH(mpc, &mac_policy_list, mpc_list) {
if (mpc->mpc_ops->mpo_ ## check != NULL)
error = error_select(
mpc->mpc_ops->mpo_ ## check (args),
error);
}
MAC_POLICY_LIST_UNBUSY();
} while (0)
?
我們可以看到,代碼中使用 LIST_FOREACH 宏來遍歷安全策略鏈表中的每個策略,然后調用其在 mpc_ops 中注冊的檢查函數 mpo_##check 。 mac_policy_ops 這個結構中定義了很多事件處理函數,進行細粒度的訪問控制,限于篇幅,在此不一一列出。這些函數共分四類:第一類是策略本身的初始化和銷毀函數;第二類是對安全標記的操作函數,包括各種內核對象的安全標記的初始化與銷毀函數,以及獲取與設置安全標記的接口函數;第三類是對文件系統、網絡系統及進程對象的標記進行操作的事件處理函數;第四類是檢查對各個內核對象的訪問是否安全的檢查函數。詳見 mac_policy.h 。
?
2.4 安全策略的注冊
?
分析 MLS 策略,我們可以發現,安全策略向 MAC 框架注冊是一件很簡單的事情。 mac_mls.c 文件中花了大量篇幅定義了所有的事件處理函數,如前所述,這些函數是安全策略的核心,用于判斷訪問的安全性等。定義完這些函數后,在源代碼的最后,把這些函數的地址填入 mac_policy_ops 結構中,再用一個 MAC_POLICY_SET 宏把 mac_policy_ops 結構掛接到上面提到的 mac_policy_list 鏈表中去就可以了,如下:
?
MAC_POLICY_SET(&mac_mls_ops, trustedbsd_mac_mls, "TrustedBSD MAC/MLS", MPC_LOADTIME_FLAG_NOTLATE, &mac_mls_slot);
?
mac_policy_modevent 函數的核心代碼如下:
?
case MOD_LOAD:
if (mpc->mpc_loadtime_flags & MPC_LOADTIME_FLAG_NOTLATE &&
mac_late) {
printf("mac_policy_modevent: can't load %s policy "
"after booting", mpc->mpc_name);
error = EBUSY;
break;
}
error = mac_policy_register(mpc);
break;
case MOD_UNLOAD:
/* Don't unregister the module if it was never registered. */
if ((mpc->mpc_runtime_flags & MPC_RUNTIME_FLAG_REGISTERED) != 0)
error = mac_policy_unregister(mpc);
else
error = 0;
break;
?
當模塊被 LOAD 的時候,會調用 mac_policy_register 函數,它首先檢查該模塊是否已注冊,如果沒有,就會執行下面的代碼: LIST_INSERT_HEAD(&mac_policy_list, mpc, mpc_list);
其中 mpc 為我們上面提到的 mac_policy_conf 結構的地址,這樣到此為止,就把 MLS 策略添加到 MAC 框架的策略列表中去了。
?
2.5 MLS策略源代碼分析
?
根據 MLS 策略,高安全級別的主體不能寫低安全級別的客體,而低安全級別的主體則不能讀高安全級別的客體,只有主客體的安全級別相同,才能既讀又寫。 MLS 定義了三種標記比較函數:
?
mac_mls_dominate_single(a,b) :用于檢查 a 的單一標記的安全級是否高于 b 的單一標記的安全級。這個函數主要用于對內核變量的讀、寫、查詢等事件進行安全性檢查。
?
我們只解釋一下最后一個 case ,即當 a 、 b 的類型均為 MAC_MLS_TYPE_LEVEL 時,也就是它們都使用一個數( 0 到 254 )標識它們的安全級時,判斷是如何進行的。首先:
#define MAC_MLS_BIT_TEST(b, w) ((w)[(((b) - 1) >> 3)] & (1 << (((b) - 1) & 7)))
?
我們可以看到,在比較二者安全級之前,對它們的 mme_compartments 進行了位測試。 mme_compartments 是一個 256bit 的數據(由 uchar[32] 數組定義),每個 bit 都可以設置為 1 來表示該對象屬于某一個安全域,當然也可以設置多個 bit 表示這個對象屬于多個安全域。 MLS 要求如果 a 可以訪問 b 的話, b 所屬的安全域必須要是 a 的子集,否則訪問被拒絕。對 mme_compartments 進行測試的目的也即是要判斷 b 中設置了 1 的那些 bit 在 a 中有否設置。
?
2.6 對MAC框架及MLS策略的擴充
?
最后我們對 MAC 框架作一點改進。前面提過, MAC 框架目前并不支持對用戶的安全級進行設置,也沒有提供存儲用戶安全級信息的地方,我們想對之做改進,以使其更實用。我們改進的思想是,攔截系統的 setuid 系統調用,在系統作 setuid 的時候(用戶登錄、運行 su 命令等),讀入用戶安全級別信息并作相應設置。我們做了如下工作:
-
在 mac_policy.h 文件中的 mac_policy_ops 結構中增加一個新函數 mpo_get_cred_from_file() ,其參數和返回值與 mpo_create_cred() 完全一樣。
-
在 mac_mls.c 文件中增加一個新函數 mac_mls_get_cred_from_file() ,其參數和返回值與 mac_mls_create_cred() 完全一樣。在這個函數里,增加在內核中讀寫用戶安全級配置文件的語句,從而把用戶的安全級在這里讀到標記中。然后在對 mac_policy_ops 結構的設置語句中,增加一條語句: .mpo_get_cred_from_file = mac_mls_get_cred_from_file 。
-
在 kern_prot.c 文件中的 setuid() 函數里 crcopy() 函數替換為我們自己的 mac_crcopy() 函數,并在調用這個函數之前加一條語句: newcred->cr_uid = uid; 。
?
mac_crcopy() 函數的代碼如下:
?
void mac_crcopy(struct ucred *dest,struct ucred *src)
{
uid_t uid = dest->cr_uid;
KASSERT(crshared(dest) == 0,("crcopy of shared ucred"));
bcopy(&src->cr_startcopy,&dest->cr_startcopy,
(unsigned)((caddr_t)&src->cr_endcopy -
(caddr_t)&src->cr_startcopy));
uihold(dest->cr_uidinfo);
uihold(dest->cr_ruidinfo);
if(jailed(dest))
prison_hold(dest->cr_prison);
#ifdef MAC
dest->cr_uid = uid;
mac_get_cred(src,dest);
dest->cr_uid = src->cr_uid;
#endif
}
修改相應的頭文件,并重新編譯內核。
2.7 小結
?
上面分析了 MAC 框架與 MLS 策略的主要內容,限于篇幅,我們只給出主要邏輯,如果您想要了解更為詳細的技術細節,請閱讀其源代碼或聯系我。另外, MAC 框架和 MLS 策略還用到了一些很重要的技術,如系統控制( sysctl )機制、鎖等,我們也沒有提到,如有興趣的讀者可以繼續鉆研。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
