一、Linux Security Modules
Linux Security Modules (LSM) 是一種 Linux 內(nèi)核子系統(tǒng),旨在將內(nèi)核以模塊形式集成到各種安全模塊中。在 2001 年的 Linux Kernel 峰會(huì)上,NSA 代表建議在 Linux 內(nèi)核版本 2.5 中包含強(qiáng)制控制訪(fǎng)問(wèn)系統(tǒng) Security-Enhanced Linux。然而,Linus Torvalds 拒絕了這一提議,因?yàn)?SELinux 并不是惟一一個(gè)用于增強(qiáng) Linux 安全性的安全系統(tǒng)。除此之外,并不是所有的開(kāi)發(fā)人員都認(rèn)為 SELinux 是最佳解決方案。SELinux 并沒(méi)有直接包含在內(nèi)核中,相反,創(chuàng)建了 Linux Security Modules 系統(tǒng),允許安全子系統(tǒng)作為模塊使用,這意味著可以比較方便地連接新的模塊。
LSM 子系統(tǒng)的開(kāi)發(fā)工作持續(xù)了大約三年時(shí)間,并從版本 2.6 開(kāi)始,就被包含到 Linux 內(nèi)核中。目前具備正式官方支持的安全模塊包括?SELinux、Apparmor、Smack?和?TOMOYO Linux。
Linux Security Modules內(nèi)核配置選項(xiàng):
LSM的初始化階段發(fā)生在系統(tǒng)內(nèi)核的初始化階段,linux在init/main.c的start_kernel()函數(shù)中啟動(dòng)了一系列的內(nèi)核模塊,當(dāng)然也包含了安全模塊的初始化函數(shù)調(diào)用,可以看到有:
asmlinkage void __init start_kernel(void){ char * command_line; extern const struct kernel_param __start___param[], __stop___param[]; ... thread_info_cache_init(); cred_init(); fork_init(totalram_pages); proc_caches_init(); buffer_init(); key_init();security_init();dbg_late_init(); vfs_caches_init(totalram_pages); signals_init(); /* rootfs populating might need page-writeback */ page_writeback_init();#ifdef CONFIG_PROC_FS proc_root_init();#endif cgroup_init(); cpuset_init(); taskstats_init_early(); delayacct_init(); check_bugs(); acpi_early_init(); /* before LAPIC and SMP init */ sfi_init_late(); ftrace_init(); /* Do the rest non-__init'ed, we're now alive */ rest_init();}
其中security_init()便是初始化LSM,在key_init()之前,dbg_late_init()及vfs_caches_init()之后,這就是其所處的層次。
接著看security_init()的實(shí)現(xiàn),其位于security/security.c中,安全相關(guān)的代碼基本都位于security目錄下,包括selinux的實(shí)現(xiàn)源碼:
/** * security_init - initializes the security framework * * This should be called early in the kernel initialization sequence. */int __init security_init(void){ printk(KERN_INFO "Security Framework initialized\n"); security_fixup_ops(&default_security_ops); security_ops = &default_security_ops; do_security_initcalls(); return 0;}
這里做了三件事:
(1)設(shè)置默認(rèn)安全操作對(duì)象(default_security_ops)中的鉤子函數(shù),這個(gè)操作在security_fixup_ops函數(shù)中完成,它將LSM提供的默認(rèn)鉤子函數(shù)賦值給default_security_ops,這些默認(rèn)的鉤子函數(shù)并不做任何事,全是返回NULL的,其具體實(shí)現(xiàn)留給其它安全模型,LSM只是提供了一個(gè)良好可擴(kuò)展的框架。
(2)將default_security_ops賦值給全局變量security_ops,也即主體訪(fǎng)問(wèn)客體時(shí)應(yīng)用的安全操作對(duì)象。在內(nèi)核配置表里選上Socket and Networking Security Hooks,會(huì)產(chǎn)生CONFIG_SECURITY_NETWORK,當(dāng)沒(méi)有具體的安全策略注冊(cè)時(shí),系統(tǒng)會(huì)調(diào)用default_security_ops的成員函數(shù)。
(3)調(diào)用do_security_initcalls調(diào)用各個(gè)安全策略的注冊(cè)函數(shù),具體的安全策略注冊(cè),例如SELinux、Apparmor、Smack?和?TOMOYO Linux。
static void __init do_security_initcalls(void){ initcall_t *call; call = __security_initcall_start; while (call < __security_initcall_end) { (*call) (); call++; }}#define SECURITY_INITCALL \ VMLINUX_SYMBOL(__security_initcall_start) = .; \ *(.security_initcall.init) \ VMLINUX_SYMBOL(__security_initcall_end) = .;
do_security_initcalls()調(diào)用__security_initcall_start和__security_initcall_end之間的初始化函數(shù),這些函數(shù)security_initcall()的注冊(cè):
#define security_initcall(fn) \ static initcall_t __initcall_##fn \ __used __section(.security_initcall.init) = fn
security_initcall(selinux_init);//security/selinux/hooks.c?SELinux 安全策略
security_initcall(apparmor_init);//security/apparmor/lsm.c?Apparmor 安全策略
security_initcall(smack_init);//security/smack/smack_lsm.c?Smack?安全策略
security_initcall(tomoyo_init);//security/tomoyo/tomoyo.c?TOMOYO Linux 安全策略
我的內(nèi)核沒(méi)有選擇任何安全策略,默認(rèn)的策略。
二、SELinux 架構(gòu)
如前所述,SELinux 系統(tǒng)從研究操作系統(tǒng) Flask 那里繼承了安全子系統(tǒng)架構(gòu)。Flask 的主要特性是它使用了 “最小特權(quán)” 的概念向用戶(hù)或應(yīng)用程序授權(quán),并且該權(quán)限僅夠用于執(zhí)行所請(qǐng)求的操作。該概念使用類(lèi)型強(qiáng)制實(shí)現(xiàn)(參見(jiàn)?類(lèi)型強(qiáng)制),這都?xì)w功于 SELinux 中的強(qiáng)制訪(fǎng)問(wèn)能夠作為域類(lèi)型模型的一部分操作。在該模型中,每個(gè)流程主體在一個(gè)特定的安全上下文(域)中啟動(dòng)(即它有一個(gè)特定的訪(fǎng)問(wèn)級(jí)別),而所有操作系統(tǒng)的資源對(duì)象(文件、目錄、套接字和其他)都有一個(gè)特定的類(lèi)型(保密級(jí)別)與之相關(guān)聯(lián)。
由于采用了類(lèi)型增強(qiáng),SELinux 的訪(fǎng)問(wèn)控制選項(xiàng)極大地?cái)U(kuò)展了 UNIX? 類(lèi)型系統(tǒng)中使用的基本自主 (discretionary) 訪(fǎng)問(wèn)控制模型中的選項(xiàng)。例如,SELinux 可用于嚴(yán)格限制網(wǎng)絡(luò)服務(wù)器能夠訪(fǎng)問(wèn)的網(wǎng)絡(luò)端口號(hào)。它還允許創(chuàng)建單獨(dú)的文件并將數(shù)據(jù)保存到文件中,但是無(wú)法刪除這些文件,等等。這種操作系統(tǒng)對(duì)象分級(jí)有助于對(duì)系統(tǒng)和用戶(hù)流程進(jìn)行限制,這是通過(guò)使用明確分配給特定資源的訪(fǎng)問(wèn)權(quán)限實(shí)現(xiàn)的。如果 SELinux 控制的任何一個(gè)服務(wù)受到破壞,那么入侵者將無(wú)法越過(guò)沙盒(由規(guī)則集限制),即使入侵者具有超級(jí)用戶(hù)的權(quán)限。
規(guī)則列表也是一種安全策略,它定義允許某些域訪(fǎng)問(wèn)某些類(lèi)型的權(quán)限。該安全策略將在系統(tǒng)啟動(dòng)時(shí)應(yīng)用,包含一組文本文件,這些文本文件將在系統(tǒng)啟動(dòng)時(shí)載入到 Linux 內(nèi)核的內(nèi)存中。
這些規(guī)則采用可讀的形式,甚至可以被普通用戶(hù)理解。例如,在如下所示的 http 服務(wù)器域規(guī)則中,給出了允許讀取包含網(wǎng)絡(luò)配置文件的權(quán)限:
allow httpd_t net_conf_t:file { read getattr lock ioctl };
SELinux 從 Flask 安全子系統(tǒng)中繼承了使用標(biāo)簽定義操作系統(tǒng)對(duì)象和主體的安全上下文的結(jié)構(gòu)和規(guī)則,以及 “域類(lèi)型” 模型。要確保實(shí)現(xiàn)整體保護(hù),必須對(duì)系統(tǒng)中的每個(gè)對(duì)象和主體定義安全上下文。標(biāo)簽采用以下形式:
::
例如,分布式安全上下文的標(biāo)簽采用下面的形式:system_u:object_r:httpd_exec_t。在 SELinux 中,用戶(hù)?system_u?通常為系統(tǒng)的 daemon 的默認(rèn)名稱(chēng)。角色?object_r?被分配給普通文件或設(shè)備等系統(tǒng)對(duì)象。httpd_exec_t?類(lèi)型應(yīng)用到正在執(zhí)行的 httpd file 文件,地址為 /usr/sbin/httpd。user、role?和?type?元素將在下一篇文章中詳細(xì)討論。
圖 1. 使用 SELinux 的強(qiáng)制訪(fǎng)問(wèn)控制系統(tǒng)概述
SELinux 包含五個(gè)基本組成:
用于處理文件系統(tǒng)的輔助模塊
用于集成 Linux Security Modules 事件鉤的模塊
一個(gè)用于組織訪(fǎng)問(wèn)控制的基本機(jī)制
Policy Enforcement Server,一個(gè)系統(tǒng)安全性策略數(shù)據(jù)庫(kù)
Access Vector Cache (AVC),一種輔助機(jī)制,用于提高生產(chǎn)力
SELinux 的操作可以分為下面幾個(gè)步驟:
操作系統(tǒng)主體(流程)嘗試訪(fǎng)問(wèn)特定對(duì)象(文件、流程、套接字)上的某個(gè)操作,這在 Linux 標(biāo)準(zhǔn)自主安全系統(tǒng)(DAC)中是允許的。這將向?qū)ο蟀l(fā)起一個(gè)請(qǐng)求流。
每個(gè)要求對(duì)對(duì)象執(zhí)行操作的請(qǐng)求都由 Linux Security Modules 截獲并傳遞給 SELinux Abstraction & Hook Logic 子系統(tǒng),同時(shí)還包括主體和對(duì)象的安全上下文,SELinux Abstraction & Hook Logic 子系統(tǒng)負(fù)責(zé)與 LSM 交互。
從 SELinux Abstraction and Hook Logic 子系統(tǒng)接收到的信息將轉(zhuǎn)發(fā)給基本的 Policy Enforcement Server 模塊,后者負(fù)責(zé)確定是否允許主體訪(fǎng)問(wèn)該對(duì)象。
要接收是否允許或禁止該操作的決定,策略實(shí)施服務(wù)器將與 Access Vector Cache 子系統(tǒng)通信,后者通常會(huì)緩存要使用的規(guī)則。
如果 AVC 沒(méi)有包含相關(guān)策略的緩存規(guī)則,對(duì)所需的安全策略的請(qǐng)求將再次轉(zhuǎn)發(fā)給安全策略數(shù)據(jù)庫(kù)。
在找到安全策略后,該策略將被傳遞給接收決策的策略服務(wù)器。
如果所請(qǐng)求的操作符合找到的策略,那么將允許執(zhí)行該操作。反之,將禁止執(zhí)行該操作,并且所有決策制定信息將被寫(xiě)入到 SELinux 日志文件中。
除了判斷是否允許或禁止某些操作外,Policy Enforcement Server 模塊還負(fù)責(zé)執(zhí)行一些輔助任務(wù),例如安全標(biāo)簽管理(分配和移除)。
和所有優(yōu)秀的系統(tǒng)一樣,SELinux 實(shí)現(xiàn)了操作簡(jiǎn)便性,確保通過(guò)完整的強(qiáng)制訪(fǎng)問(wèn)控制系統(tǒng)實(shí)現(xiàn)可靠的操作、低資源需求和良好的生產(chǎn)力。
三、SElinux代碼分析
1> 初始化第一步: 向LSM注冊(cè)策略
SE Linux的初始化是在內(nèi)核的加載過(guò)程中的初期就開(kāi)始的,第一個(gè)被系統(tǒng)執(zhí)行的函數(shù)是hook.c文件中的selinux_init函數(shù),其代碼如下:
static __init int selinux_init(void){ //判斷SELinux是否為配置的默認(rèn)安全模塊 if (!security_module_enable(&selinux_ops)) { selinux_enabled = 0; return 0; } //如果沒(méi)有配置SELinux為默認(rèn)安全模塊,則退出 if (!selinux_enabled) { printk(KERN_INFO "SELinux: Disabled at boot.\n"); return 0; } printk(KERN_INFO "SELinux: Initializing.\n"); // 設(shè)置當(dāng)前進(jìn)程的安全狀態(tài) cred_init_security(); default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC); // 給inode安全屬性對(duì)象sel_inode_cache分配空間 sel_inode_cache = kmem_cache_create("selinux_inode_security", sizeof(struct inode_security_struct), 0, SLAB_PANIC, NULL); //初始化訪(fǎng)問(wèn)向量緩存(AVC) avc_init(); //將SELinux注冊(cè)到LSM中 if (register_security(&selinux_ops)) panic("SELinux: Unable to register with kernel.\n"); // 顯示SE Linux的強(qiáng)制模式 if (selinux_enforcing) printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n"); else printk(KERN_DEBUG "SELinux: Starting in permissive mode\n"); return 0;}// 確保可以盡早啟動(dòng),以便在創(chuàng)建進(jìn)程和對(duì)象時(shí)對(duì)其進(jìn)行標(biāo)識(shí)// 該宏在include/linux/init.h文件中定義security_initcall(selinux_init);
首先來(lái)看看cred_init_security()函數(shù)中用到的task_security_struct數(shù)據(jù)結(jié)構(gòu),其在include/objsec.h文件中定義:
struct task_security_struct { u32 osid; // 最后一次execve前的SID u32 sid; // 當(dāng)前SID u32 exec_sid; // exec的SID u32 create_sid; // 創(chuàng)建文件系統(tǒng)的SID u32 keycreate_sid; // 創(chuàng)建密鑰的SID u32 sockcreate_sid; // 創(chuàng)建套接字的SID};
函數(shù)調(diào)用hook.c文件中的cred_init_security()函數(shù)給當(dāng)前進(jìn)程設(shè)置安全狀態(tài),其代碼如下:
/* * initialise the security for the init task */static void cred_init_security(void){ struct cred *cred = (struct cred *) current->real_cred; struct task_security_struct *tsec; // 創(chuàng)建描述進(jìn)程安全屬性的數(shù)據(jù)結(jié)構(gòu) tsec = kzalloc(sizeof(struct task_security_struct), GFP_KERNEL); if (!tsec) panic("SELinux: Failed to initialize initial task.\n"); // 設(shè)置當(dāng)前進(jìn)程的安全屬性 tsec->osid = tsec->sid = SECINITSID_KERNEL; cred->security = tsec;}
在給inode安全屬性對(duì)象分配空間后,函數(shù)會(huì)調(diào)用avc.c文件中的avc_init函數(shù)來(lái)初始化AVC(必須在執(zhí)行任意權(quán)限檢測(cè)前完成),其代碼如下:
#define AVC_CACHE_SLOTS 512void __init avc_init(void){ int i; // 初始化每一個(gè)緩存槽 for (i = 0; i < AVC_CACHE_SLOTS; i++) { INIT_LIST_HEAD(&avc_cache.slots[i]); spin_lock_init(&avc_cache.slots_lock[i]); // 寫(xiě)時(shí)加鎖 } atomic_set(&avc_cache.active_nodes, 0); atomic_set(&avc_cache.lru_hint, 0); // 給AVC緩存記錄分配空間 avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), 0, SLAB_PANIC, NULL, NULL); // 記錄AVC初始化 audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n");}
該函數(shù)中用到了一個(gè)名為avc_cache的結(jié)構(gòu),在同一文件中定義:
struct avc_cache { struct list_head slots[AVC_CACHE_SLOTS]; spinlock_t slots_lock[AVC_CACHE_SLOTS]; // 用以在寫(xiě)入時(shí)加鎖 atomic_t lru_hint; // 在reclaim搜索時(shí)的LRU提示 atomic_t active_nodes; u32 latest_notif; // 最近撤銷(xiāo)提醒};
然后,selinux_init函數(shù)將會(huì)調(diào)用register_security函數(shù)向LSM注冊(cè)SE Linux模塊,其代碼如下:
/** * register_security - registers a security framework with the kernel * @ops: a pointer to the struct security_options that is to be registered * * This function allows a security module to register itself with the * kernel security subsystem. Some rudimentary checking is done on the @ops * value passed to this function. You'll need to check first if your LSM * is allowed to register its @ops by calling security_module_enable(@ops). * * If there is already a security module registered with the kernel, * an error will be returned. Otherwise %0 is returned on success. */int __init register_security(struct security_operations *ops){ if (verify(ops)) { printk(KERN_DEBUG "%s could not verify " "security_operations structure.\n", __func__); return -EINVAL; } if (security_ops != &default_security_ops) return -EAGAIN; security_ops = ops; return 0;}
至此,SE Linux就完成了初始化過(guò)程的第一步,而其個(gè)模塊的初始化函數(shù)均被定義成普通的初始化調(diào)用,在Linux內(nèi)核的后續(xù)初始化過(guò)程中進(jìn)一步完成。
2> 初始化第二步:文件系統(tǒng)的掛載
static struct file_system_type sel_fs_type = { .name = "selinuxfs", .mount = sel_mount, .kill_sb = kill_litter_super,};struct vfsmount *selinuxfs_mount;static int __init init_sel_fs(void){ int err; if (!selinux_enabled) return 0; err = register_filesystem(&sel_fs_type); if (!err) { selinuxfs_mount = kern_mount(&sel_fs_type); if (IS_ERR(selinuxfs_mount)) { printk(KERN_ERR "selinuxfs: could not mount!\n"); err = PTR_ERR(selinuxfs_mount); selinuxfs_mount = NULL; } } return err;}__initcall(init_sel_fs);
3> 初始化第三步
static struct tree_descr selinux_files[] = { [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR}, [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR}, [SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO}, [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR}, [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO}, [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO}, [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUSR}, /* last one */ {""} };
最后,/sbin/init進(jìn)程將載入SE Linux的初始策略庫(kù),并調(diào)用sel_load_ops->sel_write_load->sel_make_policycap->security_load_policy->selinux_complete_init函數(shù)來(lái)完成整個(gè)初始化過(guò)程。
?
評(píng)論