linux lsm框架(selinux)

LSM 是(Linux Security Module)的简称,它是Linux 从2.6 内核版本开始增加的一个通用的访问控制框架。

LSM 是一个轻量的通用访问控制框架,它必须满足两方面的要求:对不需要它的人保持透明;让需要它的人从内核代码中解放出来,专注于控制策略和安全模块。所以LSM 在设计之初,就明确了其原则:

(1)真正的通用性,使用不同的安全模型仅仅通过加载不同的内核模块实现,而不用改变已有的内核代码。

(2)概念简单,不涉及具体的安全策略,尽量减少对原有内核的修改,对内核的运行效率不会产生大的影响。

(3)支持已有的 POSIX.1e capabilities 机制,将其分离出来,作为可选的安全模块。

为了满足这些目标,LSM 在内核资源访问的源代码中,放置了钩子函数,来仲裁对内部资源的访问,这些资源包括文件、inode 节点、进程、网络套接字等。Linux 已经实现了UNIX 经典的访问控制机制,即基于权限位的自主访问控制。LSM 钩子函数通常放在内核自主访问控制后,在内核进行真正内部资源访问前。也就是说,经过了自主访问控制的检查,还需要经过LSM 钩子函数的判定,才能访问到资源。

       由于内核版本不同,LSM的注册、初始化实现方式可能不同,底层实现差异不大,因此本文针对内核版本5.4进行LSM框架分析

一、内核拉起LSM

asmlinkage __visible void __init start_kernel(void)
{
        char *command_line;
        char *after_dashes;

        set_task_stack_end_magic(&init_task);
        smp_setup_processor_id();
        debug_objects_early_init();

        cgroup_init_early();

        local_irq_disable();
        early_boot_irqs_disabled = true;

        /*
         * Interrupts are still disabled. Do necessary setups, then
         * enable them.
         */
        ...
        //lsm early init
        early_security_init();
        ...
        //lsm init   
        security_init();
        ...
}

二、LSM初始化

对于内核怎么拉起lsm不是我们关注的重点,下面重点看看security.c里面做了什么

struct security_hook_heads {
        struct hlist_head binder_set_context_mgr;
        struct hlist_head binder_transaction;
        struct hlist_head binder_transfer_binder;
        struct hlist_head binder_transfer_file;
        struct hlist_head ptrace_access_check;
        struct hlist_head ptrace_traceme;
        struct hlist_head capget;
        struct hlist_head capset;
        struct hlist_head capable;
        struct hlist_head quotactl;
        struct hlist_head quota_on;
        struct hlist_head syslog;
        struct hlist_head settime;
        struct hlist_head vm_enough_memory;
        struct hlist_head bprm_set_creds;
        struct hlist_head bprm_check_security;
        struct hlist_head bprm_committing_creds;
        struct hlist_head bprm_committed_creds;
        struct hlist_head fs_context_dup;
        struct hlist_head fs_context_parse_param;
        struct hlist_head sb_alloc_security;
        struct hlist_head sb_free_security;
        struct hlist_head sb_free_mnt_opts;
        struct hlist_head sb_eat_lsm_opts;
        struct hlist_head sb_remount;
        struct hlist_head sb_kern_mount;
        struct hlist_head sb_show_options;
        struct hlist_head sb_statfs;
        struct hlist_head sb_mount;
        struct hlist_head sb_umount;
        struct hlist_head sb_pivotroot;
        struct hlist_head sb_set_mnt_opts;
        struct hlist_head sb_clone_mnt_opts;
        struct hlist_head sb_add_mnt_opt;
        struct hlist_head move_mount;
        ...
}

        
struct lsm_info {
        const char *name;       /* Required. */
        enum lsm_order order;   /* Optional: default is LSM_ORDER_MUTABLE */
        unsigned long flags;    /* Optional: flags describing LSM */
        int *enabled;           /* Optional: controlled by CONFIG_LSM */
        int (*init)(void);      /* Required. */
        struct lsm_blob_sizes *blobs; /* Optional: for blob sharing. */
};

int __init early_security_init(void)
{
        int i;
        struct hlist_head *list = (struct hlist_head *) &security_hook_heads;
        struct lsm_info *lsm;

        for (i = 0; i < sizeof(security_hook_heads) / sizeof(struct hlist_head);
             i++)
                INIT_HLIST_HEAD(&list[i]);

        for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
                if (!lsm->enabled)
                        lsm->enabled = &lsm_enabled_true;
                prepare_lsm(lsm);
                initialize_lsm(lsm);
        }

        return 0;
}

early_security_init

early_security_init主要做了几件事情:

1.将lsm的钩子函数都放到一个链表内,里面的有些钩子函数可能是通用的,有的钩子函数是特定框架才有的,如binder_set_context_mgr只有selinux下才有,而bprm_committing_creds则除了selinux外apparmor框架也有,但是此时lsm还不知道系统当前支持哪些安全框架,因此选择全部进行初始化

2.根据__start_early_lsm_info以及__end_early_lsm_info结构体查看当前使能了哪些lsm模块,在编译内核的时候需要配置一些宏,如CONFIG_SECURIRY_SELINUX、CONFIG_SECURITY_DEFAULT="selinux"等,最终会编译打包到镜像内,有兴趣的可以去解析一下vmlinux.lds和System.map这两个编译生成物。

3.根据当前设定的default lsm model去调用对应安全框架的初始化函数

security_init

security_init主要是用于内存空间申请,并且调用对应的安全模块初始化函数

/* Initialize a given LSM, if it is enabled. */
static void __init initialize_lsm(struct lsm_info *lsm)
{
        if (is_enabled(lsm)) {
                int ret;

                init_debug("initializing %s\n", lsm->name);
                ret = lsm->init();
                WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret);
        }
}

三、安全模块初始化(以SELINUX为例)

static __init int selinux_init(void)
{
        pr_info("SELinux:  Initializing.\n");

        memset(&selinux_state, 0, sizeof(selinux_state));
        enforcing_set(&selinux_state, selinux_enforcing_boot);
        selinux_state.checkreqprot = selinux_checkreqprot_boot;
        selinux_ss_init(&selinux_state.ss);
        selinux_avc_init(&selinux_state.avc);

        /* Set the security state for the initial task. */
        cred_init_security();

        default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC);

        avc_init();

        avtab_cache_init();

        ebitmap_cache_init();

        hashtab_cache_init();

        security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux");

        if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
                panic("SELinux: Unable to register AVC netcache callback\n");

        if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET))
                panic("SELinux: Unable to register AVC LSM notifier callback\n");

        if (selinux_enforcing_boot)
                pr_debug("SELinux:  Starting in enforcing mode\n");
        else
                pr_debug("SELinux:  Starting in permissive mode\n");

        fs_validate_description(&selinux_fs_parameters);

        return 0;
}

    selinux_init最重要的部分就是security_add_hooks,将各个操作都与对应的钩子函数进行绑定,例如LSM_HOOK_INIT(file_open, selinux_file_open),将file_open与selinux_file_open进行绑定。每次调用file_open时会调用selinux_file_open,进行权限检查。如下

1.文件系统初始化时会设置.open的实现为ovl_open

const struct file_operations ovl_file_operations = {
        .open           = ovl_open,
        .release        = ovl_release,
        .llseek         = ovl_llseek,
        .read_iter      = ovl_read_iter,
        .write_iter     = ovl_write_iter,
        .fsync          = ovl_fsync,
        .mmap           = ovl_mmap,
        .fallocate      = ovl_fallocate,
        .fadvise        = ovl_fadvise,
        .unlocked_ioctl = ovl_ioctl,
        .compat_ioctl   = ovl_compat_ioctl,
        .splice_read    = generic_file_splice_read,
        .splice_write   = ovl_splice_write,

        .copy_file_range        = ovl_copy_file_range,
        .remap_file_range       = ovl_remap_file_range,
};

2.ovl_open会调用到ovl_open_realfile

static int ovl_open(struct inode *inode, struct file *file)
{
        struct file *realfile;
        int err;

        err = ovl_maybe_copy_up(file_dentry(file), file->f_flags);
        if (err)
                return err;

        /* No longer need these flags, so don't pass them on to underlying fs */
        file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);

        realfile = ovl_open_realfile(file, ovl_inode_realdata(inode));
        if (IS_ERR(realfile))
                return PTR_ERR(realfile);

        file->private_data = realfile;

        return 0;
}

3.ovl_open_realfile会再去调用open.c内的open_with_fake_path

static struct file *ovl_open_realfile(const struct file *file,
                                      struct inode *realinode)
{
        struct inode *inode = file_inode(file);
        struct file *realfile;
        const struct cred *old_cred;
        int flags = file->f_flags | OVL_OPEN_FLAGS;

        old_cred = ovl_override_creds(inode->i_sb);
        realfile = open_with_fake_path(&file->f_path, flags, realinode,
                                       current_cred());
        ovl_revert_creds(inode->i_sb, old_cred);

        pr_debug("open(%p[%pD2/%c], 0%o) -> (%p, 0%o)\n",
                 file, file, ovl_whatisit(inode, realinode), file->f_flags,
                 realfile, IS_ERR(realfile) ? 0 : realfile->f_flags);

        return realfile;
}

4.open.c内的do_dentry_open就是实际上进行file_open操作的入口,通过上面的1、2、3这几个api调用后,拿到file_path,在对文件进行操作之前,会调用security_file_open进行selinux的权限检查。

static int do_dentry_open(struct file *f,
                          struct inode *inode,
                          int (*open)(struct inode *, struct file *))
{
        static const struct file_operations empty_fops = {};
        int error;

        ...
        
        f->f_op = fops_get(inode->i_fop);
        if (WARN_ON(!f->f_op)) {
                error = -ENODEV;
                goto cleanup_all;
        }

        error = security_file_open(f);
        if (error)
                goto cleanup_all;

        error = break_lease(locks_inode(f), f->f_flags);
        if (error)
                goto cleanup_all;

        /* normally all 3 are set; ->open() can clear them if needed */
        f->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
        if (!open)
                open = f->f_op->open;
        if (open) {
                error = open(inode, f);
                if (error)
                        goto cleanup_all;
        }
        ...
}

5.security.c内通过事先做好的钩子函数绑定,去调用对应的处理函数。

#define call_int_hook(FUNC, IRC, ...) ({                        \
        int RC = IRC;                                           \
        do {                                                    \
                struct security_hook_list *P;                   \
                                                                \
                hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
                        RC = P->hook.FUNC(__VA_ARGS__);         \
                        if (RC != 0)                            \
                                break;                          \
                }                                               \
        } while (0);                                            \
        RC;                                                     \
})


int security_file_open(struct file *file)
{
        int ret;

        ret = call_int_hook(file_open, 0, file);
        if (ret)
                return ret;

        return fsnotify_perm(file, MAY_OPEN);
}

6.对应的selinux钩子函数(selinux_file_open)会去判断当前节点是否有权限,再一层一层返回上去。至此,内核在执行真正的file_open之前的selinux权限检查结束

static int selinux_file_open(struct file *file)
{
        struct file_security_struct *fsec;
        struct inode_security_struct *isec;

        fsec = selinux_file(file);
        isec = inode_security(file_inode(file));
        /*
         * Save inode label and policy sequence number
         * at open-time so that selinux_file_permission
         * can determine whether revalidation is necessary.
         * Task label is already saved in the file security
         * struct as its SID.
         */
        fsec->isid = isec->sid;
        fsec->pseqno = avc_policy_seqno(&selinux_state);
        /*
         * Since the inode label or policy seqno may have changed
         * between the selinux_inode_permission check and the saving
         * of state above, recheck that access is still permitted.
         * Otherwise, access might never be revalidated against the
         * new inode label or new policy.
         * This check is not redundant - do not remove.
         */
        return file_path_has_perm(file->f_cred, file, open_file_to_av(file));
}

    内核的selinux处理目前是比较完善的,虽然实际调试上不会用到太多内核的LSM框架部分,但是还是了解一下比较好。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值