LSM内核源代码分析与测试(一)

来源:互联网 发布:任天堂vb用电池的吗 编辑:程序博客网 时间:2024/06/05 07:17

    • LSM初始化
      • 全局变量security_ops的初始化
      • do_security_initcalls
    • 自定义钩子
    • 测试

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_opsinode_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_t
  • typedef 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..

0 0
原创粉丝点击