LSM是Linux Security Module的简写,是一种安全框架,它定义了很多钩子函数,安插在内核的关键路径上,当执行到这些关键路径时,调用安插的钩子进行安全检查。每个钩子上安装一个函数指针链,当调用钩子时,依次执行挂载在钩子上的函数,为linux实现的不同的安全模块实际上就是在这些钩子上安装函数。
由于钩子时事先定义好的,并且已经安插到了内核的关键路径上,所以开发一个新的安全模块并不会修改内核的其他的部分,由于一个钩子上可以挂在多个函数指针,所以不能的安全模块也是可以共存的,结果就是执行钩子函数时,依次调用安装到钩子上的安全模块函数进行检查。
LSM定义的钩子有:
//所有的钩子都定义在security/security.c文件中
int security_file_open(struct file *file, const struct cred *cred)
{
int ret;
ret = call_int_hook(file_open, 0, file, cred);
if (ret)
return ret;
return fsnotify_perm(file, MAY_OPEN);
}
int security_task_create(unsigned long clone_flags)
{
return call_int_hook(task_create, 0, clone_flags);
}
void security_task_free(struct task_struct *task)
{
call_void_hook(task_free, task);
}
在内核其他模块需要安全检查的地方调用这些钩子函数进行检查:
static int do_dentry_open(struct file *f,
struct inode *inode,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
...
error = security_file_open(f, cred);
if (error)
goto cleanup_all;
...
}
下面具体来分析钩子函数:
int security_task_create(unsigned long clone_flags)
{
return call_int_hook(task_create, 0, clone_flags);
}
// 看看call_int_hook的定义
#define call_int_hook(FUNC, IRC, ...) ({ \
int RC = IRC; \
do { \
struct security_hook_list *P; \
\
list_for_each_entry(P, &security_hook_heads.FUNC, list) { \
RC = P->hook.FUNC(__VA_ARGS__); \
if (RC != 0) \
break; \
} \
} while (0); \
RC; \
})
从security_hook_heads.FUNC 头中指向的链表依次取出执行,那么这些链表是怎么生成的呢,可以通过阅读loadpin和yama这两个安全模块来了解,这两个模块非常简单,只有一个文件:
//loadpin/loadpin.c
static struct security_hook_list loadpin_hooks[] = {
LSM_HOOK_INIT(sb_free_security, loadpin_sb_free_security),
LSM_HOOK_INIT(kernel_read_file, loadpin_read_file),
};
void __init loadpin_add_hooks(void)
{
pr_info("ready to pin (currently %sabled)", enabled ? "en" : "dis");
security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks));
}
static void loadpin_sb_free_security(struct super_block *mnt_sb)
{
...
}
static int loadpin_read_file(struct file *file, enum kernel_read_file_id id)
{
...
}
//include/linux/lsm_hooks.h
#define LSM_HOOK_INIT(HEAD, HOOK) \
{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
struct security_hook_list {
struct list_head list;
struct list_head *head;
union security_list_options hook;
};
struct security_hook_heads security_hook_heads = {
...
.sb_free_security =
LIST_HEAD_INIT(security_hook_heads.sb_free_security),
.kernel_read_file =
LIST_HEAD_INIT(security_hook_heads.kernel_read_file),
...
}
struct security_hook_heads {
...
struct list_head sb_free_security;
struct list_head kernel_read_file;
...
}
static inline void security_add_hooks(struct security_hook_list *hooks,
int count)
{
int i;
for (i = 0; i < count; i++)
list_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
以上关于链表的操作我就不过多讲解了,自己安全数据结构的定义画个图就一目了然了。
通过上面的分析可以知道,不同的安全模块理论上来说是可以共存的,即不同的安全模块的函数钩子可以挂在同一个security_hook_heads.head, 这样不同的安全模块将被依次调用。
但是实际上并不是这样的,有些安全模块之间做了一些互斥选择:
static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
CONFIG_DEFAULT_SECURITY;
//通过配置可默认选择一个
//也可以通过内核的cmdline指定这次使用哪个
static int __init choose_lsm(char *str)
{
strncpy(chosen_lsm, str, SECURITY_NAME_MAX);
return 1;
}
__setup("security=", choose_lsm);
int __init security_module_enable(const char *module)
{
return !strcmp(module, chosen_lsm);
}
安全模块在加载时会检查:
./…/security/smack/smack_lsm.c:4791: if (!security_module_enable(“smack”))
./…/security/tomoyo/tomoyo.c:542: if (!security_module_enable(“tomoyo”))
./…/security/apparmor/lsm.c:871: if (!apparmor_enabled || !security_module_enable(“apparmor”)) {
./…/security/selinux/hooks.c:6460: if (!security_module_enable(“selinux”)) {
因此smack,tomoyo, apparmor,selinux只能同时使用一个,因为它们都是实现了MAC,只是实现的方法不一样罢了,因此没有必要共存,它们和其他的安全模块之间可以共存。