LSM初始化
LSM系统初始化发生在内核初始化阶段,内核的启动函数为init/main.c::start_kernel()
:
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
...
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
...
rest_init();
}
该函数调用了security/security.c::security_init()
,用来初始化LSM框架
/**
* 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;
}
全局变量security_ops的初始化
函数的前部分利用default_security_ops来初始化security_ops结构体,两个结构体的定义为:
static struct security_operations *security_ops;
static struct security_operations default_security_ops = {
.name = "default",
};
default_security_ops
在定义时只有name字段,security/capability.c::security_fixup_ops()
函数的作用在于将ptrace_access_check
等等函数指针指向cap_##function
:
#define set_to_cap_if_null(ops, function) \
do { \
if (!ops->function) { \
ops->function = cap_##function; \
pr_debug("Had to override the " #function \
" security operation with the default.\n");\
} \
} while (0)
void __init security_fixup_ops(struct security_operations *ops)
{
set_to_cap_if_null(ops, ptrace_access_check);
set_to_cap_if_null(ops, ptrace_traceme);
...
在函数式宏定义中,#运算符用于创建字符串,而##则将运算符把前后两个预处理Token连接成一个预处理Token。
比如在执行set_to_cap_if_null(ops, inode_create);
时,security_ops
的inode_create
函数指针将指向函数cap_inode_create
(该函数也在security/capability.c,并默认不做操作)
static int cap_inode_create(struct inode *inode, struct dentry *dentry,
umode_t mask)
{
return 0;
}
do_security_initcalls
在初始化security_ops
后,security_init
调用do_security_initcalls()
static void __init do_security_initcalls(void)
{
initcall_t *call;
call = __security_initcall_start;
while (call < __security_initcall_end) {
(*call) ();
call++;
}
}
initcall_t *
的定义在include/linux/init.h
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
extern initcall_t __security_initcall_start[], __security_initcall_end[];
我们知道typedef能够对类型取别名,如typedef int MyInt;
typedef int(init_fnc_t) (void)
是对一个 int (void)类型的函数类型进行取别名init_fnc_ttypedef int (*init_fnc_t_p)(void)
则是取一个int (void)类型的函数指针
如:
#include <stdio.h>
int GetData(void) { return 101; }
int main() {
typedef int (init_fnc_t)(void);
typedef int (*init_fnc_t_p)(void);
init_fnc_t *MyFunction;
init_fnc_t_p MyFunctionP;
MyFunction = GetData;
MyFunctionP = GetData;
printf("(init_fnc_t)(void) = %d \n", MyFunction());
printf("(*init_fnc_t_p)(void) = %d \n", MyFunctionP());
return 0;
}
打印出来的结果都是101。
回到函数do_security_initcalls
,可以发现其作用为依次调用__security_initcall_start
到__security_initcall_end
之间的函数
那么到底具体调用了哪些函数呢?
查阅资料发现include/asm-generic/vmlinux.lds.h
定义了一些宏用于辅助写连接脚本,从其中可以看到最终会出现在连接脚本中的各个内存section以及它们的相对位置。在这个文件里我们可以找到security_initcall
所在的内存section的相关代码:
#define SECURITY_INIT \
.security_initcall.init : AT(ADDR(.security_initcall.init) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__security_initcall_start) = .; \
*(.security_initcall.init) \
VMLINUX_SYMBOL(__security_initcall_end) = .; \
}
从上述内存段的定义可以看出,该宏定义的内存区域起始于__security_initcall_start
,结束于__security_initcall_end
,也就是do_security_initcalls
要调用的函数了
注意include/linux/init.h
中还有这么一个宏。这里对于形参fn,将定义一个__initcall_##fn
函数,并且指定使用内存段.security_initcall.init
#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.security_initcall.init) = fn
因此,调用这个宏的函数就是do_security_initcalls
要调用的函数
用xargs grep查找下整个内核代码:
root@BlockIMATest:/usr/src/octa-blockIMA/octa-blockIMA# find . -type f | xargs grep "security_initcall("
./include/linux/init.h:#define security_initcall(fn) \
./include/linux/init.h:#define security_initcall(fn) module_init(fn)
./security/yama/yama_lsm.c:security_initcall(yama_init);
./security/selinux/hooks.c:security_initcall(selinux_init);
./security/apparmor/lsm.c:security_initcall(apparmor_init);
./security/integrity/iint.c:security_initcall(integrity_iintcache_init);
./security/tomoyo/tomoyo.c:security_initcall(tomoyo_init);
./security/smack/smack_lsm.c:security_initcall(smack_init);
root@BlockIMATest:/usr/src/octa-blockIMA/octa-blockIMA#
那么do_security_initcalls
调用了(如果模块enable的话,而且感觉应该最多只有一个security_ops
会生效[待分析]??):
yama_init
selinux_init
apparmor_init
integrity_iintcache_init
tomoyo_init
smack_init
自定义钩子
假设机器enable了SELinux,则security_init
将会调用selinux_init
,该函数位于security/selinux/hooks.c
static __init int selinux_init(void)
{
if (!security_module_enable(&selinux_ops)) {
selinux_enabled = 0;
return 0;
}
...
if (register_security(&selinux_ops))
panic("SELinux: Unable to register with kernel.\n");
...
return 0;
}
我们知道LSM的具体做法是在内核关键代码处打上了安全钩子,并且允许安全模块来定义这些安全钩子的具体操作,如访问控制等。使用安全模块自定义钩子的函数为register_security
,该函数是在security/security.c
中
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;
}
verify函数只是简单地调用了security_fixup_ops
,这个在初始化过程中已经介绍了。register_security
实际上就是用传入的形参赋值给全局的security_ops
。对于SELinux来说,则是让内核使用selinux_ops
。而且这也意味着只能有一个register_security
生效
static struct security_operations selinux_ops = {
.name = "selinux",
.ptrace_access_check = selinux_ptrace_access_check,
.ptrace_traceme = selinux_ptrace_traceme,
.capget = selinux_capget,
.capset = selinux_capset,
.capable = selinux_capable,
...
};
测试
修改内核代码来测试LSM见下一篇文章http://blog.csdn.net/lwyeluo/article/details/55215792..