开源中文网

您的位置: 首页 > FreeBSD > 正文

详细介绍BSD系统的内核安全和级别

来源:  作者:

bsd一直是公认的最安全的unix操作系统,因为它提供了基于内核的安全保护而不仅仅是老式unix的基于访问控制的安全保护。bsd为内核划分了安全等级,这样就可以限制很多不安全的操作,而且bsd在安全设置上是单点的,使得不可能轻易改变安全级别。linux提供的lsm“可加载安全模块”可以从外部加载安全模块,而bsd将此行为也作为可能的安全隐患,所以bsd中由内核直接来负责安全,如果内核认为加载的安全模块是不可信的,那么内核将禁止加载模块,这在bsd内核中通过安全级别(securelevel)来实现。下面先引用一段介绍安全级别的文字然后我分析bsd的init程序中与安全级别相关的代码: 
FreeBSD内核有一个安全级别(securelevel)的概念,这是指系统内核运行使用的安全等级,不同的等级具备不同的保护和检查机制。因为这是内核的检查机制,因此相当严格,没有办法能绕过这个机制提供的保护,因此就对保护FreeBSD的安全性十分有用。内核的安全级别按照提供安全保护的程度分为-1、0、1、2共分为四个级别,安全级别能提供的保护有: 
系统文件:系统文件可以设置保护标志"不可更改"和 "只能附加" ,具有这些保护标志的文件在系统的文件属性之外,还受这些保护标志的保护。安全级别可以规定这些标志能否取消。 
磁盘设备文件:磁盘设备文件具备两种访问方式,随机访问的方式对应的块设备文件和顺序访问方式对应的字符设备文件,其中字符设备文件可以直接读取硬件设备,因此对于安全至关重要。内核安全级别可以决定是否允许以直接读取硬件的方式操作硬盘设备文件。 
直接内存访问:/dev/mem和/dev/kmem是系统内存的映射文件,访问它们就能直接访问系统内存,一些需要获取系统信息和需要进程间共享内存机制的程序需要访问这两个设备文件以直接访问内存,然而访问内存空间显然也影响系统的安全运行。内核安全级别可以决定是否允许访问系统内存。 
安全级别-1为一种永久性的不安全级别,系统内核不提供任何额外的保护。系统缺省就处于这个级别,此时系统文件的保护标志能被root用户取消,所有的设备,包括磁盘设备和内存映射设备,均能按照其属性来访问。 
安全级别0为不安全的级别,它和等级-1一样没有对系统提供额外的安全保护,但它影响到内核进程init的行为。当内核处于级别-1时,内核init程序不会自动更改运行级别,因此一直到进入能够登录的状态,系统安全级别仍然为-1。这是系统的缺省行为,没有打开安全级别保护机制。但如果安全级别不为 -1,init在进入单用户状态时将改变为0级别,在进入多用户模式时改变为安全级别 1。因此安全级别0为设置了安全级别保护之后,单用户状态下的安全级别。 
安全级别1为安全的级别,提供了对系统的保护能力。此时系统文件的那两个保护标志不能被取消,已安装文件系统对应的磁盘设备,以及/dev/mem,/dev/kmem不可以用写入模式打开。 
安全级别2与级别1类似,只是进一步增加了对磁盘设备低级操作的限制,不管该磁盘设备是否安装,都不允许直接以写入方式访问,这样就无法进行fdisk、disklabel以及newfs等操作。 
可以使用sysctl来查看当前系统的安全级别,但如果没有经过特别设置,FreeBSD的缺省安全级别应该为-1: 
bash-2.03# sysctl kern.securelevel 
kern.securelevel: -1 
安全级别中最重要的一点是,除了内核的init进程之外,即使是root用户,也只能不断提高安全级别,没有办法将安全级别降低。这样就基本上保证远程入侵者在没有重新启动计算机的情况下,无法降低系统运行级别。如果root 想提高系统运行的安全级别,也需要使用sysctl命令。 
bash-2.03# sysctl -w kern.securelevel=0 
kern.securelevel: -1 -> 0 
安全级别的意义就在于对文件和设备的保护,如果要对文件提供保护,就需要对文件设置保护标志schg。设置这个标志需要使用chflags命令,系统文件如 /kernel,系统安全的时候就具备这个保护标志。即使在非安全级别下要更改这些文件的时候,也要首先取消保护标志才能进行正常操作。 
bash-2.03# mv /kernel /kernel.bak 
mv: rename /kernel to /kernel.bak: Operation not permitted 
bash-2.03# chflags noschg /kernel 
bash-2.03# mv /kernel /kernel.bak 
bash-2.03# mv /kernel.bak /kernel 
bash-2.03# chflags schg /kernel 
上面操作先取消了kernel文件的不可更改标志schg,显然这是在非安全级别下的操作。当安全级别处于 1或2时,就不能使用chflags改变文件的保护标志了。 
bash-2.03# chflags noschg /kernel 
chflags: /kernel: Operation not permitted 
可以使用带-o参数的ls来查看文件具备的标志。 
bash-2.03# ls -lo /kernel 
-r-xr-xr-x 1 root wheel schg 1061679 Jun 30 01:27 /kernel 
因此可以将系统安全相关的很多程序都设置保护标志,这样入侵者就不能轻易更改这些文件了。建议将/bin,/sbin下的文件都设置这个标志。 
bash-2.03# chflags schg /bin 
bash-2.03# chflags schg /bin/* 
bash-2.03# chflags schg /sbin 
bash-2.03# chflags schg /sbin/* 
这里首先将相关目录本身设置保护标志,这样入侵者就不能通过将目录更改名字的方法创造一个新的/sbin或 /bin目录。 
当文件具备了保护标志,并且安全级别高于1时,保护标志就无法取消,这些文件就不能更改,因此就带来一些必要的操作无法进行,例如重新生成内核的操作等。此时就必须再重新启动系统进入单用户状态执行这些操作。通常情况下,如果FreeBSD系统只提供网络服务,那么使用安全级别1或2毫无问题。然而如果要运行X Server ,由于X Server使用了共享内存机制,需要访问/dev/mem和/dev/kmem,这样就会带来问题。这时的一种解决办法是在启动X Server之后(例如使用Xdm),再升高安全级别,以避开这个问题,但此时 X Server已经打开了/dev/mem和/dev/kmem,安全级别的保护就不再是完美无缺的了(dog250:攻击者可以利用X的漏洞实施攻击)。如果不使用X Server之类的程序,那么就可以将设置安全级别的命令直接放入系统的启动rc文件中,以便自动提高安全级别。这需要在rc.conf文件中设置两个变量:kern_securelevel_enable和kern_securelevel。 
kern_securelevel_enable=”YES” 
kern_securelevel=0 
上面将安全级别设置为0,那么在启动之后将自动更改为1,这是一种标准的做法。 
以上就是关于bsd安全级别的标准描述,那么有句话说只有init进程可以调整安全级别是什么意思呢?还得通过bsd的init源代码来理解,于是翻出bsd的init.c文件(前面的文章分析过),里面有一个函数:setsecuritylevel 


以下为引用的内容: 

void setsecuritylevel(int newlevel) 

#ifdef KERN_SECURELVL //在定义安全级别的情况下使能此函数 
int name[2], curlevel; 
extern int errno; 
curlevel = getsecuritylevel(); //得到当前的安全级别 
if (newlevel == curlevel) //已经是当前的级别了,直接返回 
return; 
name[0] = CTL_KERN; 
name[1] = KERN_SECURELVL; 
if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) { //一切ok,无条件设置 
emergency( "cannot change kernel security level from %d to %d: %s", curlevel, newlevel, strerror(errno)); 
return; 

#ifdef SECURE 
warning("kernel security level changed from %d to %d", curlevel, newlevel); 
#endif 
#endif 



下面是getsecuritylevel: 

以下为引用的内容: 

int getsecuritylevel(void) 

#ifdef KERN_SECURELVL 
int name[2], curlevel; 
size_t len; 
name[0] = CTL_KERN; 
name[1] = KERN_SECURELVL; 
len = sizeof curlevel; 
if (sysctl(name, 2, &curlevel, &len, NULL, 0) == -1) { 
emergency("cannot get kernel security level: %s", strerror(errno)); 
return (-1); 

return (curlevel); 
#else 
return (-1); 
#endif 



安全性体现在哪里呢?前面的设置函数可是无条件设置的啊,如果我在我自己的程序里面也来一个sysctl调用,岂不是也成功了,实际上,sysctl是个系统调用,bsd的实现是在该系统调用的实现里面如果是设置安全级别那么就检查进程的pid,如果pid不是1还想降低安全级别,那么很抱歉,出错返回。见FreeBSD的代码:src/sys/kern/kern_mib.c 
以下为引用的内容: 

mtx_lock(&securelevel_mtx); 
if (!regression_securelevel_nonmonotonic && 
(level < securelevel) && //新安全级别小于原安全级别 
(req->td->td_proc->p_pid != 1)) { //设置安全级别的进程不是init进程 
mtx_unlock(&securelevel_mtx); 
return (EPERM); //则出错返回 

bsd的init不允许重新加载也是通过pid来判断的,在main函数里面的第二个大句: 
以下为引用的内容: 

if (getpid() != 1) { //这就禁止了init的重新加载,以不给别的存在不轨意图的人任何机会 
(void)fprintf(stderr, "init: already running\n"); 
exit (1); 


说bsd的安全设置是单点的就是上面这个意思,这样不至于最后混乱,那么init在什么时候调用setsecuritylevel了呢?通查代码发现在两个地方调用了setsecuritylevel,一个就是multi_user,另一个是single_user。(如果不明白这两个函数是干甚的,请查阅我前面的文章《System V的启动风格和BSD的启动风格(2)---代码角度》)就是进入多用户模式和进入单用户模式的时候进行调用。 
以下为引用的内容: 

state_func_t multi_user(void) 

...//前面没有什么语句,仅仅是一些变量的定义。 
if (getsecuritylevel() == 0) //如果当前级别为0,那么就调整为1 
setsecuritylevel(1); 
... 

state_func_t single_user(void) 

...//前面没有什么语句,仅仅是一些变量的定义。 
if (getsecuritylevel() > 0) //这个判断说明内核处于安全保护之下,那么进入单用户后将安全级别降为0,否则单用户就什么也做不了 
setsecuritylevel(0); 
... 


由上面的代码可见,安全级别为0就是一个占位符的作用,提示init一会一旦进入多用户就将安全级别升为1,除此之外它和安全级别-1没有任何区别。深入到了init内部,是不是对安全级别的控制有了一些新的认识呢?init进程并没有什么神力,只不过是内核将这个单点控制权给了1号进程而已。 

Tags:详细介绍 系统 内核
关于开源中文网 - 联系我们 - 广告服务 - 网站地图 - 版权声明