Smack(Simplified Mandatory Access Control Kernel)是Casey Schaufler[15]于2007年在LSM基础上实现的Linux强制访问控制安全模块,它以内核安全补丁的形式存在于Linux操作系统中,其设计思想是利用LSM安全域将Linux内核中所有主体与客体都打上安全标签,并规定安全策略,只有符合安全策略的访问方式才被容许。与SELinux和DTE相比,Smack安全策略要简单得多,但却能实现它们相似的强制访问控制功能,并且Smack对内核性能损耗比较低,因此,Smack也逐渐被业界看好和推广。
2.4.1 Smack基本概念
1. 主体
Smack主体是指Linux内核进程。
2. 客体
Smack客体是指Linux内核客体对象,如文件、消息队列、套接字、共享内存、信号量等,客体也可以是Linux进程或者IPC。
3. 安全标签
Smack安全标签是C语言的字符串,但最多包含24个字符(包括‘/0’),Smack修改了进程task_struct安全域,在进程被初始创建时,其安全标签是“_”。同样,Smack修改了虚拟文件系统的inode和super_block安全域,使得文件系统被创建时所有文件的安全标签是“_”。
4. 访问方式
Smack最初版本的访问方式只有四种,即读(r或R)、写(w或W)、执行(x或X)、盲写(a或A)。其中,在进程之间通信中,一个进程发送消息或者数据包给另一个进程时,这样的操作属于写操作。
5. 安全策略
Smack的安全策略分为Smack内置的安全策略和用户可定制的安全策略。Smack内置的安全策略是指原本就已经被Smack访问控制代码所规定的,包括如下几条:
(1)安全标签是“*”的进程发起的任何形式的访问都被拒绝;
(2)安全标签是“^”的进程发起的读或执行的请求都被容许;
(3)任何进程对安全标签是“_”的客体发起的读或执行的请求都被容许;
(4)任何进程对安全标签是“*”的客体发起的任何形式的请求都被容许;
(5)如果主体和客体的安全标签相同,那么该主体对该客体发起的任何形式的访问都被容许。
除此之外,用户可以根据自己的安全需求,在主、客体安全标签都已经存在的前提下,通过“smackload”工具写入安全策略,例如:
(1) abc xyz rwxarW
(2) abc xyz rwr
(3) abc xyz _
smackload工具会自动配置访问方式的格式,(1)表示主体abc对客体xyz有读、写、执行、盲写的权限,(2)表示主体abc对客体xyz有读、写权限,(3)表示主体abc对客体xyz没有任何权限。当用户连续写入以上三条规则后,Smack会将相同主、客体的规则进行覆盖,最终,abc对xyz没有任何权限。
2.4.2 Smack源码分析
2007版本的smack源码[16]并不庞大,它被组织成了smack.h、smack_access.c、smack_lsm.c和smackfs.c四个源文件。下面本小节就针对这四个文件来分析Smack的实现原理。
1. Smack的结构体
由以上分析可以知道LSM的安全域是“void *security”,它可以指向任意类型的指针,正因为此,Smack定义了几个重要的结构体,使得内核对象的安全域指向它们,下表总结了LSM安全域和Smack结构体之间的对应关系:
内核对象 |
内核数据结构 |
安全域 |
Smack结构体 |
文件系统 |
super_block |
void *security |
superblock_smack |
管道、文件等 |
inode |
void *security |
inode_smack |
进程 |
task_struct |
void *security |
task_smack |
网络套接字 |
sock |
void *sk_security |
sockect_smack |
除此之外,结构体smack_rule表示访问规则,它以内核链表的形式存在于内存中,用户通过“smackload”工具每写入一个规则,这个规则就被插入到Smack规则链表中,规则结构体只会被插入或者替换,而不会被删除。结构体smack_known包含了smack安全标签以及与之对应的smack安全级别和cipso结构体。smack_cipso包含了cipso的安全级别和安全分类,smk_netlbladdr包含了主机IP地址以及与之对应的Smack安全标签。
2. Smack访问控制函数
Smack访问控制函数smk_access定义如下所示:
int smk_access(char *subject_label, char*object_label, int request);
smk_access首先根据Smack默认的五条访问规则进行判断,然后在Smack内核规则链表中去查找和参数主客体标签一致的结点,判断此结点的权限是否包含请求的权限request。访问控制函数smk_curacc用于判断当前进程对目标客体是否有访问权限,smk_curacc_on_task用来判断当前进程对目标进程是否有访问权限,它们最终是调用smk_access来进行访问控制。需要指出的是,smk_curacc可以被特权进程绕过,正因为此,Smack不能阻止超级用户或特权进程的一切行为。
3.Smack的LSM内核
Smack是在LSM的基础上实现钩子函数以达到强制访问控制的目的,下表总结了Smack访问控制对象及其相应的钩子函数:
主体 |
客体 |
控制行为 |
钩子函数 |
要求权限 |
当前进程 |
目标进程 |
setpgid |
smack_task_setpgid |
写 |
当前进程 |
目标进程 |
getpgid |
smack_task_getpgid |
读 |
当前进程 |
目标进程 |
getsid |
smack_task_getsid |
读 |
当前进程 |
目标进程 |
setnice |
smack_task_setnice |
写 |
当前进程 |
目标进程 |
setioprio |
smack_task_setioprio |
写 |
当前进程 |
目标进程 |
getioprio |
smack_task_getioprio |
读 |
当前进程 |
目标进程 |
setscheduler |
smack_task_setscheduler |
写 |
当前进程 |
目标进程 |
getscheduler |
smack_task_getscheduler |
读 |
当前进程 |
目标进程 |
movememory |
smack_task_movememory |
写 |
当前进程 |
目标进程 |
kill |
smack_task_kill |
写 |
当前进程 |
目标进程 |
wait |
smack_task_wait |
写 |
主体 |
客体 |
控制行为 |
钩子函数 |
要求权限 |
当前进程 |
目标文件 |
ioctl |
smack_file_ioctl |
根据请求方式 |
当前进程 |
目标文件 |
lock |
smack_file_lock |
写 |
当前进程 |
目标文件 |
fcntl |
smack_file_fcntl |
根据请求方式 |
当前文件 |
目标进程 |
send_sigiotask |
smack_file_send_sigiotask |
写 |
当前进程 |
目标文件 |
file_receive |
smack_file_receive |
根据请求方式 |
源套接字 |
目标套接字 |
netlabel_send |
smack_netlabel_send |
写 |
源套接字 |
目标套接字 |
socket_connect |
smack_socket_connect |
写 |
当前进程 |
共享内存 |
shm_associate |
smack_shm_associate |
根据请求方式 |
当前进程 |
共享内存 |
shm_shmctl |
smack_shm_shmctl |
根据请求方式 |
当前进程 |
共享内存 |
shm_shmat |
smack_shm_shmat |
根据请求方式 |
当前进程 |
信号量 |
sem_associate |
smack_sem_associate |
根据请求方式 |
当前进程 |
信号量 |
sem_semctl |
smack_sem_semctl |
根据请求方式 |
当前进程 |
信号量 |
sem_semop |
smack_sem_semop |
读和写 |
当前进程 |
消息队列 |
msgctl |
smack_msg_queue_msgctl |
根据请求方式 |
当前进程 |
消息队列 |
msgsnd |
smack_msg_queue_msgsnd |
根据请求方式 |
当前进程 |
IPC |
ipc_permission |
smack_ipc_permission |
根据请求方式 |
源套接字 |
目标套接字 |
unix_stream_connect |
smack_unix_stream_connect |
读和写 |
源套接字 |
目标套接字 |
unix_may_send |
smack_unix_may_send |
写 |
源套接字 |
目标套接字 |
socket_sendmsg |
smack_socket_sendmsg |
写 |
目标套接字 |
源套接字 |
sock_rcv_skb |
smack_socket_sock_rcv_skb |
写 |
正如上表所示,Smack是在LSM基础上实现了对进程、文件、套接字、共享内存、消息队列、信号量等的控制。Smack的访问控制非常简单,就是检查主体对客体有没有Smack访问方式,除此之外,Smack使用了钩子函数为进程和文件设置安全标签。当把Smack编译进Linux内核后,Linux操作系统上所有文件和进程默认的安全标签都是“_”。只有特权进程才能改变自身的安全标签,只有特权进程才能设置文件的安全标签。一个进程无法改变另一个进程的安全标签。当父进程创建子进程时,子进程的安全标签就是父进程的安全标签。当一个进程创建文件时,这些文件的安全标签就是进程的安全标签。当一个进程创建套接字时,套接字的安全标签就是该进程的安全标签。当两个进程要通过网络进行通信时,发送和接收IP包的进程必须对彼此都有写权限。
4. Smack的虚拟文件系统
smack_access.c和smack_lsm.c构成了Smack强制访问控制机制,但强制访问控制机制是需要安全策略数据库的支持,为此,Smack采取了虚拟文件系统作为其安全策略存储系统,这样Smack决策时间会很短,但这也就意味着,一旦计算机系统重新启动,需求人为得再次设定安全策略。Smack文件系统位于“/smack”目录下,包含了load、cipso、doi、direct、ambient、netlabel、onlycap和logging这几个虚拟文件,它们被组织成Linux内核链表形式,其中最常用到的是load、cipso和netlabel。load文件存放了Smack的安全策略,cipso存放cipso值,包括安全级别和安全分类,netlabel存放了主机的IP地址和其相关的Smack标签,cipso和netlabel用于Smack网络通信控制。Smack重写文件系统操作函数,并使用注册文件系统、挂载文件系统和初始化系统调用等内核API来实现虚拟文件系统。
2.4.3 Android内核的Smack编译
Smack内核代码除了上面介绍的四个文件外,还有makefile和Kconfig两个文件。makefile文件设定了Smack内核编译规则,而Kconfig文件指定了Smack的配置信息,其中就有一项是“bool simplified mandatoryaccess control kernel depend on NETLABEL & SECURITY_NETWORK”,从这条信息中可以看出,要想编译Smack,Android内核必须配置“NETLABEL”和“SECURITY_NETWORK”两项。
本节以panda内核为例演示编译Smack内核的过程,首先从Google官网或pandaboard官网上下载panda内核,解压后会有panda/kernel-ics-chipsee-panda文件夹。
1. 打开“shell”,输入指令“cdpanda/kernel-ics-chipsee-panda,cd arch/arm/configs”,找到目录下的缺省配置文件panda_chinese_def;
2. 在终端上输入指令“vim panda_chinese_def”,打开此配置文件,经过查找,可以发现这个文件里没有配置“NETLABEL”和“SECURITY_NETWORK”两项,这也是Smack没有被编译进panda内核的原因;
3. 用vim添加配置信息,如下所示:
4. 保存退出后,输入如下指令:
以上指令也同样适用于模拟器内核编译,需要强调的是,在编译Smack之前,要确保panda内核或模拟器内核已经配置了ext2或ext4或者支持扩展属性的yaffs2文件系统,否则Smack无法正常运行。为了验证Smack确实在Android系统上发挥了效果,本小节给出一个实验进行验证。
1. 实验环境:
软件环境为Ubuntu10.04,编译好的Android4.0源码和goldfish内核。
2. 实验过程:
(1)编写getsmk.c源码,getsmk.c输入是命令行参数,输出是文件的安全标签;
(2)在Android源码目录下新建getsmk文件夹,并将getsmk.c拷贝到给文件夹下,编写Android.mk文件,让getsmk能够被交叉编译;
(3)使用如下指令来启动模拟器:
“emulator –sdcard/host/android4.0/sdcard.img –kernel/host/android4.0/kernel/goldfish/arch/arm/boot/zImage”
(4)等待模拟器运行正常后,打开终端shell,使用“adb push”指令将可执行文件getsmk拷贝到“/data”目录下,随机选择文件,进行测试,测试结果如下:
3. 实验结论:
Smack会在系统初始化时使用钩子函数smack_d_instantiate将Linux系统所有文件和进程的安全标签都设置为“_”,因此,通过以上实验,smack确实在Android系统上生效了。