zoukankan      html  css  js  c++  java
  • 给linux安全模块LSM添加可链式调用模块(一)

      前些日子接了个外包的活,了解了一下Linux安全模块,发现了安全模块中的一些问题。

      关于linux安全模块LSM在此就不多说了,大家google下就明白了。

      这里主要介绍的是如何修改这个模块,使它可链栈化。

      关于LSM,旧版本的提供了register_security/mod_reg_security接口用于注册用户的安全模块,register_security注册接口只支持一个的安全模块存在,mod_reg_security 支持注册多个安全模块,不过模块之间的调用需要用户自己维护(也就是不提供多个安全模块并存的机制)。更不幸的是,2.6.19(据说这个版本)之后内核取消了mod_reg_security注册接口(鬼知道什么原因,据说是出于安全考虑,反正给我制造了不少的麻烦)。现在的内核模块只留下了register_security注册接口了。

      问题来了,我是要帮人家写个安全模块的,现在内核不让我插入安全模块,这戏就没法玩了。

      什么,还有一个register_security接口,呵呵,linux的安全模块selinux(红帽,centos发行版自带的安全模块)或者apparmor(ubuntu自带的安全模块)还有linux的capability模块都占着了它了,总不能让用户关了自带的安全模块用你的来玩吧(除非你比NSA还牛)。我写的安全模块主要是提供用户定制的一些安全机制,简单的说,就是需要第三方的规则。

      好吧,废话不多说了,问题现在很明显了,我们的主题也就出来了。

      简单介绍一下LSM模块吧,这里不得不佩服Linux的设计者,LSM模块提供的接口很简单,它提供了一个类似文件系统的抽象层一样,用户只要实现它提供的操作结构(security_operations结构),用它提供的安全模块注册接口注册到LSM模块下面就可以实现它自己的安全功能了。注册接口代码如下,其主要的工作为检查用户的security_operations结构实例,对一些未定义的接口,修改为默认行为,并把security_ops地址指针指向这个结构,这样就完成了安全模块的注册工作。

      

     1  int __init register_security(struct security_operations *ops)
     2  {
     3      if (verify(ops)) {
     4         printk(KERN_DEBUG "%s could not verify "
     5               "security_operations structure.
    ", __func__);
     6          return -EINVAL;
     7      }
     8  
     9      if (security_ops != &default_security_ops)
    10          return -EAGAIN;
    11  
    12      security_ops = ops;
    13 
    14      return 0;
    15  }

      

      了解完这些之后,我们来分析下一步该如何改造LSM吧:

      LSM提供一个静态指针security_ops做为指向访问模块的渠道,安全模块如selinux等在系统启动之后就把这个指针修改为它们的模块结构地址了。

      首先,我们想到的就是能不能找到这个指针,把这个指针引到我们的安全模块这边来。嘿嘿,那样我们的安全模块就活起来了。

      其次,我们不能只管自己的模块,不管理系统的死活,至少你不能影响原来系统的安全机制吧。所以,也就是我们在让自己的安全模块活的同时,也让原来的安全模块一直存活着。

      好了,思路分析清楚了,动手查查能不能做吧!

      我们来找找security_ops在内核中的地址:

      

      太好了,lsm中的静态指针地址就在内核符号表中列着,这就说明我们可以通过修改这个地址,去引用我们的安全模块。

      这里给出内核探测引用这个指针的代码

      

    /**
     * probe_kernel_read - Wrapper for kernel_read().
     *
     * @file:   Pointer to "struct file".
     * @offset: Starting position.
     * @addr:   Buffer.
     * @count:  Size of @addr.
     *
     * Returns return value from kernel_read().
     */
    static int __init probe_kernel_read(struct file *file, unsigned long offset,
                        char *addr, unsigned long count)
    {
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 8)
        /*
         * I can't use kernel_read() because seq_read() returns -EPIPE
         * if &pos != &file->f_pos .
         */
        mm_segment_t old_fs;
        unsigned long pos = file->f_pos;
        int result;
        file->f_pos = offset;
        old_fs = get_fs();
        set_fs(get_ds());
        result = vfs_read(file, (void __user *)addr, count, &file->f_pos);
        set_fs(old_fs);
        file->f_pos = pos;
        return result;
    #else
        return kernel_read(file, offset, addr, count);
    #endif
    }
    
    /**
     * probe_find_symbol - Find function's address from /proc/kallsyms .
     *
     * @keyline: Function to find.
     *
     * Returns address of specified function on success, NULL otherwise.
     */
    void *__init probe_find_symbol(const char *keyline)
    {
        struct file *file = NULL;
        char *buf;
        unsigned long entry = 0;
        {
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
            struct file_system_type *fstype = get_fs_type("proc");
            struct vfsmount *mnt = vfs_kern_mount(fstype, 0, "proc", NULL);
    #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 8)
            struct file_system_type *fstype = NULL;
            struct vfsmount *mnt = do_kern_mount("proc", 0, "proc", NULL);
    #else
            struct file_system_type *fstype = get_fs_type("proc");
            struct vfsmount *mnt = kern_mount(fstype);
    #endif
            struct dentry *root;
            struct dentry *dentry;
            /*
             * We embed put_filesystem() here because it is not exported.
             */
            if (fstype)
                module_put(fstype->owner);
            if (IS_ERR(mnt))
                goto out;
            root = dget(mnt->mnt_root);
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
            mutex_lock(&root->d_inode->i_mutex);
            dentry = lookup_one_len("kallsyms", root, 8);
            mutex_unlock(&root->d_inode->i_mutex);
    #else
            down(&root->d_inode->i_sem);
            dentry = lookup_one_len("kallsyms", root, 8);
            up(&root->d_inode->i_sem);
    #endif
            dput(root);
            if (IS_ERR(dentry))
                mntput(mnt);
            else {
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
                struct path path = { mnt, dentry };
                file = dentry_open(&path, O_RDONLY, current_cred());
    #else
                file = dentry_open(dentry, mnt, O_RDONLY
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
                           , current_cred()
    #endif
                           );
    #endif
            }
        }
        if (IS_ERR(file) || !file)
            goto out;
        buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
        if (buf) {
            int len;
            int offset = 0;
            while ((len = probe_kernel_read(file, offset, buf,
                            PAGE_SIZE - 1)) > 0) {
                char *cp;
                buf[len] = '';
                cp = strrchr(buf, '
    ');
                if (!cp)
                    break;
                *(cp + 1) = '';
                offset += strlen(buf);
                cp = strstr(buf, keyline);
                if (!cp)
                    continue;
                *cp = '';
                while (cp > buf && *(cp - 1) != '
    ')
                    cp--;
                entry = simple_strtoul(cp, NULL, 16);
                break;
            }
            kfree(buf);
        }
        filp_close(file, NULL);
    out:
        return (void *) entry;
    }

      代码很简单,就是打开/proc/kallsyms文件,去找符号对应的地址,当然,这里考虑了内核版本的问题,使用#if/#endif等,代码写得比较乱。

      下一步是设计一个合理的链式调用,使安全模块之前能链式调用,这个留着下遍再说吧。

     

      

      

      

      

  • 相关阅读:
    团队项目冲刺第十天
    gradle文件配置
    idea无Android项目
    php第二次实验报告
    最长回文字串(hdu 3068)
    优先队列实现哈弗曼最小权值
    最小生成树 克鲁斯卡尔(Kruskal)算法求最小生成树
    背包问题------ 分类: ACM 2015-08-03 20:57 1人阅读 评论(0
    Cent Savings (DP) 分类: ACM dp 2015-08-0
    Judging Troubles (multiset查找) 分类: ACM STL
  • 原文地址:https://www.cnblogs.com/Richard-chen/p/5178320.html
Copyright © 2011-2022 走看看