一、Linux Security Modules
Linux Security Modules (LSM) 是一种 Linux 内核子体系,旨在将内核以模块方式集成到各种安全模块中。在 2001 年的 Linux Kernel 峰会上,NSA 代表主张在 Linux 内核版别 2.5 中包括强制操控拜访体系 Security-Enhanced Linux。可是,Linus Torvalds 拒绝了这一提议,因为 SELinux 并不是专一一个用于增强 Linux 安全性的安全体系。除此之外,并不是一切的开发人员都以为 SELinux 是最佳解决方案。SELinux 并没有直接包括在内核中,相反,创立了 Linux Security Modules 体系,答应安全子体系作为模块运用,这意味着能够比较方便地衔接新的模块。
LSM 子体系的开发作业继续了大约三年时刻,并从版别 2.6 开端,就被包括到 Linux 内核中。现在具有正式官方支撑的安全模块包括 SELinux、Apparmor、Smack 和 TOMOYO Linux。
Linux Security Modules内核装备选项:
LSM的初始化阶段发生在体系内核的初始化阶段,linux在init/main.c的start_kernel()函数中发动了一系列的内核模块,当然也包括了安全模块的初始化函数调用,能够看到有:
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()的完结,其坐落security/security.c中,安全相关的代码根本都坐落security目录下,包括selinux的完结源码:
/** * 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)设置默许安全操作目标(default_security_ops)中的钩子函数,这个操作在security_fixup_ops函数中完结,它将LSM供给的默许钩子函数赋值给default_security_ops,这些默许的钩子函数并不做任何事,满是回来NULL的,其具体完结留给其它安全模型,LSM仅仅供给了一个杰出可扩展的结构。
(2)将default_security_ops赋值给全局变量security_ops,也即主体拜访客体时运用的安全操作目标。在内核装备表里选上Socket and Networking Security Hooks,会发生CONFIG_SECURITY_NETWORK,当没有具体的安全战略注册时,体系会调用default_security_ops的成员函数。
(3)调用do_security_initcalls调用各个安全战略的注册函数,具体的安全战略注册,例如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()调用__security_initcall_start和__security_initcall_end之间的初始化函数,这些函数security_initcall()的注册:
#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 安全战略
我的内核没有挑选任何安全战略,默许的战略。
二、SELinux 架构
如前所述,SELinux 体系从研讨操作体系 Flask 那里承继了安全子体系架构。Flask 的首要特性是它运用了 “最小特权” 的概念向用户或运用程序授权,而且该权限仅够用于履行所恳求的操作。该概念运用类型强制完结(拜见 类型强制),这都归功于 SELinux 中的强制拜访能够作为域类型模型的一部分操作。在该模型中,每个流程主体在一个特定的安全上下文(域)中发动(即它有一个特定的拜访等级),而一切操作体系的资源目标(文件、目录、套接字和其他)都有一个特定的类型(保密等级)与之相关联。
因为选用了类型增强,SELinux 的拜访操控选项极大地扩展了 UNIX® 类型体系中运用的根本自主 (discretionary) 拜访操控模型中的选项。例如,SELinux 可用于严厉约束网络服务器能够拜访的网络端口号。它还答应创立独自的文件并将数据保存到文件中,可是无法删去这些文件,等等。这种操作体系目标分级有助于对体系和用户流程进行约束,这是经过运用清晰分配给特定资源的拜访权限完结的。假如 SELinux 操控的任何一个服务受到破坏,那么入侵者将无法跳过沙盒(由规矩集约束),即便入侵者具有超级用户的权限。
规矩列表也是一种安全战略,它界说答应某些域拜访某些类型的权限。该安全战略将在体系发动时运用,包括一组文本文件,这些文本文件将在体系发动时载入到 Linux 内核的内存中。
这些规矩选用可读的方式,乃至能够被一般用户了解。例如,在如下所示的 http 服务器域规矩中,给出了答应读取包括网络装备文件的权限:
allow httpd_t net_conf_t:file { read getattr lock ioctl };
SELinux 从 Flask 安全子体系中承继了运用标签界说操作体系目标和主体的安全上下文的结构和规矩,以及 “域类型” 模型。要保证完结全体维护,有必要对体系中的每个目标和主体界说安全上下文。标签选用以下方式:
::
例如,分布式安全上下文的标签选用下面的方式:system_u:object_r:httpd_exec_t。在 SELinux 中,用户 system_u 一般为体系的 daemon 的默许称号。人物 object_r 被分配给一般文件或设备等体系目标。httpd_exec_t 类型运用到正在履行的 httpd file 文件,地址为 /usr/sbin/httpd。user、role 和 type 元素将在下一篇文章中具体评论。
图 1. 运用 SELinux 的强制拜访操控体系概述
SELinux 包括五个根本组成:
用于处理文件体系的辅佐模块
用于集成 Linux Security Modules 事情钩的模块
一个用于安排拜访操控的根本机制
Policy Enforcement Server,一个体系安全性战略数据库
Access Vector Cache (AVC),一种辅佐机制,用于进步生产力
SELinux 的操作能够分为下面几个进程:
操作体系主体(流程)测验拜访特定目标(文件、流程、套接字)上的某个操作,这在 Linux 规范自主安全体系(DAC)中是答应的。这将向目标建议一个恳求流。
每个要求对目标履行操作的恳求都由 Linux Security Modules 截获并传递给 SELinux Abstraction & Hook Logic 子体系,一起还包括主体和目标的安全上下文,SELinux Abstraction & Hook Logic 子体系担任与 LSM 交互。
从 SELinux Abstraction and Hook Logic 子体系接纳到的信息将转发给根本的 Policy Enforcement Server 模块,后者担任确认是否答应主体拜访该目标。
要接纳是否答应或制止该操作的决议,战略施行服务器将与 Access Vector Cache 子体系通讯,后者一般会缓存要运用的规矩。
假如 AVC 没有包括相关战略的缓存规矩,对所需的安全战略的恳求将再次转发给安全战略数据库。
在找到安全战略后,该战略将被传递给接纳决议计划的战略服务器。
假如所恳求的操作契合找到的战略,那么将答应履行该操作。反之,将制止履行该操作,而且一切决议计划拟定信息将被写入到 SELinux 日志文件中。
除了判别是否答应或制止某些操作外,Policy Enforcement Server 模块还担任履行一些辅佐使命,例如安全标签办理(分配和移除)。
和一切优异的体系相同,SELinux 完结了操作简便性,保证经过完好的强制拜访操控体系完结牢靠的操作、低资源需求和杰出的生产力。
三、SElinux代码剖析
1> 初始化第一步: 向LSM注册战略
SE Linux的初始化是在内核的加载进程中的初期就开端的,第一个被体系履行的函数是hook.c文件中的selinux_init函数,其代码如下:
static __init int selinux_init(void){ //判别SELinux是否为装备的默许安全模块 if (!security_module_enable(&selinux_ops)) { selinux_enabled = 0; return 0; } //假如没有装备SELinux为默许安全模块,则退出 if (!selinux_enabled) { printk(KERN_INFO “SELinux: Disabled at boot.\n”); return 0; } printk(KERN_INFO “SELinux: Initializing.\n”); // 设置当时进程的安全状况 cred_init_security(); default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC); // 给inode安全特点目标sel_inode_cache分配空间 sel_inode_cache = kmem_cache_create(“selinux_inode_security”, sizeof(struct inode_security_struct), 0, SLAB_PANIC, NULL); //初始化拜访向量缓存(AVC) avc_init(); //将SELinux注册到LSM中 if (register_security(&selinux_ops)) panic(“SELinux: Unable to register with kernel.\n”); // 显现SE Linux的强制形式 if (selinux_enforcing) printk(KERN_DEBUG “SELinux: Starting in enforcing mode\n”); else printk(KERN_DEBUG “SELinux: Starting in permissive mode\n”); return 0;}// 保证能够尽早发动,以便在创立进程和目标时对其进行标识// 该宏在include/linux/init.h文件中界说security_initcall(selinux_init);
首要来看看cred_init_security()函数顶用到的task_security_struct数据结构,其在include/objsec.h文件中界说:
struct task_security_struct { u32 osid; // 最终一次execve前的SID u32 sid; // 当时SID u32 exec_sid; // exec的SID u32 create_sid; // 创立文件体系的SID u32 keycreate_sid; // 创立密钥的SID u32 sockcreate_sid; // 创立套接字的SID};
函数调用hook.c文件中的cred_init_security()函数给当时进程设置安全状况,其代码如下:
/* * 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; // 创立描绘进程安全特点的数据结构 tsec = kzalloc(sizeof(struct task_security_struct), GFP_KERNEL); if (!tsec) panic(“SELinux: Failed to initialize initial task.\n”); // 设置当时进程的安全特点 tsec->osid = tsec->sid = SECINITSID_KERNEL; cred->security = tsec;}
在给inode安全特点目标分配空间后,函数会调用avc.c文件中的avc_init函数来初始化AVC(有必要在履行恣意权限检测前完结),其代码如下:
#define AVC_CACHE_SLOTS 512void __init avc_init(void){ int i; // 初始化每一个缓存槽 for (i = 0; i < AVC_CACHE_SLOTS; i++) { INIT_LIST_HEAD(&avc_cache.slots[i]); spin_lock_init(&avc_cache.slots_lock[i]); // 写时加锁 } 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”);}
该函数顶用到了一个名为avc_cache的结构,在同一文件中界说:
struct avc_cache { struct list_head slots[AVC_CACHE_SLOTS]; spinlock_t slots_lock[AVC_CACHE_SLOTS]; // 用以在写入时加锁 atomic_t lru_hint; // 在reclaim查找时的LRU提示 atomic_t active_nodes; u32 latest_notif; // 最近吊销提示};
然后,selinux_init函数将会调用register_security函数向LSM注册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就完结了初始化进程的第一步,而其个模块的初始化函数均被界说成一般的初始化调用,在Linux内核的后续初始化进程中进一步完结。
2> 初始化第二步:文件体系的挂载
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进程将载入SE Linux的初始战略库,并调用sel_load_ops->sel_write_load->sel_make_policycap->security_load_policy->selinux_complete_init函数来完结整个初始化进程。