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

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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值