基本原理
LSM的产生是具有其历史原因的。随着分布式信息系统的发展和应用业务的日益复杂,信息系统的安全需求也趋于多样化。现有的安全策略往往是针对特定的安全需求而设计的,因此单一的安全策略越来越难以满足信息系统安全需求。在这种情况下,允许各种安全策略同时存在于系统内并发挥安全作用的安全框架成为研究重点。GFAC、Flask等安全框架由于出现较早,且有专门的研究机构对其进行研究,目前已有如RSBAC、SELinux等较为成熟的应用。但由于二者对内核影响相对较大,且未形成统一标准,因此并未融入主流操作系统1。
2001年,在Linux内核2.5高峰会议上,Linux创始人Linus Torvalds提出Linux内核需要自己的通用访问框架,并建议以内核模块的方式来实施各种安全策略。响应Linus号召Chris Wright等人完成了LSM的设计开发工作,并与2002年4月28日以程序补丁的形式对外公布。2003年,LSM正式成为内核组成部分,随内核2.6发行。LSM作为轻量级、通用的访问控制框架,具有以下特点:
真正通用,使得采用不同的安全模型仅仅是加载不同的内核模块;
概念简单、高效,而且对内核冲击较小;
支持现有的POSIX.1e的capabilities逻辑,将其作为一种可选安全模块。
作为通用访问控制框架,LSM本身并不提供任何的安全策略,而是为各种安全策略提供了通用的框架。安全策略以安全模块的形式实现,并加通过LSM加载进入系统,为系统提供安全保障。为此保证此目的,LSM对系统内核做了如下修改:
为内核关键数据结构增加安全域字段,用以关联安全信息;
在关键系统调用之前插入hook函数,对访问进行判断;
提供模块注册与卸载函数,用于安全模块的加载与卸载;
提供通用的安全系统调用接口,用以安全系统调用的扩展;
部分分离capabilities机制,将其作为独立的安全模块。
安全模块通过模块注册函数加载进入LSM,对内核关键资源设置安全信息,并将自己的安全策略函数与LSM的hook函数进行关联。用户进程提出系统资源访问访问请求,转入内核空间,并做传统的Linux系统DAC(Discretionary Access Control)判断。之后,LSM调用hook函数,该hook函数以指针的形式与特定安全模块的安全策略函数关联,用以判断对该对象的访问是否符合安全策略,从而进行访问控制。LSM基本原理如图所示。
实现机制安全域LSM在内核关键数据结构添加了安全域字段。该字段由具体的安全模块设置管理,存储着内核关键数据结构的安全信息。安全信息是系统资源的标识,是多数访问控制策略实现其安全机制的重要信息。不同的安全策略,所针对的安全问题不同,其关注的客体安全信息也不同。因此,LSM没有在内核中添加特定的安全信息,而是为内核添加了透明安全域(Opaque Security Fields),以保证其通用性。
安全域字段是一个空类型的指针,特定安全模块加载后,该指针所指向的数据空间存储着内核数据结构的安全信息。该安全信息是由具体的安全模块设置和管理的,不受LSM框架的影响。如此,便可以保证LSM对多种安全模块的通用性。但是,安全域字段无法同时指向两个及两个以上安全模块对内核设置的安全信息,这也正是本课题拟解决的问题之一。
LSM为安全模块提供了设置和管理内核数据结构安全域字段的hook函数接口。安全模块只需要实现这些函数,便可以灵活对内核数据结构的安全信息进行设置和管理。例如,LSM提供了alloc_security和frce_security函数用于申请和释放安全信息空间,提供了post_lookup函数用于对inode查询之后修改安全信息。
以SELinux的file结构为例,LSM为内核数据结构file增添了透明的安全域字段,该字段指向SELinux对file结构设置的安全信息。修改后的file结构如下:
struct file{…… void* f-security;/*空类型指针f-security*/ ……};SELinux对file结构设置的安全信息如下:
struct file_security_struct{ u32 sid; u32 fown—sid; u32 isid; u32 pseqno;};SELinux设置file结构的安全信息,并file安全域关联:
static int filc_alloc_security(struct file *file){…… struct file_security_struct *fsec; fsec=kzalloc(sizeof(struct1File_security_struct),GFP_KERNEL); if(!fSec) return -NNOMEM; file->f_security=fSec;/*将指针f-security与安全数据fcsc关联*/……}SELinux撤销file结构的安全信息,一般发生在SELinux模块从LSM卸载时:
static void file_free_security(struct file *file){ struct file_security_struct *fsec=file->f_security; file一>f_security=NULL; kfree(fSec);}可见,对内核安全域字段的定义及管理都是由具体的安全模块实现,这便保证了LSM框架的高通用性。LSM为内核的9个关键数据结添加了安全域字段,如表所示。
hook函数调用LSM在执行对内核关键资源访问之前,插入对hook函数的调用。这些hook函数从功能上讲,大致可分成两类:
用于管理内核对象安全域的hook函数
用以执行访问控制的hook函数。
前文所提到的alloc_security,free_security和post_lookup函数都属于第一类hook函数。而如控制创建和删除目录的inode_mkdir和inode_rmdir等hook函数则属于第二类。所有的hook函数都是由具体的安全模块根据其自身的安全策略实现的。安全模块加载后,LSM框架的钩子函数表security_ops将与安全模块实现的hook函数关联。在执行对系统关键资源访问之前,LSM将通过security_ops调用安全模块的hook函数对。访问行为进行安全控制,此时调用的hook函数一般属于第二类。第二类hook函数的返回值一般为整数,若为0,则表示安全模块允许对内核对象的访问;否则,表示拒绝访问。
security_ops是struct security_operations类型的结构体,该结构内的函数指针指向安全模块的hook函数。LSM通过secu“tyops.>functionname来实现对安全模块hook函数的调用。LSM在内核中插入了160个hook函数。根据所针对的内核对象不同,这些h00k函数可以分为七类:进程、程序加载、IPC、文件及文件系统、网络、Linux动态加载模块及一个系统hook函数类。这些hook函数通过security-ops结构组织起来并调用。security-ops的结构如下:
struct security_operations{ ……/*其它hook函数*/ int(*inode_create)(struct inode*dir,struct dentry*dentry,int mode);/*检验创建普通文件权限*/ int(*task_create)(unsigned long clone_flags);/*检验创建子进程权限*/ int(*socket_create)(int family'int type,int protocol,int kern);/*检验创建新socket权限*/ int(*ipc_permission)(struct kern_ipc_pem*ipcp,short flag);/*检验访问IPC权限*/ };安全模块只需要对security-ops所引用的hook函数进行实现,便可以发挥访问控制功能。所针对的安全问题不同,不同的安全模块实现的hook函数的种类、数量和方式也可以不同。SELinux安全模块对几乎全部的hook函数做了实现,而LSM自带的默认的dummy安全模块对任何的hook函数只是简单的返回零值,不做任何安全检测。
综上,LSM框架可以通过hook函数调用安全模块对系统访问实施访问控制,同时,为安全模块提供了统一的函数接口,使安全模块的设计更加灵活和独立。
安全模块加载与卸载安全模块需要先向LSM注册,通过后加载进入系统,其实现的钩子函数才能够被LSM调用,发挥安全作用。在系统内核引导时,LSM默认加载dummy安全模块。Dummy安全模块对全部的钩子函数不做任何实现,仅仅是返回允许系统访问的零值。
除dummy之外的安全模块加载卸载时,需要调用模块加载与卸载函数。LSM提供了两对函数以完成安全模块的加载和卸载:
int register_security(struct security_operations *security_ops)int unregister_security(struct security_operations *security_ops)int mod_reg_security(const char *name,struct security_operations *ops)int mod_unreg_security(const char *name,struct security_operations *ops)其中,register_security/unregister_security用于主安全模块向LSM框架的加载与卸载,mod_reg_security/ mod_unreg_security用于其它安全模块向主安全模块注册。主安全模块是指除dummy模块之外首先加载进入LSM的安全模块,在主安全模块之后加载的安全模块都属于从安全模块。从安全模块无法直接向LSM注册,而需要通过向主安全模块注册,间接加载进入LSM,并且受主安全模块安全策略的管理。通过这种多安全模块栈式堆叠的机制,LSM提供了对多安全模块支持。但是,堆叠进入LSM的从安全模块并不能独立的发挥安全作用:从安全模块何时被调用、如何被调用,返回结果的决策,都受主安全模块的管理。如果主安全模块为提供对后继模块的加载管理的支持,LSM的栈式堆叠机制也就无法再起作用。这种机制实质上是LSM框架在多安全模块管理和决策结果处理方面对主安全模块的依赖。
以SELinux安全模块为例,模块在初始化的过程中,调用LSM的register_security函数,并将LSM的security_ops表指向自身的钩子函数表。关键代码如下
static _init int selinux_init(void){ …… if(registcr_security(&selinux_ops);/*调用register_security函数*/ ……}其中,selinux_ops为Selinux模块实现的hook函数表,其定义为:
extern struct security_operations *security_ops;
register_security函数由LSM提供,其过程为:
int _init register_security(struct_security_operations *ops){ …… if(security_ops!=&default_security_ops)/*判断是否有已加载的主安全模块*/ return -EAGAIN; security_ops=ops; }模块加载过程中,若检测到已有主安全模块加载到LSM,则模块只能通过IIlod—reg—security函数向从安全模块注册。模块卸载时,将LSM的security-ops设置为默认的dummy模块的h00k函数表。mod—reg—security/moo—unreg—security函数由特定的主安全模块提供。由上述代码可见,安全模块加载的过程,实质上就是将LSM的hook函数表与安全模块实现的hook函数表相关联的过程。
安全系统调用LSM在内核中添加了一个通用的安全系统调用,以提供对安全模块安全应用的支持。该调用的接口允许安全模块实施自己的系统调用,对已有的系统调用进行扩展,以保证安全策略的实施。LSM在内核中对安全模块提供的通用接口是sys_security,参数为unsigned int id,unsigned int call,unsigned long *args。一般说来,三个参数分别代表安全模块标识符,系统调用标识符以及调用的参数,但是安全模块可以根据其安全策略和函数的实现,对三个参数进行不同的解释。安全模块可以根据自身安全策略的需要选择对安全系统调用进行实现或者不实现,若不需要对此调用进行实现,仅需要令函数syssecurity返回.NOSYS值。
Capabilities机制Capabilities机制的基本思想是分割超级用户权限,防止超级权限的误用和滥用,以实现最小特权原则。LSM的设计目标之一是把传统的分散在内核多处的capabilities机制分离出内核‘,形成一个独立的capabilities安全模块。Capabilities机制的分离,具有两方面的优势:首先,用户对capabilities机制的使用更加灵活,不需要该机制的用户只需将其从LSM卸载;其次,capabilities机制的分离,使得对capabilities的后继研究对Linux内核影响变得很小。
目前,LSM框架已把大部分的capabilities机制独立出内核,但仍有部分遗留在内核中。例如,标识进程capability使用一个位于taskstruct中的位向量表示,该位向量可以移到taskstruct的安全域中,但由于模块堆叠机制下的安全域共享问题,该位向量及相关逻辑仍保留在内核中。