LSM HOOK 学习及踩坑

本文中的内容基于内核版本:4.4.232

注意,据说低版本(2.x)的LSM只能有一个hook,本文不适用于此类版本

LSM HOOK和其他hook的不同

1.正常的hook一般流程是,(1)保存原始的函数指针 (2)用hook函数替换原始函数 (3)在hook函数执行后将指针指向原函数指针 (4) 执行原函数

2.LSM HOOK采用的方式不一样,它维护了一个hook函数的链表,每次有新的hook函数添加时,就在链表的尾部插入该函数,并不影响原链表的执行,也没有修改原函数的函数指针

从结构体开始

这里先把每个结构体讲清楚,后边好串起来用,要觉得麻烦,直接跳到后面的流程

1.struct security_hook_heads

从文件include/lsm_hooks.h中可以找到该结构体

	struct security_hook_heads {
	struct list_head binder_set_context_mgr;
	struct list_head binder_transaction;
	struct list_head binder_transfer_binder;
	struct list_head binder_transfer_file;
	struct list_head ptrace_access_check;
	struct list_head ptrace_traceme;
	struct list_head capget;
	struct list_head capset;
	struct list_head capable;
	struct list_head quotactl;
	struct list_head quota_on;
	struct list_head syslog;
	struct list_head settime;
	struct list_head vm_enough_memory;
	struct list_head bprm_set_creds;
	struct list_head bprm_check_security;
	struct list_head bprm_secureexec;
	struct list_head bprm_committing_creds;
	struct list_head bprm_committed_creds;
	struct list_head sb_alloc_security;
	struct list_head sb_free_security;
	struct list_head sb_copy_data;
	struct list_head sb_remount;
	struct list_head sb_kern_mount;
	struct list_head sb_show_options;
	struct list_head sb_statfs;
	struct list_head sb_mount;
	struct list_head sb_umount;
	struct list_head sb_pivotroot;
	struct list_head sb_set_mnt_opts;
	struct list_head sb_clone_mnt_opts;
	struct list_head sb_parse_opts_str;
	struct list_head dentry_init_security;
	....
	}

可以看到,该结构体由许许多多个list_head 结构体组成,再来看看list_head这个结构体

struct list_head {
    struct list_head *next, *prev;
};

就是两个list_head指针组成结构体,可以理解为双链表的一个基础单元

struct security_hook_heads 在security/security.c中实例化,并通过宏LIST_HEAD_INIT进行初始化

// 实例并初始化security_hook_heads
struct security_hook_heads security_hook_heads = {
	.binder_set_context_mgr =
		LIST_HEAD_INIT(security_hook_heads.binder_set_context_mgr),
	.binder_transaction =
		LIST_HEAD_INIT(security_hook_heads.binder_transaction),
	.binder_transfer_binder =
		LIST_HEAD_INIT(security_hook_heads.binder_transfer_binder),
	.binder_transfer_file =
		LIST_HEAD_INIT(security_hook_heads.binder_transfer_file),

	.ptrace_access_check =
		LIST_HEAD_INIT(security_hook_heads.ptrace_access_check),
	......
	};
LIST_HEAD_INIT(security_hook_heads.abc)
{
.abc = {&security_hook_heads.abc = &security_hook_heads.abc}
}

这里先记住 security_hook_heads 这个结构体,稍后用得着

2.struct security_hook_list

还是在include/lsm_hooks.h中能找到定义

struct security_hook_list {
	struct list_head		list;
	struct list_head		*head;
	union security_list_options	hook;
};

可以看到,该结构体有一个list_head 一个list_head 指针和一个联合体union security_list_options组成,先不管这个结构体具体的作用,再来看一下这个联合体union security_list_options

3.union security_list_options

同样是在include/lsm_hooks.h中

union security_list_options {
	int (*binder_set_context_mgr)(struct task_struct *mgr);
	int (*binder_transaction)(struct task_struct *from,
					struct task_struct *to);
	int (*binder_transfer_binder)(struct task_struct *from,
					struct task_struct *to);
	int (*binder_transfer_file)(struct task_struct *from,
					struct task_struct *to,
					struct file *file);
	int (*ptrace_access_check)(struct task_struct *child,
					unsigned int mode);
	int (*ptrace_traceme)(struct task_struct *parent);
	int (*capget)(struct task_struct *target, kernel_cap_t *effective,
			kernel_cap_t *inheritable, kernel_cap_t *permitted);
	int (*capset)(struct cred *new, const struct cred *old,
			const kernel_cap_t *effective,
			const kernel_cap_t *inheritable,
			const kernel_cap_t *permitted);
	int (*capable)(const struct cred *cred, struct user_namespace *ns,
			int cap, int audit);
	int (*quotactl)(int cmds, int type, int id, struct super_block *sb);
	int (*quota_on)(struct dentry *dentry);
	int (*syslog)(int type);
	int (*settime)(const struct timespec *ts, const struct timezone *tz);
	int (*vm_enough_memory)(struct mm_struct *mm, long pages);
	....}

可以理解为该联合体中定义了许许多多的不同的可hook函数,每一个可选的lsm hook都必须和该联合体中的某一个函数定义一致

不知道取啥标题

LSM_HOOK_INIT

该宏用于初始化一个security_hook_list

#define LSM_HOOK_INIT(HEAD, HOOK) \
	{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }

该宏进行解析以后就是

举个例子:
LSM_HOOK_INIT(abc,def)
{
.head = &security_hook_heads.HEAD.abc,
.hook = {
		.abc = def
	}
}
// 还记得前面的security_hook_heads吗?这里的.head 就会取到一个heads实例的abc成员的地址,该成员是一个list_head 结构
// 然后.hook 是一个union结构,其中也包含了一个.abc的成员,这里就是将该union转换成.abc成员,然后赋值为def
// 这点,我看了好几次才明白,害,菜是原罪

维护一个要hook的所有函数的数组

struct security_hook_list hooks[] ={
	LSM_HOOK_INIT(abc,def),
	LSM_HOOK_INIT(cba,fed),
	......
};
// 其中abc和cba为系统提供的security_hook_heads中的某个成员名
// def,fed为用户自定义的与之相匹配的hook函数函数名

函数 security_add_hooks

至此,hooks已经初始化完毕,就需要将hooks添加到相应的security_hook_heads实例对应的成员链表中

static inline void security_add_hooks(struct security_hook_list *hooks,
				      int count)
{
	int i;

	for (i = 0; i < count; i++)
		list_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
// hooks[i]对应的即为一个security_hook_list结构
// 利用list_add_tail_rcu,将security_hook_list插入到security_hook_heads实例对应成员链表中,该成员由security_hook_list.head成员指定,在security_hook_list初始化时已经初始化

security_add_hooks以后,lsm hook已经注册到security_hook_heads里面,那么什么时候开始调用呢?

call_int_hookcall_void_hook

当触发到相应的事件以后,相应事件的security_xxx函数就会根据xxx的定义调用相应的上述两个函数

//  security/security.c
/*
 * Hook list operation macros.
 *
 * call_void_hook:
 *	This is a hook that does not return a value.
 *
 * call_int_hook:
 *	This is a hook that returns a value.
 */

#define call_void_hook(FUNC, ...)				\
	do {							\
		struct security_hook_list *P;			\
								\
		list_for_each_entry(P, &security_hook_heads.FUNC, list)	\
			P->hook.FUNC(__VA_ARGS__);		\
	} while (0)

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

//比如触发函数abc的条件
int security_abc(__VA_ARGS__)
{
    return call_int_hook(abc,0,__VA_ARGS__);
    // 或者call_void_hook
}


// 因为abc要作为参数,不能直接未定义,所以内核会定义一个bool型的函数来占位
#ifdef CONFIG_XXX
extern bool abc(__AV_ARGS);
....
#else
static inline bool abc(__VA_ARGS)
{
	return true;
};
....
#endif

可以看到,该宏会遍历security_hook_heads对应的成员链表,然后执行hook函数

从start_kernel开始

在 init/main.c中有一个函数start_kernel

 //   init/main.c
asmlinkage __visible void __init start_kernel(void){
.....
	thread_info_cache_init();
	cred_init();
	fork_init();
	proc_caches_init();
	buffer_init();
	key_init();
	security_init(); // 注意到这个函数
	dbg_late_init();
	vfs_caches_init();
	signals_init();
......
}
//  security\security.c
int __init security_init(void)
{
	pr_info("Security Framework initialized\n");

	/*
	 * Load minor LSMs, with the capability module always first.
	 */
	capability_add_hooks();
	yama_add_hooks();

	/*
	 * Load all the remaining security modules.
	 */
	do_security_initcalls();

	return 0;
}
// security_init 初始化了两个hooks数组,取其中一个看看
void __init capability_add_hooks(void)
{
	security_add_hooks(capability_hooks, ARRAY_SIZE(capability_hooks));
}
// 可以看到套路和前面讲的差不多,最终就是初始化一个hooks数组,并注册到到security_hook_heads实例相应的链表

实例???没有,俺写不出来

/** 此代码运行不起来,只做示范用,因为security_add_hooks 和LSM_HOOK_INIT中的security_hook_heads并不导出,make时会报如下错

ERROR: "security_hook_heads" [/root/zyc/test/lsm/test.ko] undefined!
ERROR: "security_add_hooks" [/root/zyc/test/lsm/test.ko] undefined!

解决办法是通过kallsyms_lookup_name("xxxx")将xxx符号导出,至于是否需要重新再定义一次LSM_HOOK_INIT,有待测试

此代码编译测试环境为:Linux ubuntu 5.3.0-51-generic  其security_add_hooks函数和hook用的my_rename函数原型都与本文前面贴的代码有细微差异,建议按照自身系统对应的源代码文件去调用
*/
#include <linux/security.h>
#include <linux/module.h>
#include <linux/lsm_hooks.h>

//struct security_hook_heads sec_hd;


static int my_rename(const struct path *old_dir, struct dentry *old_dentry,
                                const struct path *new_dir,
                                struct dentry *new_dentry)
{
        printk("you are rename some file\n");
        return 0;
}

struct security_hook_list hooks[] =
{
        LSM_HOOK_INIT(path_rename,my_rename),
};

static int lsm_init(void)
{
        security_add_hooks(hooks,1,"yclsm");
        printk("start");
        return 0;
}

static void lsm_exit(void)
{
// lsm 可以调用security_delete_hooks进行hook卸载,这里就不写了
        printk("remove\n");
}

MODULE_LICENSE("GPL");
module_init(lsm_init);
module_exit(lsm_exit);

参考资料: https://blog.51cto.com/2559640/2365794 这个链接从内核源码取一个实例进行讲解,非常不错。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐